Flash8制作超酷MP3播放器全解

酷软学堂

一、场景中的准备工作

1.设计播放器界面

MP3播放器的界面,我们完全可以发挥创意,打造最具个性化的界面。不过,为了学习本中所讲述的代码,我们必须在这个界面上包含一些特定命名元件。在界面中,按照功能的划分,可以包含以下3类元件:

flash1.jpg
呈现场景中元件的布置

(1)按钮

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

025.jpg

(2)动态文本框

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

026.jpg

(3)影片剪辑

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

027.jpg

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()方法即可实现。但习惯上,大家都使用一个可以拖动的滑块来控制音量大小。所以这个问题的关键是,将滑块与滑条的位置关系换算为音量的大小。

flash2.jpg
音量控制的滑块与滑条的位置

换算方法公式如下:

滑块的位移/滑块的移动范围 = 当前音量/最大音量

这里,需要注意一个细节,即滑块的移动范围,应该是:

滑条的位置+滑条的长度-滑块的宽度

如果漏掉了滑块的宽度,那么最后你会发现滑块的可移动范围不对。

编码如下:

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;

}

};

2.LRC歌词的读取与分析

(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播放器制作完毕。