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>
相关推荐
艾伦野鸽ggg1 小时前
JavaScript 基础语法速通
前端·javascript
小糯米6011 小时前
C语言 动态内存管理
c语言·开发语言
zhengfei6111 小时前
第2章 Agent 核心组件深度解析
前端·javascript·react.js
say_fall2 小时前
可编程中断控制器8259A工作方式超详细解析
android·开发语言·学习·硬件架构·硬件工程
San813_LDD2 小时前
[QT]《Qt 开发避坑指南:随机数、容器操作与 VS 环境配置》
开发语言·qt
GuWen_yue2 小时前
LeetCode 76 最小覆盖子串|JS 滑动窗口标准解法(逐行精讲)
javascript·算法·leetcode
小糯米6012 小时前
C语言 自定义类型:联合和枚举
java·c语言·开发语言
weixin_523185322 小时前
Java基础知识总结(二):JVM内存结构与变量生命周期
java·开发语言·jvm
石山代码2 小时前
Python 进阶学习指南
开发语言·python