Flash8制作超酷MP3播放器全解
酷软学堂
一、场景中的准备工作
1.设计播放器界面
MP3播放器的界面,我们完全可以发挥创意,打造最具个性化的界面。不过,为了学习本中所讲述的代码,我们必须在这个界面上包含一些特定命名元件。在界面中,按照功能的划分,可以包含以下3类元件:

(1)按钮
按钮包括播放、停止、暂停、上一首、下一首、左声道、立体声、右声道。建议将这些元件创建为“按钮”,这将方便你实现“鼠标滑过”、“鼠标按下”和“鼠标感应区域”等操作。按钮的命名规则如下:

(2)动态文本框
动态文本框包括音乐播放时的时间显示文本框、LRC歌词显示文本框。动态文本框的命名规则如下:

(3)影片剪辑
影片剪辑包括用于音量控制的滑条与滑块、用于音乐进度控制的滑条与滑块,以及用于显示歌词列表的影片剪辑(可以在场景中画一个矩形图形,再转为影片剪辑,歌曲播放列表将显示在这个影片剪辑中)。影片剪辑的命名规则如下:

2.准备外部文件
为了能最方便地更新播放器中的音乐,我们的MP3文件、MP3播放列表等都置于SWF文件之外,在这里,我们需要预先准备好这些外部文件。
建立一个名为data的文件夹,这个文件夹与你的fla文档位于同一路径,复制几首MP3文件到该文件夹中,MP3文件命名为该歌曲名。复制这几首MP3文件对应的LRC歌词文件到data文件夹中,LRC歌词文件命名为该歌曲名。
在data文件夹中新建一个文本,在其中添加如下信息:
<?xml version="1.0" encoding="gb2312"?>
<合久必婚/>
<旧欢如梦/>
<月半小夜曲/>
<大会堂演奏厅/>
<爱/>
将其另存为“mp3.xml”,该文件将用于创建MP3文件索引,从第二至第五行,即书写具体的MP3歌曲名,也即是你的MP3文件以及LRC文件的命名。
打开每一个LRC文件,在开头添加“t=”字符,这是为了让Flash能够读取文件中的内容。假设我们打算与后台程序配合,这一步可以省略,但如果单独使用Flash来实现,却不得不多此一举。
提示:LRC文件一般都可以在网上获取。你也可以直接跳至本文第五部分,参看LRC文件的具体制作。
为叙述方便,在这里,我们假设外部有5个MP3文件,其命名如上文XML文件中的内容所示。在播放不同的MP3文件时,我们只需更替换data目录中的MP3文件、LRC歌词文件,以及更新XML文件中的索引就可以了。
二、实现MP3播放控制
MP3播放控制是指“播放”、“暂停”、“停止”等按钮的控制,这可以通过调用Sound对象提供的方法来实现。
首先来尝试用Sound对象载入MP3音乐并播放。新建一个fla文件,打开“动作”面板,测试如下代码:
s = new Sound(this);
s.loadSound(“data/合久必婚.mp3”,true);
如果你的音乐文件没有问题,那么应该可以听到声音了。既然可以听音乐,那么你已经拥有一个MP3播放器了。
注意:某些情况下,MP3文件无法被Flash载入,我们可采用音频编辑软件,将无法载入的MP3文件打开,然后将其另存为mp3Pro制式的MP3文件,即可解决。
下面,让我们来实现对声音的最基本的控制,即“播放”、“停止”与“暂停”按钮的功能。其中,“停止”按钮可直接使用Sound对象的stop()方法实现。而在编写“播放”与“暂停”按钮时,需要注意一个问题,当单击“播放”按钮时,会有几种情况发生:
若事先单击“停止”按钮,则单击“播放”按钮应从头开始播放;
若事先单击过“暂停”按钮,则单击“播放”按钮应从上次暂停处播放。这需要我们在暂停时记录下音乐已播放的毫秒数,在start()方法中进行参数传递;
当音乐从头播放或停止时,需将音乐已播放的毫秒数清除。
编码如下:
//“播放”按钮的功能
playBt.onRelease = function() {
if (pauseTime == undefined) {
s.start();
} else {
s.start(Math.floor(pauseTime/1000));
pauseTime = undefined;
}
};
//“停止”按钮的功能
stopBt.onRelease = function() {
s.stop();
pauseTime = s.position;
};
//“暂停”按钮的功能
pauseBt.onRelease = function() {
s.stop();
pauseTime = s.position;
};
至此,MP3播放器大部分的按键我们都做好了。
三、实现MP3声音控制
1.立体声和单声道
Sound对象中的setPan()方法能够让我们轻易改变音乐播放时的声道,从而实现单声道与立体声效果的切换。setPan()方法的参数值范围为-100~100,当值为-100时为左声道,值为100时为右声道,值为0时则为立体声。下面,我们对“左声道”、“右声道”、“立体声”按钮编写代码以实现功能。编码如下:
//“左声道”按钮的功能
eqLeftBt.onRelease = function() {
s.setPan(-100);
};
//“右声道”按钮的功能
eqRightBt.onRelease = function() {
s.setPan(100);
};
//“立体声”按钮的功能
plBt.onRelease = function() {
s.setPan(0);
};
2.音量控制的滑条与滑块
控制音量本身很简单,用Sound对象的setVolume()方法即可实现。但习惯上,大家都使用一个可以拖动的滑块来控制音量大小。所以这个问题的关键是,将滑块与滑条的位置关系换算为音量的大小。

换算方法公式如下:
滑块的位移/滑块的移动范围 = 当前音量/最大音量
这里,需要注意一个细节,即滑块的移动范围,应该是:
滑条的位置+滑条的长度-滑块的宽度
如果漏掉了滑块的宽度,那么最后你会发现滑块的可移动范围不对。
编码如下:
function setVolumeCon() {
//初始化滑块的位置
volumeSquall._x = volumeBar._x+volumeBar._width-volumeSquall._width;
//初始化滑块能够移动的最小坐标
volumeSquall.sx = volumeBar._x;
//初始化滑块能够移动的最大坐标
volumeSquall.ex = volumeBar._x+volumeBar._width-volumeSquall._width;
//当鼠标按下,滑块在滑动范围内开始被拖拽,
volumeSquall.onPress = function() {
this.startDrag(false, this.sx, this._y, this.ex, this._y);
};
//当鼠标释放,停止滑块的拖拽,并由滑块的位置计算得出音量的大小,并通过Sound对象的setVolume()方法改变音量大小
volumeSquall.onRelease = volumeSquall.onReleaseOutside=function () {
this.stopDrag();
s.setVolume(Math.floor(100*(this._x-this.sx)/(this.ex-this.sx)));
};
}
四、实现MP3音乐进度控制
1.音乐进度控制的滑块与滑条
这里我们遇到的麻烦与前面音量控制一样,为了满足用户的操作习惯,我们必须将滑块与滑条的位置关系换算为MP3音乐的播放起点。
换算公式如下:
滑块的位移/滑块的移动范围 = 新的播放时间起点/总播放时间
接下来,看看下面这段与“音量控制”类似的编码:
function setPlayCon() {
//初始化滑块的坐标
playSquall._x = playBar._x;
//初始化滑块能够移动的最小坐标
playSquall.sx = playBar._x;
//初始化滑块能够移动的最大坐标
playSquall.ex = playBar._x+playBar._width-playSquall._width;
//当鼠标按下,滑块开始被拖动,并通过设置timeStop标记,停止计时器的运行
playSquall.onPress = function() {
this.startDrag(false, this.sx, this._y, this.ex, this._y);
timeStop = true;
};
//当鼠标释放,滑块停止拖拽,通过换算得到MP3音乐的新播放起点,通过Sound对象的start()方法让音乐从新的起点开始播放。并通过设置stopTime标记,让计时器重新运行
playSquall.onRelease = playSquall.onReleaseOutside=function () {
this.stopDrag();
s.start(Math.floor((this._x-this.sx)/(this.ex-this.sx)*s.duration/1000));
timeStop = false;
};
}
有一点需要补充,即当音乐播放时,控制进度的滑块是跟随音乐进度而移动,并非像音量控制中的滑块一样静止不动。所以我们再编制一个计算滑块位置的函数,原理与换算公式并没有变,只是反过来运算一次而已,即从音乐的播放时间,计算到滑块的位置。这里,我们需要用到计时器来随时更新滑块的位置。计时器的问题放到后面再讲。
编码如下:
function setPlayMove() {
playSquall._x = (s.position/s.duration)*(playSquall.ex-playSquall.sx)+playSquall.sx;
}
测试时,你可能会看见滑块并未跟随音乐移动,那是因为我们还没有定义“计时器”的原因,不过,通过滑块的拖动来控制音乐进度的功能已经实现了。
2.音乐播放时间的显示
播放时间的显示能够让我们的播放器看起来更专业,但要实现这个功能也麻烦。基本上,我们要解决以下几个问题:
用Sound对象的属性取得音乐的已播放时间和音乐播放的总时间,但返回的数值单位为毫秒,我们必须将毫秒换算为秒;
没有一个MP3播放器显示的时间是秒数,而是以“分:秒”形式显示,因此我们需要将秒数换算为“分:秒”的形式;
当音乐的播放时间只有一位数时,为了界面的美观和专业,我们需要在前面补“0”,也就是说,当音乐的播放时间为“1”时,我们需要显示“01”。
编码如下:
function setTime() {
//定义一个变量P,用于存放已播放时间
var p;
//定义一个变量d,用于存放总的播放时间
var d;
//取得已播放时间,先除以1000,将毫秒换算为秒,再与60取模,得到已播放时间的秒数
var p_second = Math.floor(s.position/1000)%60;
//取得已播放时间,先除以1000,将毫秒换算为秒,再除以60取商,得到已播放时间的分钟数
var p_munite = Math.floor(Math.floor(s.position/1000)/60);
//取得总播放时间,先除以1000,将毫秒换算为秒,再与60取模,得到总播放时间的秒数
var d_second = Math.floor(s.duration/1000)%60;
//取得总播放时间,先除以1000,将毫秒换算为秒,再除以60取商,得到总播放时间的分钟数
var d_munite = Math.floor(Math.floor(s.duration/1000)/60);
//如果已播放的秒数只有1位数,那么在前面补“0”
if (p_second<10) {
p_second = "0"+p_second;
}
//如果已播放的分钟数只有1位数,那么在前面补“0”
if (p_munite<10) {
p_munite = "0"+p_munite;
}
//如果总播放时间的秒数只有1位数,那么在前面补“0”
if (d_second<10) {
d_second = "0"+d_second;
}
//如果总播放的分钟数只有1位数,那么在前面补“0”
if (d_munite<10) {
d_munite = "0"+d_munite;
}
//将已播放的秒数与已播放的分钟数组合为“分:秒”形式的已播放时间
p = p_munite+":"+p_second;
//将总播放时间的秒数与总播放时间的分钟数组合为“分:秒”形式的总播放时间
d = d_munite+":"+d_second;
//将已播放时间与总播放时间以“|”隔开,并赋值给动态文本框timeText的text属性
timeText.text = p+"|"+d;
}
下面,我们需要用到计时器来随时更新动态文本框的内容。也即是说,每一帧触发setTime()函数,使时间的显示总是最新的。为了能尽快完成这部分的测试,下面编写计时器代码:
//创建一个空的MovieClip
this.createEmptyMovieClilp("timeMc",1);
//此函数用于控制所有与播放时间有关的显示
function timeControl() {
timeMc.onEnterFrame = function() {
//如果标记变量为true,则什么事都不做,相当于计时器停止
if (timeStop) {
return;
}
//调用setTime()函数刷新音乐播放时间
setTime();
//调用setPlayMove()函数刷新音乐进度滑块的位置
setPlayMove();
//调用showLrc()函数控制歌词的显示
}
需要注意的是,有时我们需要停止这个计时器,可通过一个变量的值来传达该信息。在上面编码中的timeStop变量就是为我们传递这个信息的。现在你可以看到测试结果了,但播放MP3时,显示时间在刷新,而且控制音乐进度的滑块也开始随音乐移动了。
五、实现MP3播放列表与歌词读取
1.建立MP3播放列表
在这里,我们要用到一个新的对象,即XML对象。XML是一种使用非常方便的数据组织形式,在各种不同的编程语言之间交互时,常常采用XML进行沟通。
此外,还有一个要注意的问题,即默认情况下,Flash播放器将按照Unicode来解释外部文本。一般情况下,我们的文本格式并非保存为Unicode格式,导致Flash中对外部文本的显示出现问题。这里有两种办法可以解决,一是将所有的外部文本另存为Unicode格式,二是在“动作”面板中加入一句代码:System.useCodepage = true;,它将通知flashplayer以运行播放器的操作系统的传统代码页来解释外部文本文件。这里我们采用第二种方法。
(1)创建XML对象,读取XML数据
编码如下:
System.useCodepage = true;
//创建一个新xml对象
myXml = new XML();
//忽略xml文件中的空白
myXml.ignoreWhite = true;
//读取我们做的XML文件,即data目录下的mp3.xml文件
myXml.load("data/mp3.xml");
//设置xml的载入事件,触发XML的解析函数
myXml.onLoad = function(ok) {
if (ok) {
parseXML();
}
};
(2)解析XML数据,将播放列表保存到数组
编码如下:
//此函数用于XML文件的解析
function parseXML() {
//取得XML文件的节点数,也即是MP3歌曲的总数
n = myXml.childNodes.length;
//将每一首mp3歌曲的名字放进mp3Name数组
for (var i = 0; i<=n-1; i++) {
mp3Name.push(myXml.childNodes[i].nodeName);
}
//调用函数创建播放列表
createList();
}
(3)创建动态文本,显示歌曲列表
编码如下:
//此函数用于创建播放列表
function createList() {
//在mp3List中创建影片剪辑mp3
mp3List.createEmptyMovieClip("mp3", 1);
for (var i = 0; i<=mp3Name.length-1; i++) {
//在mp3List中的MP3影片剪辑中创建动态文本,用以显示歌曲名
mp3List.mp3.createTextField("mp3_"+i, i, 0, i*15, 0, 0);
//为动态文本框设置自动拉伸
mp3List.mp3["mp3_"+i].autoSize = true;
//为动态文本框的text属性赋值
mp3List.mp3["mp3_"+i].text = mp3Name[i];
(4)创建透明按钮,用于鼠标单击事件
编码如下:
//在mp3List中的mp3影片剪辑中创建用于响应鼠标的按钮
mp3List.mp3.createEmptyMovieClip("bt"+i, i+mp3Name.length);
//设置各按钮的位置,使之与歌曲名的文字对应
mp3List.mp3["bt"+i]._y = mp3List.mp3["mp3_"+i]._y+3;
//通过简单的绘制,给每个按钮绘制一个透明的矩形,用以感应鼠标
with (mp3List.mp3["bt"+i]) {
beginFill(0x00ff00, 10);
moveTo(0, 0);
lineTo(160, 0);
lineTo(160, 12);
lineTo(0, 12);
lineTo(0, 0);
endFill();
}
//为每个按钮赋予相应的属性,记录下它对应的MP3文件的位置,以及LRC文件的位置,以便载入时使用
mp3List.mp3["bt"+i].mp3Url = "data/"+mp3Name[i]+".mp3";
mp3List.mp3["bt"+i].lrcUrl = "data/"+mp3Name[i]+".lrc";
mp3List.mp3["bt"+i].i = i;
//设置各按钮的单击事件,调用playMp3函数,播放音乐
mp3List.mp3["bt"+i].onRelease = function() {
playMp3(this.mp3Url, this.lrcUrl);
list = this.i;
};
}
}
最后,还剩下一个问题,就是当MP3音乐播放列表的长度超过了你事先制作的mp3List的高度怎么办?我们可以通过遮罩来隐藏多余播放器列表,再通过单击按钮控制影片剪辑MP3的上下移动来实现播放列表的滚动。我们前面已经讲过了关于滑块拖动,以及滑块的位置与其他数值的换算公式,也通过音量控制与进度控制来演示了两个例子,你完全可以举一反三。
在末尾的按钮onRelease事件中,我们触发了playMp3函数,这是我们的MP3音乐的播放函数。前面我们写过如下代码:
s = new Sound(this);
s.loadSound(“data/合久必婚.mp3”,true);
这是为了让大家测试Sound对象的常用属性与方法,实际编码中我们是不会这样写的,因为音乐的播放是通过按钮单击事件来触发的,并非一打开播放器就自动播放固定的歌曲。所以,现在你可以把这两行代码删掉了。
我们来写一个MP3播放函数,用于按钮单击的触发事件。编写这部分代码时,需要考虑到当播放的歌曲到达播放列表的最前与最后时的情况。编码如下:
function playMp3(u1, u2) {
//先删除原来的Sound对象,以便删除Sound对象中遗留的旧属性
delete s;
//创建新的Sound对象
s = new Sound(this);
//载入歌曲
s.loadSound(u1, true);
//当歌曲播放完毕时,如果列表中还有下一首,则切换到下一首继续播放。list变量用于记录当前播放的歌曲,所以将它更新。
s.onSoundComplete = function() {
if (list playMp3(playMp3(mp3List.mp3["bt"+(list+1)].mp3Url)); list += 1; } }; //载入并分析相应的LRC歌词文件 loadLrc(u2); //初始化LRC歌词显示 lrcInit(); } 在末尾调用的loadLrc()与lrcInit()函数用于LRC歌词文件的读取和分析,以及显示初始化,我们将在后面一节中讲解。 在建立了播放列表之后,我们还可以通过“上一首”与“下一首”按钮来实现对这个列表的操作。通过读取我们定义的list变量,取得上一首或下一首的歌曲路径,然后调用playMp3函数,播放新的MP3音乐。这里需要注意,当歌曲列表播放至最前与最后时,需要进行处理,否则将出现错误。编码如下: //此函数用于切换歌曲上一首 prevBt.onRelease = function() { //若列表进行到最前,则播放最后一首,否则播放上一首 if (list == 0) { playMp3(mp3List.mp3["bt"+(n-1)].mp3Url); list = n-1; } else { playMp3(mp3List.mp3["bt"+(list-1)].mp3Url); list -= 1; } }; //此函数用于切换歌曲下一首 nextBt.onRelease = function() { //若列表进行到最后,则播放最前一首,否则播放下一首 if (list == n-1) { playMp3(mp3List.mp3["bt"+0].mp3Url); list = 0; } else { playMp3(mp3List.mp3["bt"+(list+1)].mp3Url); list += 1; } }; (1)分析LRC文件 由于Flash本身无法识别LRC歌词文件,需要对它进行解析。下面,让我们来看看LRC文件的格式: t=[ti:爱] [ar:小虎队] [al:] [by:天涯孤人] [00:00.93]爱 小虎队 [04:00.06][03:07.95][01:38.02][00:07.56]喔…… [02:06.99][00:37.53]把你的心、我的心串一串 [02:10.50][00:40.18]串一株幸运草、串一个同心圆 [02:14.63][00:44.32]让所有期待未来的呼唤 [02:17.86][00:47.87]趁青春做个伴 [03:14.76][02:22.35][00:52.11]別让年轻越长大越孤单 [03:17.88][02:25.62][00:55.49]把我的幸运草种在你的梦田 [03:22.13][02:29.52][00:59.69]让地球随我们的同心圆 [03:25.57][02:33.08][01:02.86]永远的不停转 [03:29.98][02:37.53][01:07.16]向天空大声的呼唤说声我爱你 [03:33.36][02:41.14][01:11.20]向那流浪的白云说声我想你 [03:37.27][02:45.04][01:15.06]让那天空听得见、让那白云看得见 [03:41.51][02:49.38][01:19.12]谁也擦不掉我们许下的诺言 [03:45.75][02:53.47][01:23.30]想带你一起看大海说声我爱你 [03:49.38][02:57.32][01:26.97]给你最亮的星星说声我想你 [03:53.40][03:01.12][01:31.04]听听大海的誓言、看看执着的蓝天 [03:57.40][03:05.15][01:34.91]让我们自由自在地恋爱 其中,用方括号括起来的数字是歌词显示的时间,而数字之后的内容是歌词,每一行中的几个括起来的时间,代表本行末尾的歌词出现的时间。例如,“[03:57.40][03:05.15][01:34.91]让我们自由自在地恋爱”,表示“让我们自由自在地恋爱”这句歌词将出现在03:57.40、03:05.15、01:34.91这3个时间。 (2)解析LRC文件的思路 将每一个时间放进数组,并按从小到大排序; 将每一个时间对应的歌词放进数组,并与时间一一对应; 用计时器进行判断,当越过某个时间,即出现对应的歌词。 (3)编写代码实现 //下面是载入LRC歌词文件 function loadLrc(u) { //定义一个loadVars对象,用它来载入歌词文件 v = new LoadVars(); v.load(u); //当载入成功后,调用readLrc函数进行分析 v.onLoad = function(ok) { if (ok) { readLrc(); } }; } //分析LRC文件的函数 function readLrc() { //lrc_t数组用于放置时间 var lrc_t = []; //lrc_c数组用于放置歌词 var lrc_c = []; //用“[”将内容进行第一次划分 var a = v.t.split("["); //定义数组b用于再次划分数组a的内容 var b = []; for (var i = 0; i<=a.length-1; i++) { //定义b为2维数组 b[i] = []; //在b[i]中填入数组a的相应元素划分后的内容 b[i] = a[i].slice(0, a[i].length-1).split("]"); for (var k = 0; k<=b[i].length-1; k++) { //判断b数组元素特征,若首字符为数字,则该元素为时间,否则,该元素为歌词,如此将时间与歌词放进不同数组中 if (b[i][k].substr(0, 1).charCodeAt()>=48 and b[i][k].substr(0, 1).charCodeAt()<=57) { lrc_t.push(b[i][k]); //歌词数组的长度一定会少于时间数组的长度,我们事先作好准备,将歌词数组中与时间数组对应的空位以任意字符串填入 lrc_c.push("iloveyou"); } else { lrc_c[lrc_c.length-1] = b[i][k].slice(0, b[i][k].length-1); } } } //将正确的内容填充进歌词数组中的空位,使歌词与时间一一对应 for (var i = 0; i<=lrc_c.length-1; i++) { var k = i; while (lrc_c[i] == "iloveyou") { lrc_c[i] = lrc_c[k++]; } } //去掉两个数组前面的空元素 lrc_t.shift(); lrc_c.shift(); //将时间数组的元素转化为毫秒时间 for (var i = 0; i<=lrc_t.length-1; i++) { lrc_t[i] = lrc_t[i].split(":")[0]*60000+lrc_t[i].split(":")[1]*1000; } //将时间数组与歌词数组组合,并整理为sortOn()方法能够操作的形式 for (var i = 0; i<=lrc_t.length-1; i++) { lrc.push({index_time:lrc_t[i], index_text:lrc_c[i]}); } //将数组进行排序 lrc.sortOn("index_time", 16); } //此函数用于LRC歌词显示的初始化 function lrcInit() { r = 0; } //此函数在计时器中用于歌词的显示 function showLrc() { //如果歌曲播放的时间(毫秒)越过时间刻度,则在动态文本框里显示相应的歌词 if (s.position>=lrc[r].index_time) { lrcText.text = lrc[r].index_text; r++; } } 最后,不要忘记在我们的计时器里补充showLrc()函数,计时器将在每一帧刷新显示的歌词。 至此,一个个性化的超酷MP3播放器制作完毕。2.LRC歌词的读取与分析