Flash mp3 Lrc 歌词秀

作者: Molay

性质: 原创

阅读次数: 11032

发表时间: 2007-04-12 15:41:28


作者声明:本教程以及源代码仅仅由作者本人许可给WS和VNICES使用,未经作者同意,任何组织和个人不得转载~


ps by egoldy: 此教程为Molay的第一篇关于flash的教程,如有不足之处或是发现问题请去论坛提问.同时感谢Molay奉献他的开发经验.

 

一、Lrc歌词文件格式的讨论

王菲 - 红豆的歌词内容如下:
[ti:红豆]
[ar:王菲]
[al:唱游]
[by:Molay]
[00:00.00]王菲 - 红豆
[00:08.00]曲:柳重言  词:林夕
[00:16.00]还没好好的感受
[00:20.00]雪花绽放的气候
[00:24.00]我们一起颤抖
[00:27.00]会更明白  什么是温柔
[00:33.00]还没跟你牵著手
[00:37.00]走过荒芜的沙丘
[00:41.00]可能从此以後  学会珍惜
[00:46.00]天长和地久
[00:50.00][02:18.00][03:07.00]有时候  有时候
[00:54.00][02:22.00][03:11.00]我会相信一切有尽头
[00:58.00][02:26.00][03:15.00]相聚离开  都有时候
[01:03.00][02:31.00][03:20.00]没有什么会永垂不朽
[01:07.00][02:35.00][03:24.00]可是我  有时候
[01:11.00][02:39.00][03:28.00]宁愿选择留恋不放手
[01:16.00][02:44.00][03:33.00]等到风景都看透
[01:20.00][02:48.00][03:37.00]也许你会陪我  看细水长流
[01:26.00][02:54.00][03:46.00](音乐)
[01:41.00]还没为你把红豆
[01:45.00]熬成缠绵的伤口
[01:50.00]然後一起分享
[01:53.00]会更明白  相思的哀愁
[01:58.00]还没好好的感受
[02:03.00]醒著亲吻的温柔
[02:07.00]可能在我左右
[02:10.00]你才追求  孤独的自由

规律:[02:07.00]是时间,后面是歌词,且时间一行可以有多个(节约文件大小,不错)

摸索:先不谈固定标记的LRC文件头部,单看歌词部分
我们以“[00:58.00][02:26.00][03:15.00]相聚离开 都有时候”来讨论
如何形成两个数组,一个为时间,一个为歌词,一一对应呢?
这时字符串的split方法给了我们灵感,重复的“[]”是否能成为划分成数组元素的依据呢?

如果我们先用.split("["),会得到数组:
//
00:58.00]
02:26.00]
03:15.00]相聚离开 都有时候
//
这样最后一个时间不能一次和歌词分离

于是我们就用.split("]"),得到数组:
//
[00:58.00
[02:26.00
[03:15.00
相聚离开 都有时候
//
可是那个“[”怎么去掉呢?
于是乖乖想干脆写个替换函数Replace转换开始时就把该行的“[”给去掉方便点

这样每行的数组解释完成
那么怎么将他们从这个解释来的过渡数组分别装入我们先设定的那两个数组呢:

// 下面有几个函数是自己遍的功能函数,后续
// long:[]是每行歌词文本在一特定的条件下占用的长度,方便后面的使用
// 逐行分解歌词文件
var lrctemp:Object = {stime:[], msg:[], long:[], ti:"曲目名称", ar:"艺术家", al:"专辑名称", by:"歌词制作"};
var str:Array = lrcdata.split("
");
var s = str.length;
var k = 0;
for (var i = 0; i<s; i++) {
    // [00:58.00][02:26.00][03:15.00]相聚离开  都有时候
    // 转换成
    // 00:58.00]02:26.00]03:15.00]相聚离开  都有时候
    var temp = Replace(str, "[", "");
    // 形成数组:00:58.00|||02:26.00|||03:15.00|||相聚离开  都有时候
    var my_array:Array = temp.split("]");
    var n = my_array.length;
    for (var j = 0; j<n-1; j++) {
        // 清除LRC头信息,重新装入关联数组lrc
        if (my_array[j].indexOf("ti:") != -1) {
            lrctemp.ti = Replace(my_array[j], "ti:", "");
        } else if (my_array[j].indexOf("ar:") != -1) {
            lrctemp.ar = Replace(my_array[j], "ar:", "");
        } else if (my_array[j].indexOf("al:") != -1) {
            lrctemp.al = Replace(my_array[j], "al:", "");
        } else if (my_array[j].indexOf("by:") != -1) {
            lrctemp.by = Replace(my_array[j], "by:", "");
        } else {
            // 转换时间从60进制到10进制
            var times = Time2Ten(my_array[j]);
            var msgstr = my_array[n-1];
            // 形成新数组
            lrctemp.stime[k] = times;
            lrctemp.msg[k] = msgstr;
            lrctemp.long[k] = Str_Length(msgstr);
            k++;
        }
    }
}

这样就成功装入了我们设定的时间和歌词数组
但是一个问题来了,这时歌词与时间不一定是从小到大顺序的呀
于是后面还要加上一个排序
// 对歌词数据进行排序
lrctemp = Order(lrctemp);
上面的Order函数就是根据时间先后的顺序排列所有相关的数组
这里再附上所有用到的函数(注意使用Lrc文件时必须使用utf-8编码  )
到此我们已经得到了正确按照时间先后的排列的两个数组

二、读取lrc文件并转换

import Vnices.*;
var my_lv:LoadVars = new LoadVars();
my_lv.onLoad = function(success:Boolean) {
if (success) {
// 第一步将读入的数据尾部清除
var temp:String = Lrc.Replace(unescape(my_lv.toString()), "=&onLoad=[type Function]", "");
// 输出
trace(unescape(temp));
} else {
trace("数据加载出错......");
}
};
my_lv.load("红豆.lrc");

在第一桢上写下上述代码,运行输出,我们就能获得纯净的LRC文件体

解释:
在加载函数前定义一个Object
var lrc:Object = {};
然后加载成功后写上调用代码
lrc = Lrc.Parse(unescape(temp));
运行查看变量列表
可以看到:

Variable _level0.lrc = [object #3, class 'Object'] {
    by:"Molay",
    al:"唱游",
    ar:"王菲",
    ti:"红豆",
    long:[object #4, class 'Array'] [
      0:76.2,
      1:124.2,
      2:88.2,
      3:88.2,
      4:76.2,
      5:124.2,
      6:88.2,
      7:88.2,
      8:136.2,
      9:64.2,
      10:88.2,
      11:112.2,
      12:112.2,
      13:112.2,
      14:88.2,
      15:112.2,
      16:88.2,
      17:148.2,
      18:52.2,
      19:88.2,
      20:88.2,
      21:76.2,
      22:124.2,
      23:88.2,
      24:88.2,
      25:76.2,
      26:124.2,
      27:88.2,
      28:112.2,
      29:112.2,
      30:112.2,
      31:88.2,
      32:112.2,
      33:88.2,
      34:148.2,
      35:52.2,
      36:88.2,
      37:112.2,
      38:112.2,
      39:112.2,
      40:88.2,
      41:112.2,
      42:88.2,
      43:148.2,
      44:52.2
    ],
    msg:[object #5, class 'Array'] [
      0:"王菲 - 红豆",
      1:"曲:柳重言  词:林夕",
      2:"还没好好的感受",
      3:"雪花绽放的气候",
      4:"我们一起颤抖",
      5:"会更明白  什么是温柔",
      6:"还没跟你牵著手",
      7:"走过荒芜的沙丘",
      8:"可能从此以後  学会珍惜",
      9:"天长和地久",
      10:"有时候  有时候",
      11:"我会相信一切有尽头",
      12:"相聚离开  都有时候",
      13:"没有什么会永垂不朽",
      14:"可是我  有时候",
      15:"宁愿选择留恋不放手",
      16:"等到风景都看透",
      17:"也许你会陪我  看细水长流",
      18:"(音乐)",
      19:"还没为你把红豆",
      20:"熬成缠绵的伤口",
      21:"然後一起分享",
      22:"会更明白  相思的哀愁",
      23:"还没好好的感受",
      24:"醒著亲吻的温柔",
      25:"可能在我左右",
      26:"你才追求  孤独的自由",
      27:"有时候  有时候",
      28:"我会相信一切有尽头",
      29:"相聚离开  都有时候",
      30:"没有什么会永垂不朽",
      31:"可是我  有时候",
      32:"宁愿选择留恋不放手",
      33:"等到风景都看透",
      34:"也许你会陪我  看细水长流",
      35:"(音乐)",
      36:"有时候  有时候",
      37:"我会相信一切有尽头",
      38:"相聚离开  都有时候",
      39:"没有什么会永垂不朽",
      40:"可是我  有时候",
      41:"宁愿选择留恋不放手",
      42:"等到风景都看透",
      43:"也许你会陪我  看细水长流",
      44:"(音乐)"
    ],
    stime:[object #6, class 'Array'] [
      0:0,
      1:8,
      2:16,
      3:20,
      4:24,
      5:27,
      6:33,
      7:37,
      8:41,
      9:46,
      10:50,
      11:54,
      12:58,
      13:63,
      14:67,
      15:71,
      16:76,
      17:80,
      18:86,
      19:101,
      20:105,
      21:110,
      22:113,
      23:118,
      24:123,
      25:127,
      26:130,
      27:138,
      28:142,
      29:146,
      30:151,
      31:155,
      32:159,
      33:164,
      34:168,
      35:174,
      36:187,
      37:191,
      38:195,
      39:200,
      40:204,
      41:208,
      42:213,
      43:217,
      44:226
    ]
  } 


至此,FLASH的歌词秀准备工作已经完成~

三、歌词在flash中的同步显示讨论

接上面的讨论,歌词文件解释已经完成了
那我们怎么才能在FLASH中使用这些数组来完成歌词同步显示呢?

在FLASH中,声音播放时我们可以获得的参数有.duration和.position
前者是已下载的声音流的持续时间
后者是声音流已播放的时间,两者单位都是以毫秒记的

我们得到的歌词对象的时间数组stime[]是每条歌词开始显示的时间
是以秒记(毫秒记无意义,歌词没有必要对到毫秒这么精确)的

如果我们在播放的时候,一旦播放的时间大于或者等于某段歌词的开始显示时间
那么就显示这个歌词,然后播放时间大于或者等于了下一段歌词的开始显示时间
就移除前面显示的歌词,并显示现在这段歌词的话
也就完成了LRC同步显示功能,不是么?

import Vnices.*;
// 创建歌词对象
var lrc:Object = {};
// 创建Text
var mc:MovieClip = this.createEmptyMovieClip("mc", 1);
var tt:TextField = mc.createTextField("tt", 1, 20, 100, 260, 60);
tt.border = true;
tt.textColor = 0x70C1F2;
tt.borderColor = 0x999999;
tt.autoSize = true;
tt.html = true;
tt.selectable = false;
tt.multiline = true;
tt.wordWrap = true;
// 加载并解释Lrc文件
function Load(url:String) {
    var my_lv:LoadVars = new LoadVars();
    my_lv.onLoad = function(success:Boolean) {
        if (success) {
            // 第一步将读入的数据尾部清除
            var temp:String = Lrc.Replace(unescape(my_lv.toString()), "=&onLoad=[type Function]", "");
            lrc = Lrc.Parse(unescape(temp));
            LrcShow();
        } else {
            trace("数据加载出错......");
        }
    };
    my_lv.load(url);
}
Load("红豆.lrc");
// 开始播放音乐并同步显示
function LrcShow() {
    var My_Mp3:Sound = new Sound();
    My_Mp3.loadSound("红豆.mp3", true);
    My_Mp3.setVolume(100);
    My_Mp3.start();
    // 轮询
    var inc:Number = setInterval(test, 100);
    var counter:Number = 0;
    // 一个简单的歌词秀演示
    function test() {
        if (My_Mp3.position-lrc.stime[counter]*1000>0) {
            while (My_Mp3.position-lrc.stime[counter]*1000>0) {
                counter++;
            }
            counter--;
            var newhtml:String = "<P align='center'>";
            var sline:Number = Math.max(counter-2, 0);
            var eline:Number = Math.max(Math.min(counter+3, lrc.msg.length), 5);
            for (var i = sline; i<eline; i++) {
                if (i == counter) {
                    newhtml += "<FONT color="#FF8080"><b>"+lrc.msg+"</b></FONT><BR/>";
                } else {
                    newhtml += lrc.msg+"<BR/>";
                }
            }
            tt.htmlText = newhtml.slice(0, newhtml.length-3)+"</P>";
            counter++;
            if (counter>=lrc.msg.length) {
                tt.htmlText += "<---曲终--->";
                clearInterval(inc);
            }
        }
    }
}

大家也可以下载源文件自行演示,不过要自行下载红豆哦哈哈:)

四、换肤功能的思考

接上面的讨论
大家想必已经按照自己定义的显示效果来使FLASH带有酷酷的LRC同步显示功能了吧?
接下来我们讨论下换肤  

乖乖我想的换肤概念是在主FLASH里定义好所有功能参数
在主题SWF里导出所有需要的元件

主FLASH加载完毕主题SWF后,调用相关代码
创建显示界面并实现相应的功能

但是一个问题出现了:
只有主题SWF(即所需的元件)完全下载到主FLASH里后
才能对其进行创建操作

所以按照FLASH8 内置帮助的建议
我们使用MovieClipLoader.loadClip方法:

// 创建监听对象
var loadListener:Object = new Object();
// 当使用MovieClipLoader.loadClip()加载的文件完全下载时调用
loadListener.onLoadComplete = function(target_mc:MovieClip, httpStatus:Number):Void  {
};
// 当执行加载的剪辑的第一帧上的动作时调用,此时就能与加载的影片进行交互
loadListener.onLoadInit = function(target_mc:MovieClip):Void  {
    // 进行交互
    OK();
};
// 当使用 MovieClipLoader.loadClip() 加载的文件未能加载时调用
loadListener.onLoadError = function(target_mc:MovieClip):Void  {
    trace("数据加载错误!");
};
// 创建MovieClipLoader对象
var mcLoader:MovieClipLoader = new MovieClipLoader();
// 注册监听
mcLoader.addListener(loadListener);
// 创建元件,主题包将加载至此
var mc:MovieClip = _root.createEmptyMovieClip("mc", this.getNextHighestDepth());
mcLoader.loadClip("主题包路径", mc);
// 交互函数
function OK() {
    // 交互操作的代码
}

注意:只有在loadListener.onLoadInit方法执行时才能调用已下载的元件
如果你的创建函数在该方法之前执行,那么它将是无效的
OK,加载的问题已经解决,那么接下来我们讨论合理的主题包的定义吧:)

依据乖乖自己的感觉,主题包应该符合以下几个要求:
1、只包含该主题必须的配置代码,功能代码没有必要
2、直接将元素拖到场景里命名,然后在配置代码里写上该元素的路径,不要用库链接
3、自己先定义好所有可能用到的元件(排布、名称等等)

定义好主题包的各项后,就可以开始制作你喜欢的样式了呵呵
随便做哦,你想到什么就能做什么
给出个演示(见附件)。

在这个演示中,index.swf是主FLASH,skin.swf是主题包
主FLASH里调用函数创建一个自定义鼠标,并且有弹性移动效果
在主题包中定义了鼠标的效果的弹性系数以及鼠标样式
大家可以改动函数试试,体验各种可能性
例如把OK();调用放在onLoadComplete或者其他的位置看看还能不能正常运行效果:)

五、播放器实例挑战
想必大家对于上面的讨论已经自己试验过了
现在乖乖就以自己制作的一个简陋的FLASH MP3 PLAYER作为蓝本
和大家一起播放器实例挑战~!

 

源文件及演示

服务项目_SERVICE

关于我们

万博思图(北京)信息技术有限公司,专业的flash,flex开发团队,5年经验。公司致力于互联网上的业务的开展,对于互动网站行销,互联网应用程序开发有成熟的解决方案。我们关注互联网市场动态,关注新技术,更注重在新的领域不断探索发现。
万博思图业务内容主要包括企业品牌Flash网站开发,企业形象宣传Flash设计,动画,多媒体演示,Flex企业级应用程序开发,拥有众多成功案例,欢迎来电咨询。
 
COPYRIGHT BY WEBSTUDIO INTERACTIVE DESIGN Co.,Ltd. ALL RIGHTS RESERVED.
公司地址: 北京市朝阳区光华路15号院泰达时代中心4号楼704 邮编: 100026 EMAIL: WEBSTUDIO@WEBSTUDIO.COM.CN
电话: 010-59070059   (新号:010-59897050 010-59897060)  手机: 13693660520 传真: 010-59070059-801
京公网安备:110108006741      京ICP备:05013074号-1
王先生