如何手动停止 videojs 直播视频流 m3u8 请求?

简介: 在可视化大屏项目中,多个VideoJS组件播放m3u8流时,即使暂停仍持续请求,导致页面卡顿。通过监听display属性,结合`dispose()`销毁实例并重建同ID的video DOM元素,有效释放资源且保留组件结构,解决性能问题,提升用户体验。

问题描述

在公司某个可视化大屏项目中,大屏页面会有多个 videojs 组件,每个组件都会对应一个视频流地址。每当视频开始播放,视频流m3u8 会不断请求,即便是暂停了播放,这个请求也不会终止。大量的请求会导致页面卡顿,长此以往会带来性能问题,导致浏览器卡死甚至崩溃。

而大屏操作中,经常会用到组件联动,点击百度地图的点位,出现一个视频弹窗,点击关闭视频,其实是隐藏了视频,而视频的请求还在继续。为了解决这个问题,我花了一些时间研究,找到了解决办法。

解决办法

从videojs官方文档可以查到,有一个 dispose 方法。这个方法是用来销毁 videojs 对象的。但这个方法不能直接使用,直接使用会导致一个新的问题,那就是销毁实例后 ,原本的 video 标签dom 元素也一并销毁了,这个特性从官方文档中可以看出。

Videojs Removing Players

请在此添加图片描述

其实我个人觉得,这个方法的操作 2 的特性非常不好,这样导致关闭后组件直接被销毁,导致下次触发视频弹窗(业务需求是点击百度地图图例,出现弹窗播放视频直播流),没有视频组件可以显示播放。

于是我针对这个项目组件,写了一个 Vue 的watch,用来监听监听 display 属性。在这个项目中,这个属性每个组件都有,display 为 false 是显示,true 是隐藏(别问为什么是反的,我也不知道)。代码示例如下:

<template>
  <div :style="styleObject" ref="myvideojs">
    <!-- <video
      :id="videojsId"
      class="video-js vjs-big-play-centered vjs-fluid"
      style="width: 100%; height: 100%; object-fit: fill"
    ></video> -->
    <!-- padding-top: 0,解决减小高度到一定数值后,高度不能自适应的问题 -->
    <video :id="videojsId" class="video-js vjs-big-play-centered vjs-fluid" style="width: 100%; height: 100%; object-fit: fill; padding-top: 0" ref="videoPlayer"></video>
  </div>
</template>
 watch: {
    ...
    display: {
      handler(newVal) {
        // true 代表隐藏
        if (newVal) {
          console.log("隐藏 :>> ")
          if (this.myVideo) {
            this.$nextTick(() => {
              // 必须先暂停,后销毁,销毁后 dom 元素也会被移除,所以需要手动添加相同 id 的 dom
              this.myVideo.dispose()
              // 下面这个 dom 跟 video 标签属性一致
              const videoElement = document.createElement("video")
              videoElement.setAttribute("id", this.videojsId) //注意 id 要一致
              videoElement.setAttribute("class", "video-js vjs-big-play-centered vjs-fluid")
              videoElement.setAttribute("style", "width: 100%; height: 100%; object-fit: fill; padding-top: 0")
              videoElement.setAttribute("ref", "videoPlayer")
              this.$refs.myvideojs.appendChild(videoElement) //添加相同 DOM
              this.myVideo = null
            })
          }
        } else {
          console.log("显示 :>> ")
        }
      },
    },
  }

下面是绘制组件的方法:

methods: {
    // 绘制方法
    drawChar(result) {
      let that = this
      if (result.length > 1) {
        console.log(this.MapPoiChange)
        console.log(this.option.Playtype)
        let key = this.MapPoiChange ? this.MapPoiChange : this.option.Playtype ? this.option.Playtype : "SXT"
        result = result.filter(v => {
          return v.key == key
        })[0]
      } else {
        result = result[0]
      }
      // 这些options属性也可直接设置在video标签上,见 muted
      // 实例化过,修改最新的url
      if (this.myVideo) {
        this.myVideo.src({ type: result.type, src: result.value })
      } else {
        let options = {
          autoplay: this.option.autoplay, // 设置自动播放
          controls: true, // 显示播放的控件
          width: this.component.width,
          height: this.component.height,
          sources: [
            {
              src: result.value,
              type: result.type, // 告诉videojs,这是一个hls流
            },
          ],
        } // videojs的第一个参数表示的是,文档中video的id
        this.myVideo = Videojs(this.videojsId, options, function onPlayerReady() {
          this.on("error", function () {
            // 报错信息
            var mediaError = this.error()
            console.log("mediaError", mediaError)
            // 异常处理
            that.updateData()
          })
        })
      }
    },
  },

以上代码的核心有3点:

  1. 关闭的时候,销毁 videojs;
  2. 销毁后立即创建一个与先前videojs 相同的 dom,尤其是 id 要保持一致;
  3. 显示时候重新初始化渲染 videojs(因为全局方法默认会调用绘制 drawChar,否则需要再显示逻辑里面新增绘制方法)

只要注意到这三个核心点,类似的问题也能迎刃而解。

注意事项

  1. 销毁要包裹在\$nextTick里面,不然会出现报错。

请在此添加图片描述

Error:Invalid target for nutl#on;must be a DOM node or evented object
  1. 需要通过 appendChild,添加一个跟之前 videojs 一样的根 dom,不然会报错找不到这个元素的 id:

请在此添加图片描述

TypeError: The element or ID supplied is not valid. (videojs)

总结

关于 videojs,实际项目用到的比较多,坑也是真的坑。文档不太好找,搜索查询了好长时间,才摸索出一套可行的解决方案。面对这类问题,需要善用搜索,从别人的文章和问答中寻找解决问题的思路和方案。查阅官方文档也是个不错的选择,但并不是每个类库框架的官方文档写的都易于理解。videojs 新版的文档和旧版本有些区别,很多 API 看起来并不十分直观,所以版本问题也要注意下。

以上是我解决这个问题的经验分享,欢迎评论区交流。

参考

vue使用videojs控制后台m3u8数据请求 - bomdeyada - 博客园

目录
相关文章
|
Android开发
flutter中实现仿Android端的onResume和onPause方法
flutter中实现仿Android端的onResume和onPause方法
|
25天前
|
弹性计算 应用服务中间件
阿里云轻量应用服务器200M峰值带宽详细说明,200Mbps适用哪种使用场景?
阿里云轻量应用服务器提供200Mbps峰值带宽(上下行对等),理论下载速度约25MB/s,属共享型带宽,非持续保障。适合个人网站、开发测试等轻量场景,不适用于高并发或企业级业务。38元/年起,详情见官方页面。
208 0
|
前端开发 Android开发 容器
surfaceview组件的surfaceCreated()不被调用的解决方案
原文:surfaceview组件的surfaceCreated()不被调用的解决方案 有时候我们有需要在native层做在surfaceview的上下文中做渲染,这个时候只是提供了一个单独什么都不做的surfaceview。
4079 0
|
7月前
|
前端开发 JavaScript
CSS变量实战:动态主题切换技巧
CSS变量实战:动态主题切换技巧
350 81
|
9月前
|
弹性计算 安全 Linux
阿里云服务器镜像解析:镜像类型对比、适用场景与选择策略参考
阿里云服务器镜像,作为ECS实例的“装机盘”,不仅提供了操作系统,还包含了初始化应用数据和预装软件,云服务器镜像的选择对于云服务器的性能和稳定性起着至关重要的作用,选择合适的镜像对于云服务器的性能和稳定性至关重要。本文将深入解析阿里云服务器提供的多种镜像类型,从公共镜像到社区镜像,全面介绍每种镜像的特点、优势以及选择建议,帮助用户根据自身需求做出适合自己的选择。
1229 12
|
Web App开发 移动开发 编解码
浏览器播放RTSP视频流几种解决方案
Streamedian 提供了一种“html5_rtsp_player + websock_rtsp_proxy”的技术方案,可以通过html5的video标签直接播放RTSP的视频流。
2333 0
|
前端开发 小程序 JavaScript
面试官:px、em、rem、vw、rpx 之间有什么区别?
面试官:px、em、rem、vw、rpx 之间有什么区别?
413 0
|
前端开发
使用el-menu的时候遇到的bug以及解决方式
使用el-menu的时候遇到的bug以及解决方式
1177 1
Echarts折线图的x和y轴坐标颜色修改
Echarts折线图的x和y轴坐标颜色修改
1157 1