这篇文章上次修改于 434 天前,可能其部分内容已经发生变化,如有疑问可询问作者。
公司的项目使用了声网 SDK 实现语音通话,但在其他同事接入了另外一个功能后,被发现断开麦克风结束通话后,浏览器窗口上显示“小红点”,依旧占用麦克风的情况。这很容易导致客户认为我们在继续“监听”他说话,影响使用体验。这是一个遗留很久的 Bug,一直都没有人找出具体的原因,但在今天,我终于有了新的发现!查看日记全文
我打算使用浏览器的原生方法,实现一个获取麦克风源并使用播放器实时播放的功能,简单模拟使用麦克风并消除占用的过程。如果能在这个最小的代码示例里成功复现一样的“小红点”占用效果,就需要好好检查下对应的库是否存在产生此问题的代码了。
使用 navigator.mediaDevices.getUserMedia()
获取源,再使用 getAudioTracks()
方法成功获取到麦克风的轨道。按照公司项目的源代码,应该要把这个轨道复制到另外一个源里。
const stream = await navigator.mediaDevices.getUserMedia(constraints);
let newStream = new MediaStream();
const audioTracks = stream.getAudioTracks();
audioTracks.forEach(item => {
newStream.addTrack(item);
});
想要浏览器对应的窗口停止占用麦克风,则需要停止该轨道对麦克风声音的实时获取。使用 MediaStreamTrack 的 stop()
方法就可以实现,执行后我浏览器上的“小红点”确实消失了。
const newStreamTracks = newStream.getTracks();
newStreamTracks.forEach(item => {
// 只要 stop 理论上就会停止占用麦克风设备了
item.stop();
});
期间通过使用 MDN 查询文档,发现 MediaStreamTrack 有一个叫做 clone
的 方法,该方法将返回复制的轨道对象,主要可见变化是更换了其 id
。
我尝试将它应用到 我的代码 里面,之后打算停止占用,结果“小红点”确实出现了无法消失,始终占用的情况。
audioTracks.forEach(item => {
// newStream.addTrack(item);
// ! 只要使用 clone 方法,就会导致 stop 轨道时,浏览器依旧显示小红点(麦克风设备使用中)
newStream.addTrack(item.clone());
});
经过 Google 后发现,貌似存在类似的 问题,属于浏览器内核的 Bug。但保罗还是比较菜,就没有就此问题继续研究了。
这就已经有了不小的发现了,接下来需要排查下是否是某个第三方库执行了类似的代码所致。我们项目除了声网以外,还使用到了另外一个服务,也需要引用当前用户使用的麦克风源。
将声网 SDK 返回的麦克风源替换为其内置的方法(会返回第一个麦克风源,假设你有多个麦克风,则会触发另外一个麦克风音源不对的 Bug)后,“小红点”并没有始终显示的情况。所以基本上可以确认是声网 SDK 返回的轨道可能是 clone
过的,导致“小红点”始终显示,该 SDK 的源码闭源,但我也从压缩混淆的代码里找到了类似的 clone
方法。
clone(t, r, i, n) {
const o = this._mediaStreamTrack.clone();
return new e(o, t, r, i, n)
}
上面这么多的差分测试我觉得已经足够说明问题,SDK 代码内部的具体情况,我这边就不细探了。
知道了问题产生的原因,却没有好的解决办法。接下来可能会就此问题向声网那边提工单,看看他们那边的回复了。有一说一,第一次解决这种疑难杂症,还是挺有成就感的!
以下内容于 9.27 日补充:
给同事看了本文提供的最小 Demo,他那边修改了下,其实只需要将 clone()
前后的所有 MediaStreamTrack 都 stop()
掉即可完成释放了。也就是说 clone()
之后就完全新增了一个占用项,只要有一个没有释放,就会存在“小红点”的占用效果。
const newStreamTracks = newStream.getTracks();
// 原流所有轨道
audioTracks.forEach(item => {
item.stop();
});
// 新流的所有轨道
newStreamTracks.forEach(item => {
// 只要 stop 理论上就会停止占用麦克风设备了
item.stop();
});
那问题就变成了两种可能性,一个是 React Hooks 编写逻辑问题,导致反复获取到新的 clone()
后的轨道。再一个就是“那另外一个服务”是否是将传入的轨道持续占用,而没有提供对应的销毁代码?这个问题还需要写一个最小复现,从而排除下是第三方的问题还是 Hooks 逻辑的问题。
已有 4 条评论
很棒
保罗更新了,好耶
确实点击卸载小红点都没用,而且这个小红点让我感觉是 摄像头被偷拍了尴尬。
好耶!好怪