解决浏览器麦克风持续显示使用中的问题

813 阅读3分钟

最近在开发过程中遇到一个奇怪的问题:当用户进入某语音评测功能页面时,浏览器地址栏的麦克风图标一直显示为“正在使用中”,即便切换到其他页面也未能清除这一状态。

image.png

用户因此误会我们在后台偷偷录音,解决问题后在此做一个记录,详细分析问题产生的原因及最终解决方案。

问题排查

image.png

问题出现在使用语音评测 SDK 的页面上,页面 sdk 调用逻辑如下:

  1. 进入页面时初始化 SDK。
  2. 用户点击录音按钮时触发开始录音,后续进行语音打分。
  3. 用户点击停止录音后调用停止录音接口。
  4. 页面销毁时,尝试释放所有相关资源。

按照这个调用逻辑有两个问题:

  1. 为什么进入页面就显示麦克风在使用中?
  2. 为什么切换页面后仍显示显示麦克风在使用中?

为什么进入页面就显示麦克风在使用中?

咨询 SDK 的售后得出执行 init 方法就会执行 navigator.mediaDevices.getUserMedia

image.png

这会导致:

  1. 进入页面时即申请了麦克风权限。
  2. 即使用户没有主动点击录音按钮,浏览器也会显示麦克风正在使用中。

解决思路就是调整调用顺序:

  1. 用户点击开始录音时才申请麦克风权限。
  2. 停止录音的同时释放已占用的媒体流。

为什么切换页面后仍显示显示麦克风在使用中

按照正常的逻辑来说 SDK 应该在释放 SDK 的时候清除麦克风的使用状态但该 SDK 没有。

SDK 的售后让使用这个方法但无法释放麦克风的使用!

image.png

image.png

尝试后并无法解决,没有办法了吗?

如何释放麦克风的使用?

浏览器的 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 没有提供获取麦克风流引用的操作或方法。

image.png

解决方案:利用代理模式重写 getUserMedia

虽然无法修改 SDK 代码,但我们可以通过“代理大法”重写 navigator.mediaDevices.getUserMedia 方法,在获取媒体流时先保存引用。当页面离开前,再统一调用释放方法结束所有音频轨道。

逻辑大致如下:

  1. 重写 getUserMedia 方法,拦截 SDK 调用。
  2. 保存每次获取的媒体流引用。
  3. 在页面卸载或停止录音时,遍历所有保存的媒体流,逐个停止所有音频轨道。
  4. 离开页面的时候还原 getUserMedia(自行实现)

这样,通过代理方式拦截所有 getUserMedia 调用,就可以在必要时确保所有音频轨道都被干净地停止,不再占用麦克风。

iShot_2025-03-03_11.00.16.gif

如果上边的在线运行失败的话请查看详情使用

image.png