返回列表 回复 发帖

MP3及LRC歌词同步显示卡拉OK播放器

这几天,做了个mp3播放器。发上来大家提提意见。
功能:
1、通过浏览来加载外部的mp3歌曲,可以加载一首,也可以加载多首歌曲。
2、同时加载相同文件名的 .lrc 格式歌词。这是标准格式的歌词文本,网上到处可下,不用转格式,直接读取。
3、用组件显示mp3歌曲列表。
4、播放控制,可在列表中进行,也可用常规按钮。有“播放” ”暂停” “停止” “上一首” “下一首” “循环模式” 。
5、有声道控制,立体声,混和声(L+R),L+L,R+R等模式,可模拟卡拉OK功能。
6、播放时间进度显示。歌曲时间和播放时间显示。
7、歌词同步显示,并有演唱进度色条(这个同步不理想,若要理想,需要调整歌词文本文件)。

主要使用了:
声音类、上载文件类、组件List。注意要Flash8才能正常播放。

先发效果swf文件上来,下载到本机。准备好mp3和相应歌词文件,注意:歌词要与mp3文件同名,不同的扩展名。歌词是.lrc,歌曲是.mp3,并放在相同的文件夹里。swf与歌曲可以不在相同文件夹。

打开swf后先用浏览按钮找到歌曲,打开就可以播放。

界面很糟,哈哈。

[ 本帖最后由 ybzjllj 于 2006-12-26 13:23 编辑 ]

test_lrc3.swf (57.95 KB)

向大家学习!
代码有些乱糟糟的,慢慢看吧。
舞台上有些实例,名称看源文件吧。不列举了

  1. System.useCodepage = true;//使用操作系统编码
  2. //通过浏览来加载外部的mp3歌曲
  3. //import flash.net.FileReference;
  4. import flash.net.FileReferenceList;
  5. import mx.styles.CSSStyleDeclaration;
  6. var SouMc:MovieClip=this.createEmptyMovieClip("SouMc",100);//装载声音的MC
  7. var Sou:Sound=new Sound(SouMc);
  8. var LoadTxt:LoadVars = new LoadVars();
  9. var TxtArray:Array = [];//存放歌词的数组
  10. var mylist:Array = [];//存放歌曲列表的数组
  11. var TxtArr:Array;
  12. var SouT:Number=0;//播放头位置标记
  13. var LoadLrcSouOk:Boolean=false;//加载成功标志
  14. 歌词.进度._width=0;
  15. 歌词.ii=0;//歌词指针
  16. //列表组件样式
  17. List_mp3.vScrollPolicy="auto";
  18. _global.styles.ScrollSelectList.setStyle("color",0xFFFF66);//文本颜色
  19. _global.styles.ScrollSelectList.setStyle("backgroundColor",0x990000);//背景颜色
  20. _global.styles.ScrollSelectList.setStyle("borderColor",0xFFFF00);//边框色
  21. _global.styles.ScrollSelectList.setStyle("rollOverColor",0xFFFFCC);//鼠标滑过高亮色
  22. _global.styles.ScrollSelectList.setStyle("textRollOverColor",0xCC0000);//鼠标滑过文本色
  23. var listener:Object = new Object();//共用侦听对象
  24. listener.change = function(evt){
  25. SouStop();
  26. listNum=evt.target.selectedIndex;
  27. SouLoad(listNum);
  28. }
  29. List_mp3.addEventListener("change", listener);
  30. prev_btn.enabled=false;
  31. next_btn.enabled=false;
  32. var listNum:Number;//歌曲指针
  33. var listMaxNum:Number;//歌曲数
  34. var allTypes:Array = [];
  35. var 浏览类型:Object = new Object();
  36. 浏览类型.description = "选择歌曲(*.mp3)";
  37. 浏览类型.extension = "*.mp3";
  38. allTypes.push(浏览类型);
  39. var fileRef:FileReferenceList = new FileReferenceList();
  40. listener.onSelect = function(file:FileReferenceList) {
  41. mylist = file.fileList;//获取选中文件数组
  42. listNum = 0;
  43. listMaxNum = mylist.length;
  44. List_mp3.removeAll();//清空列表
  45. for(i=0;i<listMaxNum;i++){
  46.   List_mp3.addItem((i+1)+"."+mylist[i].name,i);//显示播放列表
  47. }
  48. SouLoad(0); //立即加载第一曲
  49. };
  50. fileRef.addListener(listener);
  51. //歌曲浏览按钮
  52. op_mp3.onRelease = function() {
  53. SouStop();
  54.     fileRef.browse(allTypes);//触发浏览
  55. };
  56. //下一首曲
  57. next_btn.onRelease = function() {
  58. listNum++;
  59. SouStop();
  60. SouLoad(listNum);
  61. };
  62. //上一首曲
  63. prev_btn.onRelease = function() {
  64. listNum--;
  65. SouStop();
  66. SouLoad(listNum);
  67. };
  68. //歌词加载处理
  69. LoadTxt.onData = function(data) {
  70. if(data!=undefined){
  71.   TxtArr = data.split("\r\n"); //以回车换行符为界分开存入数组
  72.   //读出lrc文件各种信息
  73.   for(var i=0;i<TxtArr.length;i++){
  74.    if(TxtArr[i].indexOf("[ti:")==0){
  75.     TxtArray.ti = TxtArr[i].slice(4,TxtArr[i].indexOf("]"));//歌曲名
  76.    }else if(TxtArr[i].indexOf("[ar:")==0){
  77.     TxtArray.ar = TxtArr[i].slice(4,TxtArr[i].indexOf("]"));//演唱者
  78.    }else if(TxtArr[i].indexOf("[al:")==0){
  79.     TxtArray.al = TxtArr[i].slice(4,TxtArr[i].indexOf("]"));//专集名(唱片名)
  80.    }else if(TxtArr[i].indexOf("[by:")==0){
  81.     TxtArray.by = TxtArr[i].slice(4,TxtArr[i].indexOf("]"));//出品人
  82.    }else if(TxtArr[i].indexOf("[offset:")==0){
  83.     TxtArray.offset = TxtArr[i].slice(8,TxtArr[i].indexOf("]"));//歌词偏移量
  84.    }else if(TxtArr[i].indexOf("[")==0 && TxtArr[i].indexOf("]")==9){
  85.     TxtArr[i]=TxtArr[i].split("]");//把时间和歌词分开。
  86.     for(j=0;j<TxtArr[i].length-1;j++){
  87.      TxtArray.push([Number(TxtArr[i][j].slice(1,3))*60000+Number(TxtArr[i][j].slice(4,6))*1000+Number(TxtArr[i][j].slice(7))*10,TxtArr[i][TxtArr[i].length-1]]);
  88.     }   
  89.    }
  90.   }
  91.   TxtArray.sortOn("0",16);//按时间顺序排序歌词数组  
  92.   歌词.txt.text=TxtArray.ti;
  93.   dir_lrc.text="歌曲名:"+TxtArray.ti+"\r\n演唱者:"+TxtArray.ar+"\r\n唱片名:"+TxtArray.al+"\r\n出品人:"+TxtArray.by
  94.   lrc_Sou();//歌词与歌曲匹配吗?
  95. }else{
  96.   歌词.txt.text="歌词加载失败!";
  97. }
  98. }
  99. //加载外部MP3后处理
  100. Sou.onLoad = function(success:Boolean) {
  101. if (success) {
  102.   //trace(Math.floor(Sou.duration/60000)+":"+Math.floor(Sou.duration%60000/1000)+"."+Sou.duration%60000%1000);//总时间
  103.   更新();
  104.   LoadLrcSouOk=false;
  105.   lrc_Sou();//歌词与歌曲匹配吗?
  106. }else{
  107.   _root.dir_lrc.text="MP3加载失败!";
  108. }
  109. };
  110. //开始播放
  111. SouPlay.onPress=function(){
  112. 更新();
  113. }
  114. //暂停
  115. SouPause.onPress=function(){
  116. //trace(Math.floor(Sou.position/60000)+":"+Math.floor(Sou.position%60000/1000)+"."+Sou.position%60000%1000;//输出播放头位置,写歌词时间用。
  117. if(SouT!=0){
  118.   SouPlay.enabled=true;
  119.      Sou.stop();
  120.      delete SouMc.onEnterFrame;
  121. }
  122. }
  123. //停止
  124. SouStopBtn.onPress=function(){
  125. SouStop();
  126. }
  127. //循环模式
  128. z_btn.onPress=function(){
  129. this.play();
  130. }
  131. //Sou_LR声道转换
  132. var 左声道:Object={ll:100,lr:0,rr:0,rl:100};
  133. var 右声道:Object={ll:0,lr:100,rr:100,rl:0};
  134. var 混合声:Object={ll:50,lr:50,rr:50,rl:50};
  135. var 立体声:Object={ll:100,lr:0,rr:100,rl:0};
  136. Sou_LR.onPress=function(){
  137. if(this._currentframe==1){
  138.   Sou.setTransform(左声道);
  139.   this.gotoAndStop(2);
  140. }else if(this._currentframe==2){
  141.   Sou.setTransform(右声道);
  142.   this.gotoAndStop(3);
  143. }else if(this._currentframe==3){
  144.   Sou.setTransform(混合声);
  145.   this.gotoAndStop(4);
  146. }else if(this._currentframe==4){
  147.   Sou.setTransform(立体声);
  148.   this.gotoAndStop(1);
  149. }
  150. }
  151. //播放完毕
  152. Sou.onSoundComplete = function() {
  153. SouStop();
  154. if(z_btn._currentframe==1){
  155.   (listNum>=listMaxNum-1)?listNum=0:listNum++;
  156.   SouLoad(listNum);
  157. }else if(z_btn._currentframe==2){
  158.   if(listNum<listMaxNum-1){
  159.    listNum++;
  160.    SouLoad(listNum);
  161.   }
  162. }
  163. }
  164. SouDir(Sou.position);
  165. //停止播放函数,进行一些复位工作
  166. function SouStop(){
  167. delete SouMc.onEnterFrame;
  168. SouT=0;
  169. Sou.stop();
  170. SouPlay.enabled=true;
  171. SouDir(SouT);
  172. _root.歌词.txt.text=TxtArray[0][1];
  173. _root.歌词.进度._width=0;
  174.   歌词.ii = 0;
  175. }
  176. //加载新歌曲
  177. function SouLoad(Num:Number){
  178. LoadLrcSouOk=false;
  179. TxtArray=[];
  180. prev_btn.enabled=(listNum<=0)?false:true;
  181. next_btn.enabled=(listNum>=listMaxNum-1)?false:true;  
  182. List_mp3.selectedIndex=Num;
  183. LoadTxt.load("/"+mylist[Num].name.slice(0,-4)+".lrc");//加载歌词文本
  184. Sou.loadSound("/"+mylist[Num].name, false);//加载外部MP3
  185. dir_lrc.text=mylist[Num].name;  
  186. }
  187. //有关声音信息显示的函数,传入参数:当前声音位置
  188. function SouDir(t):Void{
  189. if(Sou.duration!=undefined){
  190.   时间.text=Math.floor(t/60000)+":"+Math.floor(t%60000/1000)+"/"+Math.floor(Sou.duration/60000)+":"+Math.floor(Sou.duration%60000/1000);//显示播放时间:当前位置/总时间
  191. }else{
  192.   时间.text="";
  193. }
  194. mcb._width=t/Sou.duration*_root.mca._width;//进度条
  195. }
  196. //更新
  197. function  更新():Void{
  198. SouPlay.enabled=false;
  199. Sou.start(SouT/1000);//从指定位置开始播放
  200. SouMc.onEnterFrame = function() {
  201.   SouT=Sou.position;//更新播放头位置标记
  202.   SouDir(SouT);
  203.   //更新歌词显示
  204.   for(var i=0;i<TxtArray.length;i++){
  205.    if(Number(TxtArray[i][0])<=SouT && Number(TxtArray[i+1][0])>SouT && 歌词.ii!=i){
  206.      歌词.ii = i;
  207.     _root.歌词.txt.text=TxtArray[i][1];
  208.    }
  209.   }
  210.   _root.歌词.进度._width=(SouT-Number(TxtArray[歌词.ii][0]))/(Number(TxtArray[歌词.ii+1][0])-Number(TxtArray[歌词.ii][0]))*歌词.txt.textWidth;
  211.     }
  212. }
  213. //歌词与歌曲匹配检查
  214. function lrc_Sou(){
  215. if(LoadLrcSouOk){
  216.   if(Sou.duration<=TxtArray[TxtArray.length-1][0]||Sou.duration-60000>TxtArray[TxtArray.length-1][0]){
  217.    _root.歌词.txt.text="歌词与歌曲不匹配!";
  218.    _root.dir_lrc.text="歌词与歌曲不匹配!";
  219.    TxtArray=[];
  220.   }else{
  221.    TxtArray.push([Sou.duration,"结束"]);
  222.   }
  223. }else{
  224.   LoadLrcSouOk=true;  
  225. }
  226. }
  227. stop();
复制代码

test_lrc3.fla (353 KB)

向大家学习!
不错,嘿嘿,原来FLASH还支持加载LRC呀,呵呵
第一次看见加载lrc文件的,实用,
非常感谢
怎么就老是说歌词不匹配啊
歌词不匹配,只有两个判断:
1、歌词中的时间,比MP3歌曲实际时间长。
2、歌词中的最大时间,比MP3歌曲时间短1分钟以上。
可改AS中的参数来改变它。
向大家学习!
很好的贴子啊,还真没想到flash可以支持这个,如果想功能更强大,那么就需要用到3。0的脚本写一些有个音频频谱显示的东西了,你做的真好,向你学习,如果有机会多多交流啊。
好东西,顶一个
只能添加一首歌啊,要添加多首要怎么弄?
如果一个文件夹里有多首歌曲,就同时选中,确定。
向大家学习!
歌词与音乐同步性稍差 每句都慢一些!
你看一下这个 蓝屋老康的 歌词同步 曲库选择 视频播放 在线留言 功能很多的!

http://www.hslk.com/ml/mlplayer.swf
看帖就回是美德~~~:lol

回复 #11 ttbbtt 的帖子

歌词 里有个这样的参数,[offset:***]
我没利用。

还有我没用流式声音,看这句:
Sou.loadSound("/"+mylist[Num].name, false);//加载外部MP3

谢谢批评!
向大家学习!
其实歌词整句显示与演唱同步容易实现。
我考虑要把每个字的节拍都与演唱同步,我现在只能自己写歌词文体来实现。
能不能直接利用网上现有的lrc?
向大家学习!
终于打到了,打印了源代码,看了N天,看明白了,嘿嘿!
下载的歌曲可以播放,但歌词显示不了???总是提示歌词与歌曲不匹配,我的LRC歌词文件与歌曲文件同名,且在同一目录下。。。

回复 #16 樵下客 的帖子

#6楼
向大家学习!
楼主,这种情况怎么解决?在AS中怎么设定
源代码中这段if判断:
if(Sou.duration<=TxtArray[TxtArray.length-1][0]||Sou.duration-60000>TxtArray[TxtArray.length-1][0])

声音的长度Sou.duration小于歌词时间,或者比歌词时间还长1分钟(60000)以上。
向大家学习!
很好   研究研究啊
写得很好啊,只是 歌曲和歌词 必须 是对于 flash是一个 可以用相对地址表示的文件,
楼主的这个是同级地址,
提示: 作者被禁止或删除 内容自动屏蔽
好好研究下源代码!!
支持!
我顶
好好研究下源代码!!
支持!
请问你可以教我用flash做个速度表吗?
像这样的文章,以前寂寞火山有出过这样的一个播放器 都挺不错的
  顶..
我没有天份。。但有后天的自信、顽固、拼搏、坚持。。。。。。FLASH技术交流群:56785596
顶,值得好好研究!
顶,看来跟高手的距离相差还是很远啊~
非行尊里手怎么能做出如此之播放器,只恨相见得晚。
返回列表