Vue文件video标签配合flv.js实现监控画面实时转播

问题描述

之前已经把监控的画面实时在页面上显示出来了,但是发现有一个问题,因为我要做一个类似于点击某一个通道单独切换到这个通道的资源,因此我需要在开启这个通道之前,切断之前的通道,否则在没有使用资源的时候,也会占用端口,会造成不必要的内存使用以及后续通道无法正常开启(默认服务器只开启6条通道,需在后台配置)。

然后,出现的问题是,每次利用flvPlayer实例上的destroy方法时,发现只有最后一条通道被关闭了。排查后是因为实例丢失的原因,因为我将整个flvPlayer的实例都定义在data模块中,因此通过遍历后,每次最新的都会覆盖上一次的,因此实质上我只是在对最后一次的flvPlayer进行操作。

解决思路:

将这个flvPlayer放在数据列表中,然后循环时将这个实例当作参数传入该函数,即可实现效果,

代码:

<template>
  <div class="video-container">
    <div class="tree-select-container">
      <h2 class="title">网络视频监控系统</h2>
      <a-divider />
      <a-tree
        class="draggable-tree"
        :defaultSelectedKeys="selectedKeys"
        :default-expanded-keys="expandedKeys"
        draggable
        :tree-data="gData"
        @dragenter="onDragEnter"
        @drop="onDrop"
        show-icon
        @select="onSelect"
      >
        <a-icon slot="all" type="appstore"
      /></a-tree>
    </div>
    <div class="video-page-container flex-column">
      <div class="main-body">
        <div class="carousel-container">
          <a-carousel
            dot-position="left"
            v-show="isFullShow"
            class="carousel-outside-box"
          >
            <div class="carousel">
              <video
                id="videoElement"
                ref="videoElement"
                muted
                poster="/img/loading/loading-2.gif"
                autoplay
              ></video>
            </div>
          </a-carousel>
          <div
            v-show="!isFullShow && !aloneVideoShow"
            class="split-screen-container"
          >
            <!-- 分区显示(默认显示6块,做懒加载) -->
            <video
              v-for="(item, index) in videoName"
              :id="item.key"
              :key="item.key"
              class="video-split-screen"
              type="video/mp4"
              poster="/img/loading/loading-1.gif"
              muted
              autoplay
            ></video>
          </div>
          <div
            v-show="!isFullShow && aloneVideoShow"
            style="background-color: #000; height: 100%"
            class="flex-center-h-v"
          >
            <video
              poster="/img/loading/loading-2.gif"
              muted
              autoplay
              id="videoElement"
              ref="videoElementSplit"
            ></video>
          </div>
        </div>
      </div>
      <a-collapse v-model="activeKey" class="log-report">
        <a-collapse-panel key="1" header="日志监控">
          <a-alert
            v-for="(item, index) in alertInfoList"
            :key="item.key"
            :message="item.message"
            :type="item.type"
            class="alert"
          />
        </a-collapse-panel>
      </a-collapse>
    </div>
  </div>
</template>
<script>
// 模拟数据
import { GetCurrentTime } from "@/assets/js/utils/time";
const gData = [
  {
    title: "查看所有监控",
    key: "all",
    scopedSlots: { icon: "all" },
  },
  {
    title: "工位一",
    key: "工位一",
    children: [
      {
        title: "实验区域一",
        key: "technology1",
      },
      {
        title: "实验区域二",
        key: "technology",
      },
    ],
  },
  {
    title: "工位二",
    key: "工位二",
    children: [
      {
        title: "实验区域一",
        key: "technology3",
      },
      {
        title: "实验区域二",
        key: "technology5",
      },
      {
        title: "实验区域三",
        key: "technology6",
      },
      {
        title: "实验区域四",
        key: "technology4",
      },
    ],
  },
  {
    title: "工位三",
    key: "工位三",
    children: [
      {
        title: "实验区域一",
        key: "technology7",
      },
      {
        title: "实验区域二",
        key: "technology8",
      },
      {
        title: "实验区域三",
        key: "technology9",
      },
      {
        title: "实验区域四",
        key: "technology10",
      },
    ],
  },
];
export default {
  data() {
    return {
      activeKey: "1",
      gData,
      expandedKeys: ["工位一", "工位二", "工位三"],
      selectedKeys: ["all"],
      currentTreeSelectKey: "technology1",
      currentTime: "",
      // 是否展示全屏
      isFullShow: false,
      videoName: [
        { key: "technology", value: "technology", flvPlayer: null },
        { key: "technology1", value: "technology1", flvPlayer: null },
        { key: "technology15", value: "technology15", flvPlayer: null },
        { key: "technology3", value: "technology3", flvPlayer: null },
        { key: "technology11", value: "technology11", flvPlayer: null },
        { key: "technology13", value: "technology13", flvPlayer: null },
      ],
      // 单屏显示的flv信息列表
      currentVideoList: {},
      alertInfoList: [
        {
          key: 0,
          message: "提示信息:当前工位有员工进行走动",
          type: "warning",
        },
        {
          key: 1,
          message: "提示信息:当前工位有员工进行走动",
          type: "warning",
        },
        {
          key: 2,
          message: "提示信息:当前工位有员工进行走动",
          type: "warning",
        },
        {
          key: 3,
          message: "提示信息:当前工位有员工进行走动",
          type: "warning",
        },
        {
          key: 4,
          message: "提示信息:当前工位有员工进行走动",
          type: "warning",
        },
      ],
      // 是否销毁监控请求
      isDestroyVideo: false,
      aloneVideoShow: false,
    };
  },
  mounted() {
    this.init();
    setTimeout(() => {
      this.activeKey = null;
    }, 3000);
    this.videoDbClick();
  },
  // 每次离开之前关闭资源请求通道
  beforeRouteLeave(from, to, next) {
    this.isDestroyVideo = true;
    next();
  },
  watch: {
    isFullShow: {
      handler(n) {
        if (n) {
          // 全屏
          const treeELe = document.querySelector(".tree-select-container");
          const logELe = document.querySelector(".log-report");
          treeELe.classList.add("miss");
          const videoElement = this.$refs["videoElement"];
          // this.currentVideoList = {
          //   key: this.currentTreeSelectKey,
          //   value: this.currentTreeSelectKey,
          //   flvPlayer: null,
          // };
          this.loadVideoSource(this.currentVideoList, videoElement);
          // logELe.classList.add("miss");
          // this.loadVideoSource("technology5");
        }
      },
    },
  },
  methods: {
    init() {
      this.videoName.forEach((item, index) => {
        if (index < 6)
          var videoElement = document.querySelector(`#${item.key}`);
        this.loadVideoSource(item, videoElement);
      });
    },
    // 点击拖动时触发的函数
    onDragEnter(info) {
      console.log(info);
    },
    // 松开时触发的函数
    onDrop(info) {
      console.log(info);
      const dropKey = info.node.eventKey;
      const dragKey = info.dragNode.eventKey;
      const dropPos = info.node.pos.split("-");
      const dropPosition =
        info.dropPosition - Number(dropPos[dropPos.length - 1]);
      const loop = (data, key, callback) => {
        data.forEach((item, index, arr) => {
          if (item.key === key) {
            return callback(item, index, arr);
          }
          if (item.children) {
            return loop(item.children, key, callback);
          }
        });
      };
      const data = [...this.gData];

      // Find dragObject
      let dragObj;
      loop(data, dragKey, (item, index, arr) => {
        arr.splice(index, 1);
        dragObj = item;
      });
      if (!info.dropToGap) {
        // Drop on the content
        loop(data, dropKey, (item) => {
          item.children = item.children || [];
          // where to insert 示例添加到尾部,可以是随意位置
          item.children.push(dragObj);
        });
      } else if (
        (info.node.children || []).length > 0 && // Has children
        info.node.expanded && // Is expanded
        dropPosition === 1 // On the bottom gap
      ) {
        loop(data, dropKey, (item) => {
          item.children = item.children || [];
          // where to insert 示例添加到尾部,可以是随意位置
          item.children.unshift(dragObj);
        });
      } else {
        let ar;
        let i;
        loop(data, dropKey, (item, index, arr) => {
          ar = arr;
          i = index;
        });
        if (dropPosition === -1) {
          ar.splice(i, 0, dragObj);
        } else {
          ar.splice(i + 1, 0, dragObj);
        }
      }
      this.gData = data;
    },
    // 点击树节点时触发
    onSelect(selectedKeys, e) {
      console.log(selectedKeys[0], "selectedKeys");
      if (selectedKeys[0] && selectedKeys[0] === "all") {
        this.init();
        this.isFullShow = false;
        this.aloneVideoShow = false;
      }
      if (selectedKeys[0] && selectedKeys[0] !== "all") {
        this.isFullShow = false;
        this.aloneVideoShow = true;
        // 只要点击单独的就清空所有的通道
        this.videoName.forEach((item) => (item.flvPlayer._isDestroy = true));
        // 将当前的状态清空
        if (this.currentVideoList.flvPlayer) {
          this.destoryVideo(this.currentVideoList.flvPlayer);
        }
        this.currentTreeSelectKey = selectedKeys[0];
        const videoElement = this.$refs["videoElementSplit"];
        this.currentVideoList = {
          key: this.currentTreeSelectKey,
          value: this.currentTreeSelectKey,
          flvPlayer: null,
        };
        this.loadVideoSource(this.currentVideoList, videoElement, 7999);
      }
    },
    // 加日志(模拟)
    addDate() {
      setInterval(() => {
        return () => {
          const Time = GetCurrentTime();
          console.log("触发了" + Time.hms);
          this.currentTime = `${Time.YMD} ${Time.hms} 警告触发:${currentTreeSelectKey}`;
        };
      }, 1000);
    },
    // 读取监控资源
    loadVideoSource(source, element, port = 8000) {
      if (flvjs.isSupported()) {
        source.flvPlayer = flvjs.createPlayer({
          type: "flv",
          url: `http://192.168.18.148:${port}/live/${source.key}.flv`, //你的地址
          autoCleanupSourceBuffer: true, //自动清除缓存
        });
        source.flvPlayer.attachMediaElement(element);
        source.flvPlayer.load();
        // flvPlayer.play();
      }
      // 【重要事件监听】http请求建立好后,该事件会一直监听flvjs实例
      // 注意,对于flv.js状态的更改只能在函数内部,因为外部循环的话,所访问到的flv实例只是最后一次的实例
      source.flvPlayer.on(flvjs.Events.STATISTICS_INFO, (res) => {
        try {
          // console.log("请求数据信息", res);
          if (this.isDestroyVideo || source.flvPlayer._isDestroy) {
            // 离开路由或切换设备
            console.warn("销毁实例");
            // 销毁实例
            this.destoryVideo(source.flvPlayer);
          }
        } catch (error) {
          console.log(error);
        }
      });
    },
    destoryVideo(flvPlayer) {
      flvPlayer.pause();
      flvPlayer.unload();
      flvPlayer.detachMediaElement();
      flvPlayer.destroy();
      flvPlayer = null;
    },
    videoDbClick() {
      const videos = document.querySelectorAll("video"); //获取所有video标签
      videos.forEach((video) => {
        // 阻止单击暂停
        video.addEventListener("click", (event) => {
          event.preventDefault();
        });
        // 添加双击全屏放大功能
        video.addEventListener("dblclick", () => {
          if (!document.fullscreenElement) {
            if (video.requestFullscreen) {
              video.requestFullscreen();
            } else if (video.mozRequestFullScreen) {
              // 兼容火狐浏览器
              video.mozRequestFullScreen();
            } else if (video.webkitRequestFullscreen) {
              // 兼容谷歌浏览器和Safari
              video.webkitRequestFullscreen();
            }
          } else {
            if (document.exitFullscreen) {
              document.exitFullscreen();
            } else if (document.mozCancelFullScreen) {
              // 兼容火狐浏览器
              document.mozCancelFullScreen();
            } else if (document.webkitExitFullscreen) {
              // 兼容谷歌浏览器和Safari
              document.webkitExitFullscreen();
            }
          }
        });
      });
    },
  },
};
</script>
<style lang="less" scoped>
.miss {
  display: none;
}
.active {
  border: 1px solid red;
}
.video-container {
  display: flex;
  .tree-select-container {
    flex-shrink: 0;
    padding-top: 0.2rem;
    border: 1px solid @primary-border-color-lv3;
    width: 15%;
    padding-left: 0.4rem;
    .title {
      display: flex;
      color: @primary-color;
    }
  }
  .video-page-container {
    flex-grow: 1;
    border: 1px solid @primary-border-color-lv1;
    overflow-y: auto;
    height: 100vh;
    .main-body {
      width: 100%;
      overflow-y: auto;
      border: 1px solid @primary-border-color-lv3;
      flex-grow: 1;
      .carousel-container {
        height: 100%;
        .carousel {
          background-color: #000;
          padding: 1rem;
        }
        .split-screen-container {
          width: 100%;
          height: 100%;
          display: flex;
          flex-wrap: wrap;
          overflow-y: auto;
          .video-split-screen {
            border: 1px solid #fff;
            width: 33%;
            height: 50%;
            background-color: #000;
          }
        }
      }
    }
    .log-report {
      padding: 0.125rem;
      max-height: 20%;
      border: 1px solid @primary-border-color-lv3;
      overflow: auto;
      .alert {
        margin-top: 0.1rem;
      }
    }
  }
}
</style>

注意这只是一个demo,关于将监控的资源发布到服务器还需要后端的配合。

  • 7
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值