本文首发于:https://github.com/bigo-frontend/blog/ 欢迎关注、转载。
h5实时语音
一、背景
项目组需要推出一款可实时收听客户端内语音房麦位用户实时语音的web页面,作为客户端语音房的分享页面。从客户端分享出去后,可直接在浏览器打开收听麦位上用户的实时语音。
二、技术方案
技术选型
实时收听麦位语音,流媒体直播拉流。市面上的几种流媒体解决方案对比
协议 | httpflv | rtmp | hls | dash |
---|---|---|---|---|
延时 | 低 | 低 | 高 | 高 |
数据分段 | 连续流 | 连续流 | 切片文件 | 切片文件 |
兼容性 | 移动端兼容性不理想 | 依赖flashplayer | 移动端兼容性良好 | 移动端兼容性不理想 |
基于此活动是移动端项目;且页面只有收听功能,延迟用户对基本是无感知。hls显然是最理想的选择。
hls简介
HLS协议由苹果公司提出并推广,下面是来自维基百科的定义。
HTTP Live Streaming,缩写为HLS,是由苹果公司提出基于HTTP的流媒体网络传输协议。是苹果公司QuickTime X和iPhone软件系统的一部分。它的工作原理是把整个流分成一个个小的基于HTTP的文件来下载,每次只下载一些。当媒体流正在播放时,客户端可以选择从许多不同的备用源中以不同的速率下载同样的资源,允许流媒体会话适应不同的数据速率。在开始一个流媒体会话时,客户端会下载一个包含元数据的扩展 M3U (m3u8) 播放列表文件,用于寻找可用的媒体流。
其中一句话很重要“是由苹果公司提出基于HTTP的流媒体网络传输协议。是苹果公司QuickTime X和iPhone软件系统的一部分”,ios safari本身支持hls协议的流媒体播放,无需借助Media Source Extensions Api。并且Media Source Extensions Api在ios safari的兼容性一言难尽,所以像httpflv、dash这些在ios safari上都无法支持。
hls.js
确定了hls协议流媒体后,选择了比较知名hls.js开源框架作为hls解码的js库。hls.js想必大家也都有耳闻,这里摘抄了hls官方文档中的一段话,完全阐述了其核心。
hls.js 是一个 JavaScript 库,用于实现HTTP Live Streaming客户端。它依赖于HTML5 视频和MediaSource 扩展进行播放。它的工作原理是将 MPEG-2 传输流和 AAC/MP3 流转换为 ISO BMFF (MP4) 片段。当在浏览器中可用时,使用Web Worker异步执行传输。
三、准备就绪,发车
确定技术方案后,着手开发。开发过程没太多可聊的,hls.js提供的文档基本够用。这块会主要谈下这次的踩坑历程。
安卓微信和qq内置浏览器播放m3u8 等20s+才播放出声音的问题
hls.js官网文档中给了两种初始化Hls方法的方式,区别就是优先使用浏览器的本机HLS支持,还是优先使用hls.js播放。
本着使用 JavaScript 处理 HLS 流可能会降低功耗和内存效率。也可能存在不如浏览器的本机HLS支持稳定的考量。选择了优先使用浏览器的本机HLS支持,也就是当浏览器既支持hls协议的流媒体播放也支持hls.js库播放时,会优先使用浏览器的hls播放能力。
想法是美好的,调试时意外总是接踵而至。在安卓微信和qq内置浏览器播放时,基本都要等20s以上才播放出声音。在浏览器直接播放m3u8文件表现相同,当时怀疑是流本身问题和媒体组同事pk了半天。最后切成hls.js(mse)来播放没有出现过一段时间才播放声音的问题。微信官方也建议在微信内使用hls.js播放m3u8。查看链接-见评论
进一步优化进页面到播放出声音的时间
解决了“安卓微信和qq内置浏览器播放m3u8 等20s+才播放出声音的问题”后,浏览器在加载到足够资源ts包后,就会开始播放。起初到媒体服务器拉流时,服务器才会开始生成ts包,等到有3个ts包后才返回给前端,每个ts包的大小为2s,所以前端最快也要等到6s才能拿到流资源开始播放。由于进入页面后让用户的等待时间过于长,我们针对这个问题做了一些优化:
ts包大小改为1s一个媒体服务器缓存最新的3个ts包产生一个ts包后就立即返回 不等到3个后才返回优化后,用户进入页面基本都是在1s多后就可以听到声音,同时也降低了延时。
在ios手机上使用video播放hls流 会导致video控件全屏展示
此活动页面通过video标签播放hls,video标签对用户是不可见的,通过点击页面播放按钮触发video的播放,在一些低版本ios手机上点击播放时会触发video自动全屏播放的机制。在网上找了很多方法,比如在video上添加属性“ x5-playsinline=“” playsinline=“” webkit-playsinline=‘’”等效果都不佳,最后是引入了一个js库完美解决iphone-inline-video
多路流与混音
页面里有多个麦位,支持多人同时聊天,会有多人同时开语音推流的情况。起初,web拉流是用一个m3u8链接拉一路流,如果有多人同时聊天,就要同时维护多个m3u8链接。这种情况从移动端的性能(播放一个和播放多个m3u8)和多流同步的角度看都不太友好。最终采用的是在媒体服务器将多流混音为一条流后返回给web,web只需要维护一个m3u8即可。
跨域问题
m3u8资源的域名需要加上CORS headers,使用Media Source Extensions操作数据时是需要允许跨域的。hls.js文档中也有提及,这里就不展开说明。
四、m3u8调试
在和媒体同事对接时,也出现了些其他播放问题。比如无法播放、声音卡顿问题等,如何快速定位是前端的问题还是媒体流的问题呢?我们前端也需要掌握一些调试手段。
流地址直接用浏览器的播放器播放 排除前端代码干扰
我们可以把m3u8文件单独拎出来,放到浏览器里播放。移动端大部分浏览器都是支持直接播放m3u8文件,如果正常播放,基本可以认定是前端问题。
电脑上调试会更加方便,只需在chrome安装一个插件Native HLS Playback,即可支持m3u8播放。打开devtools,可查看m3u8的返回和每个ts包数据。查看m3u8字段详解
本地推hls流 使用本地流调试
需安装应用:nginx、nginx-rmtp-module(Nginx服务器的流媒体插件)、ffmpeg
准备完毕后copy下面到配置conf/nginx-hls.conf
worker_processes auto;error_log logs/error.log debug;events { worker_connections 1024;}# 推流rtmp { server { listen 1935; chunk_size 4000; application hls { live on; hls on; hls_path f:/tmp/hls; # 生成m3u8文件和ts文件位置 hls_playlist_length 1d; # hls_playlist_length 时间 设置HLS播放列表长度。 默认为30秒。 hls_sync 100ms; # 音视频的同步时间 hls_continuous on; # on|off 设置连续模式,是从停止播放的点开始还是直接跳过 hls_fragment 1s; # hls_fragment 片段时间 设置HLS片段长度。 默认为5秒。 ts大小还与gop_size设置有关 } }}# 拉流http { server { listen 80; location / { root html; } location /hls { add_header Access-Control-Allow-Origin *; add_header Access-Control-Allow-Headers "Origin, X-Requested-With, Content-Type, Accept"; add_header Access-Control-Allow-Methods "GET, POST, OPTIONS"; types{ application/vnd.apple.mpegurl m3u8; video/mp2t ts; } alias f:/tmp/hls; # 读取生成m3u8文件和ts文件位置 expires -1; } }}
进入nginx目录下,启动nginx。
./nginx.exe -c conf\nginx-hls.conf
接下来准备一个mp4文件,然后在命令行中执行下面命令
ffmpeg -re -i "E:\file\test.mp4(mp4文件地址)" -vcodec libx264 -vprofile baseline -acodec aac -ar 44100 -strict -2 -ac 1 -f flv -s 1280x720 -q 10 rtmp://127.0.0.1:1935/hls/test2
推流成功。
通过 http://127.0.0.1/hls/test2.m3u8 地址即可访问本地推的流。
欢迎大家留言讨论,祝工作顺利、生活愉快!
我是bigo前端,下期见。