关键词:
模拟制作网易云音乐(AudioContext)
记得好早前在慕课网上看到一款可视化音乐播放器,当前是觉得很是神奇,还能这么玩。由于当时刚刚转行不久,好多东西看得稀里糊涂不明白,于是趁着现在有时间又重新梳理了一遍,然后参照官网的API模拟做了一款网易播放器。没有什么创新的点,只是想到了就想做一下而已。
效果可以看这里:http://music.poemghost.com/,如果看不了,说明博主的服务器已经不在工作啦。(建议使用电脑浏览器打开,同时切换到手机模式来打开,因为在手机上测试时有问题,而且有很大性能损耗,经常会导致浏览器奔溃)
代码在这里:github
效果图一览:
看着自己洋洋洒洒写了快1000多行的js
,我现在心里也是一万屁草泥马飘过。当然其中还有很多代码没有经过提炼,很多变量可以公用,用对象化的方式来说写这个会更有条理,这个博主以后有时间再梳理一遍。下面来讲讲主要的实现过程。
一、整体思路
API
可以到https://webaudio.github.io/web-audio-api/#dom-audiobuffersourcenode上面去看,只是一个草案,并没有纳入标准,所以有些地方还是有问题,在下面我会说到我遇到了什么问题。但是这个草案上的东西其实可以做出很多其他的效果。比如多音频源来达到混音效果、音频振荡器效果等等...
整体的思路图如下:
大致上来说就是通过window
上的AudioContext
方法来创建一个音频对象,然后连接上数据,分析器和音量控制。最后通过BufferSourceNode
的start
方法来启动音频。
二、具体分析
2.1 路由
routes/index.js
router.get('/', function(req, res, next) {
fs.readdir(media, function(err, names) {
var first = names[0];
// 如果第一个文件不是mp3文件,说明是MAC系统
if (first.indexOf('mp3') === -1) {
first = names[1];
names = names.slice(1);
}
var song = first.split(' - ')[1].replace('.mp3', '');
var singer = first.split(' - ')[0];
if (err) {
console.log(err);
} else {
res.render('index', {
title: '网易云音乐',
music: names,
posts: listPosts,
song: song,
singer: singer,
post: listPosts[0]
});
}
});
});
这里mac
平台和windows
不同,mac
文件夹会有一个.DS_Store
文件,因此作了一点小处理。
另外由于用的海外服务器,所以请求mp3
资源的时候会有很长时间,因此我把音频资源放在了七牛云,而不是从本地获取,但是数据还是在本地拿,因为并没有用到数据库。
2.2 主页面
页面运用了手淘的flexible
,因此在最开始切换到手机模式的时候,可能需要刷新一下浏览器才能显示正常。样式采用的是预处理sass
,感兴趣的可以去看一下代码
2.3 创建音频
/**
* 创建音频
* @param AudioBuffer buffer AudioBuffer对象
* @return void
*/
function createAudio(buffer) {
// 如果音频是关闭状态,则重新新建一个全局音频上下文
if (ac.state === 'closed') {
ac = new (window.AudioContext || window.webkitAudioContext)();
}
audioBuffer = buffer;
ac.onstatechange = onStateChange;
// 创建BufferSrouceNode
bufferSource = ac.createBufferSource();
bufferSource.buffer = buffer;
// 创建音量节点
gainNode = ac.createGain();
gainNode.gain.value = gainValue;
// 创建分析节点
analyser = ac.createAnalyser();
analyser.fftSize = fftSize;
bufferSource.onended = onPlayEnded;
// 嵌套连接
bufferSource.connect(analyser);
analyser.connect(gainNode);
gainNode.connect(ac.destination);
}
结合上面的图,这里创建音频的代码就比较好理解了。
2.4 播放
播放其实是一个非常简单的API
,直接调用BufferSourceNode
的start
方法即可,start
方法有两个我们会用到的参数,第一个是开始时间,第二个是时间位移,决定了我们从什么时候开始,这将在跳播的时候会用到。
另外有一个注意的点是,不能再同一个BufferSourceNode
上调用两次start
方法,否则会报错。
bufferSource && bufferSource.start(0);
2.5 获取频谱图数据
/**
* 获取音频解析数据
* @return void
*/
function getByteFrequencyData() {
var arr = new Uint8Array(analyser.frequencyBinCount);
analyser.getByteFrequencyData(arr);
renderCanvas(arr);
renderInter = window.requestAnimationFrame(getByteFrequencyData);
}
通过不断触发这个函数,将最新的数据填充到一个8位的无符号数组中,进而开始渲染数据。此时的音频范围默认设置为256。
2.6 音量调节
音量调节也有现成的API
,这点也没什么可讲的。
// gain 的值默认为1
// 因此这里如果想做继续音量放大的可以大于1
// 但是太大可能会出现破音效果,大家感兴趣可以试试
gainNode.gain.value = [0 ~ 1];
2.7 暂停与恢复播放
我在AudioBufferSourceNode
上找了好久,本来以为有start/stop
方法,那么就会有类似于puase
方法之类的,但是遗憾的是,确实没有。最开始我也不知道怎么做播放和暂停,但是好在天无绝人之路,意外发现在全局的AudioContext
上有两个方法resume/suspend
,这也是实现播放和暂停的两个方法。
/**
* 恢复播放
* @return null
*/
function resumeAudio() {
playState = PLAY_STATE.RUNNING;
// 放下磁头
downPin();
// 在当前AudioContext被挂起的状态下,才能使用resume进行重新激活
ac.resume();
// 重新恢复可视化
resumeRenderCanvas();
// 重启定时器
startInter && clearInterval(startInter);
startInter = setInterval(function() {
renderTime(start, executeTime(startSecond));
updateProgress(startSecond, totalTime);
startSecond++;
}, 1000);
}
/**
* 暂停播放
* @return null
*/
function suspendAudio() {
playState = PLAY_STATE.SUSPENDED;
// 停止可视化
stopRenderCanvas();
// 收起磁头
upPin();
startInter && clearInterval(startInter);
// 挂起当前播放
ac.suspend();
}
2.8 跳动播放
跳动播放需要用到开始时间,这里我默认设置为0,接下来就是时间位移了。通过跳动播放进度条的游标,我们不难计算出我们应该播放的时间。
这里有一个问题,我之前也说到过,就是在同一个AudioBufferSourceNode
上不能同时start
两次,那么也就是说,我如果这里再直接调用start(0, offsetTime)
将会报错,是的,这里我也卡了好久,最后再一个论坛(是哪个我倒是忘记了)上给了一个建议,不能同时在一个AudioBufferSourceNode
上start
两次,那就在不同的AudioBufferSourceNode
上start
,也就意味着我可以新建一个节点,然后依然用之前ajax
请求到的数据来创建一个新的音频数据。实验是可以行的。
/**
* 跳动播放
* @param number time 跳跃时间秒数
* @return void
*/
function skipAudio(time) {
// 先释放之前的AudioBufferSourceNode对象
// 然后再重新连接
// 因为不允许在一个Node上start两次
analyser && analyser.disconnect(gainNode);
gainNode && gainNode.disconnect(ac.destination);
bufferSource = ac.createBufferSource();
bufferSource.buffer = audioBuffer;
// 创建音频节点
gainNode = ac.createGain();
gainNode.gain.value = gainValue;
// 创建分析节点
analyser = ac.createAnalyser();
analyser.fftSize = fftSize;
bufferSource.connect(analyser);
analyser.connect(gainNode);
gainNode.connect(ac.destination);
bufferSource.onended = onPlayEnded;
bufferSource.start(0, time);
playState = PLAY_STATE.RUNNING;
changeSuspendBtn();
// 开始分析
getByteFrequencyData();
// 填充当前播放的时间
renderTime(start, executeTime(time));
startSecond = time;
// 放下磁头
downPin();
// 重新开始计时
startInter && clearInterval(startInter);
startSecond++;
startInter = setInterval(function() {
renderTime(start, executeTime(startSecond));
updateProgress(startSecond, totalTime);
startSecond++;
}, 1000);
}
2.9 列表循环
列表循环用到了bufferSource
上的一个回调方法onended
,在播放完成之后就自动执行下一曲。
/**
* 播放完成后的回调
* @return null
*/
function onPlayEnded() {
var acState = ac.state;
// 在进行上一曲和下一曲或者跳跃播放的时候
// 如果调用stop方法,会进入当前回调,因此要作区分
// 上一曲和下一曲的时候,由于是新的资源,因此采用关闭当前的AduioContext, load的时候重新生成
// 这样acState的状态就是suspended,这样就不会出现播放错位
// 而在跳跃播放的时候,由于是同一个资源,因此加上skip标志就可以判断出来
// 发现如果是循环播放,onPlayEnded方法不会被执行,因此采用加载相同索引的方式
if (acState === 'running' && !skip) {
var index = getNextPlayIndex();
loadMusic(playItems[index], index);
}
}
这里有一个坑就是当我点击了上一曲和下一曲的时候,发现也会执行这个回调,因此点击下一曲的时候,实际上播放的是下两曲的歌曲。因此这里做了区分,当点击上一曲和下一曲的时候,会给skip
设置为true
,这样就不会执行这个方法中默认的行为。
三、手机端会有的问题
之前说过,建议不要在手机端运行,因为会有一些问题,主要表现在:
AudioContext
需要兼容,我在Chrome
和Safari
测试的时候一直得不到音频数据,之后才发现需要兼容写法,不然页面播放不了。兼容写法为:webkitAudioContext
。- 最开始加载音频的时候,
AudioContext
默认的状态是suspended
,这也是我最开始最纳闷的事,当我点击播放按钮的时候没有声音,而点击跳播的时候会播放声音,后来调试发现走到了resumeAudio
中。 - 性能还是有一定的问题,在手机上播放的时候,经常会出现卡死的现象。渲染柱状条的时候感到有明显的卡顿。、
- 由于手机浏览器上页面高度还包括地址栏、导航条高度,因此,唱片可能会超出范围
四、总结
我就是发现了一个好玩的东西,然后发了兴致好好玩了一下,之前照着别人的代码敲了一遍代码,后来发现什么都忘了,不如自己动手来得牢靠。有些东西一时看不懂,不要死磕,那是因为水平不够,不过记住就好,慢慢学习,然后再来攻克它,以此共勉。
第2次作业:关于网易云音乐
1我选择网易云音乐。其实也差不多三个月没用网易云了,只是最近没有什么听音乐的需求,其余产品要么很少用要么不用。网易云给我的印象要优于其它产品。网易云音乐是基于大数据云技术的发展在“云时代”诞生的。发展... 查看详情
网易云音乐api
网易云音乐APIhttp://47.103.29.206:3000/ 查看详情
网易云音乐api
网易云音乐APIhttp://47.103.29.206:3000/ 查看详情
网易云音乐怎么搜歌单?
参考技术A问题一:网易云音乐怎么搜索别人的歌单?右上角,搜索框问题二:网易云音乐怎么查找别人的歌单可以的,搜索后下方选择歌单即可查看,如图问题三:网易云音乐怎么看谁收听过自己的歌单点击评论里面有歌单的... 查看详情
网易云音乐案例分析
1.1介绍产品相关信息 你选择的产品是?网易云音乐 为什么选择该产品作为分析? 在这些产品中,我比较中意网易云音乐,因为我听歌常用网易云音乐,想对它更深入的了解。 该产品是怎么诞生的(在什么样... 查看详情
网易云音乐——一份情感的寄托
...sp;1.1介绍产品相关信息你选择的产品是?我选择的产品是网易云音乐。为什么选择该产品作为分析?网易云音乐是我从高中就开始使用的音乐app,一直用到现在。我熟悉它喜欢它欣赏他,觉的这个产品身上有许多值得分析的地方... 查看详情
网易云音乐评论爬虫:歌曲的全部评论
用过网易云音乐听歌的朋友都知道,网易云音乐每首歌曲后面都有很多评论,热门歌曲的评论更是接近百万或者是超过百万条.现在我就来分享一下如何爬取网易云音乐歌曲的全部评论,由于网易云音乐的评论都做了混淆加密处... 查看详情
第二次作业:网易云音乐产品分析
2.1介绍产品相关信息1.我选择的产品是网易云音乐 2.因为我一直在用它听音乐,相对比较了解这款软件 3.网易云音乐是网易于2013年4月23日正式发布的一款音乐产品,此时中国无线音乐市场规模达397.1亿元,其中酷狗音乐最... 查看详情
《网易云音乐》扫描本地音乐方法
如果我们的手机中有本地音乐文件,就可以在网易云app中进行扫描,然后添加到软件内进行播放。网易云怎么扫描本地音乐?下面我就来为大家介绍一下具体的方法。网易云怎么扫描本地音乐?1、打开网易云音乐,点击我的,... 查看详情
关于网易云音乐的一些感想
...音乐,然后找个特定的人来演唱.当时我第一个想到的就是网易云音乐,我们都知道网易云的推荐算法是很强大的,它推送的音乐真的匹配度挺高的.我在想网易云为什么不利用这些数据来扩展业务呢,利用网易的数据完全可以侵入娱乐... 查看详情
网易云音乐v2.2.0.190597绿色便携版本
网易云音乐PC客户端现已更新至v2.2.0.190597,2.0版全新视觉设计,轻盈扁平风格,版本控们不妨更新吧,良心软件!网易音乐原生纯绿色,无碍眼广告和弹窗,拥有良好口碑和广大用户的肯定及赞赏。网易云音乐,听见好时光!网... 查看详情
网易云怎么找歌手
参考技术A网易云找歌手的步骤如下:1、首先,在自己的手机上找到网易云音乐APP,点击打开。2、打开后,在界面的右上角点击放大镜的图标。3、在点开后的界面找到【按歌手搜索】,点击进入。4、进入之后,在界面中会显示... 查看详情
第二次作业:为网易云音乐打电话
我选择的产品是网易云音乐,之所以选择此软件,是因为自己手机上唯一的音乐播放器就是它,对它十分喜爱。2013年,在QQ音乐、酷狗音乐大行其道的时候,网易云音乐出现在大家视野里。2014年推出pc端和适配iPad的HD版本。2014... 查看详情
第二次作业网易云音乐
2.1介绍产品相关信息你选择的产品是? 网易云音乐为什么选择该产品作为分析? 网易云音乐是我从大学开始就一直使用的播放器,大量的歌单,人性化的设计都让我非常满意,其中的私人fm,日推都非常对胃口... 查看详情
软工书面作业1——分析网易云音乐的创新
在音乐APP中,网易云音乐算是地地道道的后发者,它的诞生比酷狗音乐、酷我音乐、QQ音乐等晚了数年,却以其卓越的创新能力、非凡的用户体验,很快超越了诸多先发者,成为如今国内数一数二的移动音乐应用软件。以下,我... 查看详情
网易云音乐app特点优势与发展建议毕业论文
参考技术A网易云音乐APP特点优势与发展建议毕业论文 紧张又充实的大学生活将要谢下帷幕,毕业生要通过最后的毕业论文,毕业论文是一种比较重要的检验学生学习成果的形式,那么你有了解过毕业论文吗?以下是我为大... 查看详情
网易云音乐下载
...可。http://music.163.com/song/media/outer/url?id=1405702656.mp3获取网易云音乐播放链接首先打开网易云音乐官网,搜索自己喜欢的歌曲。然后按F12打开开发者工具。按F5刷新一下页面后,依次点击Network 查看详情
编写一个网易云音乐爬虫程序(代码片段)
本次借助wxPython编写一个网易云音乐的爬虫程序,能够根据一个歌单链接下载其下的所有音乐前置说明网易云音乐提供了一个下载接口:http://music.163.com/song/media/outer/url?id=xxx所以只需要拿到歌单中每首歌曲对应的id即可1.分析歌单... 查看详情