简介:面向需要在浏览器中播放多格式视频与流媒体文件的前端、运维及系统集成人员,这套浏览器集成 VLC 视频插件资料聚焦 IE8+、Chrome、Firefox 等环境下的兼容播放方案,尤其适合仍维护旧版 IE、内网流媒体页面或跨浏览器视频组件的项目。压缩包共 3 个文件,约 38.98MB,包含 html 演示页、doc 说明文档和 VLC 安装程序,分别对应嵌入示例、配置参考与运行环境准备,文件不多但覆盖了从环境到页面的主要环节,便于按需查阅。资源已有 7297 人学习,说明其在旧浏览器视频播放场景中具有一定参考价值。读者可从中获取插件嵌入思路、播放参数设置、脚本控制接口组织等内容,并据此理解多格式、多协议视频流在浏览器中的兼容处理方式,为类似项目提供可复用的实现参考和排错方向。
1. 老后台要播 rtsp,绕一圈还是回到 IE 加 VLC 插件
一个内网设备管理后台,视频预览页要从几十路网络摄像机拉 rtsp,值班终端是锁了策略的 Win7 加 IE8,浏览器换不了,插件装不上去。H5 的 video 标签根本不认 rtsp,为几十路视频单独搭一套转码服务又要多两台机器和一条运维线。最后能落地的方案只有一个:终端装 32 位 VLC,页面里塞一个 classid 指向 VLC ActiveX 控件的 object,把 VLC 的解封装和解码能力借给浏览器用,页面自己只管 UI 和参数。
这套东西本质不是"浏览器播放器",而是"浏览器调用本地播放器内核"。它适合三类人:只能跑 IE 内核的老后台维护者、要播 rtsp 或 udp 组播的监控预览页开发者、不想为几十路视频单独上转码服务的小团队。下面按能不能用、怎么嵌、参数怎么调、坑在哪,一层层拆开。
2. 先算清能力账:能播什么、不能播什么、装哪一版
2.1 三条嵌入路线,为什么只剩 ActiveX 一条
浏览器里集成 VLC,历史上存在过三条路,今天能走的只剩一条,这个判断必须先做对,否则后面全是白费功夫。
第一条是 NPAPI 插件。老版本 VLC 提供npvlc.dll,页面里写<embed type="application/x-vlc-plugin">就能在 Chrome、Firefox 里播 rtsp。这条路已经彻底断了:主流浏览器移除 NPAPI 插件支持之后,这段标签只会渲染成一个空白框,控制台连报错都不给。网上大量老教程还在教这个写法,照着抄会浪费一整天去怀疑自己装错了版本。
第二条是 ActiveX 控件,也就是axvlc.dll。它随 32 位 VLC 安装包一起注册到系统,页面里靠一个固定的 classid 把它拉起来。这条路只对 IE 内核有效:IE8、IE11,以及很多工控组态软件、设备管理客户端里内嵌的 WebBrowser 控件,本质上都是 IE 内核,所以都能加载。这是当前唯一稳妥的路线。
第三条是外部协议唤起,用vlc://之类的自定义协议把本地 VLC 进程拉起来播放。它的播放能力最强,什么编码都吃得下,但问题也很直接:视频跑到独立窗口里去了,页面上没法叠加云台控制按钮、没法做画中画、拿不到播放状态和进度,切一路流就要重新拉起一次进程,体验是割裂的。我一般只把它当成"右键全屏看"的辅助入口,不做主力预览。
所以选型逻辑很清楚:终端环境能改(允许装 VLC、允许调 IE 安全设置),就走 ActiveX;终端环境完全改不了,那就只能走服务端转码成 HLS 或 FLV 再喂给 H5,那是另一条技术路线,不在这次讨论范围内。
2.2 协议与编码的支持边界:哪些能直接喂,哪些必须转
搞清楚控件能吃什么,比搞清楚怎么嵌更重要。很多"播不出来"的问题,其实是源本身就不在支持列表里。
| 流类型 | 能否直接播 | 实际使用中的注意点 |
|---|---|---|
| rtsp:// 默认(RTP over UDP) | 能 | 局域网内最省事,但丢包就花屏,跨网段基本不可用 |
| rtsp:// 强制 TCP | 能 | 穿 NAT、跨网段稳,延迟比 UDP 高几十毫秒 |
| udp://@239.x.x.x:port | 能 | @表示加入组播组,需要网卡和交换机侧配合 |
| http:// 渐进式 mp4 / flv | 能 | 服务端要支持 Range 请求,否则进度条拖不动 |
| http:// multipart/x-mixed-replace | 能 | 大量网络摄像机走这个(MJPEG),兼容性最好但带宽大 |
| HLS(m3u8) | 能 | 延迟通常 10 秒起步,不适合云台联动、对讲场景 |
| RTMP | 能 | 依赖编译时是否带 librtmp,老安装包要实测确认 |
| WebRTC、各家私有低延迟协议 | 不能 | 别在这上面耗时间,直接换方案 |
编码侧的经验值:H.264 + AAC 最稳,几乎不会出问题;H.265 在 3.x 上能解但是纯软解,老机器开三四路 CPU 就满了,表现出来就是画面卡住然后控件崩溃;MJPEG 兼容性最好,代价是带宽通常是 H.264 的五六倍;监控里常见的 G.711 音频(alaw/mulaw)能正常解,不用额外装解码包。
提示:如果摄像机同时开了主码流和子码流,预览页优先用子码流(一般 720p、2Mbps 以内),点开单画面再切主码流。这一条能省掉后面一半的性能问题。
2.3 装哪一版、怎么确认控件真的注册上了
三个硬性约束先记住:必须装 32 位安装包;安装时不能选精简模式(有的精简包会剔掉 Web 插件);版本锁在 3.x 分支,不要升到 4.x,那条分支已经不再提供 ActiveX 控件了。这条"不升级"的纪律要写进运维文档,不然某天有人手贱点了升级,整页视频全黑。
装完之后先别急着写页面,用两条命令确认控件在系统里。
rem 第一步:确认 axvlc.dll 确实在安装目录里 rem VLC_DIR 换成你机器上的实际安装目录(32 位安装包默认在 Program Files (x86) 下) set "VLC_DIR=C:\Program Files (x86)\VLC" dir "%VLC_DIR%\axvlc.dll" rem 第二步:注册控件 rem 64 位系统上注册 32 位 DLL,必须用 SysWOW64 目录下那个 regsvr32 C:\Windows\SysWOW64\regsvr32.exe /s "%VLC_DIR%\axvlc.dll"/s是静默模式,不加会弹一个成功对话框。执行完再查注册表,能查到InprocServer32的值就说明注册成功:
reg query "HKCR\CLSID\{9BE31822-FDAD-461B-AD51-BE1D1C159921}\InprocServer32"这个 CLSID 就是 VLC ActiveX 的固定标识,全篇所有 classid 用的都是它,不能改、不能少大括号。
注册失败时的返回值很有信息量,值得记一下:报0x80070005是权限不够,用管理员身份开命令行重来;报0x8007007e是找不到依赖模块,九成是把axvlc.dll单独拷到别的目录去注册了,它依赖同目录下的libvlccore.dll和plugins文件夹,脱离安装目录就是废的;报0x8002801c是用错了 regsvr32 的位数,64 位的System32\regsvr32.exe注册不了 32 位 DLL。顺便说一句,绿色免安装版走不通这条路,原因就是这个目录依赖,血泪经验。
3. 把控件嵌进页面:object 标签、参数和 JS 控制
3.1 一个能跑起来的最小页面
先给一个剥到最干净的版本,除了必要的东西一律不加。这个页面能出画面,就说明环境这一层已经通了,剩下的都是业务逻辑。
<!DOCTYPE html> <html> <head> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <title>视频预览</title> </head> <body> <!-- classid 固定,events="True" 是事件开关,不写就收不到播放回调 --> <object id="vlc" classid="clsid:9BE31822-FDAD-461B-AD51-BE1D1C159921" width="800" height="450" events="True"> <param name="ShowDisplay" value="True" /> <param name="AutoLoop" value="False" /> <param name="AutoPlay" value="False" /> <param name="Volume" value="60" /> <param name="toolbar" value="False" /> <param name="bgcolor" value="#000000" /> </object> </body> </html>关键点逐条说。classid是控件身份,IE 靠它去注册表找对应的 DLL。events="True"决定能不能收到MediaPlayerPlaying、MediaPlayerEncounteredError这类回调,漏写的话事件全哑,只能靠定时器轮询状态。
另一个容易忽略的是:不要写codebase属性。网上很多示例带一个指向外网 cab 包的 codebase,IE 会先尝试去下载那个包,内网机器连不上外网,页面就会卡十几秒然后放弃,表现出来就是"第一次打开特别慢"。控件靠安装包注册,不靠 cab 下载。
MRL参数这里故意留空,流地址全部通过 JS 注入,这样切流不用重新渲染 object。
3.2 参数逐个过一遍
| 参数名 | 取值范围 | 用途与常见误用 |
|---|---|---|
MRL | 流地址字符串 | 初始播放地址。填了它但AutoPlay为 False,页面出来是黑屏待播状态 |
AutoPlay | True / False | 只在同时给了MRL时才生效,单独设没有意义 |
AutoLoop | True / False | 播完循环。直播流用不上,点播文件才需要 |
Volume | 0 - 200 | 100 是原始音量,200 是软件放大。部分版本初始化不生效,要在 JS 里再设一次 |
toolbar | True / False | VLC 自带的那条控制栏。业务页面基本都设 False,用自己的按钮 |
bgcolor | #RRGGBB | 背景色。填#000000比默认灰底好看得多 |
ShowDisplay | True / False | 是否显示视频画面,调试时可以先设 False 排除渲染问题 |
Volume这个参数值得单独提一句:它是个兼容性玄学,同一个页面在不同小版本上表现不一致,有的能生效有的不能。稳妥做法是页面加载完成后再用vlc.audio.volume = 60设一次,不依赖参数。
3.3 JS 侧控制:playlist.add的三个参数才是重点
控件加载出来之后,所有操作都挂在playlist、audio、video、input这四个对象上。播放一个流的核心就三行,但三个参数各有讲究。
// mrl : 流地址,rtsp:// 或 udp://@ 开头 // opts : 选项数组,每个元素以冒号开头,如 ":network-caching=800" function playStream(mrl, opts) { var vlc = document.getElementById("vlc"); // 控件没加载完时 vlc.playlist 是 undefined,先判掉,避免抛"未指明的错误" if (!vlc || !vlc.playlist) { return false; } try { vlc.playlist.items.clear(); // 先清空,防止多次调用后变成轮播 vlc.playlist.add(mrl, "ch1", opts); // 第二个参数是列表项名,界面上看不到,调试时有用 vlc.playlist.play(); return true; } catch (e) { return false; } }vlc.playlist.items.clear()放在add之前是硬性顺序。漏掉这一行,切换三次流之后你会发现画面在自己轮播三个地址,很多人第一次遇到会以为是摄像机在推流,其实是播放列表堆起来了。
常用控制方法就那么几个,记住就够用:
var vlc = document.getElementById("vlc"); vlc.playlist.togglePause(); // 暂停 / 继续,注意不是 pause() vlc.playlist.stop(); // 停止,停止后要重新 add 才能播 vlc.audio.volume = 30; // 0 - 200 vlc.audio.mute = true; // 静音 vlc.video.fullscreen = true; // 全屏 vlc.video.aspectRatio = "16:9"; // 强制宽高比,源没带 DAR 时非常有用aspectRatio这个属性在实际项目里出镜率很高。有些摄像机的 rtsp 流里不带显示宽高比信息,VLC 会按编码尺寸直接渲染,画面被拉成 4:3 或者扁的,客户第一句话就是"人怎么变形了"。设一下16:9就正常了。
3.4 事件绑定和状态轮询,两套都要有
IE 里绑事件用的是attachEvent,不是addEventListener,写错了不报错、就是不触发,特别费时间。
var vlc = document.getElementById("vlc"); // 前提:object 标签上必须有 events="True" vlc.attachEvent("MediaPlayerPlaying", function () { // 真正开始出画面了,这时候再把 loading 遮罩去掉 }); vlc.attachEvent("MediaPlayerEncounteredError", function () { // 拉流失败:地址错、网络不通、编码不支持,都会走到这里 }); vlc.attachEvent("MediaPlayerEndReached", function () { // 播放结束,直播场景可以在这里触发重连 });光有事件不够,还得加一层轮询兜底。原因很实在:事件在某些版本加某些流类型的组合下会漏发,尤其是组播断流再恢复之后,MediaPlayerPlaying死活不来,页面就一直卡在 loading 状态。这时候能救场的就是最笨的定时器。
// vlc.input.state 的取值: // 0 空闲 1 打开中 2 缓冲中 3 播放中 4 暂停 5 停止 6 结束 7 错误 function watchState() { var vlc = document.getElementById("vlc"); var s = -1; try { s = vlc.input.state; } catch (e) { s = -1; // 控件还没就绪,下一轮再看 } if (s === 7) { // 错误状态,走重连逻辑 } else if (s === 6) { // 正常结束,直播流一般不会到这里,点播会 } } setInterval(watchState, 2000);两秒一次的开销可以忽略,但能把"页面看着像死了"这类问题挡掉一大半。我一般会在 state 为 7 的时候做三次退避重连(2 秒、5 秒、10 秒),三次还不行就在画面上盖一层提示,别让用户干等。
4. 流地址和传输调优:一个冒号决定走 TCP 还是 UDP
4.1 MRL 的几种写法与各自的坑
同一个摄像机,地址写法不同,表现能差出天壤之别。下面这几种是实际项目里出现频率最高的:
// 1) RTSP 默认走 UDP,局域网内延迟最低,但丢一个包就花屏 playStream("rtsp://192.168.1.64:554/Streaming/Channels/101", []); // 2) 同一个地址强制 TCP,穿网段、跨防火墙必用 playStream("rtsp://192.168.1.64:554/Streaming/Channels/101", [":rtsp-tcp"]); // 3) UDP 组播,@ 表示加入组播组,少了 @ 会变成往这个地址发流 playStream("udp://@239.0.0.10:1234", [":network-caching=1500"]); // 4) HTTP 渐进式文件,服务端要支持 Range 才能拖拽 playStream("http://192.168.1.10/vod/demo.mp4", []); // 5) HTTP MJPEG,兼容性最好,缓存给小一点换低延迟 playStream("http://192.168.1.10/video.mjpg", [":network-caching=300"]);带认证的 rtsp 一般写成rtsp://user:pass@ip:554/...,这里有个隐蔽的坑:密码里如果有@、:、/这几个字符,必须做 URL 编码,否则 VLC 解析出来的主机名是错的,报的还是"无法打开",很难往密码方向想。
:rtsp-tcp前面那个冒号是 VLC 的选项语法固定格式,不是笔误,少了它整个字符串会被当成播放列表名而不是选项。
4.2network-caching的取值区间
这是调优里最有效的一个参数,没有之一。它的单位是毫秒,决定 VLC 在网络层攒多少数据再交给解码器。
| 取值区间 | 适用场景 | 代价 |
|---|---|---|
| 300 - 500 | 局域网、有线、对延迟敏感(云台跟随) | 网络一抖就卡顿 |
| 800 - 1000 | 通用推荐值,大部分内网环境 | 延迟约 1 秒 |
| 1500 - 2000 | 跨网段、无线 AP、链路抖动明显 | 延迟 2 秒左右,操作手感变钝 |
| 3000 以上 | 弱网、丢包严重,只要能看就行 | 延迟明显,不适合需要实时反馈的场景 |
配套还有两个降延迟的参数:clock-jitter=0和:clock-synchro=0,作用是关掉时钟抖动补偿,能把延迟再压下去几百毫秒。但它们只适合纯视频场景,有音频的时候容易音画不同步,而且越跑越歪。我在纯监控预览页里会用,带对讲的页面里绝对不用。
4.3 多路视频:数量上限和切换方式
页面里同时挂多少个 object,是有物理上限的。32 位 IE 进程的地址空间只有 2GB,每个 object 背后都是一整套解码器实例,超过 4 到 6 路之后,整页崩溃的概率直线上升,而且崩的时候往往不给任何提示,整个 IE 直接没了——这种黑匣子式的崩溃最难查。
常见的两种规避方式:一是做分页,每页固定 4 路,翻页时销毁重建;二是一个 object 配一条播放列表,左边放缩略图墙,点哪个把哪个地址塞进播放列表。第二种体验更好,但要注意切换顺序。
// 切流的标准四步,顺序不能乱 function switchTo(mrl, opts) { var vlc = document.getElementById("vlc"); try { vlc.playlist.stop(); // 先停,释放解码线程 vlc.playlist.items.clear(); // 清列表,防止堆叠 vlc.playlist.add(mrl, "", opts || []); // 再挂新地址 vlc.playlist.play(); // 最后播 } catch (e) { // 控件异常时静默,由外层状态轮询负责重试 } }只stop不clear,切两三次之后画面会停在上一路不动,因为列表里还排着队。只clear不stop,偶尔会看到新旧两路画面短暂叠在一起,那是旧解码线程还没退出。两个都要,顺序不能反。
5. 避坑:IE8 下插件不加载的五个真实现象
5.1 现象:页面里只有一个空白框,右键也没有 VLC 菜单
原因通常是控件根本没被加载起来,往下有三个分支:classid 写错(少一位、大小写无所谓但字母不能错)、终端装的是 64 位 VLC、或者打开页面的其实是 64 位 IE 进程。64 位 IE 加载不了 32 位 ActiveX,而绝大多数老后台默认走的就是 32 位 IE,混用的时候就出问题。
解决:任务管理器里看 IE 进程名后面有没有*32标记,没有就说明是 64 位进程;确认注册表里那个 CLSID 存在;重装 32 位 VLC 并重新注册一次。这三步走完,90% 的空白框问题能定位到具体原因。
5.2 现象:顶部弹出黄色信息栏,提示"此网页想运行以下加载项"
这是 IE 的安全机制在拦 ActiveX,不是代码问题。IE8 默认把互联网区域的安全级别设得比较严,"初始化并脚本运行未标记为安全的 ActiveX 控件"这一项是禁用或提示状态。
解决:把后台域名加进受信任站点,然后在自定义级别里把"初始化并脚本运行未标记为安全的 ActiveX 控件"改成"启用","运行以前未使用的 ActiveX 控件而不提示"也可以一并打开。这一步是终端侧配置,要写进部署手册,不能指望用户自己点。
5.3 现象:第一次打开页面要卡十来秒,之后才出画面
查一下 object 标签上是不是带了codebase,指向的还是外网地址。IE 解析到 codebase 会先去尝试下载那个 cab 包,内网环境连不通,于是卡到超时才放弃,然后才去用本地已注册的控件。
解决:把整个codebase属性删掉。控件靠安装包注册,不需要运行时下载,这个属性在老示例里普遍存在,属于历史残留。
5.4 现象:画面出来三五秒后黑屏,接着整个 IE 崩掉
两个高频原因。一是 H.265 编码的流在老机器上纯软解,CPU 打满之后解码线程超时,控件直接挂掉;二是一页挂了超过 4 路 object,32 位进程地址空间不够。
解决:优先排查编码格式,让摄像机切到 H.264 子码流;如果必须用 H.265,把同时播放的路数压到 2 路以内。同时用任务管理器盯一下iexplore.exe的内存曲线,一路涨到 1.2GB 以上基本就离崩不远了。
5.5 现象:切流之后画面停在上一路,或者要等十几秒才换过去
不是网络慢,是播放列表没清干净,旧地址还排在队里等着播。VLC 的播放列表是队列语义,add之后再play,如果队列非空,播的是队首那一项。
解决:严格按stop→items.clear()→add→play四步走,并且把这段封成一个函数,不允许业务代码绕过它直接调playlist.add。这一类问题最典型的表现就是"我明明改了地址,怎么还是老画面",看着像缓存,其实是队列。
6. 上线前先跑一个最小验证页,把问题挡在业务代码之前
这套东西最容易犯的错,是把环境问题和代码问题搅在一起排查。页面不出来,你不知道是控件没注册、安全设置拦了、还是 JS 写错了,三件事同时怀疑,时间就全耗在这上面了。
我的习惯是:先写一个只有十几行、不含任何业务逻辑的验证页,把它跑通再动业务代码。这个页面只做三件事——创建 object、调playlist.add+play、在状态栏打印input.state的实时值。跑通的标准很明确:能出画面,状态从 1 走到 3,EncounteredError不触发。
// 最小验证页里的自检逻辑,跑通再动业务代码 window.onload = function () { var vlc = document.getElementById("vlc"); if (!vlc || typeof vlc.playlist === "undefined") { document.title = "FAIL: 控件未加载"; // 环境问题,去查注册表和安全设置 return; } vlc.playlist.add("rtsp://192.168.1.64:554/Streaming/Channels/102", "", [":rtsp-tcp", ":network-caching=800"]); vlc.playlist.play(); setInterval(function () { try { document.title = "STATE=" + vlc.input.state; // 标题栏实时显示,不用开控制台 } catch (e) {} }, 1000); };用标题栏显示状态是刻意为之。IE8 下开控制台要么按 F12(很多终端把 F12 禁了),要么装开发工具,都不方便;标题栏一直在,一眼就能看到 state 走到哪一步。
上线前我会照着这张表过一遍,每一项都对应一个具体动作,不靠感觉判断:
| 检查项 | 通过标准 |
|---|---|
| 控件注册 | 注册表能查到 CLSID 的 InprocServer32 值 |
| 进程位数 | IE 进程带*32标记 |
| 安全设置 | 打开页面不出现黄色信息栏 |
| 首帧时间 | 局域网内 3 秒以内出画面 |
| 连续切流 | 切 5 次地址不叠加、不黑屏 |
| 弱网表现 | 拔网线 10 秒再插回,能自动恢复到播放中 |
| 长稳 | 挂 8 小时内存不上涨超过 200MB |
最后一项是我吃了亏才加上的。早前一个项目上线当天一切正常,第二天早上值班员说画面全黑,重启 IE 就好,一天要重启三四次。查下来是每次切流只stop没clear,旧解码实例一直挂在列表里不释放,内存缓慢上涨,涨到差不多就崩。从那以后我每次改切流逻辑,都强制把连续切 5 次和挂机 8 小时这两条跑一遍,再谈上线。希望这些坑你不用再踩一次。
本文还有配套的精品资源,点击获取