好像哪位老师曾说过,音乐是世界通用语言。是的,听不懂英文,但总能欣赏英文歌曲吧。

很早以前就想做个音乐播放器,但是由于跟随歌曲显示当前歌词的效果一直实现不了,所以我的想法一直无法实现。不过,最近创意不佳,没心情开发游戏了,于是闲下来搞点小发明。这次就先模仿一下手机QQ音乐中歌词显示效果。

恰巧,年末新歌蛮多的,于是我就选了一首比较好听的歌曲——手写的从前。

先看本次演示截图:

演示地址:http://yuehaowang.github.io/demo/lyrics/

上面的演示地址可能有一半以上的人都打不开,因为这首虽然很好听,但是啊,有11MB.……建议大家要听的话还是去网上自己找吧。

好了不扯远了,进入主题吧~

一,解读歌词文件

一般而言,歌词文件的格式都是一个时间对一句歌词的,例如:

[ti:手写的从前]
[ar:周杰伦]
[t_time:(04:57)]
[00:03.23]周杰伦 - 手写的从前
[00:06.56]词:方文山 曲:周杰伦
[00:11.43]这风铃跟心动很接近 这封信还在怀念旅行
[00:21.80]路过的爱情都太年轻 你是我想要再回去的风景
[00:31.79]这别离被瓶装成秘密 这雏菊美的像诗句
[00:39.30]而我在风中等你的消息 等月光落雪地
[00:48.92]等枫红染秋季等相遇 我重温午后的阳光
[00:58.24]将吉他斜背在肩上 跟多年前一样
[01:05.71]我们轻轻的唱 去任何地方
[01:14.54]我看着你的脸 轻刷著和弦
[01:20.00]情人节卡片 手写的永远
[01:24.05]还记得广场公园 一起表演
[01:29.27]校园旁糖果店 记忆里在微甜
[01:34.99]我看着你的脸 轻刷著和弦
[01:40.31]初恋是整遍手写的从前
[01:44.50]还记得 那年秋天说了再见
[01:49.51]当恋情已走远 我将你深埋在心里面
[02:16.25]微风需要竹林 溪流需要蜻蜓
[02:18.59]乡愁般的离开 需要片片浮萍
[02:21.17]记得那年的雨季 回忆里特安静
[02:23.84]哭过后的决定 是否还能进行
[02:26.52]我傻傻等待 傻傻等春暖花开
[02:28.80]等终等于等明等白 等爱情回来
[02:31.40]青春属于表白 阳光属于窗台
[02:33.95]而我想我属于 一个拥有你的未来
…………………………
[03:39.42]我重温午后的阳光
[03:44.10]将吉他斜背在肩上 跟多年前一样
[03:51.54]我们轻轻的唱 去任何地方
[04:00.53]我看着你的脸 轻刷著和弦
[04:05.98]情人节卡片 手写的永远
[04:10.15]还记得广场公园 一起表演
[04:15.03]校园旁糖果店 记忆里在微甜
[04:20.97]我看着你的脸 轻刷著和弦
[04:26.26]初恋是整遍手写的从前
[04:30.44]还记得 那年秋天说了再见
[04:35.50]当恋情已走远 我将你深埋在心里面

不难发现,在这里,除了前三行,方括号代表的不是区间而是时间。在前三行中,方括号里的内容分别代表:歌曲名,演唱者,音乐长度。但是这些算不上歌词吧,所以真正要处理的就是前三行后的内容。但是看上去要解析这些东西会很麻烦,那怎么办呢?想用正则表达式,正则又不熟悉……于是我只好想些歪门邪道的方法了。

其实要解析一种格式,说白了就是找规律。找规律嘛,听说测智商时就要看找规律的能力……

前面也说了,格式大致就是一个时间对应一句歌词,简化一下就是:[time] lyrics [time2] lyrics2 ...

经过我努力地挖掘规律,终于发现了其中的奥秘:

1)我们先把所有歌词后面\n给去掉,这样一来,歌词就连成了一排;

2)每一条时间+歌词之间通过“[”来分隔;

3)每一条中的时间和歌词之间通过“]”来分割;

有了这些发现,接下来的工作就是把这些字符串转换成程序里的数据结构。这里我准备选择了JSON作为装载数据的结构。有了这个JSON,我们要通过时间取歌词就可以直接到这个JSON中找了。

OK,歌词这种肤浅的东西就被我粗略地解读了。该上代码了。

二,手写的代码

先来看最基本的HTML代码。为了方便,本次开发用到了lufylegend.js,希望了解此引擎的朋友可以看看我以前的文章,大多数都是关于它的。

<!DOCTYPE html>
<html>
<head><title>Lyrics</title><meta charset="utf-8" /><meta name="viewport" content="width=device-width,initial-scale=1.0,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no" /><script type="text/javascript" src="./lib/lufylegend-1.9.7.simple.min.js"></script><script type="text/javascript" src="./lib/lufylegend.LoadingSample1-0.1.0.min.js"></script><script type="text/javascript" src="./js/ytMain.js"></script>
</head>
<body>
<div id="mylegend"></div>
</body>
</html>

我们还用到了ytMain.js这个文件,在这个文件里,首先是初始化引擎和一系列准备工作:

LInit(50, "mylegend", 350, 550, main);var datalist = {};
var music = null;function main () {document.body.style.background = "black";document.body.style.margin = "0px";document.body.style.padding = "0px";if (LGlobal.mobile) {LGlobal.stageScale = LStageScaleMode.SHOW_ALL;}LGlobal.screen(LGlobal.FULL_SCREEN);var loadData = [{path : "./js/ytLyrics.js"},{name : "pic", path : "./resource/albumpic.jpg"},{name : "lyrics", path : "./resource/shou_xie_de_cong_qian.js"},{name : "music", path : "./resource/shou_xie_de_cong_qian.mp3"}];var loadingLayer = new LoadingSample1();addChild(loadingLayer);LLoadManage.load(loadData,function (p) {loadingLayer.setProgress(p);},function (r) {datalist = r;loadingLayer.remove();addBackgroundPic();addTitle();addAlbumPic();addMusic();addLyrics();});
}

以上代码要做的就是首先使用LInit这个引擎内部函数初始化整个界面,并且进行全屏处理,然后通过静态类LLoadManage的load函数来加载图片和音乐,并将加载好的内容放到datalist中,然后调用addBackgroundPic,addTitle,addAlbumPic,addMusic,addLyrics这几个函数来添加显示对象和播放音乐。至于刚说到的LLoadManage和LInit等引擎中的用法可以参看lufylegend.js的API手册:

http://lufylegend.com/api/zh_CN/out/index.html

为了方便本地测试,我直接把歌词写到了shou_xie_de_cong_qian.js中。这么做可以避免使用ajax来读取文本文件而产生的本地运行时的问题。当然在实际应用中,歌词实质就是一个文本文件,是通过ajax等方式来读取这种文件,然后用我前面提到的类似的方法来解读文件。接下来先来看看shou_xie_de_cong_qian.js的内容:

var lyrics = "[ti:手写的从前][ar:周杰伦][t_time:(04:57)][00:03.23]周杰伦 - 手写的从前[00:06.56]词:方文山 曲:周杰伦[00:11.43]这风铃跟心动很接近 这封信还在怀念旅行[00:21.80]路过的爱情都太年轻 你是我想要再回去的风景[00:31.79]这别离被瓶装成秘密 这雏菊美的像诗句[00:39.30]而我在风中等你的消息 等月光落雪地[00:48.92]等枫红染秋季等相遇 我重温午后的阳光[00:58.24]将吉他斜背在肩上 跟多年前一样[01:05.71]我们轻轻的唱 去任何地方[01:14.54]我看着你的脸 轻刷著和弦[01:20.00]情人节卡片 手写的永远[01:24.05]还记得广场公园 一起表演[01:29.27]校园旁糖果店 记忆里在微甜…………";

图个方便,我就直接手动把歌词全部变成了一行。然后该到添加显示对象这一步了:

function addTitle () {var txt = new LTextField();txt.text = "手写的从前";txt.color = "white";txt.size = 25;txt.x = (LGlobal.width - txt.getWidth()) / 2;txt.y = 30;addChild(txt);
}function addBackgroundPic () {var bmpd = new LBitmapData(datalist["pic"]);var bmp = new LBitmap(bmpd);bmp.scaleX = bmp.scaleY = 2;bmp.x = (LGlobal.width - bmp.getWidth()) / 2;bmp.y = (LGlobal.height - bmp.getHeight()) / 2;addChild(bmp);var curtain = new LSprite();curtain.graphics.drawRect(0, "", [0, 0, LGlobal.width, LGlobal.height], true, "black");curtain.alpha = 0.8;addChild(curtain);
}function addAlbumPic () {var bmpd = new LBitmapData(datalist["pic"]);var bmp = new LBitmap(bmpd);bmp.scaleX = bmp.scaleY = 0.6;bmp.x = (LGlobal.width - bmp.getWidth()) / 2;bmp.y = 100;addChild(bmp);
}function addMusic () {music = new LSound(datalist["music"]);music.play();
}function addLyrics () {var lyricsLayer = new ytLyrics(music, getLyrics(lyrics));lyricsLayer.x = (LGlobal.width - lyricsLayer.getWidth()) / 2;lyricsLayer.y = 300;addChild(lyricsLayer);
}

这些函数中主要用到了lufylegend中的几个类:LTextField文本类,LBitmap和LBitmapData位图类,LSound音乐类,LSprite精灵类。具体用法请查阅API参考手册。在这里我们只谈addLyrics这个函数。这个函数中用到了我自己写的一个类ytLyrics,这个类派生自LSprite类,是个显示对象,作用就正如类名所示,是用来显示歌词的,有两个参数,第一个是当前播放的音乐,第二个是音乐的歌词。这个类留到过一会儿讲。

接下来是ytMain.js中解析歌词的函数:

function getLyrics (content) {var result = new Array();var cArr = content.split("[");cArr.shift();for (var i = 0; i < cArr.length; i++) {var o = cArr[i].split("]");if (o.length >= 2 && o[1] != "") {var tArr = o[0].split(":"), t = 0;if (tArr.length >= 2) {var mtArr = tArr[0].split(""), mt = 0;for (var k = 0; k < mtArr.length; k++) {if (Number(mtArr[k]) > 0) {mt += mtArr[k] * Math.pow(10, mtArr.length - k - 1);}}t += mt * 60;var stArr = tArr[1].split("."), intStArr = stArr[0].split(""), st = 0;for (var j = 0; j < intStArr.length; j++) {if (Number(intStArr[j]) > 0) {st += intStArr[j] * Math.pow(10, intStArr.length - j - 1);                  }}t += Number(st + "." + stArr[1]);}result.push({time : t, content : o[1]});}}return result;
}

代码算不上长,但是本次研究到的精髓就在这里。在shou_xie_de_cong_qian.js中,我们已经定义了一个lyrics变量来装载歌词,因此我们目前传给这个函数的参数就是lyrics。在这里我主要用到了String的split来切割字符串,并循环由切割而得到的数组,将其继续解析下去,直到分出时间和歌词。由于歌词的格式不算太难,所以我就只分了两次就获取了时间和相应的歌词(第一次按“[”来分,第二次按“]”来分)。为了去除没有歌词的那三项,我通过判断第二次切割得到的数组中第二个元素是否为""来屏蔽掉没有歌词的那几项。大家可以仔细看一下代码,不难发现我把两次切割而得到的结果先放入JSON,然后再把JSON放到了result这个数组中,最后返回这个result数组。

也许你会发现我用到了t这个变量,这个变量也就是最后传到result中歌词对应的时间。为什么要另外弄一个t来显示时间呢?原因很简单,在HTML5 Audio中获取当前播放到的时间时,得到的是以秒作为单位,但是我们的歌词中时间的形式是[分:秒],所以我们就必须要把这种格式转换为以秒为单位的数字。实现方法就在上面提供的代码中。主要还是用split来切割这些时间字符串,然后得到以分为单位的数字,和以秒得到的数字。然后t = 以分得到的数字*60+以秒的数字,就能得到最后的结果了。

解析完歌词,再来看显示歌词的类ytLyrics,此物乃lufylegend中LSprite之后,得其父之显示属性,又另添己能,终可显示歌词也(译文:这个类是lufylegend引擎中LSprite的子类,有父类LSprite各项显示方面的属性和方法,再加上另外拓展的功能,最终就能显示歌词了):

function ytLyrics (music, lyricsList) {var s = this;LExtends(s, LSprite, []);s.index = 0;s.list = lyricsList;s.music = music;s.scrollY = 0;s.contentLayer = new LSprite();s.addChild(s.contentLayer);s.showContent();s.addEventListener(LEvent.ENTER_FRAME, s.loop);
}ytLyrics.prototype.showContent = function () {var s = this;for (var i = 0; i < s.list.length; i++) {var txt = new LTextField();txt.text = s.list[i].content;txt.color = "white";txt.x = (LGlobal.width - txt.getWidth()) / 2;txt.y = s.contentLayer.getHeight() + 20;s.contentLayer.addChild(txt)}s.scrollY = 30;var maskLayer = new LSprite();maskLayer.graphics.drawRect(0, "", [0, 0, LGlobal.width, 200]);s.contentLayer.mask = maskLayer;s.contentLayer.filters = [new LDropShadowFilter()]
};ytLyrics.prototype.loop = function (e) {var s = e.currentTarget;if (s.index >= s.list.length) {return;}var ct = s.music.getCurrentTime();var minT = s.list[s.index].time,maxT = ((s.index + 1) < s.list.length) ? s.list[s.index + 1].time : s.music.length;if (ct >= minT && ct <= maxT) {var preTxt = s.contentLayer.getChildAt(s.index - 1);if (preTxt) {preTxt.color = "white";LTweenLite.to(s.contentLayer, 1, {y : s.contentLayer.y - preTxt.getHeight() - 20});}var currentTxt = s.contentLayer.getChildAt(s.index);if (currentTxt) {currentTxt.color = "#33FF00";}s.index ++;}
};

代码也不长,主要用到了ENTER_FRAME时间轴事件来驱动滚动歌词。具体来说就是在时间轴时间中,不停地获取歌曲当前时间,然后在歌词列表中得到当前歌词对应的时间和下一条歌词的时间,比较歌曲的当前时间和当前歌词对应的时间、下一条歌词的时间,以此得到是否播放下一条歌词。显示方面还是用到了lufylegend,还是那句话,想要了解参考手册即可。

ok,运行代码就得到了咱们想要的效果。

三,源代码下载

上面讲了点精髓部分,其他的边角还需要大家自己来研究了,如果有任何不清除的地方,欢迎大家到本文下方留言。我会尽力回复各位的~

源代码下载地址:https://github.com/yuehaowang/lyrics

本文到此就结束了,欢迎大家交流~

----------------------------------------------------------------

欢迎大家转载我的文章。

转载请注明:转自Yorhom's Game Box

http://blog.csdn.net/yorhomwang

欢迎继续关注我的博客

『HTML5梦幻之旅』 - 跟随歌曲显示当前歌词相关推荐

  1. 『HTML5梦幻之旅』-缤纷多姿的烟花效果

    天花无数月中开,五采祥云绕绛台.堕地忽惊星彩散,飞空旋作雨声来.怒撞玉斗翻晴雪,勇踏金轮起疾雷.更漏已深人渐散,闹竿挑得彩灯回. --明·瞿佑·<烟火戏> 记得每年过春节的那段时间,除了欣 ...

  2. 『HTML5梦幻之旅』 - 炫酷的节日贺卡

    刚过完春节,想必大家收到了各种祝福和贺卡吧-Y某我今年也为同学和家人准备了贺卡.不一样的是,我的贺卡可不是made from树,而是一行行代码凝聚而来的. 考虑到本次开发需要的功能不多,所以就没有用库 ...

  3. 『HTML5梦幻之旅』-滚动播放的幻灯片效果

    很久又没写博客了,一者是因为要上课,平时没有什么零碎的时间研究新东西:二者最近在开发一款塔防游戏,有整块的时间都拿去开发这个项目了.因此最近没有什么好的东西和大家分享.这周末看在游戏快完工的份上,抽出 ...

  4. obs点歌插件 html效果,OBS歌曲显示插件使用图文教程

    亲爱的葫芦丝们,大家好! (•̀ᴗ•́)و ̑̑ 响应群主大大的号召,小葫芦科技为广大的葫芦丝提供超级全面的各项插件教程.让萌新们远离困扰,小白们远离烦恼~ 闲话不多说,小编我开始讲解: (。・ω・) ...

  5. obs点歌插件 html效果,OBS 歌曲显示插件使用图文教程

    亲爱的葫芦丝们,大家好! (•̀ᴗ•́)و ̑̑ 响应群主大大的号召,小葫芦科技为广大的葫芦丝提供超级全面的各项插件教程.让萌新们远离困扰,小白们远离烦恼~ 闲话不多说,小编我开始讲解: (。・ω・) ...

  6. 关于『HTML5』第一弹

    关于『HTML5』:第一弹 建议缩放90%食用 祝各位国庆节快乐!!1

  7. 『HTML5挑战经典』是英雄就下100层-开源讲座(二)危险!英雄

    本篇为<『HTML5挑战经典』是英雄就下100层-开源讲座>第二篇,需要用到开源引擎lufylegend,可以到这里下载: 下载地址:http://lufylegend.googlecod ...

  8. 『遥かに仰ぎ、丽しの』游戏初回版特典原声集 GAME SP OST(下载、中日双语歌词)...

    记得苍月的结局音乐和内容一样很棒. 大师就给大家送上遥かに仰ぎ.丽しの GAME SP OST 本期策划:大师♂罗莊 翻译:天界白魔导 为什么网上已经有翻译,大师还要组织翻译歌词呢呢? 同学,这个翻译 ...

  9. MuPlayer『百度音乐播放内核』

    MuPlayer『百度音乐播放内核』 -- 跨平台.轻量级的音频播放解决方案. 多端(PC & WebApp)通用,统一的API调用方式 HTML5 Audio与Flash内核的平滑切换(支持 ...

最新文章

  1. 机器人操作系统来到Windows
  2. MySQL乱码问题解决步骤详解
  3. 山体等高线怎么看_地貌图知识(学了军事地图也会看)定向运动及野外生存入门技巧4...
  4. Unable to compile class for JSP的解决方法
  5. mysql 报错1045 - Access denied for user “root” @ 192.111.111.11 (using password: YES)
  6. BZOJ3239 Discrete Logging
  7. CodeForces - 78E Evacuation(最大流)
  8. java 继承 实现 会重写 方法吗_java 中继承,组合,重载,重写的实现原理 (转)...
  9. 1003 我要通过! (20 分)
  10. 160805、oracle查询:取出每组中的第一条记录
  11. Non-ASCII character \xe6 in file
  12. 腾讯封杀linux/mac QQ
  13. Vue图片上传删除预览操作
  14. php提交字符串中有加号(+)时会后台得到空格的问题
  15. pg数据库客户端linux,PostgresSQL客户端pgAdmin4使用
  16. String类的常用方法总结
  17. 工程项目管理工作流程图大全(打包带走)
  18. Android学习系列(33)--App应用之提交到各大市场渠道
  19. Redis源码初探(1)简单动态字符串SDS
  20. linux服务器503怎么解决,宝塔linux面板apache网站访问报错503 Service Unavailable怎么解决...

热门文章

  1. 数据分析04-朴素贝叶斯
  2. 让4K融入家庭 F1车迷带你走进赛车世界
  3. Python-Data-Science-Toolbox-Part-2
  4. 激光SLAM第三章作业去除运动畸变
  5. 华硕fl8000u是什么型号_华硕FL8000U笔记本
  6. 放大器跟随的Rf加与不加
  7. 开始为Oracle 10g的OCA认证做准备,先从1z0-007开始~
  8. 图片怎么在线压缩?图片在线压缩方法分享
  9. 如何删除网络邻居里面没用到的计算机?删除网络位置
  10. Unity3D视频绿幕抠图的实现及优化