vue2录像回放时间轴组件

<template>
  <div class="timeLineContainer" ref="timeLineContainer">
    <canvas
      ref="canvas"
      @mousemove="onMouseMove"
      @mouseleave="onMouseLeave"
      @click="onClick"
    ></canvas>

    <!-- 悬浮提示 -->
<!--    <div v-if="showTooltip" :style="tooltipStyle">{{ tooltipText }}</div>-->
  </div>
</template>

<script>
import moment from "moment";

export default {
  name: "PlaybackTimeline",
  props: {
    startTime: {
      type: Date,
      required: true,
    },
    endTime: {
      type: Date,
      required: true,
    },
    highlightedSegments: {
      type: Array,
      default: () => [],
    },
  },
  data() {
    return {
      width: null,
      height: 100,
      ctx: null,

      showTooltip: false,
      tooltipText: "",
      tooltipPosition: { x: 0, y: 0 },
      mouseX: null,

      drawPending: false, // 是否已经在等待重绘
      lastDrawTime: 0,
      minRedrawInterval: 50, // 最小重绘间隔,防抖用

      visibleSegmentsCache: [], // 当前可见区域内的 segments 缓存
    };
  },
  mounted() {
    this.init();
    window.addEventListener("resize", this.debounceRedraw);
  },
  beforeDestroy() {
    window.removeEventListener("resize", this.debounceRedraw);
  },
  watch: {
    highlightedSegments: {
      handler(newVal) {
        this.debounceRedraw();
      },
      deep: true,
    },
    startTime: "debounceRedraw",
    endTime: "debounceRedraw",
  },
  methods: {
    init() {
      const container = this.$refs.timeLineContainer;
      const canvas = this.$refs.canvas;
      const { width } = container.getBoundingClientRect();

      this.width = width;
      canvas.width = width;
      canvas.height = this.height;
      this.ctx = canvas.getContext("2d");

      this.debounceRedraw();
    },

    debounceRedraw() {
      if (this.drawPending) return;

      this.drawPending = true;
      requestAnimationFrame(() => {
        const now = Date.now();
        if (now - this.lastDrawTime > this.minRedrawInterval) {
          this.draw();
          this.lastDrawTime = now;
        }
        this.drawPending = false;
      });
    },

    onClick(e) {
      const rect = this.$refs.canvas.getBoundingClientRect();
      const x = e.clientX - rect.left;
      const y = e.clientY - rect.top;

      const { startTime, endTime, width, height } = this;
      const totalSeconds = (endTime.getTime() - startTime.getTime()) / 1000;
      const pxPerMs = width / totalSeconds / 1000;

      for (let seg of this.visibleSegmentsCache) {
        const startX = (seg.beginTime - startTime.getTime()) * pxPerMs;
        const endX = (seg.endTime - startTime.getTime()) * pxPerMs;

        const highlightStartX = Math.max(0, startX);
        const highlightEndX = Math.min(width, endX);

        const highlightTop = height / 2 - 6;
        const highlightBottom = height / 2 + 6;

        if (
          x >= highlightStartX &&
          x <= highlightEndX &&
          y >= highlightTop &&
          y <= highlightBottom
        ) {
          const clickedTime = new Date(startTime.getTime() + x / pxPerMs);
          this.$emit("highlight-click", {
            time: clickedTime,
            segment: seg,
          });
          return;
        }
      }
    },

    draw() {
      const { ctx, width, height, startTime, endTime } = this;
      ctx.clearRect(0, 0, width, height);
      ctx.fillStyle = "#1a1a1a";
      ctx.fillRect(0, 0, width, height);

      const totalSeconds = (endTime - startTime) / 1000;
      const pxPerMs = width / totalSeconds / 1000;

      // 刻度线绘制逻辑不变...
      this.drawTicks(pxPerMs);

      // 可视区域筛选出需要绘制的 segments
      this.visibleSegmentsCache = this.highlightedSegments.filter(seg => {
        return seg.beginTime <= endTime && seg.endTime >= startTime;
      });

      // 绘制高亮段
      this.visibleSegmentsCache.forEach((seg) => {
        const startX = Math.max(
          0,
          (seg.beginTime - startTime.getTime()) * pxPerMs
        );
        const endX = Math.min(
          width,
          (seg.endTime - startTime.getTime()) * pxPerMs
        );

        if (endX > startX) {
          ctx.fillStyle = "#FFD700"; // 黄色
          ctx.fillRect(startX, height / 2 - 6, endX - startX, 20);
        }
      });

      // 绘制鼠标线
      if (this.mouseX !== null) {
        ctx.strokeStyle = "#fff";
        ctx.beginPath();
        ctx.moveTo(this.mouseX, 0);
        ctx.lineTo(this.mouseX, height);
        ctx.stroke();
      }
    },


    drawTicks(pxPerMs) {
      const { ctx, width, height, startTime } = this;
      const intervalMinutes = this.calculateInterval();

      let current = new Date(startTime);
      let lastX = null;

      while (current <= this.endTime) {
        const msFromStart = current - startTime;
        const x = msFromStart * pxPerMs;

        if (x >= 0 && x <= width) {
          const isMajorTick = current.getMinutes() % intervalMinutes === 0;

          if (isMajorTick) {
            ctx.beginPath();
            ctx.strokeStyle = "#fff";
            ctx.moveTo(x, 0); ctx.lineTo(x, 15);
            ctx.stroke();

            ctx.fillStyle = "#ccc";
            ctx.font = "12px Arial";
            ctx.textAlign = "center";
            ctx.fillText(moment(current).format(intervalMinutes >= 1440 ? "YYYY-MM-DD" : "HH:mm"), x, 30);
          }

          if (isMajorTick && lastX !== null && x - lastX > 20) {
            const mid1 = lastX + (x - lastX) / 3;
            const mid2 = lastX + (x - lastX) * 2 / 3;

            ctx.beginPath();
            ctx.strokeStyle = "#666";
            ctx.moveTo(mid1, 4);
            ctx.lineTo(mid1, 10);
            ctx.moveTo(mid2, 4);
            ctx.lineTo(mid2, 10);
            ctx.stroke();
          }

          if (isMajorTick) lastX = x;
        }

        current.setMinutes(current.getMinutes() + intervalMinutes);
      }
    },

    calculateInterval() {
      const { startTime, endTime } = this;
      const totalHours = (endTime - startTime) / (60 * 60 * 1000);

      if (totalHours <= 12) return 30;
      else if (totalHours <= 24) return 60;
      else if (totalHours <= 48) return 120;
      else if (totalHours <= 72) return 180;
      else if (totalHours <= 168) return 360;
      else return 1440;
    },

    onMouseMove(e) {
      const rect = this.$refs.canvas.getBoundingClientRect();
      const x = e.clientX - rect.left;
      const time = this.getXTime(x);

      this.tooltipText = moment(time).format("YYYY-MM-DD HH:mm:ss");
      this.tooltipPosition = { x: e.clientX, y: e.clientY };

      this.mouseX = x;
      this.showTooltip = true;

      this.debounceRedraw();
    },

    onMouseLeave() {
      this.mouseX = null;
      this.showTooltip = false;
      this.debounceRedraw();
    },

    getXTime(x) {
      const ratio = x / this.width;
      return this.startTime.getTime() + ratio * (this.endTime.getTime() - this.startTime.getTime());
    },

    redraw() {
      this.debounceRedraw();
    },
  },
  computed: {
    tooltipStyle() {
      return {
        position: 'absolute',
        left: `${this.tooltipPosition.x + 10}px`,
        top: `${this.tooltipPosition.y - 30}px`,
        background: 'rgba(0,0,0,0.8)',
        color: '#fff',
        padding: '4px 8px',
        borderRadius: '3px',
        fontSize: '12px',
        pointerEvents: 'none',
        zIndex: 1000,
        visibility: this.showTooltip ? 'visible' : 'hidden',
        opacity: this.showTooltip ? 1 : 0,
        transition: 'opacity 0.2s ease'
      };
    }
  }
};
</script>

<style scoped>
.timeLineContainer {
  position: relative;
  width: 100%;
  height: 100px;
}
</style>

上面是子组件 接收的参数只需要开始时间,结束时间,时间轴时间段。

下面是父组件引用,其中这个方法handleHighlightClick是点击子组件黄色部分区域获取到当前时间

<div style="width: 100%; height: 50px;">
  <PlaybackTimeline
    :startTime="new Date(startTime)"
    :endTime="new Date(endTime)"
    :highlightedSegments="videoUrls[selectedIndex || 0].segments"
     @highlight-click="handleHighlightClick"
    />
</div>

注意:videoUrls[selectedIndex || 0].segments 是数组对象集合

           [{startTime:'',endTime:''},{startTime:'',endTime:''}]

           是这样的结构,所以传入多少个就是多少段黄色区间

<think>我们正在寻找一个Vue监控视频播放组件。监控视频通常需要支持RTSP、RTMP等流媒体协议,以及回放功能。 常见的解决方案包括使用视频播放器库(如video.js、hls.js)并结合相应的流处理技术(如将RTSP转成HLS流在浏览器中播放)。 在Vue中,我们可以封装这些播放器库为组件。 推荐几个可用于监控视频播放的Vue组件或库: 1. **vue-video-player**: 这是一个基于video.js的Vue组件,支持多种视频格式和流媒体协议。但它本身不支持RTSP,需要后端将RTSP转成HLS或FLV等浏览器支持的格式。 - 安装: `npm install vue-video-player` - 示例: ```vue <template> <video-player :options="playerOptions"/> </template> <script> import { videoPlayer } from 'vue-video-player' export default { components: { videoPlayer }, data() { return { playerOptions: { autoplay: true, sources: [{ type: 'application/x-mpegURL', src: 'http://your-stream-url.m3u8' // HLS流 }] } } } } </script> ``` 2. **xgplayer**: 由字节跳动开源的HTML5视频播放器,功能强大,支持多种流媒体协议,并且有Vue封装版本。同样需要将RTSP转成HLS或FLV。 - 安装: `npm install xgplayer xgplayer-vue` - 示例: ```vue <template> <xg-player :config="config"></xg-player> </template> <script> import "xgplayer/dist/index.min.css"; import Player from "xgplayer-vue"; export default { components: { Player }, data() { return { config: { url: 'http://your-stream-url.m3u8', // 播放地址 isLive: true // 直播场景 } } } } </script> ``` 3. **flv.js + vue**: 如果需要直接播放FLV流(由RTSP转换而来),可以使用flv.js,并在Vue中封装。 - 安装: `npm install flv.js` - 示例: ```vue <template> <video ref="videoElement" controls autoplay muted width="100%"></video> </template> <script> import flvjs from 'flv.js'; export default { mounted() { if (flvjs.isSupported()) { const videoElement = this.$refs.videoElement; const flvPlayer = flvjs.createPlayer({ type: 'flv', url: 'http://your-stream-url.flv' }); flvPlayer.attachMediaElement(videoElement); flvPlayer.load(); flvPlayer.play(); } } } </script> ``` 4. **对于RTSP的直接播放**:由于浏览器不支持直接播放RTSP流,通常需要转换。可以使用一些服务将RTSP转成WebRTC、HLS或FLV。例如,使用`ffmpeg`将RTSP转成HLS,然后通过HLS在浏览器中播放。 5. **集成监控厂商的SDK**:如果使用的是海康、大华等厂商的监控设备,他们通常提供Web SDK,我们可以将其封装为Vue组件。但这种方式需要引入厂商的JS文件,并按照他们的文档进行开发。 总结:在Vue中实现监控视频播放,通常需要: - 后端或中间服务将监控摄像头的RTSP流转换成HLS或FLV等浏览器支持的格式。 - 使用现有的Vue视频播放组件(如vue-video-player、xgplayer-vue)或使用原生JS库(如flv.js、hls.js)在Vue中封装成组件。 注意:由于浏览器的安全策略,可能需要处理跨域问题。 相关问题: 1. 如何将RTSP流转换成HLS或FLV格式? 2. 在Vue中如何封装一个自定义的视频播放器组件? 3. 如何解决视频流播放中的跨域问题? 4. 海康威视摄像头在Vue中如何集成? 5. 如何实现监控视频的回放功能?</think>### Vue监控视频播放组件解决方案 在Vue中实现监控视频播放,通常需要支持RTSP、RTMP等流媒体协议以及回放功能。以下是几种推荐方案: #### 1. **vue-video-player + videojs-contrib-hls** 最常用的组合方案,基于成熟的Video.js生态: ```bash npm install vue-video-player video.js videojs-contrib-hls ``` ```vue <template> <video-player ref="player" :options="playerOptions" @ready="onPlayerReady" /> </template> <script> import 'video.js/dist/video-js.css' import { videoPlayer } from 'vue-video-player' import 'videojs-contrib-hls' export default { components: { videoPlayer }, data() { return { playerOptions: { autoplay: true, techOrder: ['html5'], sources: [{ type: 'application/x-mpegURL', src: 'https://your-surveillance-feed.m3u8' // HLS流地址 }] } } }, methods: { onPlayerReady(player) { console.log('播放器已就绪', player) } } } </script> ``` **特点**: - 支持HLS流播放(需将RTSP转HLS) - 提供播放控制/全屏/画中画功能 - 兼容桌面和移动端[^1] #### 2. **flv.js + Vue封装组件** 适用于FLV格式监控流: ```vue <template> <div> <video ref="videoElement" controls muted autoplay width="100%"></video> </div> </template> <script> import flvjs from 'flv.js' export default { mounted() { if (flvjs.isSupported()) { const videoElement = this.$refs.videoElement const flvPlayer = flvjs.createPlayer({ type: 'flv', url: 'rtmp://your-surveillance-server/live/stream' }) flvPlayer.attachMediaElement(videoElement) flvPlayer.load() } } } </script> ``` #### 3. **专业监控SDK集成** 对于海康/大华等专业设备: ```vue <template> <div id="hk-player-container"></div> </template> <script> export default { mounted() { const player = new HKPlayer({ container: 'hk-player-container', videoUrl: 'rtsp://admin:password@192.168.1.100:554/stream' }) player.play() } } </script> ``` #### 流协议转换方案 | 协议 | 浏览器支持 | 转换工具 | 延迟 | |---------|------------|-------------------|-------| | RTSP | ❌ | FFmpeg/Nginx | 2-5s | | RTMP | ❌ | SRS服务器 | 1-3s | | HLS | ✅ | FFmpeg | 5-10s | | WebRTC | ✅ | Janus Gateway | <1s | **部署建议**: 1. 摄像头 → RTSP流 2. 服务器用FFmpeg转码:`ffmpeg -i rtsp://feed -c copy -f hls out.m3u8` 3. Vue前端通过HLS或WebRTC播放 #### 4. **云服务方案(免开发)** - 七牛云直播 SDK:提供完整的监控播放组件 - 阿里云视频点播:支持监控录像回放 - 腾讯云智能视图:即插即用组件--- ### 相关问题 1. **如何将RTSP监控流转换为浏览器可播放的格式?** [查看RTSP转HLS/WebRTC方案](#) 2. **监控视频延迟过高有哪些优化方法?** [WebRTC低延迟方案解析](#) 3. **如何在Vue中实现多路监控画面同时播放?** [多画面监控组件实现](#) 4. **专业安防设备(海康/大华)如何集成到Vue项目?** [厂商SDK对接指南](#) 5. **监控视频回放功能的技术实现方案?** [基于时间轴回放控制](#) [^1]: 视频播放器兼容性参考:Video.js官方文档
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值