所谓无缝切换,目的是想在没有完整获取文件之前,先加载第一部分。从而能够保证音频内容可以提前漏出,或者实现一些直播的效果。
背景
区别于直接推流,这里的需求方式其实可以不是直播的方式。只要是几个片段歌曲在切换的时候可以做到无缝切换,同时歌曲拆分片段的时候能够按照节拍对齐,也是同样可以实现无缝切换的。客户端本身是具有无缝播放的能力,那前端是否可以基于能力本身做出无缝切换的效果呢?本篇文章就是通过几种维度进行尝试,目的是希望能够达到类似客户端一样的无缝切换的。
直接推流
这个方案是最彻底的,就是单纯直播/推流的方式。主要就是通过搭建后端服务,针对音频进行封装,通过某种协议进行实时推流的方式。
目前主流的协议主要包含
FLV
- 基于后端进行推流
- 前端可以基于flv.js进行定制,同时需要注意兼容性问题:https://github.com/bilibili/flv.js/blob/master/docs/livestream.md
RTMP
基于flash方案,显然不适合当下
HLS
苹果推崇的一种协议标准,参考hls.js
以上是一些主流的推流方案,而本文将要探索的是不基于推流的方案,单纯基于音频的片段拼接,实现在前端层面的无缝衔接播放。
音频片段无缝播放
探索1:
要想达到无缝播放的效果,最容易想到的是通过数据提前准备,在合适的时机进行切换。方案也特别简单:
懂得都懂,这里推荐下Hoverjs可以比较方便的使用webAudio,然而实现的效果却特别不理想,会出现播放中断的现象。
原因其实也比较容易接受/理解,就是在切换播放的时候,涉及到上下文的销毁创建,以及在监听播放结束的时候,音频已经断开输出以及一些其他不可能完美衔接的原因。
效果演示:https://demo.onehacker.top/seamless-play/?type=demo2 点击【方案1】进行试听
【推荐】探索2:
基于“探索1”的理所当然的失败,就需要探索一种不需要切换播放上下文,那就需要一种直接进行数据拼接的方式。前端进行数据拼接,同时在这里我们还有一种需求就是需要进行动态数据拼接,也就是我们不能在拼接完成之后进行数据拼接,而是需要在播放的过程中进行数据拼接。
想法有了,大概要怎么做呢?
整体实现流程:
也就是将原有的数据转化为二进制数据,基于audioBuffer进行动态填充,从而实现了在不中断播放的前提下,实时推送数据的目的。
在实现过程中有两个坑点:
- AudioContext.decodeAudioData在iOS下不支持Promise,需要通过回调的方式进行调用
- AudioBuffer.copyToChannel的实现也在iOS下实现不完整,需要通过
buffer.getChannelData(0).set(audioData.getChannelData(0), 0);
进行代替。
同时需要注意的是,进行decodeAudioData
的时候,会根据当前的音频进行音频重采样,这个一定程度会对音频的质量存在影响,同时即使使用高的采样率也没办法保证音频质量,反而会使音频变声。
总之,此种方案能够比较好的满足当前的场景,是一种比较推荐的方案。其实这里还没有用到 webAudio processNode,或许基于此种方案能够实现更令人意外的效果,这里暂时先不展开了,毕竟当前方案已经满足了😄
效果演示:https://demo.onehacker.top/seamless-play/?type=demo2 点击【方案2】进行试听
探索3:
媒体源扩展 API(MSE)提供了实现无插件且基于 Web 的流媒体的功能。使用 MSE,媒体串流能够通过 JavaScript 创建,并且能通过使用 <audio> 和 <video> 元素进行播放。
基于媒体源拓展API,同样可以实现类似“探索2”的方案,只不过实现的流程有点区别。
此种方案也能够达到数据拼接的效果,但是MSE在iOS尤其是iPhone上支持度很差
实际播放的效果没有方案2流程,猜想可能的原因:
- 音频本身的原因,待验证
- 通过sourceBuffer进行appendBuffer进行数据解码,导致前后没有一致性?但方案2也存在类似的问题才对。
- 最终使用audio进行播放,或许改为webAudio会有所变化,待验证
效果演示:https://demo.onehacker.top/seamless-play/?type=demo2 点击【方案3】进行试听
总结
在 android 上可直接使用“方案3”作为首选,方案2作为降级方案;iOS直接使用方案2。当前整体方案还处于demo阶段,是否能够“in Production”还需要进一步推敲。