前端视频流
HLS前端浏览器播放
常用前端浏览器播放器
HTML5 原生播放器:HTML5 原生播放器是浏览器自带的播放器,支持 HLS 协议的播放。
第三方播放器:除了浏览器自带的播放器外,还有一些第三方播放器可以支持 HLS 协议的播放,如 JW Player、Video.js、xg Player、Vue3-video-play、easyPlayer、hls.js 等。
但是很多时候HLS直播链接不是这些第三方播放器都可以播放,需要自己一个一个使用这些第三方播放器demo测试是否可以播放。
使用h265web.js库播放HLS视频流
比如最近项目有个视联设备摄像头的HLS直播链接使用上面这些第三方播放器都无法播放,最后使用了h265web.js这个第三方播放器才可以播放。
github地址:https://github.com/numberwolf/h265web.js
使用方法:(Vue3+Vite项目)
在GitHub里下载对应的
h265web.js、index.js、missile.js、missile.wasm文件。(下载对应时间的版本,如果有问题,可以去issue看下有没有解决方法)将这几个文件放置于public文件夹下,并在
index.html中引入。 选择的是20211104更新的这个版本。html<body> <div id="app"></div> <script src="/missile.js"></script> <script src="/h265webjs-v20211104.js"></script> <script type="module" src="/src/main.ts"></script> </body>创建公共utils/player.ts文件
JS// 将10以下数字转换成0X字符串 const durationFormmat = (val) => { val = val.toString() if (val.length < 2) return '0' + val return val } // 时间格式化成字符串 const setDurationText = (duration) => { if (duration < 0) return 'Player' const randDuration = Math.round(duration) return ( durationFormmat(Math.floor(randDuration / 3600)) + ':' + durationFormmat(Math.floor((randDuration % 3600) / 60)) + ':' + durationFormmat(Math.floor(randDuration % 60)) ) } // 创建播放器实例并返回 export const createPlayerServer = (videoUrl, config) => { // @ts-ignore return window.new265webjs(videoUrl, config) } // 加载播放器实例 export const executePlayerServer = (player, timelineEl) => { let mediaInfo = null player.onLoadFinish = () => { mediaInfo = player.mediaInfo() if (mediaInfo.videoType === 'vod') { timelineEl.textContent = setDurationText(0) + '/' + setDurationText(mediaInfo.meta.durationMs / 1000) } } player.onPlayTime = (pts) => { if (mediaInfo.videoType === 'vod') { timelineEl.textContent = setDurationText(pts) + '/' + setDurationText(mediaInfo.meta.durationMs / 1000) } } player.do() } // 释放播放器实例并清空指针 export const destoryPlayerServer = (playerInstance) => { playerInstance.release() playerInstance = null }在组件中使用
HTML
HTML<div class="player-container" ref="container"> <div id="glplayer" class="glplayer"></div> </div>JS
JSimport { createPlayerServer, executePlayerServer, destoryPlayerServer } from '@/utils/player' const URL = ref(""); const H265JS_DEFAULT_PLAYER_CONFIG = { player: "glplayer", width: 1450, height: 900, token: "base64:QXV0aG9yOmNoYW5neWFubG9uZ3xudW1iZXJ3b2xmLEdpdGh1YjpodHRwczovL2dpdGh1Yi5j b20vbnVtYmVyd29sZixFbWFpbDpwb3JzY2hlZ3QyM0Bmb3htYWlsLmNvbSxRUTo1MzEzNjU4NzIsSG9t ZVBhZ2U6aHR0cDovL3h2aWRlby52aWRlbyxEaXNjb3JkOm51bWJlcndvbGYjODY5NCx3ZWNoYXI6bnVt YmVyd29sZjExLEJlaWppbmcsV29ya0luOkJhaWR1", extInfo: { moovStartFlag: true, }, }; const timelineRef = ref(null); let playerObject = null; const container = ref(null); const resolveConfig = (conf) => Object.assign(H265JS_DEFAULT_PLAYER_CONFIG, conf); // 初始化播放器 const initPlayer = () => { playerObject = createPlayerServer(URL.value, resolveConfig()); executePlayerServer(playerObject, timelineRef.value); let timer = setInterval(() => { resumeHandler(); if (playerObject.isPlaying()) { console.log("播放中"); isloading_video.value = false; isBtnShow.value = true; clearInterval(timer); } }, 500); }; onUnmounted(() => destoryPlayerServer(playerObject)); const playAction = (action) => { // 获取当前播放的视频文件的信息数据 // let mediaInfo = playerObject.mediaInfo(); // console.log(mediaInfo); if (action === "resume" && !playerObject.isPlaying()) { console.log("[执行]: 启动"); playerObject.play(); return; } if (action === "pause" && playerObject.isPlaying()) { console.log("[执行]: 暂停"); playerObject.pause(); return; } if (action === "reload") { console.log("[执行]: 重启"); isloading_video.value = true; document.querySelector("#glplayer").innerHTML = ""; playerObject.release(); playerObject = null; initPlayer(); return; } }; const resumeHandler = () => playAction("resume"); const pauseHandler = () => playAction("pause"); const reloadHandler = () => playAction("reload");把URL替换成你自己的HLS直播链接即可,记得要做代理,不然的话会有跨域问题。
使用easyPlayer.js库播放HLS视频流
h265web.js这个库是不算是特别好用,毕竟他不是一个播放器,只是将画面通过canvas画在页面上。
而easyPlayer.js这个库是一个播放器,能够播放HLS视频流。
github地址:https://github.com/EasyDarwin/EasyPlayer.js
里面有原生html的demo,也有react版本和vue2版本的demo(下面是对vue2版本的vue3改良版本)。
使用方法:(Vue3+Vite项目)
在GitHub里下载对应的
EasyPlayer-pro.js、EasyPlayer-pro.wasm和EasyPlayer-lib.js文件。(在vue-demo文件夹下的js文件夹中)将这两个文件放置于public文件夹下,并在
index.html中引入。html<body> <div id="app"></div> <script src="/EasyPlayer-pro.js"></script> <script type="module" src="/src/main.ts"></script> </body>创建EasyPlayer组件
HTML部分
HTML<template> <div class="context"> <div :class="['player_container', 'player_container_1']"> <div class="player_item"> <div class="player_box" id="player_box1"></div> </div> </div> </div> </template>JS部分
JSconst videoUrl = ref(""); const playerInfo = ref(null); const playCreate = () => { const container = document.getElementById("player_box1"); const easyplayer = new EasyPlayerPro(container, { isLive: true, bufferTime: 0.2, // 缓存时长 stretch: false, // 拉伸模式 WASM: true, autoplay: true, }); easyplayer.on("fullscreen", function (flag) { console.log("is fullscreen", flag); }); easyplayer.on("error", function (e) { console.log("error", e); }); playerInfo.value = easyplayer; }; //播放 const onPlayer = () => { setTimeout( (url) => { playerInfo.value && playerInfo.value .play(url) .then(() => {}) .catch((e) => { console.error(e); }); }, 0, videoUrl.value ); }; //销毁 const onDestroy = () => { return new Promise((resolve, reject) => { if (playerInfo.value) { playerInfo.value.destroy(); playerInfo.value = null; } setTimeout(() => { resolve(); }, 100); }); }; onMounted(() => { playCreate(); });
RTSP前端浏览器播放
前端浏览器正常来说是无法直接播放RTSP视频流的,需要使用FFmpeg等工具将RTSP视频流转换成其他能够在前端播放的视频流格式,所以是没有纯前端的处理方法,除非是使用第三方的浏览器播放插件来使用。
可以将RTSP视频流通过FFmpeg转成rtmp,但是rtmp视频流基于flash才能播放,所以你的电脑必须安装flash,但是当前各大浏览器都准备不再支持flash,而且rtmp视频流播放还是具有2-3秒的延迟实现,所以不推荐使用。
主流的浏览器播放RTSP视频流的方式:
使用FFmpeg和WebSocket:FFmpeg是一个强大的多媒体处理工具,可以将RTSP流转换为其他格式。
可以使用FFmpeg将RTSP流转换为MEPG-TS或DASH格式,然后通过WebSocket将转换后的流发送到浏览器。
浏览器端需要使用相应的播放器库来播放,例如JSMPEG。
使用FFmpeg和WebRTC:WebRTC是一种实时通信技术,它允许浏览器之间进行实时音频、视频和数据传输。
可以使用WebRTC将RTSP流转换为浏览器可播放的格式,如VP8或VP9。
需要在服务器端进行转码和代理,将RTSP流转换为WebRTC流。
使用FFmpeg和WebSocket
首先先下载FFMPEG
官方地址:https://ffmpeg.org/
不是安装包类型的,根据系统下载源码后,将其放到环境变量中,使其能够全局调用。
RTSP视频流转换成WebSocket
使用的是github开源的项目
https://github.com/xiaosen125/video
掘金地址:
https://juejin.cn/post/6844903949309313037?searchId=20240816191001FD5B207680FC9CA96B7D#heading-7
使用FFMPEG本地主码流转码的方式转换成mpeg-ts格式,前端通过websocket方式接收二进制流并在canvas中展示画面
复刻项目:https://gitee.com/lily0325/rtsp2web
核心调用:
在项目的routes文件夹中的video.js文件中,/play地址的post请求接收前端传回来的用户ID与设备Code,通过这两个参数,获取到该设备真正的RTSP视频流地址。
开启一个子进程,将RTSP视频流通过FFMPEG转成mpeg-ts格式,然后通过websocket发送出去。
router.post('/play', async (req, res, next) => {
const { clientID, cameraID } = req.body;
try {
await updateRecord(clientID);
// 通过clientID和cameraID获取到设备的RTSP视频流地址
// ···
playStatic = await videoStream(rtspUrl, clientID, cameraID);
playStatic.startTransCodo();
res.status(200).send({ cameraID });
} catch (error) {
res.status(500).send({ error: '内部服务器错误' + error });
}
});上述代码中的VideoStream类是一个封装好的类,其中的startTransCodo方法就是新建一个Mpeg1Muxer类的实例
这个Mpeg1Muxer类能够开启一个子进程,调用FFMPEG将RTSP视频流转换成mpeg-ts格式流,然后通过websocket发送出去,前端通过websocket接收到视频每一帧的二进制流,然后使用JSMPEG库在canvas中展示画面。
// 生成唯一ID
const clientID = uuid.v4();
// 播放实时视频
const transCodeApi = (data) => {
fetch("/rtspUrl/live/VnetPlay", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(data),
})
.then((res) => res.json())
.then((res) => {
// 获取 id 为 video 的元素
const videoElement = document.getElementById("video");
// 创建一个新的 canvas 元素
const canvas = document.createElement("canvas");
canvas.id = "video-canvas";
canvas.width = videoElement.clientWidth;
// 清空 videoElement 原有的子元素,并添加新的 canvas 元素
videoElement.innerHTML = "";
videoElement.appendChild(canvas);
const url = `ws://localhost:8080/videoService/clientID=${clientID}&cameraID=${data.cameraCode}`;
// const canvas_doc = document.getElementById("video-canvas");
player = new JSMpeg.Player(url, {
canvas: canvas,
videoBufferSize: 1024 * 1024 * 100,
pauseWhenHidden: false, // 标签页处于非活动状态时是否暂停播放
onPlay: function () {
console.log("onPlay");
},
onEnded: function () {
console.log("onEnded");
},
});
})
.catch((err) => {
autolog.log("设备直播流请求失败!", "error", 2500);
});
};
// 调用
await transCodeApi({ clientID, cameraCode: val });RTMP 前端浏览器播放
RTMP(Real-Time Messaging Protocol)是 Adobe 为 Flash 设计的实时消息传输协议,延迟低(1–3s),推流生态非常完整(OBS、FFmpeg、手机推流 SDK 几乎都默认支持),因此直到今天仍是推流侧的事实标准。但它的播放侧在浏览器里已经基本死亡:
现代浏览器无法原生播放 RTMP。因为 RTMP 依赖 Flash Player,而 Adobe 已于 2020 年 12 月停止 Flash 支持,所有主流浏览器(Chrome/Edge/Firefox/Safari)都已彻底移除 Flash 运行时。
所以前端播 RTMP 的通用思路是——不要在前端播 RTMP,把它在服务端转封装成别的协议。主流方案如下:
方案一:服务端 RTMP → HTTP-FLV(最常用,延迟 1–3s)
RTMP 和 FLV 共用同一套 AMF/Tag 编码格式,所以"转"其实只是剥掉 RTMP chunk header、重新拼成 FLV tag 通过 HTTP 长连接推给前端,几乎零转码成本、零额外延迟。
- 服务端:SRS / Nginx-RTMP-Module / Node-Media-Server / 自研 Go 边缘
- 前端:
flv.js/mpegts.js+ MSE
这是国内直播行业(秀场、电商、游戏直播)最常见的组合——推流侧 RTMP,播放侧 HTTP-FLV,延迟 1–3 秒。
方案二:服务端 RTMP → HLS(兼容性最好,延迟 6–30s;LL-HLS 可压到 2s)
直接用 SRS / Nginx-RTMP 把 RTMP 切成 .m3u8 + .ts,前端用 hls.js 或 Safari/iOS 原生 <video> 播。优点是兼容全平台(iOS Safari 不支持 MSE 播 FLV),缺点是传统 HLS 延迟偏大,适合点播化直播、回放。
方案三:服务端 RTMP → WebRTC(延迟 < 1s)
SRS 4.0+、MediaSoup、LiveKit、ZLMediaKit 都支持 RTMP 转 WHEP/WebRTC 下行,浏览器用 RTCPeerConnection 直接拉流。适合互动直播、连麦、云游戏,代价是服务器资源开销和部署复杂度显著更高。
方案四:服务端 RTMP → WebSocket + JSMPEG(延迟 0.5–1s)
和你 RTSP 那节用的思路一样——FFmpeg 把 RTMP 拉下来转成 MPEG-TS,WebSocket 推到前端,JSMPEG 软解在 canvas 上渲染。对前端完全透明,缺点是软解性能差、多路并发吃 CPU,只适合摄像头/监控这类小分辨率场景。
方案对比
| 方案 | 延迟 | 浏览器兼容 | 服务端成本 | 典型场景 |
|---|---|---|---|---|
| RTMP → HTTP-FLV | 1–3s | Chrome/Edge/Firefox(iOS 不行) | 低 | 秀场、电商、游戏 |
| RTMP → HLS | 6–30s / LL-HLS 2s | 全平台 | 低 | 大规模分发、iOS 端 |
| RTMP → WebRTC | < 1s | 全平台现代浏览器 | 高 | 连麦、互动 |
| RTMP → WS + JSMPEG | 0.5–1s | 全平台 | 高(每路一个 FFmpeg) | 监控、内网小并发 |
为什么不推荐前端"硬啃"RTMP
有个别项目尝试过在浏览器里用 WebSocket + JS/Wasm 直接解 RTMP 握手(videojs-flash 早已废弃、rtmp.js 之类的玩具库),结论是不值得:
- RTMP 握手依赖 TLS 之外的自定义加密字段,服务端/CDN 未必放行 WebSocket 代理
- AMF0/AMF3 反序列化 + chunk 重组在 JS 里跑性能差
- 解出来的流最终还是要经 MSE → 等于多绕一圈,不如直接让服务端吐 HTTP-FLV
使用方法示例:RTMP → HTTP-FLV + flv.js(Vue3 + Vite)
① 服务端(任选一个)
最省事的是 SRS(一行 Docker 起来):
docker run --rm -p 1935:1935 -p 1985:1985 -p 8080:8080 \
ossrs/srs:5 ./objs/srs -c conf/http-flv.live.conf推流:rtmp://你的服务器IP/live/streamKey 拉流:http://你的服务器IP:8080/live/streamKey.flv
② 前端
npm i mpegts.js # flv.js 的官方后继版本,推荐用这个<template>
<video ref="videoEl" controls autoplay muted style="width:100%"></video>
</template>
<script setup>
import { ref, onMounted, onBeforeUnmount } from 'vue'
import mpegts from 'mpegts.js'
const videoEl = ref(null)
let player = null
const FLV_URL = 'http://你的服务器IP:8080/live/streamKey.flv'
onMounted(() => {
if (!mpegts.getFeatureList().mseLivePlayback) return
player = mpegts.createPlayer({
type: 'flv', // 对应 RTMP→FLV 场景
isLive: true,
url: FLV_URL,
hasAudio: true,
hasVideo: true,
}, {
enableStashBuffer: false, // 降低延迟
liveBufferLatencyChasing: true, // 自动追帧
liveBufferLatencyMaxLatency: 2.0,
liveBufferLatencyMinRemain: 0.3,
})
player.attachMediaElement(videoEl.value)
player.load()
player.play()
})
onBeforeUnmount(() => {
if (player) {
player.pause()
player.unload()
player.detachMediaElement()
player.destroy()
player = null
}
})
</script>小坑:HTTP-FLV 必须走 HTTPS + HTTPS 源站 或前端站点也是 HTTP,否则混合内容(Mixed Content)会被浏览器拦。线上建议直接
https://...flv+ CDN 边缘节点。
FLV 前端浏览器播放
FLV(Flash Video)本身是个封装格式,常见的两种传输方式是 HTTP-FLV(HTTP 长连接流式下发)和 WebSocket-FLV(WSS 流式下发)。上面 RTMP 那一节已经把主链路讲清了,这里专注前端侧。
核心原理
CDN/源站 ──HTTP(S) 长连接──► 浏览器
│
▼
flv.js / mpegts.js
① Fetch/XHR 流式拉 FLV 字节
② Demux:解析 FLV Header + Script/Video/Audio Tag
③ Remux:重新封装成 fMP4 (ISO BMFF)
④ MSE.sourceBuffer.appendBuffer(fMP4 片段)
⑤ <video> 原生解码播放一句话:浏览器不懂 FLV,但懂 fMP4;flv.js 的唯一使命就是在 JS 里做 FLV→fMP4 的实时转封装。
库选型:flv.js vs mpegts.js
| 维度 | flv.js | mpegts.js |
|---|---|---|
| 维护方 | Bilibili(2020 后进入维护模式) | Bilibili(flv.js 的官方后继,活跃维护) |
| 支持封装 | 仅 FLV | FLV + MPEG-TS(HTTP/WebSocket/直读) |
| 编码支持 | H.264 + AAC/MP3 | H.264/H.265(Chrome 107+ HEVC)+ AAC/MP3/Opus |
| 低延迟能力 | 基础 | 支持 liveBufferLatencyChasing 自动追帧 |
| 推荐度 | 老项目维持 | 新项目一律用 mpegts.js |
使用方法:HTTP-FLV + mpegts.js(Vue3 + Vite)
HTML:
<template>
<div class="player-container" ref="container">
<video ref="videoEl" class="video" controls autoplay muted playsinline></video>
<div class="controls">
<button @click="reload">重连</button>
<button @click="chaseLive">追直播</button>
<span>{{ latency.toFixed(2) }}s</span>
</div>
</div>
</template>JS(封装成组合式函数,方便复用):
// composables/useFlvPlayer.ts
import { ref, onBeforeUnmount } from 'vue'
import mpegts from 'mpegts.js'
export function useFlvPlayer() {
const videoEl = ref<HTMLVideoElement | null>(null)
const latency = ref(0)
let player: mpegts.Player | null = null
let timer: number | null = null
const create = (url: string) => {
if (!mpegts.isSupported()) {
console.error('当前浏览器不支持 MSE')
return
}
destroy()
player = mpegts.createPlayer(
{
type: 'flv',
isLive: true,
url,
cors: true,
hasAudio: true,
hasVideo: true,
},
{
enableWorker: true, // 在 Worker 里做 demux/remux,释放主线程
enableStashBuffer: false, // 关闭 IO 缓冲区,最低延迟
stashInitialSize: 128,
liveBufferLatencyChasing: true, // 自动丢帧追直播
liveBufferLatencyMaxLatency: 1.8,
liveBufferLatencyMinRemain: 0.3,
lazyLoad: false,
autoCleanupSourceBuffer: true, // 自动清理过期分片,避免长时间播放 OOM
}
)
player.attachMediaElement(videoEl.value!)
player.load()
player.play().catch((e) => console.error('自动播放失败,需要用户交互', e))
// 错误兜底:网络中断自动重连
player.on(mpegts.Events.ERROR, (type, detail) => {
console.warn('[mpegts]', type, detail)
setTimeout(() => create(url), 1000)
})
// 延迟监控
timer = window.setInterval(() => {
if (!player || !videoEl.value) return
const buffered = videoEl.value.buffered
if (buffered.length > 0) {
latency.value = buffered.end(buffered.length - 1) - videoEl.value.currentTime
}
}, 500)
}
const destroy = () => {
if (timer) { clearInterval(timer); timer = null }
if (player) {
player.pause()
player.unload()
player.detachMediaElement()
player.destroy()
player = null
}
}
const chaseLive = () => {
if (!videoEl.value) return
const buffered = videoEl.value.buffered
if (buffered.length > 0) {
// 直接 seek 到缓冲末尾,追上直播
videoEl.value.currentTime = buffered.end(buffered.length - 1) - 0.3
}
}
onBeforeUnmount(destroy)
return { videoEl, latency, create, destroy, reload: (url: string) => create(url), chaseLive }
}组件中使用:
<script setup>
import { onMounted } from 'vue'
import { useFlvPlayer } from '@/composables/useFlvPlayer'
const { videoEl, latency, create, chaseLive } = useFlvPlayer()
const FLV_URL = 'https://your-cdn.com/live/streamKey.flv'
onMounted(() => create(FLV_URL))
</script>WebSocket-FLV 场景
部分老旧监控/视联设备只提供 ws://xxx/flv 的 WebSocket 推流,mpegts.js 原生支持:
mpegts.createPlayer({
type: 'flv',
isLive: true,
url: 'wss://your-host/live/stream.flv', // 注意是 wss:// 而不是 https://
})mpegts.js 内部会判断 scheme,自动走 WebSocket Loader。
常见踩坑清单
- CORS:HTTP-FLV 源站必须返回
Access-Control-Allow-Origin: *(或精确 origin)、Access-Control-Allow-Credentials按需。否则fetch直接挂 - Mixed Content:HTTPS 页面不能拉
http://的 FLV,必须 HTTPS 源站 - iOS/Safari 无解:iOS 全系(包括桌面 Safari)都不支持 MSE 播 FLV。iOS 下必须服务端转 HLS,让
<video src="*.m3u8">原生播 - H.265 FLV:B 站扩展了 FLV 的 H.265 CodecID(12),只有 mpegts.js 支持,flv.js 不认;且浏览器侧 Chrome 107+ 才有软/硬解 HEVC 能力
- 长时间播放内存膨胀:开启
autoCleanupSourceBuffer: true,或自己定期player.currentTime往前 seek 触发清理 - 首帧慢:确保源站关键帧间隔(GOP)≤ 2s,否则首屏要等到下一个 I 帧才出画
- 延迟越播越大:开启
liveBufferLatencyChasing自动追帧,或定期手动chaseLive() - 页面不可见时暂停:默认
pauseWhenHidden=false,切回前台可能累计大量延迟,必要时切后台主动pause(),回来再play()+ 追帧
使用哪些"播放器 UI 壳"
mpegts.js 只做拉流和解封装,UI 控件要自己套。常见组合:
xgplayer+xgplayer-flv(西瓜播放器,字节开源,国内生态友好)video.js+videojs-flvjs-es6artplayer+artplayer-plugin-flv- 自己基于
<video>原生 + 自研控件(上面那段代码的路径)
附:四种直播协议前端选型总览
把你文档里已有的 HLS、RTSP 和本次补充的 RTMP、FLV 合成一张总表:
| 协议 | 前端是否能直播 | 推荐方案 | 延迟 | 推荐库 | iOS 支持 |
|---|---|---|---|---|---|
| HLS | ✅ 直接播 | 原生 <video>(Safari/iOS)/ hls.js(其他)/ 特殊码流用 h265web.js / EasyPlayer | 6–30s(LL-HLS 2s) | hls.js、xgplayer-hls、EasyPlayer | ✅ 原生 |
| FLV (HTTP-FLV/WS-FLV) | ✅ MSE 转封装 | mpegts.js + MSE | 1–3s | mpegts.js(首选)、flv.js | ❌ 无解 |
| RTMP | ❌ 不能直接播 | 服务端转 HTTP-FLV / HLS / WebRTC | 取决于转出协议 | 同目标协议 | 取决于目标协议 |
| RTSP | ❌ 不能直接播 | FFmpeg 转 MPEG-TS + WebSocket + JSMPEG / 转 WebRTC / 转 HLS | WS+JSMPEG 0.5–1s,WebRTC <1s,HLS 6s+ | JSMPEG、WebRTC 原生 | WebRTC/HLS 可 |
选型一句话总结
- 延迟不敏感 + 兼容全平台(尤其 iOS) → HLS
- 延迟要求 1–3s + 只考虑安卓/PC Web → HTTP-FLV + mpegts.js
- 推流端只能给 RTMP → 服务端转 FLV(低延迟)或转 HLS(强兼容)
- 只有 RTSP(摄像头/安防) → 服务端 FFmpeg 转 WS/JSMPEG 或 WebRTC,再考虑 HLS 回放
- 需要 < 1s 互动延迟 → 一律走 WebRTC(WHEP/WHIP)
- 需要版权级 DRM 保护 → 放弃 FLV/RTMP,直接上 LL-HLS/LL-DASH + CMAF + Widevine/PlayReady/FairPlay
这样你的文档就覆盖了 HLS / FLV / RTMP / RTSP 四大直播协议在浏览器侧的完整落地路径。