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
相关产品推荐
相关产品推荐

