iOS端Safari中Canvas绘制视频无法自动播放如何解决
问题根源
iOS端Safari对媒体播放有严格的权限策略:
- 无用户主动交互触发的场景下,动态创建的视频元素默认会被拦截加载,
loadeddata等媒体事件不会触发,因此onload直接绑定的执行逻辑跑不起来 - 手动点击按钮触发
main()属于系统认可的用户交互行为,会临时放开媒体播放权限,因此功能可以正常运行 - 原代码里的视频元素没有配置iOS要求的必填播放属性,就算拿到权限也可能出现无法seek、强制全屏的问题
修复方案
不需要新增专门的播放按钮,做两个改动即可实现接近无感知的自动播放效果:
- 给动态创建的video元素补全iOS要求的必填属性,打开静音自动播放、内联播放的权限
- 加全局一次性交互监听,用户首次触摸屏幕任意位置(滑动、点击都算)时无感拿到播放权限,视频加载完成后自动启动逐帧绘制逻辑,用户完全不需要点专门的按钮
修改后的完整代码
JavaScript部分
let VIDEO_CANVAS = null; let video1 = null; let isVideoLoaded = false; let isPlayStarted = false; function main() { video1 = initializeVideo("mv/m4.mp4"); VIDEO_CANVAS = document.getElementById('video'); video1.addEventListener('loadeddata', () => { const context = VIDEO_CANVAS.getContext("2d"); context.canvas.width = video1.videoWidth * (740 / video1.videoHeight); context.canvas.height = 740; isVideoLoaded = true; // Mac端、部分权限宽松的iOS版本可以直接启动 tryStartPlay(); }); // 一次性监听全局交互,无感拿iOS播放权限 const getPlayPermission = () => { video1.play().then(() => { video1.pause(); // 权限拿到后如果视频已经加载完成直接启动 if(isVideoLoaded) tryStartPlay(); }).catch(() => {}); // 触发一次就移除监听,不重复执行 document.removeEventListener('touchstart', getPlayPermission); document.removeEventListener('click', getPlayPermission); }; document.addEventListener('touchstart', getPlayPermission, {passive: true}); document.addEventListener('click', getPlayPermission, {passive: true}); } function tryStartPlay() { if(isPlayStarted) return; isPlayStarted = true; processVideos(video1, 0); } function processVideos(video1,frame1){ if(frame1 <= 200) { showVideoFrame(video1,frame1); frame1++; } if(frame1 >= 200) { location.reload(); } setTimeout(() => { processVideos(video1,frame1); }, 30); } function showVideoFrame(video,frame){ const time = frame / 30; try { video.currentTime = time; const context = VIDEO_CANVAS.getContext("2d"); context.drawImage(video, 0, 0, VIDEO_CANVAS.width, VIDEO_CANVAS.height); } catch (e) { // 捕获seek未完成时的偶发报错,跳过当前帧即可 } } function initializeVideo(filename) { const video = document.createElement('video'); // 以下是iOS端播放必填属性,缺任何一个都可能被系统拦截 video.muted = true; video.playsInline = true; video.setAttribute('playsinline', ''); // 兼容iOS 10以下旧版本 video.preload = 'auto'; video.src = filename; video.load(); return video; }
HTML部分
原有body代码不需要修改,保持原有onload="main()"配置即可:
<body onload="main()"> <center> <canvas id="video"></canvas> </center> </body>
注意事项
- 视频初始必须设为静音:iOS不允许非静音媒体在无交互场景下自动播放,如果需要播放声音,可以在拿到播放权限后(也就是全局交互监听的回调里)把
video1.muted = false即可正常发声 - 不需要把动态创建的video元素插入DOM,避免被系统识别为可见的视频元素触发默认全屏逻辑
- 原逻辑用setTimeout做帧调度如果有卡顿,可以替换成
requestAnimationFrame,和屏幕刷新率对齐后流畅度会更好,也更省电 - 确保视频资源所在的服务器支持Range请求,否则iOS下大视频的seek操作会失败,导致逐帧绘制异常
内容的提问来源于stack exchange,提问作者moze2022
相关产品推荐
相关产品推荐

