一、业务需要切换 M3U8 视频源的开发场景
很多业务场景,页面里面需要切换播放不同的 M3U8 视频。比如课程列表点击切换课程视频、监控页面切换不同摄像头画面、点播列表切换选集。
不少新手写代码,直接调用hls.loadSource(新地址)就完事,简单 demo 看着没问题。但是反复来回切换几次之后,就出现各种偶现诡异问题:切换之后黑屏、有声音无画面、内存持续上涨、后台还在请求上一个视频的 TS 分片。
网上很多简短示例 demo 只演示加载第一个视频,不会讲多次切换源的完整流程。很多人忽略切换源之前的清理步骤,旧实例、旧的定时器、网络请求没有处理干净,埋下线上隐患。
很多同学遇到切换源偶现 BUG,不知道是切换逻辑写的不对,反复怀疑 M3U8 流地址本身有问题。排查这类切换源问题,我会使用 m3u8live.cn 网页调试工具,先确认每一条待切换的 M3U8 流,单独播放都是正常可用,排除流本身问题,再回头看前端切换逻辑。
二、新手切换视频源高频错误写法与现象
错误写法 1:直接调用 loadSource,不做任何前置清理
//错误示例
hls.loadSource('new.m3u8');
video.play();
现象:多次切换之后,后台残留上一个视频分片请求,内存不停上涨,偶现黑屏音画错乱。
错误写法 2:只暂停 video,直接加载新源
//错误示例
video.pause();
hls.loadSource('new.m3u8');
现象:旧 hls 内部定时器、事件监听还在运行,新旧逻辑互相干扰。
错误写法 3:复用已经 destroy 销毁过的 hls 实例
实例执行 destroy 之后,实例已经作废,还继续调用 loadSource。直接抛出 JS 报错,播放直接失效。
错误写法 4:切换源的时候,不等待动作完成,立刻调用 play
异步操作还没有完成,就直接执行 video.play,触发浏览器自动播放拦截,出现黑屏。
三、hls.js 切换视频源的标准正确流程
重要:已经 destroy 的实例不能复用,分两种场景: 场景 A:页面长期存在,只是切换视频,不销毁播放器组件,复用同一个 hls 实例:
- 执行
hls.stopLoad()停止旧视频分片加载; hls.detachMedia()解绑 video 标签;- video.pause () 暂停播放;
- 调用
hls.loadSource(新M3U8地址); hls.attachMedia(video)重新绑定 video;- 监听
Hls.Events.MANIFEST_PARSED事件,清单解析完成之后,再调用 video.play ()。
场景 B:组件会卸载销毁,切换视频优先把旧实例完整 destroy,再 new Hls () 创建全新实例,安全性更高,更适合 Vue、React 组件场景。
参考简易代码片段:
async function changeSource(hls, video, newUrl){
hls.stopLoad();
hls.detachMedia();
video.pause();
hls.loadSource(newUrl);
hls.attachMedia(video);
}
四、切换源开发容易忽略的坑点
- 不要在 destroy 之后继续复用该实例,destroy 之后实例直接作废,需要 new 新对象。
- 不要 loadSource 之后立刻 play,要等待 MANIFEST_PARSED 解析完成,再调用播放,规避自动播放策略拦截。
- 如果是多码率 Master 流,切换源之后,需要观察是否可以正常切换清晰度。
- 加密 AES‑128 流切换源,需要确认新流密钥请求可以正常访问。
五、简单排查步骤
第一步:每一条要切换的 M3U8,单独放到网页调试工具播放,确认每条流单独播放正常。如果某一条流单独就黑屏,优先修复流本身。 第二步:业务页面反复多次来回切换视频,观察 Chrome 任务管理器内存,观察 Network 面板,确认旧视频分片请求已经停止。 第三步:偶现黑屏,重点检查切换源的 stopLoad、detachMedia 步骤有没有漏掉。
六、总结
很多新手以为切换视频源只要调用 loadSource 传新地址就可以。实际上切换 M3U8 源,必须先停止加载分片、解绑 video 标签,再加载新地址。destroy 销毁过的实例不能复用。反复切换出现偶现黑屏、内存上涨,优先检查切换源的前置清理逻辑。借助网页调试工具,先确认每条流单独播放正常,再排查前端切换逻辑,减少无效调试。