flv.js解决其中一个监控断线导致其他的监控播放阻塞

问题的根因不是 JS 异步,而是 flv.js 的 WebSocket 连接一旦发起,底层资源释放不及时,坏流一直占着浏览器连接槽 。换什么库、什么信号量都打不到这个层面。

换方案:用 mpegts.js(flv.js 的现代维护分支),加 WebSocket 预探测。先用最快的 WebSocket 测每个 URL 是否可达,死的直接跳过不给它碰 flvjs,活的一个一个串行启动。这样坏流连 WebSocket 都不碰,100% 不挡。

html 复制代码
<!DOCTYPE html>
<html lang="zh-CN">

<head>
  <meta charset="UTF-8">
  <title>多路 WS-FLV 监控</title>
  <style>
    html, body { margin: 0; background: #111; color: #fff; font-family: Arial; }
    h2 { text-align: center; padding: 10px; }
    .grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(480px, 1fr)); gap: 12px; padding: 12px; }
    .player { background: #000; position: relative; }
    .title { position: absolute; top: 6px; left: 8px; font-size: 14px; background: rgba(0,0,0,.6); padding: 2px 6px; border-radius: 4px; }
    .status { position: absolute; top: 6px; right: 8px; font-size: 11px; padding: 2px 6px; border-radius: 4px; }
    .status.wait { background: #555; }
    .status.ok { background: #080; }
    .status.bad { background: #800; }
    video { width: 100%; height: 360px; background: #000; }
  </style>
</head>

<body>

  <h2>多路 WS-FLV 实时监控</h2>
  <div id="app" class="grid"></div>

  <script src="https://cdn.jsdelivr.net/npm/mpegts.js@1.7.3/dist/mpegts.js"></script>

  <script>
    var cameras = [
      { id: "dataHubRightVideo_1_2_0", title: "船1", url: "ws://10.111.222.5:8000/live/cam1.live.flv" },
      { id: "dataHubRightVideo_1_2_1", title: "船2", url: "ws://10.111.222.5:8000/live/cam1.live.flv" },
      { id: "dataHubRightVideo_1_2_2", title: "船3", url: "ws://10.111.222.5:8000/live/cam1.live.flv" },
      { id: "dataHubRightVideo_1_2_3", title: "船4", url: "ws://10.111.222.5:8000/live/cam1.live.flv" },
      { id: "dataHubRightVideo_1_2_4", title: "船5", url: "ws://10.111.222.5:8000/live/cam1.live.flv" },
      { id: "dataHubRightVideo_1_2_5", title: "船6", url: "ws://10.111.222.5:8000/live/cam1.live.flv" },
      { id: "dataHubRightVideo_1_2_6", title: "船7", url: "ws://10.111.222.5:8000/live/cam1.live.flv" },
      { id: "dataHubRightVideo_1_2_7", title: "船8", url: "ws://10.111.222.5:8000/live/cam1.live.flv" },
      { id: "dataHubRightVideo_1_2_8", title: "船9", url: "ws://10.111.222.5:8000/live/cam1.live.flv" },
      { id: "dataHubRightVideo_1_2_9", title: "T5楼顶船10", url: "ws://10.111.222.5:8000/live/cam1.live.flv" },
    ]

    var app = document.getElementById('app')
    var slots = []

    // 第一步:立即渲染所有 DOM
    cameras.forEach(function (cam) {
      var box = document.createElement('div')
      box.className = 'player'

      var title = document.createElement('div')
      title.className = 'title'
      title.innerText = cam.title
      box.appendChild(title)

      var status = document.createElement('div')
      status.className = 'status wait'
      status.innerText = '等待'
      box.appendChild(status)

      var video = document.createElement('video')
      video.controls = true
      video.muted = true
      video.autoplay = true
      video.style.background = '#222'
      box.appendChild(video)

      app.appendChild(box)

      slots.push({ cam: cam, video: video, status: status, player: null })
    })

    // 第二步:WebSocket 预探测,逐个来,互不阻塞
    var currentIndex = 0

    function probeNext() {
      if (currentIndex >= slots.length) return

      var slot = slots[currentIndex]
      currentIndex++

      slot.status.className = 'status wait'
      slot.status.innerText = '探测'

      var ws = null
      var probed = false

      function finishProbe(alive) {
        if (probed) return
        probed = true
        if (ws) { try { ws.close() } catch (_) { } ws = null }

        if (alive) {
          slot.status.className = 'status ok'
          slot.status.innerText = '播放'
          startPlayer(slot)
        } else {
          slot.status.className = 'status bad'
          slot.status.innerText = '不可达'
          slot.video.style.background = '#400'
        }
        // 立即探测下一路
        probeNext()
      }

      try {
        ws = new WebSocket(slot.cam.url)
        ws.binaryType = 'arraybuffer'

        ws.onopen = function () {
          // 收到数据才算活着
        }

        ws.onmessage = function () {
          // 收到任何数据 = 活着
          finishProbe(true)
        }

        ws.onerror = function () {
          finishProbe(false)
        }

        ws.onclose = function () {
          finishProbe(false)
        }

        // 1 秒超时
        setTimeout(function () {
          finishProbe(false)
        }, 1000)
      } catch (e) {
        finishProbe(false)
      }
    }

    function startPlayer(slot) {
      var player = mpegts.createPlayer({
        type: 'flv',
        url: slot.cam.url,
        isLive: true,
        enableWorker: false,
        lazyLoad: false,
        stashInitialSize: 128
      })

      player.attachMediaElement(slot.video)
      slot.player = player

      player.on(mpegts.Events.ERROR, function () {
        slot.player = null
        try { player.destroy() } catch (_) { }
        slot.status.className = 'status ok'
        slot.status.innerText = '重连'
        // 3 秒后重连
        setTimeout(function () { startPlayer(slot) }, 3000)
      })

      player.load()
      player.play().catch(function () { })
    }

    // 串行探测启动
    probeNext()
    // 同时启动多路探测:一次发 2 路
    setTimeout(probeNext, 200)

    // 第三步:15 秒后对失败的重试
    setInterval(function () {
      slots.forEach(function (slot) {
        if (slot.status.className.indexOf('bad') >= 0 || slot.status.innerText === '重连') {
          // 重探测
          slot.status.className = 'status wait'
          slot.status.innerText = '重试'
          var ws = null
          var probed = false
          function finish(alive) {
            if (probed) return
            probed = true
            if (ws) { try { ws.close() } catch (_) { } ws = null }
            if (alive && !slot.player) {
              slot.status.className = 'status ok'
              slot.status.innerText = '播放'
              startPlayer(slot)
            } else if (!alive) {
              slot.status.className = 'status bad'
              slot.status.innerText = '不可达'
            }
          }
          try {
            ws = new WebSocket(slot.cam.url)
            ws.binaryType = 'arraybuffer'
            ws.onmessage = function () { finish(true) }
            ws.onerror = function () { finish(false) }
            ws.onclose = function () { finish(false) }
            setTimeout(function () { finish(false) }, 1000)
          } catch (e) { finish(false) }
        }
      })
    }, 15000)

    window.addEventListener('beforeunload', function () {
      slots.forEach(function (s) {
        if (s.player) { try { s.player.destroy() } catch (_) { } }
      })
    })
  </script>

</body>

</html>
相关推荐
个 人 练 习 生17 分钟前
C++ string 类模拟实现:从底层理解字符串(上)
开发语言·c++·经验分享·学习·程序人生
2601_9669496526 分钟前
多市场量化策略的数据接口应该如何设计:从数据层架构到策略接入
开发语言·python·数据分析·pandas·量化交易·股票数据·quantdash
小宋102129 分钟前
A2UI从零实战:Agent安全生成交互界面与事件回传
javascript·人工智能·安全·交互
坤坤子吖29 分钟前
C++智能指针:RAII、shared_ptr与内存泄漏
开发语言·c++·笔记·学习
奋斗的阿狸_198639 分钟前
ESP32-S3 + ES7210 四通道麦克风录音上传方案
c语言·开发语言·fpga开发
大包子1 小时前
structuredClone vs lodash.cloneDeep:核心差异与选型指南
javascript·vue.js
weixin199701080161 小时前
《1688图片空间API踩坑:img.upload 与 album.* 的防盗链与CDN缓存问题》(附Python源码)
开发语言·python·缓存
ACP广源盛139246256731 小时前
Type‑C 扩展坞方案选型笔记|国产 DP1.4 转 HDMI2.0 桥接芯片 GSV2201S @ACP评估
c语言·开发语言·笔记·硬件架构·硬件工程·国产芯片
lerhxx1 小时前
AI 应用中的上下文管理 —— 从"上下文窗口"到"分层压缩"的工程实践
前端·javascript
niucloud-admin1 小时前
JAVA V6 多商户商城 开发文档——JAVA服务端
java·开发语言