### [HTML5 video 标签属性全解与音视频兼容处理实战:自动播放、编码格式、微信内核与流媒体选型](https://www.huociguo.com/article/1914) **Published:** 2026-10-05T05:07:23 **Author:** 米了 **Excerpt:** 音视频这块,看着就是一行 的事,真上了线才发现到处是坑:iPhone 上点播放直接跳全屏、静音能放有声放不了、… 音视频这块,看着就是一行 ` ``` 这几条都要写在 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 字幕:``,跨域字幕必须同时配 `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 截图都拿不到 ## 直接落地的播放组件写法 🔧 ``` 你的浏览器不支持 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(() => {}) ``` `});` 多个 `` 的写法要注意顺序:浏览器从上往下试,一旦选中某个就不会回头,所以把兼容面最广的 MP4 放最后兜底更稳。 音视频兼容这件事,本质上就是三张表对得上:编码格式浏览器认不认、自动播放规则浏览器放不放、服务端响应给得对不对。把这三处理顺了,剩下的属性问题基本都能在属性表和事件回调里找到答案。 **Categories:** HTML5 ---