Skip to content

前端视频流

HLS前端浏览器播放

常用前端浏览器播放器

  1. HTML5 原生播放器:HTML5 原生播放器是浏览器自带的播放器,支持 HLS 协议的播放。

  2. 第三方播放器:除了浏览器自带的播放器外,还有一些第三方播放器可以支持 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项目)

  1. 在GitHub里下载对应的h265web.jsindex.jsmissile.jsmissile.wasm文件。(下载对应时间的版本,如果有问题,可以去issue看下有没有解决方法)

  2. 将这几个文件放置于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>
  3. 创建公共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
    }
  4. 在组件中使用

    HTML

    HTML
    <div class="player-container" ref="container">
      <div id="glplayer" class="glplayer"></div>
    </div>

    JS

    JS
    import { 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项目)

  1. 在GitHub里下载对应的EasyPlayer-pro.jsEasyPlayer-pro.wasmEasyPlayer-lib.js文件。(在vue-demo文件夹下的js文件夹中)

  2. 将这两个文件放置于public文件夹下,并在index.html中引入。

    html
    <body>
    <div id="app"></div>
    <script src="/EasyPlayer-pro.js"></script>
    <script type="module" src="/src/main.ts"></script>
    </body>
  3. 创建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部分

    JS
    
    const 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视频流的方式:

  1. 使用FFmpeg和WebSocket

    FFmpeg是一个强大的多媒体处理工具,可以将RTSP流转换为其他格式。

    可以使用FFmpeg将RTSP流转换为MEPG-TS或DASH格式,然后通过WebSocket将转换后的流发送到浏览器。

    浏览器端需要使用相应的播放器库来播放,例如JSMPEG。

  2. 使用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发送出去。

JS
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中展示画面。

JS
// 生成唯一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-FLV1–3sChrome/Edge/Firefox(iOS 不行)秀场、电商、游戏
RTMP → HLS6–30s / LL-HLS 2s全平台大规模分发、iOS 端
RTMP → WebRTC< 1s全平台现代浏览器连麦、互动
RTMP → WS + JSMPEG0.5–1s全平台高(每路一个 FFmpeg)监控、内网小并发

为什么不推荐前端"硬啃"RTMP

有个别项目尝试过在浏览器里用 WebSocket + JS/Wasm 直接解 RTMP 握手(videojs-flash 早已废弃、rtmp.js 之类的玩具库),结论是不值得

  1. RTMP 握手依赖 TLS 之外的自定义加密字段,服务端/CDN 未必放行 WebSocket 代理
  2. AMF0/AMF3 反序列化 + chunk 重组在 JS 里跑性能差
  3. 解出来的流最终还是要经 MSE → 等于多绕一圈,不如直接让服务端吐 HTTP-FLV

使用方法示例:RTMP → HTTP-FLV + flv.js(Vue3 + Vite)

① 服务端(任选一个)

最省事的是 SRS(一行 Docker 起来):

bash
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

② 前端

bash
npm i mpegts.js   # flv.js 的官方后继版本,推荐用这个
vue
<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.jsmpegts.js
维护方Bilibili(2020 后进入维护模式)Bilibili(flv.js 的官方后继,活跃维护)
支持封装仅 FLVFLV + MPEG-TS(HTTP/WebSocket/直读)
编码支持H.264 + AAC/MP3H.264/H.265(Chrome 107+ HEVC)+ AAC/MP3/Opus
低延迟能力基础支持 liveBufferLatencyChasing 自动追帧
推荐度老项目维持新项目一律用 mpegts.js

使用方法:HTTP-FLV + mpegts.js(Vue3 + Vite)

HTML:

vue
<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(封装成组合式函数,方便复用):

ts
// 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 }
}

组件中使用:

vue
<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 原生支持:

ts
mpegts.createPlayer({
  type: 'flv',
  isLive: true,
  url: 'wss://your-host/live/stream.flv',   // 注意是 wss:// 而不是 https://
})

mpegts.js 内部会判断 scheme,自动走 WebSocket Loader。

常见踩坑清单

  1. CORS:HTTP-FLV 源站必须返回 Access-Control-Allow-Origin: *(或精确 origin)、Access-Control-Allow-Credentials 按需。否则 fetch 直接挂
  2. Mixed Content:HTTPS 页面不能拉 http:// 的 FLV,必须 HTTPS 源站
  3. iOS/Safari 无解:iOS 全系(包括桌面 Safari)都不支持 MSE 播 FLV。iOS 下必须服务端转 HLS,让 <video src="*.m3u8"> 原生播
  4. H.265 FLV:B 站扩展了 FLV 的 H.265 CodecID(12),只有 mpegts.js 支持,flv.js 不认;且浏览器侧 Chrome 107+ 才有软/硬解 HEVC 能力
  5. 长时间播放内存膨胀:开启 autoCleanupSourceBuffer: true,或自己定期 player.currentTime 往前 seek 触发清理
  6. 首帧慢:确保源站关键帧间隔(GOP)≤ 2s,否则首屏要等到下一个 I 帧才出画
  7. 延迟越播越大:开启 liveBufferLatencyChasing 自动追帧,或定期手动 chaseLive()
  8. 页面不可见时暂停:默认 pauseWhenHidden=false,切回前台可能累计大量延迟,必要时切后台主动 pause(),回来再 play() + 追帧

使用哪些"播放器 UI 壳"

mpegts.js 只做拉流和解封装,UI 控件要自己套。常见组合:

  • xgplayer + xgplayer-flv(西瓜播放器,字节开源,国内生态友好)
  • video.js + videojs-flvjs-es6
  • artplayer + artplayer-plugin-flv
  • 自己基于 <video> 原生 + 自研控件(上面那段代码的路径)

附:四种直播协议前端选型总览

把你文档里已有的 HLS、RTSP 和本次补充的 RTMP、FLV 合成一张总表:

协议前端是否能直播推荐方案延迟推荐库iOS 支持
HLS✅ 直接播原生 <video>(Safari/iOS)/ hls.js(其他)/ 特殊码流用 h265web.js / EasyPlayer6–30s(LL-HLS 2s)hls.js、xgplayer-hls、EasyPlayer✅ 原生
FLV (HTTP-FLV/WS-FLV)✅ MSE 转封装mpegts.js + MSE1–3smpegts.js(首选)、flv.js❌ 无解
RTMP❌ 不能直接播服务端转 HTTP-FLV / HLS / WebRTC取决于转出协议同目标协议取决于目标协议
RTSP❌ 不能直接播FFmpeg 转 MPEG-TS + WebSocket + JSMPEG / 转 WebRTC / 转 HLSWS+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 四大直播协议在浏览器侧的完整落地路径。