
先快速说明一下。“video-use”这个名字听起来很泛好像什么都能往里装。我一开始接触的时候以为是某个视频播放器的项目后来真正上手才发现这名字对应的是一整套“视频怎么进项目、怎么被处理、怎么最终呈现出来”的完整链路。用大白话讲就是搞清楚视频资源在网页端从前到后到底怎么流转、怎么用才算用对了。这篇文章我打算详细拆一拆这条链路把我踩过的坑、验证过的方案、实测下来的参数选择都写出来给那些正在做视频功能但被各种兼容性和性能问题折磨的朋友一个参考。我才开始做这个方向的时候以为视频就是一行video标签的事后来被现实狠狠教育了从摄像头采集到本地文件播放从录制到截图从桌面端到移动端每个环节都有让你头疼的细节。这篇文章就用“video-use”这个主题把这套东西讲清楚。1. 先从整体设计说起视频使用到底包含哪些环节1.1 视频资源从哪来三种典型输入源视频使用的第一步不是“怎么播放”而是“视频从哪来”。不同来源决定了后面所有处理方式的差异。我这边实际接触的训练和业务需求视频来源基本分成三大类摄像头实时画面用getUserMedia采集适合直播、短视频拍摄、扫脸登录这类场景本地视频文件用户从手机相册或电脑磁盘选择适合视频编辑、上传预览、内容审核远程地址流媒体HTTP-FLV、HLS、RTMP 等格式适合直播播放、监控画面、在线课程我踩过最深刻的一个坑就是把三种来源混为一谈以为拿到了视频流就能直接往video里塞。实际上它们的生命周期完全不同摄像头采集的流需要手动关闭摄像头指示灯文件流需要考虑预加载和内存占用远距离流媒体则需要关心浏览器是否支持对应格式。理解了来源差异才不会被后面五花八门的报错整懵。处理这三种输入源时我强烈建议规划阶段就把下面几点想清楚摄像头画面是否只做预览还是要同时录制和截图本地文件是否需要转码还是直接原格式播放远程视频流是否需要降级兼容方案比如移动端不支持某些编码时的保底策略千万别等代码写了一半才补这些设计否则就是来回返工。1.2 谁在使用视频用户角色和场景差异视频使用的第二个维度是“谁在看”这决定了你该采用什么技术策略。我实际总结下来用户角色和场景主要就三类第一类是普通消费者只希望视频点开就能播拖动进度条不卡顿画质清晰。这类人的核心诉求是“播放体验”。你要解决的是码率自适应、预加载策略、封面和边下边播这些细节。第二类是创作者或运营者他们需要录屏、剪辑、添加字幕、生成封面截图。这类人的核心诉求是“视频处理能力”。你要解决的是采集帧率控制、编码参数选择、画面尺寸裁剪这些细节。第三类是审核或管理员他们需要快速定位视频中的关键帧比如检查某一秒的画面内容。这类人的核心诉求是“视频检索与定位”。你要解决的是帧精确跳转、缩略图生成、时间轴精准渲染这些细节。我刚开始做的时候忽略了用户角色这个维度导致做的功能看起来都能用但每个角色都用得不顺手。被点醒之后才开始按角色拆解需求视频模块才真正变得好用起来。做视频相关的项目先不要急着写代码把使用者画像列出来能省后面特别多麻烦。2. 核心细节拆解视频播放前必须处理的几个关键参数2.1 视频格式与浏览器兼容为什么你的视频“白屏”视频播放不出来的最大原因大概率不是代码写错了而是视频编码格式和浏览器支持不匹配。我做视频功能遇到的第一道坎就在这。拿最常见的两个容器格式举例MP4 容器里装 H.264 编码的视频和 AAC 音频几乎所有浏览器都能播放而如果一个 MP4 文件里面装的是 HEVC 编码那在部分浏览器上就是黑屏但在苹果生态里又可能很流畅。另一对经典组合是 WebM 容器配 VP9 编码Chrome 和 Firefox 都支持但旧版 Safari 就完全不认识。这里我制作了一个快速自查表方便做项目时对照容器格式常见视频编码常见音频编码主要兼容性表现MP4H.264AAC全平台最稳首选格式MP4HEVCAAC苹果系好部分安卓/浏览器不支持WebMVP8/VP9OpusChrome生态友好Safari老版本不行MOVH.264PCM/AAC苹果拍摄原片浏览器支持有限FLVH.264AAC直播场景常见需要专门播放器处理应对方案其实很成熟服务端提供多码率多格式视频前端根据canPlayType()判断选择。这个方法非常关键实际开发的时候几乎每个视频项目都要用到。比如同时准备一个 H.264 编码的 MP4 和一个 WebM 格式的视频播放时先测一下video.canPlayType(video/webm; codecsvp9)根据结果决定用哪个地址。2.2 autoplay 策略为什么设置了自动播放却不生效自动播放是视频使用中问题频率最高的“小问题”。不是代码不生效而是浏览器策略尤其是移动端对自动播放卡得非常严。我整理了一下实际规律静音视频muted 属性为 true通常允许自动播放这是桌面端和移动端都相对宽松的点有声音的视频在绝大多数现代浏览器中不允许自动播放需要用户主动交互点击、触摸等后才能生效iOS Safari 中即使设置playsinline也必须配合 muted 才能实现自动播放这条规则坑了很多人部分安卓 WebView 对自动播放的处理跟标准浏览器不一致需要前端额外做降级方案所以我做视频页面的实用做法是如果需要自动播放加 muted 和 playsinline 属性如果需要声音那就引导用户点一次“开启声音”按钮点击后再调用video.play()。实测下来这个方案几乎所有场景都能顺利通过。还有一个容易忽略的细节video.play()返回的是一个 Promise如果播放失败Promise 会 reject。如果你没有捕获这个异常控制台会出现一个不太显眼的报错。我之前就因为这个原因调试了很久。稳妥的写法是video.play().then(() { console.log(播放成功); }).catch((error) { console.log(自动播放被拦截, error); // 这里可以展示一个提示告诉用户手动点击播放 });2.3 画质与加载权衡预加载和边下边播怎么配视频使用的体验高低很大程度上取决于预加载策略配得合不合理。这里也涉及一个常见误区不少开发者会以为把视频整个下载下来再播放最稳妥结果首屏打开时间直接爆炸。视频文件不同于普通图片一个高清视频动辄几百 MB如果每次都等全部下载完再播用户早就离开了。正确思路是“边下边播”加“预加载部分数据”。实际开发中我推荐这样配置不设置preloadnone或preloadmetadata优先加载视频元数据让播放器知道视频的总时长、尺寸、编码信息用户可以点击播放时再加载真正的数据块对于后续极可能播放的视频比如列表中的第一个视频设置preloadauto让它有更充裕的缓冲时间给video元素设置poster封面图让用户等待时不会对着黑屏发呆另外如果视频服务端支持 HTTP Range 请求那么“边下边播”体验会非常平滑如果不支持 Range浏览器往往会直接下载整个文件这种场景下再好的前端策略也白搭。所以做视频功能时服务端对 Range 的支持情况也要确认一下别只管前端不管服务端。3. 实操记录从视频采集到播放展示的完整链路3.1 摄像头采集getUserMedia 的参数选择与切换视频使用里让我折腾最久的是摄像头采集这一环。因为涉及权限、设备选择、分辨率切换、帧率控制每一样都有说头。先看一下基础权限申请怎么写这一步要求用户必须在 HTTPS 环境下才能正常工作如果部署在本地开发环境localhost是可以的但如果是局域网内其他机器访问你的 IP浏览器会默认不给摄像头权限这是个非常常见的坑。try { const stream await navigator.mediaDevices.getUserMedia({ video: { width: { ideal: 1280, height: 720 }, frameRate: { ideal: 30, max: 30 } }, audio: false }); video.srcObject stream; video.play(); } catch (error) { console.error(无法获取摄像头权限, error); }这里要注意ideal和exact的区别。ideal表示优先尝试这个值如果设备不支持就自动降级exact表示强制使用这个值不支持就直接报错。我在做多设备适配时几乎不用exact因为摄像头规格千差万别强制反而容易暴露兼容性问题。设备切换是另一个高频操作。现在的笔记本和手机上动辄前后两个摄像头甚至还有外接摄像头的场景使用前最好先枚举设备列表async function listCameras() { const devices await navigator.mediaDevices.enumerateDevices(); return devices.filter(device device.kind videoinput); }拿到设备列表后给每个设备一个 ID切换时用这个 deviceId 重新发起getUserMedia然后把旧流的每个轨道都停掉track.stop()否则你会发现摄像头指示灯一直亮着因为旧的流并没有真正释放。我在这个细节上栽过跟头后来养成习惯每次切换前都把所有 track 停干净。3.2 实时预览与快照canvas 截图的核心原理使用视频时“截图”这个动作听起来特别简单但实际上很多初学者都会掉进同一个坑直接在 video 元素上调用绘图类方法比如video.toDataURL()。这里先说明白video 元素本身没有toDataURL方法真正的做法是先把视频画面绘制到 canvas再从 canvas 导出图片。我的截图流程是这样的等视频进入canplay状态确保有画面可截创建一个和视频画面尺寸对应的 canvas用drawImage(video, 0, 0, width, height)把当前帧画到 canvas 上调用canvas.toDataURL(image/jpeg, 0.9)或canvas.toBlob()导出图片代码如下function captureFrame(videoElement) { const canvas document.createElement(canvas); canvas.width videoElement.videoWidth; canvas.height videoElement.videoHeight; const ctx canvas.getContext(2d); ctx.drawImage(videoElement, 0, 0, canvas.width, canvas.height); return canvas.toDataURL(image/jpeg, 0.9); }不要贪心把 canvas 尺寸设得巨大手机摄像头画面放大到 4K 尺寸虽然可行但生成的图片体积和内存开销都会变大。根据实际用途决定尺寸做封面缩略图就用 1280 或更小做高清截图再考虑原尺寸导出。3.3 本地录像MediaRecorder 的正确打开方式视频项目做到后期基本都会遇到录制需求。MediaRecorder 是浏览器原生提供的编码录制方案不需要额外引入第三方库。我实际踩过最重要的坑是编码格式的兼容性。MediaRecorder 在不同浏览器上默认输出的封装格式不一样Chrome 通常输出video/webmSafari 则可能输出video/mp4。如果你的录制功能要跨平台使用接收端最好用支持多格式的播放方案或者转换后上传。基础录制流程const recorder new MediaRecorder(stream, { mimeType: video/webm;codecsvp9, videoBitsPerSecond: 2500000 }); const chunks []; recorder.ondataavailable (event) { if (event.data event.data.size 0) { chunks.push(event.data); } }; recorder.onstop () { const blob new Blob(chunks, { type: recorder.mimeType }); // 这里拿到 blob可以上传、预览或下载 }; recorder.start(1000); // 参数表示分段收集数据的时间间隔单位毫秒关于分段收集时间间隔我这里补充一下背后的原理MediaRecorder 默认不会频繁触发ondataavailable设置间隔越小数据回调越频繁内存里的数据块越多。我建议不要设得过于小比如 100ms在录制长时间视频时会造成大量小片断堆积录制几分钟的视频能产生几千个 chunk。设成 1000ms 已经足够及时获取数据又不会让内存压力太大。3.4 视频播放器的增强进度条、倍速和截图按钮拿到了视频画面很多人以为直接用video标签就行其实原生控制条在样式和交互上都很简陋。现在主流的做法是用自定义控件 原生播放能力结合。我实现的自定义控制条至少包含下面几个能力播放/暂停状态切换当前时间和总时长显示可拖动的进度条倍速切换0.5x、1x、1.5x、2x音量控制和静音开关全屏切换先说进度条它依赖的核心事件是timeupdate这个事件在视频播放时会被频繁触发可以用来更新进度条位置。但要注意频繁触发也带来了性能开销。我一般不会在每次事件回调里直接操作 DOM而是先记录当前时间到一个变量然后用requestAnimationFrame再去更新进度条避免大量没有必要的重绘操作。倍速播放直接设置video.playbackRate即可这个属性原理上就是控制播放速率设置完成后浏览器会自动做音画同步处理。需要注意极端倍速比如 0.25x 或 3x在某些设备上会有音画不同步问题一般提供 1x、1.25x、1.5x、2x 就够用了。全屏有两种方式使用 video 元素的requestFullscreen()整个视频画面铺满屏幕把视频所在容器requestFullscreen()可以连同自定义控件一起全屏。这个更符合自定义播放器的预期行为4. 视频使用中的常见问题排查手册4.1 各种播放异常的原因和解决方案视频使用过程中遇到的问题大部分场景都能归到下面这个表格里。异常现象可能原因解决方案视频画面黑屏但声音正常视频编码不被浏览器支持服务端转码 H.264或加 WebM 降级地址明明设置了 autoplay 却不播放浏览器自动播放策略拦截加 muted 属性或引导用户点击iOS 上视频全屏播放而不是内嵌播放缺少 playsinline 属性给 video 标签加 playsinline并且不设置自动全屏画质模糊且有明显锯齿视频实际分辨率小于显示尺寸检查视频源分辨率显示尺寸不要超过原视频尺寸拖动进度条后要等很久预加载策略不当或没有 Range 支持设置 preload 属性确认服务端支持 Range视频中途卡顿缓冲频繁码率过高或网络差提供多码率切换或开启自适应码率控制摄像头预览绿屏设备或浏览器兼容问题检查 SDP切换浏览器验证部分摄像头需要更新驱动录制的视频没有声音采集时没加音频轨道getUserMedia 里设置 audio: true同一个视频在 Safari 和 Chrome 体积表现不同封装格式被浏览器转换成不同编码录制时确认 mimeType存储格式统一管理4.2 性能调优从视频加载到播放的卡顿排查视频使用场景里“卡顿”这个问题的排查比较费时间需要从网络、服务端、客户端三层逐步确认。我自己的排查顺序是先看 Network 面板确认视频请求是否一次拿到全部数据还是按 Range 分段返回。如果网络请求里只有一个大文件的完整下载说明服务端可能没开 Range 支持这样拖动进度条时体验会很差再看 Performance 面板确认是不是因为同时启动了太多高耗能任务比如同时解码多个视频流、大量 canvas 绘制导致主线程阻塞最后看视频数据本身同样是 1080pH.264 和 VP9 的帧率表现并不一样部分设备硬解支持和软解性能差异很大上述三点都确认过后再看是不是网络带宽问题。如果服务端没有限速策略而用户带宽有限那么高码率视频就一定会卡。一个比较好的做法是支持“码率切换”检测到网络状态下降时自动切到低码率版本。浏览器原生播放器其实没有特别通用的统一 API 来控制切流通常需要借助 HLS.js 这类库来管理。4.3 设备兼容性安卓、iOS 和桌面浏览器差异不同设备上视频使用的行为差异是让我最头疼的部分没有之一。如果不提前测试而是等上线后由用户反馈往往已经造成了比较不好的体验。iOS Safari 上最典型的问题有原生播放器会自动抢占全屏需要加playsinline规避部分机型对视频最大宽高有限制太大尺寸的视频会被压缩低电量模式下自动播放策略更严格安卓 Chrome 的问题则集中在不同手机厂商的自带浏览器内核版本参差不齐WebView 容器对 autoplay 策略不一致有的企业 App 内所有自动播放都需要手动开启部分国产浏览器对getUserMedia权限弹窗的处理逻辑不一样有的无法获取摄像头权限桌面浏览器相对温和但也要注意Firefox 对某些编码支持跟 Chrome 不同需要canPlayType()动态判断Safari 桌面版对 WebRTC 采集的支持一直存在槽点曾经出现过拿到 stream 但画面黑屏的问题多个显示器或屏幕分辨率不同时全屏的尺寸表现也可能不一致我的做法是平常开发时把“基线浏览器”收敛为 Chrome Safari 微信内置浏览器三类优先保证这三类完全可用再让其他环境降级到基础播放能力。项目早期就建立一个兼容性测试列表把关键设备型号和浏览器版本列出来每次改动后至少过一遍核心流程能避免大量因“就你环境不行”产生的返工问题。5. 方案选型解析直接抓来即用的实用工具和库5.1 播放器内核选择原生 video 还是第三方播放器复盘视频使用相关的技术选型时有一个问题一定是绕不开的到底直接用原生video还是集成第三方的播放器内核这个问题的核心不是“哪个好”而是“你要为哪些回放场景负责”。我见过不少项目一上来就集成一个巨大的第三方播放器结果页面加载变慢、样式改动困难、部分高级功能根本用不上也见过只写了一个原生video标签、结果要做直播低延迟播放时完全撑不住场面的项目。直接使用原生video的场景视频格式相对简单统一为 H.264 MP4不需要弹幕、清晰度切换、广告插播、付费鉴权等复杂业务逻辑需要完全自定义 UI不希望被播放器样式束缚对包体积极度敏感的小项目、小工具页面第三方播放器更合适的场景需要支持 HLS/FLV 等流媒体协议需要多清晰度无缝切换需要倍速、镜像、画中画、弹幕等高级组合功能需要统计播放数据、错误上报的能力我自己在正式项目中最常用的方案是原生 video 负责底层渲染第三方库负责解析协议和“源管理”比如用 hls.js 解析 HLS 流再把这些流“喂”给原生 video 播放。这套组合几乎可以覆盖绝大多数直播和点播场景。5.2 录制与截图工具链从字节流到可播放文件的完整路径视频拍摄、录制、截图这些能力浏览器原生 API 大多能覆盖。视频产生后的产业链环节才更需要设计。录制完成后你会得到一个 blob 文件。下一步的处理分几个分支如果想在页面上直接预览可以用 URL.createObjectURL(blob) 生成一个临时地址如果想上传保存建议先用 File 构造函数包装成文件对象同时携带正确文件名和后缀如果想做视频缩略图可以提取第一帧画面前面说到的 canvas 截图方式作为列表封面但这里存在一个绕不开的兼容性难题MediaRecorder 生成的文件格式并不是统一的。Chrome 下多输出 webmSafari 下可能输出 mp4。为了让后端统一处理要么后端同时接收两种格式并分别转码要么前端把 blob 传到服务端后统一转码。我比较推荐前者后端接收到文件后立刻判断容器格式再决定走哪条转码路径这样做容错率更高。上传这块不能简单地把 blob 当普通文件处理要顺便做好“断点续传”或“分片上传”规划。录制视频动辄几百 MB一旦中途断了就要重传在移动网络下用户体验非常差。我的做法是录制过程中每收集到一个 chunk 就传递给上传模块按时间顺序上传分片全部传完后再通知服务端合并。这样既避免单次上传过大也便于失败重试局部上传而不是整个文件重新上传。5.3 视频处理方向WebCodecs 和 WASM 是未来吗做视频功能久了你会发现原生 API 的边界很清晰能采集、能录制、能播放但要编辑视频比如裁剪、拼接、加滤镜、变速原生能力就不够看了。浏览器里做视频编辑目前技术演进的方向有两条一条是 WebCodecs API它允许开发者更底层的控制视频帧数据的解码和编码。相比 canvas 一帧帧绘制再重编WebCodecs 直接操作编码数据效率高很多。但问题是目前生态还比较初级配套的封装方案并没有完全稳定下来。适合有较好前端工程能力、愿意自己封装视频处理管线的团队。另一条是 WASM 方案把 C/C 生态里的 FFmpeg 编解码能力编译成 WebAssembly 在浏览器里跑。这是当前做视频编辑的硬核方案处理复杂视频任务时可以绕过浏览器原生 API 的限制但缺点是传递数据到 WASM 内存时有开销视频尺寸大时内存占用也比较高。现实中的技术路线往往是把两者结合起来获取视频源用原生 API解码处理用 WebCodecs 或 WASM渲染用 canvas 或 WebGL最终导出再用 MediaRecorder 或重新编码。这种“混搭”风格是现在视频处理技术的团队常态。6. 从开发视角再看一版一个完整视频功能页面的落地全流程6.1 页面结构与功能清单梳理把上面的内容综合起来最直观的落地方式就是自己动手做一个视频功能测试页面。我建议目标页面至少包含视频选择或摄像头预览区域播放控制栏播放/暂停、进度条、时长显示、倍速切换截图按钮与缩略图展示视频录制按钮与录制文件列表兼容性检测与错误信息提示区域我按照这个清单搭建页面原型时每个板块之间尽量解耦播放器模块管理 video 元素、播放状态、事件监听采集模块管理摄像头开启、关闭、切换录制模块只处理 MediaRecorder 逻辑拿到 blob 后交给列表管理截图模块独立触发不依赖播放器内部控制逻辑模块解耦的好处是出错时排查方便每个功能单元可以单独验证。我在实际开发中因为一开始模块耦合太紧录制时想换采集设备结果整个录制流程都受影响花了大量时间排查。后来把采集和录制彻底分开问题就消失了。6.2 流程衔接与状态管理细节视频使用功能页面最容易翻车的地方反而不是某一个单独功能的实现而是功能与功能之间的状态衔接。比如录制过程中用户切走了摄像头录制的轨道会怎样截图的时候视频还停留在暂停状态画面是否还能截到播放器切换到另一个视频源时之前的录制 blob 会不会被覆盖针对这些问题我的做法是建一个简单的状态机来管理视频模块的当前状态idle初始状态没有视频源ready视频源就绪可以播放playing播放中paused播放暂停recording录制中error出现错误需要恢复或重置每次状态切换时做必要的资源清理。比如从 playing 切到 recording要确认音量、倍速等设置不会影响录制音质从 recording 切回 idle要正确 close 掉 MediaRecorder并清理所有已收集的 chunk防止下次录制拼接了上一次的残留数据。我强烈建议视频项目的核心操作都放到一个统一的状态管理容器里哪怕不用 Vuex、Redux 这类重型方案至少用一个简单的 event bus 或者一个普通对象来集中管理状态也会比让各个模块直接互相调用方便得多。6.3 上线前的自测清单从“能跑”到“可以上线”还有一段路要走。这段路的核心不是加什么炫酷功能而是把至少这些细节都验证一遍视频在 Chrome、Edge、Firefox、Safari 中都能正常播放安卓手机微信内置浏览器能正常播放和截图iPhone Safari 内播视频不会自动全屏无网络情况下能显示统一的报错提示而不是白屏反复切换视频源后内存不爆涨摄像头指示灯能正确熄灭录制完成后 blob 能正确回放或上传播放倍速切换后画面和音频不脱节全屏切换后自定义控件仍然可用每一条自测项背后都是我实际踩过或者在别人的项目里见过的真实线上问题。不要嫌麻烦视频功能出问题的返工成本很高。像“iPhone 上自动全屏”“安卓 WebView 自动播放失败”这类问题如果没有提前在自己项目里确认等用户报出来时往往很难快速定位原因。7. 视频使用的性能优化与体验细节打磨7.1 帧率、码率与加载时间之间的取舍视频播放卡不卡本质上是一场帧率、码率、加载时间互相拉扯的比赛。画质高码率自然高加载时间就会增加分辨率高帧率可能不稳播放体验就会下降。这中间没有绝对正确的参数只有适合场景的方案。我自己的经验是短视频列表页用低码率版本720p2Mbps 左右就够了保证缩略图和快速切换的流畅性用户点进去后想看清再切换高清源长视频播放页优先保证码率稳定用 1080p 配合自适应码率避免上下滑动时频繁缓冲直播场景低延迟比画质重要码率可以适度降低但首屏出画面速度要控制在 2 秒以内录制视频上传采用稍高码率给后期编辑留空间比如视频比特率设定到 8Mbps 或者更高一点看平台限制码率是一个可以动态调节的值。如果你自己搭建视频服务建议开启转码输出多档位视频前端根据navigator.connection的 downlink 数值大致估算带宽再切换适合的档位。不需要做得很复杂能区分“弱网”和“正常”两档就够了效果提升会很明显。7.2 缩略图和封面让列表页不再卡顿如果视频列表页直接靠video预加载去展示所有视频内容页面性能和流量消耗都会很差。更合理的方式是用封面图 轻量缩略图方案。我在实际项目中一般做两层处理第一层视频列表用封面图展示封面图由服务端在视频上传时生成前端只请求图片资源。这样列表页的加载速度会快很多因为图片比视频文件小几个数量级。第二层当用户鼠标悬停或滑动到某个视频时可以再加载一个短视频预览片段一般为 5-10 秒的低码率视频用户不需要点击就能快速预览内容体验会明显好很多。这里有个小细节要注意预览视频不能跟正式视频共用一个拉流地址否则预览时会把整个视频缓存下来浪费用户流量。预览地址应该单独生成一个裁剪后的短视频或者用特定时间段的 Range 请求方式拉取对应片段。7.3 视频使用的内存泄漏问题视频功能打开多了内存会越占越高最终表现为页面卡顿甚至崩溃。这方面的原因主要来自两个方向一是没有正确释放视频流资源。播放结束后仅仅 pause() 是不够的要使用video.srcObject null来断开连接。摄像头采集场景尤其要注意每个 track 都要 stop()。我之前调试一个直播页面每次切换频道都会新增一个视频流没有释放旧流结果十几个频道换下来页面内存直接翻倍。二是事件监听器没有移除。视频元素监听的事件比较多timeupdate、progress、loadedmetadata、ended、error、pause、play。如果每次创建视频时都绑定事件但销毁时忘记解绑旧的视频对象就永远无法被垃圾回收堆内存就会持续增长。我建议创建播放器实例时把事件处理函数引用都存下来销毁时用removeEventListener统一移除这个习惯对长页面尤其有用。视频使用这个问题听起来门槛不高一旦深入进去发现需要对接和权衡的细节非常多。用一句我自己的感触收个尾如果你正在做视频相关功能别急着把各种第三方库堆上去先把原生 video、getUserMedia、MediaRecorder、canvas 这套基础能力吃透再结合自己的场景做取舍大概率能找到一条既稳健又省资源的道路。换句话说先学会怎么处理视频的冷冰冰的底层细节再谈那些酷炫的业务功能会让整个项目扎实很多少让用户在屏幕前等几秒比什么都强。