最近在开发过程中遇到一个奇怪的问题:当用户进入某语音评测功能页面时,浏览器地址栏的麦克风图标一直显示为“正在使用中”,即便切换到其他页面也未能清除这一状态。
用户因此误会我们在后台偷偷录音,解决问题后在此做一个记录,详细分析问题产生的原因及最终解决方案。
问题排查
问题出现在使用语音评测 SDK 的页面上,页面 sdk 调用逻辑如下:
- 进入页面时初始化 SDK。
- 用户点击录音按钮时触发开始录音,后续进行语音打分。
- 用户点击停止录音后调用停止录音接口。
- 页面销毁时,尝试释放所有相关资源。
按照这个调用逻辑有两个问题:
- 为什么进入页面就显示麦克风在使用中?
- 为什么切换页面后仍显示显示麦克风在使用中?
为什么进入页面就显示麦克风在使用中?
咨询 SDK 的售后得出执行 init 方法就会执行 navigator.mediaDevices.getUserMedia。
这会导致:
- 进入页面时即申请了麦克风权限。
- 即使用户没有主动点击录音按钮,浏览器也会显示麦克风正在使用中。
解决思路就是调整调用顺序:
- 用户点击开始录音时才申请麦克风权限。
- 停止录音的同时释放已占用的媒体流。
为什么切换页面后仍显示显示麦克风在使用中
按照正常的逻辑来说 SDK 应该在释放 SDK 的时候清除麦克风的使用状态但该 SDK 没有。
SDK 的售后让使用这个方法但无法释放麦克风的使用!
尝试后并无法解决,没有办法了吗?
如何释放麦克风的使用?
浏览器的 navigator.mediaDevices.getUserMedia 本身没有全局“释放”接口,它返回的媒体流需要手动停止所有轨道才能真正释放资源。常见的释放操作如下:
// 先获取并保存媒体流引用(示例)
let mediaStream;
// 请求麦克风访问
navigator.mediaDevices.getUserMedia({ audio: true })
.then(stream => {
mediaStream = stream;
// 使用麦克风流...
})
.catch(error => {
console.error('无法获取麦克风:', error);
});
// 释放麦克风的函数
function releaseMicrophone() {
if (mediaStream) {
// 停止所有音频轨道
mediaStream.getAudioTracks().forEach(track => {
track.stop(); // 停止单个轨道
mediaStream.removeTrack(track); // 从流中移除轨道(可选)
});
// 清除引用(重要)
mediaStream = null;
console.log('麦克风已释放');
}
}
// 当需要释放时调用
releaseMicrophone();
这里的重点是需要保存麦克风的流媒体引用才可以进行释放,很明显该 sdk 没有提供获取麦克风流引用的操作或方法。
解决方案:利用代理模式重写 getUserMedia
虽然无法修改 SDK 代码,但我们可以通过“代理大法”重写 navigator.mediaDevices.getUserMedia 方法,在获取媒体流时先保存引用。当页面离开前,再统一调用释放方法结束所有音频轨道。
逻辑大致如下:
- 重写
getUserMedia方法,拦截 SDK 调用。 - 保存每次获取的媒体流引用。
- 在页面卸载或停止录音时,遍历所有保存的媒体流,逐个停止所有音频轨道。
- 离开页面的时候还原
getUserMedia(自行实现)
这样,通过代理方式拦截所有 getUserMedia 调用,就可以在必要时确保所有音频轨道都被干净地停止,不再占用麦克风。
如果上边的在线运行失败的话请查看详情使用