火次果
火次果

暂无菜单项

首页/文章/技术文章/web前端/HTML5/
打开 MD 链接

HTML5 video 标签属性全解与音视频兼容处理实战:自动播放、编码格式、微信内核与流媒体选型

发布于 18小时前
1

音视频这块,看着就是一行 <video src="a.mp4" controls> 的事,真上了线才发现到处是坑:iPhone 上点播放直接跳全屏、静音能放有声放不了、微信里视频浮在弹窗上面盖不住、拖动进度条直接回到开头、监控画面延迟十几秒。可惜的不是属性的写的全了,而正是浏览器的自动播放的规则、编码的格式、服务端的响应头这三处都没能对齐上就出这样的问题了。

video、audio 的属性表摆正 🎬

很多人把 audio 当成没有画面的 video 来写,poster、width、height 全往上加,结果一个都不生效。audio 元素不认这些属性,它只有 src、preload、autoplay、loop、controls、muted、crossorigin 这些。

常用属性对照:

还有两条容易记混的:HTML 里写的是小写 playsinline,JS 里对应的是驼峰 video.playsInline,写错不报错但也不生效;muted 只在 HTML 属性阶段可靠,动态改 video.muted = true 后再 play(),在部分 iOS 版本上依然会被判为有声自动播放。

播不出来,先怀疑编码而不是属性 ⚙️

排查的第一步永远是编码。.mp4 只是容器,里面装的可能是 H.264,也可能是 H.265 或者 MPEG-4 Part 2,浏览器只认前者。

转码直接用这一条,覆盖了绝大多数兼容问题:

ffmpeg -i input.mov \
  -c:v libx264 -profile:v main -level 4.0 -pix_fmt yuv420p \
  -movflags +faststart \
  -c:a aac -profile:a aac_low -ar 48000 -b:a 128k \
output.mp4

-movflags +faststart 一定要加,它把 moov box 挪到文件头部。放在尾部的话,浏览器必须把整个文件下载完才能起播,长视频就会一直转圈。音轨统一用 AAC-LC,采样率 44100 或 48000,HE-AAC 在部分安卓机型上会直接哑掉。

浏览器端可以先用 canPlayType 探一下:

const v = document.createElement('video');
console.log(v.canPlayType('video/mp4; codecs="avc1.42E01E,mp4a.40.2"')); // probablyconsole.log(v.canPlayType('video/webm; codecs="vp9,opus"')); // maybe / ""

返回值只有 probably、maybe、空字符串三档,maybe 不代表能播,最终还是要用 error 事件兜底。

自动播放:规则早就改了,写法得跟着改 📢

现在各家浏览器的规则基本一致:静音 + 内联才允许自动播放,有声播放必须发生在用户手势里。Chrome 还有一套媒体参与度指数,用户在这个站点上频繁点播之后,有声自动播放也可能被放行,但这属于不可控因素,不能写进逻辑里。

async function tryPlay(video) {
  video.muted = true;          // 必须在 play() 之前
  video.setAttribute('playsinline', '');
  try {
    await video.play();
  } catch (e) {
    if (e.name === 'NotAllowedError') {
      // 展示「点击播放」浮层,在真实点击事件里再调一次 play()showPlayMask(() => { video.muted = false; video.play(); });
    } else if (e.name === 'AbortError') {
      // 上一次 play 还没结束又被打断,属于正常现象
    }
  }
}

三个细节:

play() 返回的是 Promise,不 catch 会在控制台抛未捕获异常,很多「偶发报错」就是这么来的

但令人不齿的是,即使开启了iOS的低电量模式,很多APP的静音自动播放的功能也都被拦了,唯一的出入就是在界面上必须留一个手动的入口了

视频切走时要主动 video.pause() 并把 src 清空后调 load(),否则后台继续解码,发热和耗电都压不住

移动端与微信 X5:全屏劫持和同层播放 📱

iOS 上不写 playsinline,一点播放就强制进原生全屏播放器,页面上的一切交互全部失效。微信安卓的 X5 内核更麻烦,它会用一个独立浮层接管 video,z-index 完全失效,弹窗被视频盖住是常态。

<videosrc="demo.mp4"mutedautoplayloopplaysinlinewebkit-playsinlinex5-playsinlinex5-video-player-type="h5-page"x5-video-player-fullscreen="true"x5-video-orientation="portrait"></video>

这几条都要写在 HTML 标签上。用 JS 动态 setAttribute 在部分版本不生效,因为内核在解析阶段就决定了用哪种播放器。需要感知全屏状态时监听 X5 的自定义事件:

document.addEventListener('x5videoenterfullscreen', () => { /* 页面进入全屏 */ });
document.addEventListener('x5videoexitfullscreen', () => { /* 退出全屏 */ });

同层播放下依然有两个坑:一是视频区域上方的 UI 层级仍然可能出问题,交互元素尽量别压在视频正上方;二是移动端多视频滑动切换时,X5 容易出现残留画面,切换前先 pause() 再换 src 更稳。

流媒体在微信里的选择也要分开:iOS 微信是 WKWebView,原生 HLS 和 MSE 都还行;安卓 X5 对 hls.js 支持不稳定,直接把 m3u8 交给内核原生播放更省事。

直播与长视频:HLS、HTTP-FLV、MSE 怎么选 📡

选型写法:

const video = document.querySelector('#player');
const m3u8 = 'https://example.com/live/stream.m3u8';


if (video.canPlayType('application/vnd.apple.mpegurl')) {
  video.src = m3u8;                                   // Safari、iOS 走原生
} else if ('MediaSource' in window &&
           MediaSource.isTypeSupported('video/mp4; codecs="avc1.42E01E,mp4a.40.2"')) {
  const hls = new Hls();                              // 其余走 hls.js
  hls.loadSource(m3u8);
  hls.attachMedia(video);
} else {
  fallbackWithPosterAndTip();                         // 都不支持就给提示
}

iPhone 上的 MSE 直到较新版本才有限支持,直播别依赖它。另外微信、QQ 浏览器会缓存 m3u8 和分片,地址后面挂个时间戳参数可以避免更新不及时。

控制条、全屏、画中画与字幕 🎛️

产品要求 UI 统一,就别用原生 controls,自己画:播放暂停绑 play / pause,进度条用 timeupdate 读 currentTime,缓冲进度读 video.buffered.end(0)。

全屏:标准是 video.requestFullscreen();iPhone 上的 video 元素只有 webkitEnterFullscreen(),而且必须在用户手势里调用

画中画:判断 document.pictureInPictureEnabled 后调 video.requestPictureInPicture(),监听 enterpictureinpicture / leavepictureinpicture 同步 UI

字幕:<track kind="captions" src="sub.vtt" srclang="zh" label="中文" default">,跨域字幕必须同时配 crossorigin="anonymous" 和服务端 CORS 头,否则静默失败,控制台什么都不报

黑边:用 object-fit: cover 裁切,或 fill 拉伸,默认的 contain 会留边

投屏和下载:controlslist="nodownload noremoteplayback" 在 Chrome 系生效,投屏还可用 disableremoteplayback 属性

排查流程:error、事件顺序与服务端响应 🚨

播放失败先看 video.error.code:

正常加载的事件顺序是 loadstart → durationchange → loadedmetadata → loadeddata → canplay → canplaythrough → playing。卡在 waiting 或 stalled 上,多半是带宽或 Range 请求的问题;stalled 持续超过 3 秒基本可以判定网络侧异常。readyState 从 0 到 4 分别对应无数据、有元数据、有当前帧、可播放、可流畅播放,loading 动画按这个切换比按 waiting 更准。

服务端必须做到三件事:

支持 Range 请求,返回 206 Partial Content,否则进度条拖动会失效或直接跳回开头

Content-Type 正确:video/mp4、video/webm、application/vnd.apple.mpegurl,类型错了浏览器会直接判为不支持

跨域资源给 Access-Control-Allow-Origin,同时标签上加 crossorigin,否则字幕和 canvas 截图都拿不到

直接落地的播放组件写法 🔧

<videoid="player"class="player"poster="cover.jpg"preload="metadata"playsinlinewebkit-playsinlinex5-playsinlinex5-video-player-type="h5-page"crossorigin="anonymous"
><source src="demo.webm" type="video/webm"><source src="demo.mp4"  type="video/mp4"><track kind="captions" src="sub.vtt" srclang="zh" label="中文" default>
  你的浏览器不支持 video 标签</video>
const video = document.querySelector('#player');


video.addEventListener('loadedmetadata', () => {
  video.muted = true;
  video.play().catch(() => showPlayMask());   // 被拦就显示点击蒙层
});


video.addEventListener('waiting', () => showLoading(true));
video.addEventListener('playing', () => showLoading(false));


video.addEventListener('error', () => {
  const code = video.error && video.error.code;
  const tips = { 2: '网络异常,请检查连接', 3: '视频编码不支持', 4: '视频地址无效或格式不支持' };
  showTip(tips[code] || '播放失败,请稍后重试');
});


// 页面隐藏时释放解码资源document.addEventListener('visibilitychange', () => {
  if (document.hidden) { video.pause(); } else { video.play().catch(() => {})

});

多个 <source> 的写法要注意顺序:浏览器从上往下试,一旦选中某个就不会回头,所以把兼容面最广的 MP4 放最后兜底更稳。

音视频兼容这件事,本质上就是三张表对得上:编码格式浏览器认不认、自动播放规则浏览器放不放、服务端响应给得对不对。把这三处理顺了,剩下的属性问题基本都能在属性表和事件回调里找到答案。

支持作者
如果这篇内容对你有帮助,可以请作者喝杯咖啡
0 点赞
0 收藏
分享
0 讨论
反馈
0 / 600
细中粗
0 讨论
热门最新
总结
暂无总结
嗨,下午好!
所有的成功,都源自一个勇敢的开始
创作
社区
购物
会员
近期热门

暂无数据

火次果
火次果
首页
资迅中心
小店
AI导航
社区
所有的成功,都源自一个勇敢的开始
不辜负每一个勇敢的开始
关于FAQ协议
火次果 © 2026鲁ICP备2025164830号-1