You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

macOS 13.6下Safari 17.0调用getUserMedia时UI冻结问题

macOS 13.6 Safari 17.0 中 getUserMedia 导致 UI 冻结的问题

在macOS 13.6系统的Safari 17.0浏览器中调用getUserMedia时,UI会冻结直至Promise状态变更。实际用户交互已发生,但界面无显示,直到getUserMedia决议或拒绝。

可通过以下本地HTML文件测试该行为:点击「getUserMedia」按钮后尝试点击「递增计数器」,界面无变化但点击次数会被统计。

<html>
  <body>
    <button onclick=add()>Increase Counter</button>
    <p id="counter">0</p>
    <button onclick=gum()>getUserMedia</button>
      <script type="text/javascript">
        var counter = 0
        function add() {
          counter ++
          document.getElementById("counter").innerHTML = counter
        }
        async function gum() {
          await navigator.mediaDevices.getUserMedia({video:true,audio:true})
        }
      </script>
  </body>
</html>

这种情况会导致收集设备名称等需后台执行的操作时用户体验不佳,请问是否有解决方案,或这是否是Safari处理getUserMedia的Bug?


问题分析与解决方案

1. 问题本质:Safari的主线程阻塞Bug

这是Safari在处理getUserMedia权限请求时的主线程阻塞问题,属于浏览器实现层面的Bug。Safari在弹出系统权限对话框期间会阻塞主线程,导致UI无法实时更新,即便用户的交互逻辑(比如计数器递增)已经在后台完成执行。

2. 临时解决方案

方案一:延迟执行getUserMedia,释放主线程

通过setTimeout将getUserMedia的调用延后,让主线程先完成当前的UI更新任务,再处理权限请求:

async function gum() {
  // 延迟100ms执行,给主线程留出UI更新时间
  await new Promise(resolve => setTimeout(resolve, 100));
  await navigator.mediaDevices.getUserMedia({video:true,audio:true});
}

方案二:提前预加载设备信息

如果需求是收集设备名称,可提前调用enumerateDevices()获取设备列表——该API不会触发权限请求,也不会阻塞主线程:

async function getDeviceNames() {
  const devices = await navigator.mediaDevices.enumerateDevices();
  const videoDevices = devices.filter(d => d.kind === 'videoinput');
  const audioDevices = devices.filter(d => d.kind === 'audioinput');
  // 处理设备名称
  console.log('视频设备:', videoDevices.map(d => d.label));
  console.log('音频设备:', audioDevices.map(d => d.label));
}

注意:若用户尚未授予媒体权限,enumerateDevices()返回的设备label会是空字符串。此时可先引导用户授予权限,再获取完整名称,但要尽量避免在权限请求期间阻塞UI。

方案三:添加UI加载反馈

在调用getUserMedia前,先显示加载状态提示,让用户明确系统正在处理,避免误以为界面无响应:

async function gum() {
  const statusElement = document.createElement('p');
  statusElement.textContent = '正在请求媒体权限...';
  document.body.appendChild(statusElement);
  
  await new Promise(resolve => setTimeout(resolve, 50));
  try {
    await navigator.mediaDevices.getUserMedia({video:true,audio:true});
    statusElement.textContent = '权限已授予';
  } catch (err) {
    statusElement.textContent = '权限被拒绝';
  }
}

内容的提问来源于stack exchange,提问作者wanderingcode

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.02 18:53:16