简介面向需要在浏览器中播放多种格式视频流文件的开发者与运维人员这套浏览器集成VLC视频插件方案围绕IE8、Chrome、Firefox等主流环境给出从插件获取、加载项启用、页面嵌入到脚本控制的完整参考。压缩包共3个文件、约38.98MB含一份doc图文说明文档、一个video.html页面示例以及vlc-3.0.8-win32.exe播放器安装程序分别对应原理梳理、代码参考与环境准备三类用途。已有7313人学习下载。文档部分讲解了ActiveX与NPAPI插件在不同浏览器下的差异以及object/embed标签中mrl、autoplay等参数的含义与配置要点video.html则演示了借助VLC提供的JavaScript接口实现播放、暂停、停止与音量调节的实际写法可在此基础上改造为响应式播放页面。对于需要兼容老旧浏览器、处理流媒体协议的项目可直接复用其中的嵌入结构与调用方式减少反复试错的时间。1. 内网页面里塞一个能播 RTSP 的窗口这个 VLC 控件值不值得用浏览器集成 VLC 视频插件这套方案放到今天看确实别扭但在一些内网系统里它仍然是唯一能用的办法。某公司的巡检客户端界面是一套跑在 IE8 内核里的 Web 页面后台要求页面上直接播放 6 路 RTSP 摄像头外加几段本地录像文件。HTML5 的video播不了 RTSP也不认 MPEG-2/TS 这些封装Flash 播放器得先有一台转码服务器把流切成 FLV 或 HLS。于是剩下的路只有一条往页面里嵌一个 VLC 的 ActiveX 控件用 CLSID 把 COM 对象找出来再用vlc.playlist这层脚本接口去控制播放、暂停、切换源。它解决的问题很具体让一台装着 IE8 的 Windows 机器在不装任何客户端软件的前提下播得动 RTSP、UDP 组播、HTTP 流和本地视频流文件。适合的人也很具体手上有一套改不动的老 Web 系统又必须把视频接进去的前端、实施和运维。如果你是做新项目的直接上 HLS 网关或者 WebRTC不用往下看。2. 把 axvlc.dll 挂进 IE8CLSID 注册、位数匹配与 2.2.x 版本锁定2.1 为什么这里只能走 ActiveX而不是 HTML5 video选型的核心矛盾在协议层和解码层不在播放器本身。浏览器video的能力边界是 HTTP 渐进下载加 HLS/DASH它不负责 RTP 解包也不负责 RTSP 的 DESCRIBE/SETUP/PLAY 握手更没有 UDP 组播的 socket 权限。而 VLC 背后的 libvlc 是一整套 demux decode 栈RTSP、RTP、UDP、HTTP、文件MPEG-2、H.264、H.265、TS、PS它都能吃下去。ActiveX 是那个年代 IE 唯一开放的进程内扩展点VLC 把它包装成axvlc.dll注册成一个 COM 组件页面用classid一引就出来了。把几种常见做法摆在一起看会更清楚方案能播的协议依赖改造成本维护风险HTML5videoHTTP 渐进、HLS无低老浏览器不支持RTSP 直接出局Flash 播放器RTMP、HTTP-FLVFlash Player 转码服务中插件已被淘汰装不上转码网关 HLS任意源转 HLS一台转码服务器高延迟大多路并发成本高VLC ActiveXRTSP/RTP/UDP/HTTP/文件客户端装 VLC 组件低只能在 IE 内核里跑锁版本对这套内网系统来说第四行的性价比是压倒性的不用加服务器不用改协议客户端本来就要装一堆控件。代价是把自己钉死在 IE 内核和一个特定版本的 VLC 上这个代价后面几章会反复出现。2.2 版本必须锁在 2.2.x别想着升级这是整个方案里最容易翻车、也最容易被忽略的一条。VLC 从 3.0 开始移除了浏览器插件NPAPI 和 ActiveX 一起所以只要你的页面上还要出现object classidclsid:9BE31822-...客户端就只能装 2.2 系列2.2.8 是这条线末尾比较常用的一个版本。很多人图省事给客户机装了最新版 VLC结果页面上一片空白控制台没有任何报错因为 CLSID 根本没被注册进去。我一般会在部署脚本里加一道硬校验先看注册表里这个 CLSID 指向哪个 DLL再看文件版本echo off rem 查这个 CLSID 当前注册到了哪个 dll reg query HKCR\CLSID\{9BE31822-FDAD-461B-AD51-BE1D1C159921}\InprocServer32 /ve rem 查文件版本正常应该看到 2.2.x wmic datafile where nameC:\\Program Files (x86)\\VideoLAN\\VLC\\axvlc.dll get versionreg query的默认值/ve就是 DLL 的完整路径。如果这条命令返回「系统找不到指定的注册表项」说明控件压根没注册页面上的object会退化成一个空白区域不会抛异常。如果返回的路径跟你预期的安装目录不一致那就是机器上装过别的版本CLSID 被抢占了后面会出现「明明改了代码却毫无反应」的问题。提示9BE31822-FDAD-461B-AD51-BE1D1C159921是 VLC ActiveX 控件的固定 CLSID页面里的classid和注册表校验都用这个值不要从别处复制相似串。2.3 注册与目录结构只拷一个 dll 必然黑屏axvlc.dll不是一个自包含的组件它依赖同目录下的libvlc.dll和整个plugins文件夹。只把axvlc.dll复制到System32再regsvr32注册是这套方案里最经典的错误做法控件能加载JS 能拿到对象playlist.play()也不报错但画面永远黑着因为解复用器和解码器模块全都找不到。正确做法是整目录部署然后把注册路径指向那个目录。另外位数必须对齐IE8 在 64 位系统上默认启动的仍然是 32 位进程所以要用 32 位 VLC而 32 位 DLL 必须用 32 位的regsvr32注册也就是%WINDIR%\SysWOW64\regsvr32.exe直接用命令行里那个默认的regsvr32会把 32 位 DLL 交给 64 位注册器处理报一个语焉不详的 0x80004005。echo off setlocal set VLC_DIRC:\Program Files (x86)\VideoLAN\VLC set AXDLL%VLC_DIR%\axvlc.dll set REG32%WINDIR%\SysWOW64\regsvr32.exe if not exist %AXDLL% goto :nodll if not exist %REG32% set REG32regsvr32.exe rem 先注销再注册避免同一 CLSID 挂两个路径 %REG32% /s /u %AXDLL% %REG32% /s %AXDLL% if errorlevel 1 goto :regfail reg query HKCR\CLSID\{9BE31822-FDAD-461B-AD51-BE1D1C159921}\InprocServer32 /ve goto :eof :nodll echo [ERR] axvlc.dll missing, check VLC_DIR exit /b 1 :regfail echo [ERR] regsvr32 failed, run as administrator exit /b 1这段脚本里没有用括号包住的多行if块是有意为之安装路径里的Program Files (x86)自带右括号在if (...)块里会把块提前截断批处理会以一种莫名其妙的方式失败。改成goto跳转就没这个问题。脚本里的reg query是最后一道保险把注册结果打出来实施人员截图留档比事后猜要省事得多。/u先注销这一步也别省。机器上装过两个版本 VLC 的情况非常普遍两个路径都注册过同一个 CLSID 时加载顺序不确定表现就是「这台机器能播那台机器不能播」靠肉眼完全看不出差别。2.4 页面嵌入object 标签里哪些参数是必需的控件注册好之后页面上就是一个objectclassid指向 CLSIDid留给 JS 取用。参数通过param传但要注意VLC 对不认识的参数名是静默忽略的拼错了不会报错只会表现成「设了没生效」。!-- IE8 下加载 VLC ActiveX 控件必须放在 body 内、可被 JS 取到 -- object idvlcPlayer classidclsid:9BE31822-FDAD-461B-AD51-BE1D1C159921 width800 height450 typeapplication/x-vlc-plugin eventsTrue param nameMRL value / param nameAutoplay valuefalse / param nameVolume value80 / param nameLoop valuefalse / param nameShowDisplay valuetrue / param nameFullscreenEnabled valuetrue / /objecteventsTrue是关键只有打开它控件才会往页面上抛MediaPlayerPlaying这类事件第三章的事件绑定全靠这个属性。MRL留空是刻意的实际的流地址交给 JS 在运行时通过playlist.add传入这样切换源的时候不用重建object。参数作用常见设置MRL初始播放地址留空改由 JS 传入Autoplay加载后是否自动播false避免页面一打开就抢带宽Volume音量0–20080100 为原始音量Loop单文件循环falseShowDisplay是否显示视频画面trueFullscreenEnabled是否允许全屏true3. 用 JS 驱动 VLCplaylist 接口、事件回传与流参数怎么给3.1 四个对象playlist、input、audio、video拿到object之后真正操作的是它暴露出来的四组子对象。刚开始接触这套接口的人容易到处找play()、stop()方法其实播放控制全在playlist上input只读负责告诉你当前播到哪儿了。对象常用成员说明playlistadd(mrl, name, opts)、play()、stop()、clear()、next()、isPlaying播放列表切换源从这里进inputlength、time、position、fps当前输入状态时间单位是毫秒audiovolume、mute、toggleMute()、track音量范围 0–200100 为原始音量videofullscreen、toggleFullscreen()、aspectRatio、width、height宽高比按16:9这样的字符串设playlist.add的签名是三个参数MRL、显示名称、选项字符串。第三个参数是这套接口里最值钱也最容易忽略的地方它能把 libvlc 的命令行级选项直接透传到这次播放会话上比如强制 RTSP over TCP、加大网络缓存、指定宽高比全在这一个字符串里解决不用去改客户端的全局配置。3.2 事件回传IE8 独有的 script for/event 写法现代浏览器用addEventListenerIE8 只有attachEvent但 ActiveX 控件的事件既不走前者也不走后者它有自己的一套语法script for控件id event事件名()。这段脚本必须写在object标签之后解析顺序错了就绑不上。script typetext/javascript forvlcPlayer eventMediaPlayerPlaying() window.vlcOnPlaying window.vlcOnPlaying(); /script script typetext/javascript forvlcPlayer eventMediaPlayerEncounteredError() window.vlcOnError window.vlcOnError(); /script script typetext/javascript forvlcPlayer eventMediaPlayerEndReached() window.vlcOnEnd window.vlcOnEnd(); /script用window.xxx window.xxx()这种写法是为了让事件脚本本身不持业务逻辑业务逻辑挂在全局回调上这样页面重排、模块加载顺序变化都不会让事件脚本报「对象为空」。直播流一般不会触发MediaPlayerEndReached它只在点播文件播完时抛出来点播页面可以用它来做自动播下一段。还有一条容易忽略object一旦被 DOM 重建比如用innerHTML重刷了容器之前绑的事件全部失效而且不会报错表现是「画面在动但状态条不动了」。所以后面第五章才会强调切换源只清playlist不动object。3.3 流参数缓存给多少、RTSP 要不要走 TCP网络抖动的现场同一路 RTSP 在 A 机器上流畅、B 机器上花屏十有八九是传输层和缓存的问题。VLC 默认对 RTSP 优先走 UDPUDP 丢包在解码端就表现为马赛克和卡顿跨网段、过防火墙的时候更明显。稳妥的配置是强制 TCP 并把网络缓存拉高// 第三个参数是透传给 libvlc 的选项串多个选项用空格分隔 var opts :rtsp-tcp :network-caching1500 :clock-jitter0 :clock-synchro0; vlc.playlist.clear(); vlc.playlist.add(rtsp://10.0.0.12:554/stream1, cam-01, opts); vlc.playlist.play();:rtsp-tcp让 RTP 走 RTSP 的同一条 TCP 连接牺牲一点点延迟换稳定性内网多路并发的场景基本都该加。:network-caching1500是 1.5 秒缓存默认值偏小1080p 多路的时候不够用但也不能无脑往大写超过 3000 之后切流要等好几秒才出画操作员会以为卡死了我一般按 1000–2000 之间调。:clock-jitter0和:clock-synchro0是针对组播源时间戳不规范的老设备的如果源端是标准 RTSP 服务器这两项可以不加。本地视频流文件则要换一种写法路径必须转成file:///形式反斜杠换成斜杠中文和空格要走encodeURI否则在中文目录下一律黑屏。// D:\录像\巡检-001.mp4 - file:///D:/录像/巡检-001.mp4URI 编码后 function toFileMRL(winPath) { var p winPath.replace(/\\/g, /); if (!/^[a-zA-Z]:/.test(p)) { return p; } return file:/// encodeURI(p); } vlc.playlist.add(toFileMRL(D:\\录像\\巡检-001.mp4), rec-01, ); vlc.playlist.play();3.4 封装一个能复用的播放器对象把上面这些拼起来做一个极简的封装。注意这份代码里的var、for循环、setTimeout都是 IE8 能吃的写法forEach、Object.keys、Promise一个都不能出现IE8 里没有。// vlc-player.js —— 仅用于 IE8 VLC 2.2.x var VlcPlayer function (id) { this.el document.getElementById(id); this.timer null; this.lastTime -1; this.stuckCount 0; }; VlcPlayer.prototype.ready function () { // 控件没注册或位数不匹配时这个属性访问会返回 undefined return !!(this.el this.el.playlist); }; VlcPlayer.prototype.play function (mrl, opts) { if (!this.ready()) { return false; } var p this.el.playlist; p.stop(); // 先停避免两路流抢同一个解码实例 p.clear(); // 清列表否则会往队列后面追加 p.add(mrl, v, opts || ); p.play(); this.startWatch(); return true; }; VlcPlayer.prototype.startWatch function () { var self this; if (this.timer) { clearInterval(this.timer); } this.lastTime -1; this.stuckCount 0; // 每秒采样一次播放进度用于识别假死画面停了但没报错 this.timer setInterval(function () { var t self.el.input ? self.el.input.time : -1; if (t self.lastTime t 0) { self.stuckCount; if (self.stuckCount 5) { self.restart(); } // 连续 5 秒不动就重启 } else { self.stuckCount 0; } self.lastTime t; }, 1000); }; VlcPlayer.prototype.restart function () { this.stuckCount 0; this.el.playlist.stop(); this.el.playlist.play(); }; VlcPlayer.prototype.dispose function () { if (this.timer) { clearInterval(this.timer); this.timer null; } if (this.ready()) { this.el.playlist.stop(); this.el.playlist.clear(); } };startWatch里的假死检测是这套方案里最实用的补丁。VLC 在流断掉但不抛错的情况下会保持最后一帧事件不触发状态显示「播放中」人眼看到的是画面定格。用input.time连续 5 秒不变来判定比等MediaPlayerEncounteredError可靠得多现场 90% 的「卡住了」都能被它兜住。stop()之后直接play()而不重新add是因为播放列表里还留着上次那个源重启只是重新建连速度更快。4. 排查清单黑屏、无声、控件加载失败这些翻车现场4.1 控件加载了但 vlc.playlist 报「不支持此属性或方法」现象页面上能看到控件边框document.getElementById(vlcPlayer)也能取到对象但只要一碰vlc.playlist就抛「对象不支持此属性或方法」。原因IE 的安全区域把控件拦在了「只加载不给脚本」的状态。默认安全级别下未标记为可安全执行脚本的 ActiveX 控件是可以初始化、但不允许脚本调用的。解决把站点加进可信站点然后在自定义级别里把「对未标记为可安全执行脚本的 ActiveX 控件初始化并执行脚本」设为启用。注意这一步要在客户端做不是服务端能控制的实施文档里必须写成截图步骤否则每台新机器都要远程一次。4.2 有窗口没画面或者画面出来了没声音现象playlist.isPlaying返回 trueinput.length也在增长但控件区域一片纯黑。原因九成是解码器缺失。前面 2.3 说过只拷axvlc.dll出去注册plugins目录没跟着走demux 和 decode 模块全部找不到表现为「能连上流、能读到元数据、就是解不出帧」。解决确认VLC_DIR下plugins目录存在且非空正常有几百个.dll并与axvlc.dll同属一个安装目录。声音问题则先看audio.volume是不是被设成了 0再确认系统混音器里 IE 的进程没被静音最后才怀疑音频编码不被支持。4.3 RTSP 能播但花屏、卡顿、几十秒后自动断现象刚连上是好的跑一会儿开始马赛克然后画面停住事件里偶尔能看到MediaPlayerEncounteredError。原因RTP over UDP 丢包。跨网段、过防火墙、无线回传的环境下UDP 丢包是常态丢了之后解码器只能等或跳等不到就断。解决把:rtsp-tcp加到选项串里同步把:network-caching从默认值提到 1500 左右。如果源端本身是组播udp://239.x.x.x:port那没法改 TCP只能靠加大缓存加上前面 3.4 里的假死重启来兜。4.4 视频窗口盖住了下拉菜单和弹窗现象页面上任何z-index拉满的 div 都压不住视频区域下拉框展开后被视频挡掉一半弹窗也被切掉一块。原因VLC ActiveX 是窗口化控件它渲染在自己的子窗口里不参与 HTML 的层叠上下文。z-index对它是无效的这是 Win32 子窗口的固有行为不是样式写错了。解决最省事的做法是在需要弹层的时候把object的visibility置为hidden或者在原位叠一层同尺寸的图片快照弹层关掉再恢复。另一种做法是把播放器整体放进iframe让弹层和视频处在不同文档里谁先谁后由 iframe 的层级决定可控性更好。4.5 反复切流之后 IE 越来越卡最后整页无响应现象切了十几次源之后页面操作开始迟滞任务管理器里能看到多个残留进程关掉 IE 也不一定退干净。原因每次切流都往playlist里add旧的输入没有被释放或者更糟用innerHTML重建了object等于每切一次就新建一个 COM 实例旧实例的窗口和解码线程还挂着。解决切流固定走stop → clear → add → play这条链路绝对不重建object。页面卸载前显式调一次stop和clear。多实例场景一路一个控件更要注意实例数控制在 4 个以内比较稳超过之后解码线程抢 CPU 会非常明显。4.6 一个二十行的最小自检页现场排查时别在主系统里改来改去直接丢一个自检页进去能省掉一半沟通成本!DOCTYPE html html headmeta http-equivX-UA-Compatible contentIEedge //head body object idp classidclsid:9BE31822-FDAD-461B-AD51-BE1D1C159921 width640 height360 eventsTrue param nameAutoplay valuefalse / param nameShowDisplay valuetrue / /object script typetext/javascript var p document.getElementById(p); var ok !!(p p.playlist p.input p.audio); document.write(pApiReady ok /p); if (ok) { p.audio.volume 80; p.playlist.add(rtsp://10.0.0.12:554/stream1, t, :rtsp-tcp :network-caching1500); p.playlist.play(); } /script script typetext/javascript forp eventMediaPlayerPlaying() document.title PLAYING; /script /body /htmlApiReadyfalse就查注册和位数ApiReadytrue但黑屏就查plugins目录和流地址标题变成PLAYING说明链路全通。一个页面把问题域切成三块比逐层猜快得多。5. 进阶多路流热切换与 iframe 隔离的回收策略多路切换的稳定性是这套方案从「能播」到「敢用」的分水岭。我试过最差的写法是每次切源就clear加add加play三连发中间不留间隔结果在 RTSP 上表现为新流有 30% 概率连不上——上一路的 TEARDOWN 还没走完新的 SETUP 就发出去了服务端会拒绝。后来固定成stop之后用setTimeout等 200 毫秒再clear、add、play并且给add之后加一个 8 秒超时超时还没收到MediaPlayerPlaying就重试一次。这个 200 毫秒没有理论依据是现场试出来的经验值但它把切换失败率压到了可以接受的范围。资源回收上removeChild删object在 IE8 上偶发崩溃display:none又只是视觉效果、窗口和解码线程还在。我最后采用的是 iframe 隔离播放器单独一个player.html外面用iframe嵌进来切源只往 iframe 里发消息需要彻底回收时直接换 iframe 的src或者把整个 iframe 从 DOM 里摘掉。iframe 卸载时浏览器会连带销毁里面文档创建的 COM 对象比在同一个文档里手动收拾要干净得多。代价是跨文档通信麻烦一点但 IE8 下本来也没postMessage用window.parent或window.top直接调用就够了同源情况下反而更简单。部署环节的检查项我整理成了一张表实施人员照着打勾基本不会再出现「客户机上装了两份 VLC」这种问题检查项合格标准不通过的典型表现CLSID 注册路径指向部署目录下的 axvlc.dll页面空白、无报错DLL 版本2.2.x控件加载失败或接口全空位数匹配32 位 IE 32 位 DLLplaylist为 undefinedplugins 目录存在且文件数百量级有窗口无画面可信站点站点已加入并放开脚本执行「不支持此属性或方法」兼容模式页面非怪异模式控件尺寸错乱、事件不触发还有一个我一直保留的习惯任何一次上线前先把 4.6 那个自检页在目标机器上跑一遍把ApiReady的值和reg query的输出一起截图存档。因为在客户现场最浪费时间的事情不是修而是搞不清到底是环境没配好还是代码写错了。从那以后我每次部署都强制走一遍这个自检页宁可多花五分钟也不想再经历一次半夜被叫起来查「为什么这台能播那台不能」。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?