HTML5原生video/audio标签实战:从基础用法到自定义播放器
HTML里的video和audio是一对被很多人低估的标签。平时做网页提到放视频第一反应往往是去复制B站、优酷的iframe嵌入代码或者找一套第三方播放器插件提到放音频又总想着要接个什么库。实际上HTML5原生video/audio标签可以覆盖绝大多数日常播放需求尤其在移动端H5、小程序内嵌页面、活动页这些场景里原生标签反而更轻、更稳、更好控制。这篇文章围绕这两个标签把基础用法、编码格式、兼容性坑、事件机制和一套可直接改成项目用的自定义播放器逻辑完整拆一遍适合刚开始学html网页制作的前端新人也适合被各种视频黑屏、音频没声音折腾过的同学做一次排查参考。1. 基础用法video 与 audio 不是“插入视频”那么简单1.1 一个最小可用的 video 播放器先看最基础的写法。你只需要一个video标签和一个视频地址就能在浏览器里出现播放器video srcmovie.mp4 controls width720/videocontrols是关键属性它让浏览器渲染出默认的控制条包括播放/暂停、进度条、音量、全屏等按钮。不同浏览器的默认控制条样式不一样这也是很多项目最后会自己做UI的原因。第二个常用的可选写法是把视频来源放到source子标签里后面讲多格式兼容时再细说。video标签还有一些经常一起出现的属性这里一次性说清楚autoplay自动播放但浏览器对自动播放有严格限制一般需要配合muted静音才生效。muted默认静音对移动端自动播放几乎是必备条件。loop循环播放适合背景视频、活动氛围视频。poster设置播放前显示的封面图避免首屏一片黑。preload预加载策略控制浏览器在页面打开时加载多少视频数据。playsinlineiOS上禁止视频自动进入全屏播放内联播放必须加。还有一个很多人忽略的点video标签就算没有设置宽高也会在页面里占据一块区域形成布局占位。如果poster没有设置视频还没加载出第一帧时这个区域是透明的页面会出现一块空白。所以做活动页时我会习惯给video设一个背景色或一个占位图至少不要让用户看到一块黑。1.2 audio 标签没画面但交互一点都不少audio标签的写法和video非常像audio srcbgm.mp3 controls preloadmetadata/audioaudio没有画面但它同样有controls、autoplay、muted、loop、preload这些属性。唯一差别比较大的地方在于audio默认占位高度很矮宽度默认跟父容器一致。你在页面上看到的就是一条控制条不同浏览器的样式差异也很大。在项目中audio常用在三个场景背景音乐比如H5活动页的BGM一般会自动播放但注意移动端自动播放需要静音或者等用户点击后再触发播放。语音消息/音频课程音频列表页、客服对话里的语音条用audio加一条进度显示即可。提示音按钮点击后播放一段极短的提示音这种建议用Audio对象的动态创建而不是在页面里写死一堆audio标签。如果你需要动态创建一个音频对象可以这样const audio new Audio(ding.mp3); audio.play();这种写法不会在页面上渲染任何控件适合提示音和短音频。但要注意自动播放策略同样适用第一次播放最好放在用户点击事件的回调里。1.3 多源回退用 source 元素给浏览器多准备几手浏览器对视频编码格式的支持一直不统一合适的做法是提供多种格式让浏览器自己挑第一个能播的video controls postercover.jpg width720 source srcmovie.mp4 typevideo/mp4 source srcmovie.webm typevideo/webm 你的浏览器不支持 video 标签请升级浏览器。 /videosource标签从上往下依次加载如果第一个格式浏览器不能播就尝试第二个全部都不行的时候显示source下面的文字。注意这里的type属性最好写全比如video/mp4、video/webm不要只写文件名后缀一些浏览器判断会很严格。audio也支持同样的多源写法audio controls source srcaudio.mp3 typeaudio/mpeg source srcaudio.ogg typeaudio/ogg 你的浏览器不支持 audio 标签。 /audio多准备几个版本确实能提高兼容性但也意味着服务器的存储成本和传输成本更高。所以实际项目里到底准备几种格式要看你的目标用户用什么浏览器。如果大部分是微信内置浏览器和手机Chrome一份标准MP4通常就够用了。2. 格式兼容性决定了视频能不能播2.1 后缀、容器、编码谁才是决定因素很多刚学html的同学以为视频能不能播看的是文件名后缀。mp4肯定能播mkv肯定不能播其实这个理解不准确。视频文件可以拆成两个层次容器格式和编码格式。.mp4是容器它里面可以装H.264编码的视频也可以装H.265/HEVC编码的视频甚至某些MP4里还装着MPEG-4 Part 2编码的旧视频。浏览器支持的实际上是“容器编码”的组合方式而不是单纯的一个后缀。一个容易踩的坑是你拿手机拍了一段视频文件名是.mp4直接放到网页里发现黑屏或无法解码。因为iPhone拍摄的视频很多时候是H.265/HEVC编码而Chrome对HEVC的支持要看系统版本和硬件解码能力不是说你给了MP4浏览器就一定放得出来。所以做网页视频前搞清楚编码格式是优先级最高的事。最常见的组合是容器格式常见编码浏览器兼容性备注MP4H.264 AACChrome、Edge、Firefox、Safari、Android/iOS WebView兼容性最好首选格式WebMVP9/VP8 OpusChrome、Edge、Firefox体积小但Safari支持不够稳定OggTheora Vorbis老旧的Firefox、Chrome情况较多基本不用M3U8(HLS)H.264 AACSafari原生支持Chrome需通过MSE配合hls.js直播、长视频场景常见2.2 用 FFmpeg 做一次标准转码与其祈祷浏览器大发慈悲不如在资源上传前就把视频转成最稳的格式。我最常用的就是FFmpeg它也是很多内置视频转码工具背后的核心引擎。很多人搜“video transcoder安卓下载”“avpro video”其实本质上都是在找这类转码能力而FFmpeg在电脑上就是最通用的一种。一个适合网页播放的MP4我的标准参数是这样的ffmpeg -i input.mov -c:v libx264 -profile:v high -level 4.1 \ -preset medium -crf 23 -c:a aac -b:a 128k \ -movflags faststart -pix_fmt yuv420p output.mp4这些参数不是随手写的每个都有讲究-c:v libx264视频编码用H.264是目前兼容性最广的编码。-pix_fmt yuv420p强制使用YUV 420色彩采样。如果你不加这个参数视频有可能是4:4:4或10bit编码在一些浏览器和旧设备上会出现绿屏或者无法解码。-movflags faststart把MP4文件的索引信息放到文件头部。这样浏览器不需要下载完整文件就能开始播放网上播放体验会好很多。-crf 23控制画面质量数字越小质量越高文件越大23是大多数短视频场景的平衡值。音频部分用AAC编码码率128k基本能兼顾音质和体积。做音频网页播放时如果拿到的是一个WAV或FLAC文件也建议统一转成MP3或AAC编码因为部分低版本浏览器的音频解码支持并不理想。2.3 浏览器兼容性速查表别靠记忆直接查表我做一个项目前通常会确定目标浏览器的最低版本然后只看一张表就够了环境MP4/H.264WebM/VP9HLSChrome支持支持不支持原生需hls.jsEdge支持支持不支持原生需hls.jsFirefox支持支持不支持原生需hls.jsSafari支持部分支持原生支持iOS微信支持不保证支持Android微信支持部分支持不保证从这张表能看出来MP4/H.264仍然是唯一的“全场景通吃”方案。我的建议是如果项目预算只允许准备一份视频那一定是MP4/H.264如果追求极致的加载速度可以再给Chrome系用户准备一份WebM体积通常可以再小30%但前提是Safari和iOS端的降级逻辑要做好。3. 打开官方文档不讲的几个属性细节3.1 autoplay 的“静音才能自动播”规则浏览器厂商不是故意刁难前端自动播放限制是因为防止页面一打开就突然放声音这对用户来说很打扰。当前主流浏览器的规则是如果video或audio带muted属性自动播放通常放行。如果带声音自动播放浏览器会拦下来并抛出一个NotAllowedError异常。用户在和页面发生一次交互点击、触摸、按键之后用JS调用play()就能正常带声音播放。所以H5活动页背景视频的写法一般是video srcbg.mp4 autoplay muted loop playsinline/video也就是说让视频自动播放时必须先静音。等用户点击页面的“开启声音”按钮后再在点击事件里把video.muted false并调用video.play()。代码逻辑大概是const video document.querySelector(video); document.getElementById(unmuteBtn).addEventListener(click, function () { video.muted false; video.play(); });如果页面里有一个需要自动播放的audio背景音乐你也得先设置muted或者干脆不自动播放等用户点击后再执行audio.play()。很多新人在本地双击打开HTML文件测试自动播放是有声音的但一放到线上就被拦截就是因为浏览器在本地文件和线上HTTPS环境下的自动播放策略不完全一样。3.2 preload页面加载速度和视频体验的平衡preload属性有三个值auto提示浏览器尽力下载整个视频视频内容多、体积大的时候慎用。metadata只加载视频的元数据比如时长、尺寸、首帧信息这也是我给大多数页面视频推荐的默认值。none不加载任何视频数据直到用户点击播放。我见过不少页面首屏加载慢的罪魁祸首就是一段没设置preload的大视频。浏览器默认行为在不同内核里并不统一有时候它会把整个视频预加载完白白消耗流量和带宽。建议在首屏视频上这样写video srcbig.mp4 preloadmetadata postercover.jpg/video用户看到封面图点击播放时才去真正加载视频内容。对于背景循环视频因为它需要第一时间播放可以考虑preloadauto但一定要给视频压缩到合理体积并且用muted保证不触发自动播放拦截。3.3 poster 和 crossOrigin封面与跨域poster不只是“封面好看”这么简单。对于一个有几百MB的长视频如果用户还在犹豫看不看你不可能让他先把视频本体下载下来等着。有了poster浏览器只加载一张图片就能让页面看起来完整。poster还有一个容易被忽视的坑如果poster的地址和页面域名不一致有些浏览器出于隐私策略不会显示跨域来源的图片。所以poster图片最好放同域CDN或者给服务器配置跨域头Access-Control-Allow-Origin。crossOrigin这个属性平时用得少但做视频截帧、视频分析、WebGL贴视频的时候绕不开。如果视频源和页面不在同一个域而你又希望通过Canvas把视频某一帧画出来比如做视频封面截取必须给video加crossOriginanonymous同时服务器返回CORS头否则Canvas会变成“被污染”状态所有toDataURL、getImageData操作都会报错。video srchttps://cdn.example.com/video.mp4 crossOriginanonymous/video3.4 音频自动播放浏览器“声音控制权”到底在谁手里音频的自动播放限制比视频更严格。粗暴一点说没有用户交互几乎不可能让带声音的audio自动播放。很多产品需求是“用户一进页面背景音乐就响起来”这时候不能硬来只能让用户先做一次点击。一个相对优雅的做法是页面弹出一个可以点击的引导层用户点击“进入活动”的同时触发音乐播放。代码可以这样组织let audioStarted false; function startAudio() { if (audioStarted) return; const bgm document.getElementById(bgm); bgm.play().then(() { audioStarted true; }).catch(() { // 用户还没交互先不处理 }); } document.addEventListener(click, startAudio, { once: true });这里用play()返回的Promise来判断是否成功。如果用户还没交互play()会reject捕获异常就行不要让它报错打断页面逻辑。凡是涉及自动播放写代码时都要有“这次播放可能失败”的容错意识。4. 动手写一个自定义播放器4.1 自定义控件布局先把UI结构搭出来默认控制条在不同浏览器里长得不一样而且很难完全统一风格。做一个自定义播放器本质上是把video和audio的默认controls去掉自己用HTML、CSS、JS做一套控件。基础结构可以这样div classplayer video idvideo srcmovie.mp4 preloadmetadata postercover.jpg/video div classcontrols button idplayBtn播放/button input idseek typerange min0 max100 step0.1 value0 span idtime00:00 / 00:00/span button idfullBtn全屏/button /div /divCSS方面要让界面看起来像个独立播放器核心是把controls覆盖到视频底下.player { position: relative; width: 720px; max-width: 100%; } .player video { width: 100%; display: block; } .controls { position: absolute; left: 0; right: 0; bottom: 0; background: rgba(0, 0, 0, 0.6); color: #fff; padding: 8px; display: flex; align-items: center; gap: 10px; }这里的思路是把controls定位到视频底部播放按钮和进度条放在同一行。实际项目里你完全可以根据视觉稿调整但核心只有一个控制层里操作的其实是同一个videoDOM对象的属性和方法。4.2 JS 控制播放、暂停、进度和时间核心逻辑并不复杂总共分成三块第一播放/暂停按钮切换const video document.getElementById(video); const playBtn document.getElementById(playBtn); playBtn.addEventListener(click, function () { if (video.paused) { video.play(); } else { video.pause(); } }); video.addEventListener(play, function () { playBtn.textContent 暂停; }); video.addEventListener(pause, function () { playBtn.textContent 播放; });第二进度条和时间显示const seek document.getElementById(seek); const time document.getElementById(time); function formatTime(seconds) { if (Number.isNaN(seconds)) return 00:00; const m Math.floor(seconds / 60); const s Math.floor(seconds % 60); return String(m).padStart(2, 0) : String(s).padStart(2, 0); } video.addEventListener(timeupdate, function () { if (video.duration) { const percent (video.currentTime / video.duration) * 100; seek.value percent; time.textContent formatTime(video.currentTime) / formatTime(video.duration); } }); seek.addEventListener(input, function () { const targetTime (seek.value / 100) * video.duration; video.currentTime targetTime; });第三全屏切换const fullBtn document.getElementById(fullBtn); fullBtn.addEventListener(click, function () { if (document.fullscreenElement) { document.exitFullscreen(); } else { document.querySelector(.player).requestFullscreen(); } });如果项目里用的是jQuery操作audio/video的机制也是一样的只是绑事件换成了$(#video).on(timeupdate, ...)读取属性时仍然要用$(#video)[0].currentTime这种原生方式因为jQuery对象不能直接读DOM属性。我见过一些老项目里把$(this).play()写成这样然后报错就是没分清jQuery对象和原生DOM对象。4.3 键盘快捷键、倍速、画中画自定义播放器做好基础控制后可以顺手加几个高频功能。键盘快捷键是最提升体验的一步尤其适合视频课程、播客页面。监听页面的keydown事件空格键控制播放暂停方向键控制进度document.addEventListener(keydown, function (e) { if (e.code Space) { e.preventDefault(); if (video.paused) { video.play(); } else { video.pause(); } } if (e.key ArrowRight) { video.currentTime 5; } if (e.key ArrowLeft) { video.currentTime - 5; } });倍速播放特别简单直接改video.playbackRatevideo.playbackRate 1.5;画中画模式使用原生APIdocument.getElementById(pipBtn).addEventListener(click, async function () { if (document.pictureInPictureElement) { await document.exitPictureInPicture(); } else { await video.requestPictureInPicture(); } });这些都是建立在原生video之上的“白嫖”能力。你不需要引入任何大型库一段几十行的JS就能做完一个轻量播放器。5. 实战里的坑能避一个是一个5.1 问题排查速查表先定位再动手实际开发里我遇到的多媒体问题有时跟HTML代码完全无关而是编码、系统、浏览器策略三者交叉的结果。整理一张排查表能省很多时间现象最可能原因处理建议视频有声音但黑屏视频编码是HEVC或者色彩采样格式不兼容用FFmpeg转成H.264 yuv420p再检查是否启用了硬件加速播放出来是绿屏视频的像素格式或色深不被当前解码器支持转码时加-pix_fmt yuv420p并更新系统视频驱动页面打开自动播放没声音浏览器autoplay策略拦截了带声音播放先用muted自动播放用户点击后取消静音iOS上视频总会自动全屏video缺少playsinline属性加上playsinline必要时也加webkit-playsinline进度条拖动没反应视频没加载到可拖拽的位置或者事件绑定在错误对象上用timeupdate事件驱动进度条确保操作的是原生DOM元素在线视频加载很慢MP4的moov元数据在文件尾部转码时加-movflags faststart线上跨域视频无法截帧CORS头缺失给CDN配置Access-Control-Allow-Originvideo加crossOrigin5.2 关于转码、编码和工具链的个人建议做了这么多年页面我的视频素材处理流程已经固定下来了手机拍完或设计给到的视频先丢进FFmpeg统一转成H.264AAC的MP4然后才敢放到video标签里。很多朋友在网上搜索“video transcoder安卓下载”“avpro video”这类工具其实不管工具名叫什么最后做的事情都是一样的把浏览器不识别的编码换成H.264。如果连FFmpeg都还没用过与其到处找各种图形化转码软件不如花半小时熟悉一下这个命令行工具它基本是万能的。有一个原则我想强调不要在Web视频里用H.265/HEVC作为唯一编码。虽然它的压缩率高但Chrome播放HEVC需要硬件和系统层面的支持一旦用户设备不支持你就只能用source回退到另一份H.264文件这样等于你要维护两套视频成本比省下的那点流量高得多。除非你的目标用户非常明确比如全公司强制使用同一型号电脑否则我不会把H.265当主力。5.3 音频驱动和浏览器无声的排查顺序如果audio标签在浏览器里没声音先别急着改代码。浏览器能不能出声涉及好几层浏览器音量、系统音量合成器、默认播放设备、音频驱动。其中任何一层静音前端代码写得再好也一样没声音。排查顺序我一般是这样打开页面另一个视频比如B站视频确认浏览器是不是整体没声音。右键点击Windows右下角喇叭打开音量合成器看当前浏览器那一栏音量是否被拉低或静音。检查默认播放设备。如果你插了HDMI显示器声音可能被自动切到显示器扬声器上。最后再看系统音频驱动常见的Intel High Definition Audio驱动如果出了问题理论上系统会统一没声音这时候前端代码什么都不用动把驱动更新或重装问题就没了。很多搜“intel high definition audio 驱动”的用户其实是在网页里播放没声音一路排查到了驱动层。这个思路没错但要记住顺序先排除浏览器和系统基础设置再谈驱动。5.4 一个更省心的扩展方向当你的项目需要更复杂的播放能力比如直播、自适应码率、多音轨、视频加密可以考虑在不推翻原生video/audio的前提下引入播放器框架比如video.js、hls.js、plyr。这些框架底层依然是多个video标签在管理播放只是帮你把UI、事件、兼容策略都封装好了。我不建议一上来就上框架。先试着用原生标签把基础功能写完过程中你会更清楚哪些问题是框架替你解决的哪些问题是内容源本身的问题。等下一次碰到真正复杂的项目你选框架时心里就有底了。在我自己的项目习惯里video和audio始终是最底层的播放器业务逻辑只通过标准事件和属性去对接。这样不管以后换CDN、换播放器皮肤、换流媒体协议页面里那层标签代码都不需要推倒重来。上面这些内容就是在无数次踩坑之后沉淀下来的那部分最常用的方案。