中文 | English
live-player 是一款浏览器摄像头和直播播放器:支持 WS-FLV 直播 + 录像文件(FLV / MP4)。
pnpm add @le5le/live-player mpegts.jsmpegts.js 是 peerDependency,需要宿主自己安装。
npm 包里只有 dist/:一个未压缩的 ESM 文件 + 各文件的 .d.ts,另带 sourcemap。
不用打包器的项目直接 <script type="module"> import dist/live-player.js 即可。
为监控摄像头这类流写的播放器,不是通用视频播放器。它解决的是这几件在监控场景里反复出问题的事:
- 一个地址既可能是直播也可能是录像。国标(GB28181)设备回放的 ws-flv 地址与直播长得一模一样,
容器也相同,只能由调用方告知语义(
live),库据此决定要不要丢帧、要不要允许拖动、断了要不要重连。 - H265 直播切后台再回来会断流重连。这是本库的起点:隐藏期积压的数据回到前台后触发 seek, 而 H265 跳到非 IDR 位置就解不开。库在 MSE 写入层拦下来,切走几分钟回来也不必等「正在恢复画面」。
- 多路同屏带不动。九宫格主码流是 9 路 4MP H265 解码,浏览器没有任何 API 能查询剩余硬解会话数。 库只能在失败后反应:一路解码失败就整组降到子码流(设备原生双码流,服务端不转码)。
- 播放地址带短寿命凭据。平台侧播放 token 常是 30 秒 TTL,而重连恰恰最容易撞上它过期。
库重连前会回调
renewSources向宿主索取新地址。
不适合:点播站、HLS/DASH 自适应码率、直播带货这类只需要「放一个 mp4/m3u8」的场景 ——
那些用 <video> 或 hls.js 更合适,本库的复杂度全花在上面四件事上。
按地址里的路径标记自动选传输方式(不是按扩展名 —— 现网直播地址常没有扩展名):
| 地址形态 | 传输方式 | 说明 |
|---|---|---|
ws:// wss:// + 路径含 flv/mpegts |
MSE 直播 | 主力路径,过 mpegts.js |
http(s):// 含 flv/ts 且路径有 .live. 或 /live/ |
MSE 直播 | ZLM / 乐吾乐的 http-flv 直播 |
http(s):// 含 flv/ts,不带 live 段 |
MSE 录像 | FLV 录像文件,可拖动 |
http(s):// 含 .mp4/fmp4 |
原生播放 | 交给浏览器,白拿 seek 与进度条 |
路径含 webrtc/whep |
WebRTC | 延迟约 0.2~0.4s,需宿主开 preferWebrtc |
- WebSocket 地址不带容器标记时不猜,直接判
unsupported—— 猜错的代价是播不出来却报错到别处。 这是前后端契约:后端新增播放地址必须在 path 里带flv/mpegts/ts/mp4之一。 - WebRTC 不由 URL 自动启用。两条路的取舍随同屏路数翻转(WebRTC 延迟低但没有 MSE、 后台仍满速解码),而库不知道自己是九宫格里的一格还是单画面,所以由宿主决定。 走不通会自动退回 FLV,不必宿主兜。
用 resolveTransport(url) 可以单独查某个地址会被判成什么,isLiveUrl(url) 判断要不要显示进度条。
容器:FLV、MPEG-TS(均过 mpegts.js)、MP4 / fMP4(交给浏览器原生播放)。
视频编码:H264、H265(HEVC)。H265 的浏览器支持是分裂的 —— Chrome 136+ 原生支持但要求硬件解码、
Safari 18+ 通、Edge 至今未跟上、Firefox 不支持。hevcSupported() 可做静态能力查询,但要注意
它只说明浏览器认不认这个 codec,不代表还有可用的硬解会话 —— 已经开了 8 路解码器它照样返回 true。
真正能不能播只有喂进去才知道,由看门狗与自动降级兜底。
音频:MSE 这条路目前固定关闭音频(hasAudio: false,不可配)。原因是国标 PS 流的音频常是
G.711,而 mpegts.js 只认 AAC/MP3,遇到别的会抛 CodecUnsupported 打断整条解复用;更要紧的是
音轨 SourceBuffer 一旦出错会把 <video> 整个置成 error 态,视频的 appendBuffer 也跟着全失败
—— 画面直接黑。监控场景不需要声音,所以选择让 demuxer 在读 soundFormat 之前就丢掉音频 tag。
WebRTC 这条路会协商 audio 轨(ZLM 少一条媒体行会整体协商失败),能不能出声取决于服务端。
import { createPlayer, createGroup, isLiveUrl } from '@le5le/live-player';
const player = createPlayer(videoEl, {
sources: {
main: 'wss://host/ws/gb/flv?streamId=xxx',
sub: 'wss://host/ws/gb/flv?streamId=xxx_sub', // 没有子码流就不传,不参与降级
},
live: true, // 录像回放必须传 false —— 国标回放地址与直播同形,库看不出区别
});
// 事件用 on/off 注册,不是构造参数
player.on('statechange', (s) => console.log(s)); // idle|connecting|playing|resyncing|reconnecting|failed
player.on('error', (e) => console.log(e.kind, e.message, e.fatal));
player.on('qualitychange', (q, reason) => console.log(q, reason));
player.on('ended', () => console.log('这一段放完了')); // 只有非实时流会触发
await player.play();播放地址带短寿命凭据时,传 renewSources 让库在重连前换一份新地址:
const player = createPlayer(videoEl, {
sources,
live: true,
renewSources: async () => (await api.renewPlay(channelId)).sources,
});想用 WebRTC 换低延迟(单画面或小宫格):
createPlayer(videoEl, {
sources: { main: flvUrl, webrtc: webrtcUrl },
preferWebrtc: true, // 走不通会自动退回 main 的 FLV
live: true,
});读 player.transport 拿到实际在用的传输方式 —— preferWebrtc 为真时也可能已经退回 FLV,
想在界面上标「低延迟」就得读这个,读 preferWebrtc 会在退回后说谎。
其余成员:pause() / destroy() / setQuality(q) / snapshot() 抓拍,以及只读的
state / quality / hasSub / live。
const group = createGroup();
group.add(player); // 任一路解码失败时,整组一起切子码流(有子码才切)
group.add(player2);createGroup() 用于同屏多路:某一路因为解码带不动而降级时整组一起切,避免只降一格、
其余仍在抢解码会话。单画面不用。
手动 setQuality() 过的实例从组自动降级中豁免 —— 用户明确要这一格看主码流,库不跟他抢。
live: false 时库允许 seek(录像本来就该能拖),live: true 时永不 seek 到未缓冲位置——
那正是断流重连的真身。