网易云音乐

模拟制作网易云音乐(AudioContext)

记得好早前在慕课网上看到一款可视化音乐播放器,当前是觉得很是神奇,还能这么玩。由于当时刚刚转行不久,好多东西看得稀里糊涂不明白,于是趁着现在有时间又重新梳理了一遍,然后参照官网的API模拟做了一款网易播放器。没有什么创新的点,只是想到了就想做一下而已。

效果可以看这里:http://music.poemghost.com/,如果看不了,说明博主的服务器已经不在工作啦。(建议使用电脑浏览器打开,同时切换到手机模式来打开,因为在手机上测试时有问题,而且有很大性能损耗,经常会导致浏览器奔溃)

代码在这里:github

效果图一览:

看着自己洋洋洒洒写了快1000多行的js,我现在心里也是一万屁草泥马飘过。当然其中还有很多代码没有经过提炼,很多变量可以公用,用对象化的方式来说写这个会更有条理,这个博主以后有时间再梳理一遍。下面来讲讲主要的实现过程。

一、整体思路

API可以到https://webaudio.github.io/web-audio-api/#dom-audiobuffersourcenode上面去看,只是一个草案,并没有纳入标准,所以有些地方还是有问题,在下面我会说到我遇到了什么问题。但是这个草案上的东西其实可以做出很多其他的效果。比如多音频源来达到混音效果、音频振荡器效果等等...

整体的思路图如下:

大致上来说就是通过window上的AudioContext方法来创建一个音频对象,然后连接上数据,分析器和音量控制。最后通过BufferSourceNodestart方法来启动音频。

二、具体分析

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,直接调用BufferSourceNodestart方法即可,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)将会报错,是的,这里我也卡了好久,最后再一个论坛(是哪个我倒是忘记了)上给了一个建议,不能同时在一个AudioBufferSourceNodestart两次,那就在不同的AudioBufferSourceNodestart,也就意味着我可以新建一个节点,然后依然用之前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需要兼容,我在ChromeSafari测试的时候一直得不到音频数据,之后才发现需要兼容写法,不然页面播放不了。兼容写法为:webkitAudioContext
  • 最开始加载音频的时候,AudioContext默认的状态是suspended,这也是我最开始最纳闷的事,当我点击播放按钮的时候没有声音,而点击跳播的时候会播放声音,后来调试发现走到了resumeAudio中。
  • 性能还是有一定的问题,在手机上播放的时候,经常会出现卡死的现象。渲染柱状条的时候感到有明显的卡顿。、
  • 由于手机浏览器上页面高度还包括地址栏、导航条高度,因此,唱片可能会超出范围

四、总结

我就是发现了一个好玩的东西,然后发了兴致好好玩了一下,之前照着别人的代码敲了一遍代码,后来发现什么都忘了,不如自己动手来得牢靠。有些东西一时看不懂,不要死磕,那是因为水平不够,不过记住就好,慢慢学习,然后再来攻克它,以此共勉。

时间: 2024-08-27 12:53:19

网易云音乐的相关文章

Python爬一爬网易云音乐

结果 对过程没有兴趣的童鞋直接看这里啦. 评论数大于五万的歌曲排行榜 首先恭喜一下我最喜欢的歌手(之一)周杰伦的<晴天>成为网易云音乐第一首评论数过百万的歌曲! 通过结果发现目前评论数过十万的歌曲正好十首,通过这前十首发现: 薛之谦现在真的很火啦~ 几乎都是男歌手啊,男歌手貌似更受欢迎?(别打我),男歌手中周杰伦.薛之谦.许嵩(这三位我都比较喜欢)几乎占了榜单半壁江山... <Fade>电音强势来袭,很带感哈(搭配炫迈写代码完全停不下来..) 根据结果做了网易云音乐歌单 : 评论数

我喜欢的网易云音乐

使用了很多音乐APP,无论是付费,还是免费APP,网易云音乐是目前为止最好的云音乐APP,没有之一. 云音乐UI 配色简介大方 布局合理舒适 功能简洁实用 我喜欢的理由 在听歌时候没有广告 纯粹的播放音乐,没有乱起八糟的广告,唯一的广告在首页的slash有推广,并且明确表示出来是推广.如果我有兴趣我自然会点击.如果没有兴趣,强制让我点击,不仅对广告带来的是负面的收益,对音乐app也是很差的体验.其它免费的音乐在你听音乐的时候会强行插入广告,立马听歌的兴趣就没了. 所有歌曲音质都是高清无损 很多音

Ubuntu16.04安装网易云音乐

去网易云音乐官网下载deb包: http://music.163.com/#/download 打开终端: cd 下载 sudo dpkg -i netease-cloud-music_1.0.0_amd64_ubuntu16.04.deb sudo apt-get -f install sudo dpkg -i netease-cloud-music_1.0.0_amd64_ubuntu16.04.deb

实现 60fps 的网易云音乐首页

网易云音乐是一款很优秀的音乐软件,我也是它的忠实用户.最近在研究如何更好的开发TableView,接着我写了一个Model驱动的小框架 - MDTable.为了去验证框架的可用性,我选择了网易云音乐的首页来作为Demo,语言是Swift 3. 本文的内容包括: 实现网易云音乐首页的思路如何建立一个轻量级的UITableViewController(不到100行)性能瓶颈原因以分析及如何优化到接近60fps Note:本文并没有用Reveal去分析网易云音乐iOS客户端的原始UI布局,所以实现方式

将 QQ 音乐、网易云音乐和虾米音乐资源「整合」一起的Chrome 扩展Listen 1

原文地址:http://whosmall.com/?post=418 本文标签: Chrome扩展 Chrome浏览器 Chrome扩展Listen1 音乐资源整合 Listen1安装方法 在 Chrome 上安装了这款名为 Listen 1 的插件,妈妈可是再也不用担心你找不到想听的歌了.它将 QQ 音乐.网易云音乐以及虾米音乐的音乐资源「整合」在了一起,你只需要输入音乐关键词,就可以方便地三大曲库中跳转搜索. 安装方法 Listen 1 的安装方法与一般的 Chrome Extension

《云阅》一个仿网易云音乐UI,使用Gank.Io及豆瓣Api开发的开源项目

CloudReader 一款基于网易云音乐UI,使用GankIo及豆瓣api开发的符合Google Material Desgin阅读类的开源项目.项目采取的是Retrofit + RxJava + MVVM-DataBinding架构开发.开发中所遇到的各种问题已归纳在这里. github地址:CloudReader What can be learned about this project 那么,从本项目中你能学到哪些知识呢? 1.干货集中营内容与豆瓣电影书籍内容. 2.高仿网易云音乐歌单

详解Qt动画框架(2)--- 实现网易云音乐tag切换

在详解Qt的动画框架(一)介绍了关于Qt动画框架一些基础知识,在这一节中,将会实际的看到有关与动画框架的实现,该案例主要实现的具体是网易云音乐tag的切换,网易云音乐中的切换如图所示: 本文介绍的方法也可以达到这种切换的简易效果. 设计动画框架 首先我们需要设计对于动画框架,其需要的动画效果是什么?对于上图,我们需要的是三个tag可以不停的切换,可以抽象为左移以及右移,即一个tag从一个矩形区域,移动到另外的矩形区域,那么对于Tag的承载体选为什么较为合适呢?因为我们仅仅只需要图片的显示,因此Q

抓取网易云音乐歌曲热门评论生成词云

前言 抓数据 抓包分析 加密信息处理 抓取热门评论内容 词云 词云运行效果 总结 前言 网易云音乐一直是我向往的"神坛",听音乐看到走心的评论的那一刻,高山流水.于是今天来抓取一下歌曲的热门评论.并做成词云来展示,看看相对于这首歌最让人有感受的评论内容是什么. 做成词云的好处就是直观以及美观, 其他的我也想不出来有什么了. 抓数据 要想做成词云,首先得有数据才行.于是需要一点点的爬虫技巧. 抓包分析 加密信息处理 抓取热门评论内容 抓包分析 使用Chrome控制台.我们可以轻松的找到评

在Ubuntu 14.04 上安装网易云音乐

之前因为电脑有网络的原因,一直使用网页网易云音乐听歌,最近电脑没网络使用,才发现网易云音乐有linux版本,果断下载. 在Chrome浏览器中,登陆官网下载Linux版本中的Ubuntu 14.04 64bit的deb包 http://music.163.com/#/download 下载完成后,双击该deb包即可自动安装 安装完成的画面 成功!

在自己网站中插入网易云音乐的外链,播放音乐

今天在网易云音乐听歌发现一个有趣的东西,音乐外链,于是尝试了一下,挺不错的 效果如下图 只要把代码放到你的网站代码里就行了 插件使用教程