AS3实例: LRC 歌词同步一、准备工作

既然要制作歌词同步程序,首先要准备一首歌,我们就以“周杰伦-青花瓷”为例。首先要下载这首“青花瓷.mp3”,保存为“C:\My Player\Music\青花瓷.mp3”。还要下载青花瓷的 LRC 文件,大家可以到网上下载(地址见附录),将文本内容保存为“C:\My Player\LRC\青花瓷.lrc”。我们的程序(类和FLA)则保存在“C:\My Player\”文件夹下。

青花瓷.lrc 文件:[ti:青花瓷]

[ar:周杰伦]

[al:我很忙]

[by:张琪]

[00:00.00]发送短信18到291199下载该歌曲到手机

[00:01.11]青花瓷

[03:36.49]

[00:21.39]素眉勾勒秋千话北风龙转丹

[00:26.08]屏层鸟绘的牡丹一如你梳妆

[00:30.46]黯然腾香透过窗心事我了然

[00:34.93]宣纸上皱边直尺各一半

[00:39.49]油色渲染侍女图因为被失藏

[00:43.83]而你嫣然的一笑如含苞待放

[00:48.30]你的美一缕飘散

[00:50.77]去到我去不了的地方

[02:23.97][00:55.77]

[03:01.92][02:25.63][00:56.90]天正在等烟雨

[03:03.57][02:27.91][00:58.99]而我在等你

[03:05.92][02:30.44][01:00.93]炊烟袅袅升起

[03:07.76][02:32.25][01:03.49]隔江千×××

[03:10.36][02:34.85][01:05.84]在平地书刻你房间上的飘影

[03:14.67][02:38.73][01:09.87]就当我为遇见你伏笔

[03:18.83][02:43.35][01:14.34]天正在等烟雨

[03:21.20][02:45.60][01:16.68]而我在等你

[03:23.71][02:48.01][01:18.99]月色被打捞起

[03:25.74][02:50.10][01:21.18]掩盖了结局

[03:28.33][02:52.54][01:23.72]如传世的青花瓷在独自美丽

[03:32.30][02:56.67][01:27.65]你眼的笑意

[01:50.25]色白花青的景已跃然于碗底

[01:54.69]临摹宋体落款时却惦记着你

[01:59.22]你隐藏在药效里一千年的秘密

[02:03.75]急溪里犹如羞花沾落地

[02:08.32]林外芭蕉 惹咒语

[02:10.57]梦幻的铜绿

[02:12.84]而我路过那江南小镇的等你

[02:17.19]在泼墨山水画里

[02:19.75]你从墨色深处被隐去

大家也可以把这个文本内容复制下来,然后在“C:\My Player\LRC\”下创建一个文本文档,将内容粘贴上去,再将文档保存为“青花瓷.lrc”,注意扩展名是“.lrc”。二、LRC 内容分析

准备工作完成了,下面分析一下这个 LRC 文件。之所以叫 LRC ,是因为它是 Lyric (歌词) 的缩写。这种格式真是一目了然,前面“[ ]”中的数字表示其后歌词的开始时间。例如,“[01:50.25]色白花青的景已跃然于碗底”表示在1分50.25秒时,歌词内容是“色白花青的景已跃然于碗底”。

还有一种形式是“[03:01.92][02:25.63][00:56.90]天正在等烟雨”这种形式常用于赋格部分(俗称:歌曲的高潮部分),它表示在 03:01.92, 02:25.63, 00:56.90 时的歌词都是“天正在等烟雨”。由于这种形式的存在,使后面的编程稍显复杂,不过没关系,凭借各位的聪明智慧一定没问题。

三、预备知识

1. ActionScript 3 中默认使用 Unicode 来解码外部文件,如果读取的文本不是 Unicode 编码,而是按照操作系统代码页编写的,比如 GB2312,那么需要先导入 flash.system.System 类,并在加载外部文本的语句前将 System.useCodePage 设为 true,默认情况下为 false,即默认不使用操作系统页解码。

如果 System.useCodePage = false 且外部 LRC 文件编码格式是 ANSI 的话,那么显示的中文歌词会是乱码。解决办法有两个:一是,将外部 LRC 文件编码格式改为 Unicode;二是,不改变外部文件编码格式,只在文档类中加入一句 System.useCodePage=true 即可。由于后一种方法使用简便,我们就采用第二种方法。

2.读取声音:

var sound:Sound=new Sound();sound.load(new URLRequest("Music/青花瓷.mp3"));

3.播放声音及获取当前播放时间(毫秒):

var sc:SoundChannel;

var sound:Sound=new Sound();

sound.load(new URLRequest("Music/青花瓷.mp3"));sc=sound.play();

stage.addEventListener(Event.ENTER_FRAME,EnterFrame);

function EnterFrame(evt:Event):void {trace(sc.position);

}

这里将 sc 声明为全局变量(或类变量),因为在多个方法中都要使用它。

4.读取外部文件:

var loader:URLLoader=new URLLoader();loader.load(new URLRequest("LRC/青花瓷.lrc"));

loader.addEventListener(Event.COMPLETE,LoadFinish);

function LoadFinish(evt:Event):void {

trace(evt.target.data);

}

5.将字符串按分隔符分隔为数组(String.split):

var str:String="FL Basic Theory Master";var array:Array=str.split(" ");

trace(array);

//输出数组:[[FL],[Basic],[Theory],[Master]]

str=" http://blog.sina.com.cn/yyy98";array=str.split(".");

trace(array);

//输出数组:[[http://blog],[sina,com],[cn/yyy98]]

6.简单的正则表达式应用:

1>获取匹配次数:

var Pattern:RegExp=/Window/g;

//意思是所有名为“Window”的字符串

var str:String="Windows seems like a Window, so called Windows OS! ";

trace(str.match(Pattern).length)

//结果:3

2>获取正确匹配:

var foo:RegExp=/[0-3][0-9]\/[0-1][0-9]\/[0-2][0-9][0-9][0-9]/g;

//意思是所有格式为“日/月/年”的字符串

var str:String="Date Format: 2006/12/25 2006-12-25 12/25/2007 25/12/2007"

trace(str.match(foo))

//结果:25/12/2007

7.字符串取子串操作(String.substr):

var str:String="[03:01.92][02:25.63][00:56.90]天正在等烟雨";

trace(str.substr(0,30));

//从0号索引开始,取30个字符

//结果:[03:01.92][02:25.63][00:56.90]

trace(str.substr(30));

//只写一个参数,表示从该索引处到字符串结束位置

//结果:天正在等烟雨

8.数组排序中比较函数的应用:

var a:Object={price:20,number:3};

var b:Object={price:10,number:7};

var c:Object={price:50,number:1};

var amountAry:Array=[a,b,c];function compare(paraA:Object,paraB:Object):int {

var resultA =paraA.price*paraA.number;

var resultB =paraB.price*paraB.number;

if (resultA > resultB) return 1;

if (resultA < resultB) return -1;

return 0;

}amountAry.sort(compare);

trace(amountAry[0].price);  //输出:50

trace(amountAry[1].price);  //输出:20

trace(amountAry[2].price);  //输出:10

四、LRC 的读取与存储转换(使用文档类设计)

1.读取 LRC 文件,这一步非常简单与读取普通的文本文件是一样的;

public function LRCPlayer() {

var loader:URLLoader=new URLLoader();loader.load(new URLRequest("LRC/青花瓷.lrc"));

loader.addEventListener(Event.COMPLETE,LoadFinish);

}

function LoadFinish(evt:Event):void {

trace(evt.target.data);

}2.将读取的 LRC 数据按行分割( "\n" 为换行符),数组的每一个元素代表 LRC 的一行内容;

function LoadFinish(evt:Event):void {

var list:String=evt.target.data;var listarray:Array=list.split("\n");

trace(listarray);

}3.在数组中提取每一行的时间及歌词,解决单时间序列的问题;

(注意!此段代码只作讲解,不以应用)

LRC 内容如下:

[00:43.83]而你嫣然的一笑如含苞待放

[00:48.30]你的美一缕飘散

[00:50.77]去到我去不了的地方

[03:01.92]天正在等烟雨

[03:03.57]而我在等你

[03:05.92]炊烟袅袅升起

[03:07.76]隔江千×××

代码如下:

function LoadFinish(evt:Event):void {

var list:String=evt.target.data;

var listarray:Array=list.split("\n");

for (var i=0; i < listarray.length; i++) {var info:String=listarray[i];//提取每行内容,用变量 info 保存var lyric:String=info.substr(10);

//将歌词内容提取到 lyric 变量中var ctime:String =info.substr(0,10);

//提取时间序列字串var ntime:Number=Number(ctime.substr(1,2))*60+Number(ctime.substr(4,5));//将时间字串转换为计算机可读取的时间

var obj:Object=new Object();obj.timer=ntime*1000;obj.lyric=lyric;LRCarray.push(obj);//将时间与歌词保存到一个 Object 中,并压入LRCarray 数组

trace(obj.timer,obj.lyric);

}

}

输出结果:

43830 而你嫣然的一笑如含苞待放

48300 你的美一缕飘散

50770 去到我去不了的地方

181920 天正在等烟雨

183570 而我在等你

185920 炊烟袅袅升起

187760 隔江千×××4.在LRC文件,还有多时间序列的存在,所以单时间序列算法不能满足实际需要,下面就来解决多时间序列问题;

LRC 内容如下:

[00:43.83]而你嫣然的一笑如含苞待放

[00:48.30]你的美一缕飘散

[00:50.77]去到我去不了的地方

[03:01.92][02:25.63][00:56.90]天正在等烟雨

[03:03.57][02:27.91][00:58.99]而我在等你

[03:05.92][02:30.44][01:00.93]炊烟袅袅升起

[03:07.76][02:32.25][01:03.49]隔江千×××

代码如下:

function LoadFinish(evt:Event):void {

var list:String=evt.target.data;

var listarray:Array=list.split("\n");

var reg:RegExp=/\[[0-5][0-9]:[0-5][0-9].[0-9][0-9]\]/g;

//建立正则表达式,范围:[00:00.00]~[59:59.99]

for (var i=0; i < listarray.length; i++) {var info:String=listarray[i];//提取每行内容,用变量 info 保存var len:int=info.match(reg).length;

//该行拥有时间序列的个数var timeAry:Array=info.match(reg);//将匹配的时间序列保存到 timeAry 数组中var lyric:String=info.substr(len*10);//根据每个时间序列占10个字符,找出歌词内容的起点

//将歌词提取到 lyric 变量中

for (var k:int=0; k < timeAry.length; k++) {var obj:Object=new Object();

var ctime:String=timeAry[k];

var ntime:Number=Number(ctime.substr(1,2))*60+Number(ctime.substr(4,5));

obj.timer=ntime*1000;

obj.lyric=lyric;

LRCarray.push(obj);

trace(obj.timer,obj.lyric);

}

//将时间序列转换为毫秒并与歌词一起保存为一个数组元素

}

}

输出结果:

43830 而你嫣然的一笑如含苞待放

48300 你的美一缕飘散

50770 去到我去不了的地方

181920 天正在等烟雨

145630 天正在等烟雨

56900 天正在等烟雨

183570 而我在等你

147910 而我在等你

58990 而我在等你

185920 炊烟袅袅升起

150440 炊烟袅袅升起

60930 炊烟袅袅升起

187760 隔江千×××

152250 隔江千×××

63490 隔江千×××5.将获得的 LRCarray 数组按起始时间排序,这对于按序读取歌词有重要意义;

LRCarray.sort(compare);private function compare(paraA:Object,paraB:Object):int{

if (paraA.timer > paraB.timer) {

return 1;

}

if (paraA.timer < paraB.timer) {

return -1;

}

return 0;

}

结果如下:

43830 而你嫣然的一笑如含苞待放

48300 你的美一缕飘散

50770 去到我去不了的地方

56900 天正在等烟雨

58990 而我在等你

60930 炊烟袅袅升起

63490 隔江千×××

145630 天正在等烟雨

147910 而我在等你

150440 炊烟袅袅升起

152250 隔江千×××

181920 天正在等烟雨

183570 而我在等你

185920 炊烟袅袅升起

187760 隔江千×××6.最后,随着音乐的播放,读取播放时间段内的歌词。用当前播放时间与 LRCarray 中的时间相比较,如果当前时间小于 LRCarray[i].timer 的时间,那么就显示 LRCarray[i-1].lyric 的歌词。为什么要显示 [i-1] 的歌词呢?比如说当前播放到第 500 秒,读取的 LRCarray[20].timer 时间是 400 秒,那么 i++ 。下一次读取的 LRCarray[21].timer 时间是 700 秒,这时当前播放时间小于读取的这个时间,就说明当前的第 500 秒仍处于 LRCarray[20].timer 的时间范围内。

var lrc_txt:TextField=new TextField();

var LRCarray:Array=new Array();

var sc:SoundChannel;

public function LRCPlayer() {

lrc_txt.width=500;

lrc_txt.selectable=false;

addChild(lrc_txt);

//歌词在文本 lrc_txt 中显示

var loader:URLLoader=new URLLoader();

loader.load(new URLRequest("LRC/青花瓷.lrc"));

loader.addEventListener(Event.COMPLETE,LoadFinish);

var sound:Sound=new Sound();

sound.load(new URLRequest("Music/青花瓷.mp3"));

sc=sound.play();

//播放声音,并生成 sc 变量,SoundChannel 类的实例

stage.addEventListener(Event.ENTER_FRAME,SoundPlaying);

//实时刷新歌词

}function SoundPlaying(evt:Event):void {

for (var i=1; i < LRCarray.length; i++) {

if (sc.position < LRCarray[i].timer) {

lrc_txt.text=LRCarray[i-1].lyric;

break;//找到歌词,跳出循环体}

lrc_txt.text=LRCarray[LRCarray.length-1].lyric;//找不到歌词,说明已超出了最后一句的时间,因此显示最后一句歌词}

}

五、全部代码(文档类 LRCPlayer.as):

package {

import flash.display.Sprite;

import flash.net.URLRequest;

import flash.net.URLLoader;

import flash.media.Sound;

import flash.media.SoundChannel;

import flash.events.Event;

import flash.text.TextField;

import flash.system.System;

public class LRCPlayer extends Sprite {

var lrc_txt:TextField=new TextField();

var LRCarray:Array=new Array();

var sc:SoundChannel;

public function LRCPlayer() {

System.useCodePage=true;

lrc_txt.width=500;

lrc_txt.selectable=false;

addChild(lrc_txt);

var loader:URLLoader=new URLLoader();

loader.load(new URLRequest("LRC/青花瓷.lrc"));

loader.addEventListener(Event.COMPLETE,LoadFinish);

var sound:Sound=new Sound();

sound.load(new URLRequest("Music/青花瓷.mp3"));

sc=sound.play();

stage.addEventListener(Event.ENTER_FRAME,SoundPlaying);

}

function SoundPlaying(evt:Event):void {

for (var i=1; i < LRCarray.length; i++) {

if (sc.position < LRCarray[i].timer) {

lrc_txt.text=LRCarray[i-1].lyric;

break;

}

lrc_txt.text=LRCarray[LRCarray.length-1].lyric;

}

}

function LoadFinish(evt:Event):void {

var list:String=evt.target.data;

var listarray:Array=list.split("\n");

var reg:RegExp=/\[[0-5][0-9]:[0-5][0-9].[0-9][0-9]\]/g;

for (var i=0; i < listarray.length; i++) {

var info:String=listarray[i];

var len:int=info.match(reg).length;

var timeAry:Array=info.match(reg);

var lyric:String=info.substr(len*10);

for (var k:int=0; k < timeAry.length; k++) {

var obj:Object=new Object();

var ctime:String=timeAry[k];

var ntime:Number=Number(ctime.substr(1,2))*60+Number(ctime.substr(4,5));

obj.timer=ntime*1000;

obj.lyric=lyric;

LRCarray.push(obj);

}

}

LRCarray.sort(compare);

}

private function compare(paraA:Object,paraB:Object):int {

if (paraA.timer > paraB.timer) {

return 1;

}

if (paraA.timer < paraB.timer) {

return -1;

}

return 0;

}

}

}

六、*无处不在的优化

至此,该程序已经可以顺利执行了,此处只讨论一下优化问题,看不懂可以跳过。

以这段代码为例:

function SoundPlaying(evt:Event):void {

for (var i=1; i < LRCarray.length; i++) {

if (sc.position < LRCarray[i].timer) {

lrc_txt.text=LRCarray[i-1].lyric;

break;

}

lrc_txt.text=LRCarray[LRCarray.length-1].lyric;

}

}

如果要进行优化,那么这个 for 循环,应该写成:

for (var i=1,j=LRCarray.length; i < j; i++) {… …}

这样在执行判断时,不必每次都进行 LRCarray.length 操作,该操用于读取数组长度,执行 Array 类的 length 方法,属于高级操作,花费的时间要比低级操作多。其实,只要读取一次长度,然后将结果保存在变量 j 中,每次判断时读取 j 的值即可。取值与赋值都属于低级别的操作,速度较快。同样的道理,在 if (sc.position < LRCarray[i].timer) {… …} 中的 sc.position 在每次判断时都要读取一遍,这时就应将它在循环之前保存到一个变量里,这段代码优化后应是这样:

function SoundPlaying(evt:Event):void {

var now:Number=sc.position;

for (var i=1,j=LRCarray.length; i < j; i++) {

if (now < LRCarray[i].timer) {

lrc_txt.text=LRCarray[i-1].lyric;

break;

}

lrc_txt.text=LRCarray[j-1].lyric;

}

}

在我们的文档类中还有几个地方用到了 for 循环,请大家按照上述方法自行优化。

其实,代码优化无处不在,其中的学问不胜枚举,有兴趣的朋友可以到我的博客中看一下关于代码优化的总结贴,见附录。

android 歌词同步 换行,AS3歌词同步详解相关推荐

  1. 运动控制器PSO位置同步输出(二):PSO模式详解

    本节我们主要去讲解一下多种PSO模式原理和使用的讲解,用户可根据实际需求灵活选择触发模式. 一.硬件说明 硬件选型的首要要求是支持PSO功能,再分析PSO的应用场合和轴数等选择具体的型号.本例以ZMC ...

  2. Android消息传递之EventBus 3.0使用详解

    前言: 前面两篇不仅学习了子线程与UI主线程之间的通信方式,也学习了如何实现组件之间通信,基于前面的知识我们今天来分析一下EventBus是如何管理事件总线的,EventBus到底是不是最佳方案?学习 ...

  3. Android 8.0学习(32)---Android 8.0源码目录结构详解

    Android 8.0源码目录结构详解 android的移植按如下流程:     (1)android linux 内核的普通驱动移植,让内核可以在目标平台上运行起来.     (2)正确挂载文件系统 ...

  4. Android 各大厂面试题汇总与详解(持续更新)

    介绍 目前网络中出现了好多各种面试题的汇总,有真实的也有虚假的,所以今年我将会汇总各大公司面试比较常见的问题,逐一进行解答.会一直集成,也会收集大家提供的面试题,如有错误,请大家指出,经过排查存在,会 ...

  5. Android进阶笔记:Messenger源码详解

    Messenger可以理解为一个是用于发送消息的一个类用法也很多,这里主要分析一下再跨进程的情况下Messenger的实现流程与源码分析.相信结合前面两篇关于aidl解析文章能够更好的对aidl有一个 ...

  6. android中怎么网络判断,Android中判断网络是否连接实例详解

    Android中判断网络是否连接实例详解 在android中,如何监测网络的状态呢,这个有的时候也是十分重要的,方法如下: public class ConnectionDetector { priv ...

  7. 【转】Android APK反编译就这么简单 详解(附图)

     转自:http://blog.csdn.net/vipzjyno1/article/details/21039349/ [置顶] Android APK反编译就这么简单 详解(附图) 分类: and ...

  8. android组件用法说明,Android第三方控件PhotoView使用方法详解

    Android第三方控件PhotoView使用方法详解 发布时间:2020-10-21 15:06:09 来源:脚本之家 阅读:74 作者:zhaihaohao1 PhotoView的简介: 这是一个 ...

  9. Android 颜色渲染(九) PorterDuff及Xfermode详解

    Android 颜色渲染(九)  PorterDuff及Xfermode详解 之前已经讲过了除ComposeShader之外Shader的全部子类, 在讲ComposeShader(组合渲染)之前,  ...

  10. android 截图 listview,Android屏幕及view的截图实例详解

    Android屏幕及view的截图实例详解 屏幕可见区域的截图 整个屏幕截图的话可以用View view = getWindow().getDecorView(); public static Bit ...

最新文章

  1. WPF框架的内存泄漏BUG
  2. 网线传输速度测试_弱电工程CAT5eCAT6CAT6aCAT7网线怎么选择
  3. Gitea 1.7.6 发布,一键部署的自助 Git 服务
  4. Toad 登陆数据库
  5. python写出的程序如何给别人使用-python如何写出表白程序
  6. 数据库连接工具HeidiSql介绍(支持MySQL,MariaDB,Microsoft SQL或PostgreSQL)
  7. Ubuntu16.04LTS安装ROS Kinetic
  8. RUNOOB python练习题17
  9. 2004-11-28+ 认识Duwamish 7.0(3)错误处理
  10. Git1天打卡 day13-查看仓库文件改动状态
  11. 清除浮动-:after伪元素法(HTML、CSS)
  12. 判断字符串是否为数字的函数
  13. 英语对混职场有用么?
  14. SW转发与MAC地址表
  15. 稳定kms服务器,kms服务器
  16. java-net-php-python-jsp安利达物流公司管理系统计算机毕业设计程序
  17. 精通 CSS+DIV 网页样式与布局 116
  18. QQ再次被大规模盗号
  19. JavaScript常见问题及答案
  20. Vue3和码上掘金实现猜数字小游戏

热门文章

  1. spring 通过yml格式配置log日志
  2. 矩阵分解 三角分解(LU分解)
  3. 算法导论习题(持续更新)
  4. 串口485接法图_rs485接口接线怎样操作?
  5. ReportMachine报表控件唯一官方论坛 - http://rmachine.5d6d.com/
  6. 设置华表Cell插件外观时的“闪烁”问题
  7. 开启阿里云linux下的pure-ftpd被动模式,解决flashfxp可连接但无法下载的问题
  8. 最详细全国区号汇总(json格式)
  9. Builder中使用Access数据库
  10. 【C#+SQL数据库】企业人事管理系统(含E-R图及源代码下载)