最近在开发多端适配的web应用,同样的一个视频通话功能在Windows,Android以及macOS的Chrome上面都是正常的,但是到了macOS的safari和iOS的webkit内核的浏览器上都会出现卡成ppt的问题。使用cursor改了半天,结果卡顿情况愈发严重,只能自己上手一步步排查。
1. 先尝试对log进行分析
在控制台查看log,发现.flv视频是在正常被传输的。具体表现为buffer是持续增加的。并且会因为buffer的积累正常触发buffer的自动清理以及追赶逻辑,追赶逻辑如下:
// 问题代码
if (bufferedAhead > 10) {
console.log('📍 iOS: Seeking to catch up');
video.currentTime = video.buffered.end(video.buffered.length - 1) - 1;
}
但是页面上的表现是每次只会播放追赶之后的一帧,然后就卡住了,继续等待下一次追赶。这就非常吊诡了,明明加载都是正常的,但是为什么不播放啊?
单独就这个问题,问了一下cursor,然后返回给我以下的答案
不是哥们,你这不是知道这个问题吗?你之前改了这么半天,这块你是一点没动啊!知道了这个问题就好说了,既然直接跳转可能会有问题,那么我用加速播放那岂不是万无一失,于是改成了如下的样子:
// ✅ 改进代码
const speedupStartTimeRef = useRef<number | null>(null);
bufferCheckIntervalRef.current = setInterval(() => {
// ...
// ===== 检查视频是否意外暂停 =====
if (video.paused && video.readyState >= 2) {
console.warn('⚠️ Video paused unexpectedly, resuming playback');
video.play().catch(err => {
console.error('Failed to resume playback:', err);
});
}
// ===== 使用加速追赶替代直接跳转 =====
if (totalBuffered > 10) {
const targetSpeed = bufferedAhead > 15 ? 2.0 : 1.5;
if (video.playbackRate !== targetSpeed) {
console.log(`⏩ Speeding up to ${targetSpeed}x to catch up (buffered: ${totalBuffered.toFixed(1)}s, ahead: ${bufferedAhead.toFixed(1)}s)`);
video.playbackRate = targetSpeed;
}
// ===== 加速时间限制(防止 A/V 不同步)=====
if (video.playbackRate > 1.0) {
if (!speedupStartTimeRef.current) {
speedupStartTimeRef.current = Date.now();
}
const speedupDuration = Date.now() - speedupStartTimeRef.current;
if (speedupDuration > 15000) { // 超过 15 秒
console.warn('⚠️ Speedup too long, forcing normal speed to prevent A/V desync');
video.playbackRate = 1.0;
speedupStartTimeRef.current = null;
}
} else {
speedupStartTimeRef.current = null;
}
} else if (totalBuffered < 5 && video.playbackRate !== 1.0) {
// ===== 追赶完成,恢复正常速度 =====
console.log('✅ Caught up, returning to normal speed');
video.playbackRate = 1.0;
speedupStartTimeRef.current = null;
}
}, 3000);
改完了以后发现,果然可以成功播放了,但是只有在第一次或者第N次触发追赶之后,才能正常播放,1~N不固定,于是我有用cursor改了几轮,还是没有解决。
2. 继续找是不是性能原因
操作了一下,发现不对啊,只有我在点击静音按钮之后才有可能会正常播放,难道是音频上下文冲突?于是我把全局音频上下文管理从这个页面删掉了,发现有好转,但是没解决。当我第一次进入这个页面的时候,可以完全正常的播放,但是在之后的每次播放都还是会先卡住再触发buffer追赶才能播放。那么只能看看第一次播放和之后的播放有什么不同了,最终发现了一个只有在当前应用首次打开才会出现的设置--静音播放!。对的,iOS端页面的自动播放是有条件的!以下是cursor的总结。
🔒 iOS 自动播放策略演变
iOS 10+(2016年)
- ✅ 允许静音视频自动播放
- ✅ 允许带 playsInline 属性的视频在页面内播放
- ❌ 禁止有声音的视频自动播放
iOS 11+(2017年)
- ✅ 放宽政策:允许更多自动播放场景
- ✅ 如果用户与页面有过交互,可能允许有声音的自动播放
- ✅ 支持 Low Power Mode(低电量模式)下的自动播放限制
iOS 13+(2019年至今)
- ✅ 基于用户行为的智能策略
- ✅ 如果用户经常在该网站播放视频,可能放宽限制
- ❌ 但仍然推荐始终使用静音 + playsInline
3. 结尾
嗯,这个问题就这么解决了,一个周六搭进去了,哎~
4. 其他safari问题
1. iOS Safari 对 scrollIntoView 的支持问题
这个是在使用页面内导航的时候会出现的问题,使用scrollIntoView的时候页面无法正常滚动。
// 旧方案(iOS Safari 不稳定)
element.scrollIntoView({ behavior: 'smooth', block: 'start' });
- iOS Safari 对 behavior: 'smooth' 的支持不稳定
- Fixed positioning 的元素会干扰滚动计算
解决方案
// 新方案(iOS Safari 兼容)
const elementPosition = element.getBoundingClientRect().top + window.scrollY;
window.scrollTo({
top: elementPosition,
behavior: 'smooth'
});
2. 异步的window.open会被浏览器拦截
在 Safari 浏览器中,window.open 必须在用户交互的同步代码中执行
解决方案:
- 在异步 API 调用之前先创建一个空白窗口(保持在用户交互的同步上下文中)
- 等 API 返回 URL 后,修改这个已打开窗口的地址
- 移除 setTimeout 延迟
// 在 startPayment 方法开始时就创建窗口
const paymentWindow = window.open('', '_blank', 'width=375,height=800');
// 显示加载动画...
// API 调用完成后重定向
paymentWindow.location.href = paymentUrl;