前端处理直播流flv和m3u8格式并且支持标注录屏和字幕展示

    <div class="w-[100%] relative" :style="{'height':props.menu?'70%':'100%'}">
      <!-- controls -->
      <video :id="'myVideo'+props.index" class="video-js vjs-default-skin vjs-big-play-centered" autoplay muted :controls="props.menu"
         width="100%" >
        <source type="application/x-mpegURL" :src="videoSrc" />
      </video>
      <!-- 录制的时间显示 -->
      <div v-if="isShow" class="text-[red] absolute top-[10px] left-[50%] translate-x-[-50%] flex items-center">
        <div
          class="rounded-[50%] w-[16px] h-[16px] p-[4px] leading-[16px] mr-[10px] mt-[-4px] border-[1px] text-[red] cursor-pointer">
          <div class="w-[6px] h-[6px] rounded-[1px] bg-[red]"></div>
        </div>
        {{ recordTime }}
      </div>
      <div v-if="props.menu" class="h-[30px]">
        <!-- 截屏 -->
        <el-icon class="absolute bottom-[-24px] right-[13px] text-[#333] cursor-pointer" @click="getCurPic"
          style="margin: 0.1em 0.1em 0 0" :size="17">
          <IconifyIconOffline :icon="Camera" />
        </el-icon>
        <!-- 录屏 -->
        <div @click="recordIng"
          class="rounded-[50%] w-[16px] h-[16px] p-[4px] leading-[16px] border-[1px] absolute bottom-[-23px] right-[60px] text-[#333] cursor-pointer">
          <div class="w-[6px] h-[6px] rounded-[50%] bg-[#333]"></div>
        </div>
        <!-- 字幕 -->
        <div class="absolute bottom-[-26px] right-[100px] flex items-center">
          <div class="text-[#333] text-[14px] mr-[5px]">字幕:</div>
          <el-switch size="small" v-model="subtitleSwitch" />
        </div>
      </div>
    </div>
	//vue3
	import { onMounted, onBeforeUnmount, ref, nextTick, reactive } from "vue";
  	import { useShow } from "hook";
	//解构需要的数据
	 const {
    recordIng,
    getVideo,
    playervideo,
    getCurPic,
    cropperModel,
    subtitleSwitch,
    canvasWidth,
    canvasHeight,
    recordTime,
    isShow,
    submit,
    posArray,
    videoSrc
  } = useShow(props.index, props.menu);
	//是否显示菜单和遍历的index
	const props = defineProps < {
    menu: {
      type: Boolean;
      default: true;
    };
    index: {
      type: Number;
      default: 9;
    };
  } > ();
  // 销毁video
  onBeforeUnmount(() => {
    // flv销毁 
    // playervideo?.value?.destroy()
    // video销毁
    playervideo?.value?.dispose();
  });
  //组件钩子初始化
  onMounted(() => {
    getVideo();
  });


//以下为hook文件代码
import { ref, reactive, nextTick } from "vue";
import flvjs from "flv.js";
import videojs from "video.js";
export function useShow(index, menu) {
  // 画布的弹窗开关
  const cropperModel = ref(false);
  // 画布的宽度
  const canvasWidth = ref();
  // 画布的高度
  const canvasHeight = ref();
  // 播放器实例
  const playervideo = ref();
  const tempPos = ref([]);
  // 标注截取的数据位置
  const posArray = reactive([]);
  // 字幕开关
  const subtitleSwitch = ref(true);
  // 直播流地址
  const videoSrc = ref("http://192.168.3.160:28323/demo/stream-1/hls.m3u8");
  //开始录制
  const isShow = ref(false);
  // 用于存储计时器 ID
  const timer = ref();
  // 录制时间
  const recordTime = ref("00:00:00");
  const hour = ref(0);
  const minutes = ref(0);
  const seconds = ref(0);
  // 提交保存
  const submit = () => {
    console.log(...posArray);
  };
  // 屏幕录制相关--------------------------------------------------------

  // 数据标注相关--------------------------------------------------------
  const history = [];
  function Point(x, y, type) {
    this.x = x;
    this.y = y;
    this.type = type; // 左击 1  右击 3
  }
  const windowToCanvas = (e, mycanvas) => {
    // 返回元素的大小以及位置
    const rect = mycanvas.getBoundingClientRect();
    // rect 的宽度会加上 canvas 的 border 会影响精度
    return new Point(
      e.clientX - rect.left * (mycanvas.width / rect.width),
      e.clientY - rect.top * (mycanvas.height / rect.height),
      e.which
    );
  };
  const showLastHistory = (ctx, history) => {
    ctx.putImageData(history[history.length - 1]["data"], 0, 0);
  };
  const addHistoy = (history, ctx, mycanvas) => {
    history.push({
      data: ctx.getImageData(0, 0, mycanvas.width, mycanvas.height)
    });
  };
  const drawerRect = (ctx, left, top, w, h) => {
    ctx.strokeStyle = "#00B42A"; // 画笔颜色
    ctx.lineWidth = "2"; // 画笔粗细
    ctx.save();
    ctx.beginPath();
    ctx.rect(left, top, w, h);
    ctx.stroke();
    ctx.restore();
    return {
      data: [left, top, w, h]
    };
  };
  const startTimer = () => {
    seconds.value += 1;
    if (seconds.value >= 60) {
      seconds.value = 0;
      minutes.value = minutes.value + 1;
    }
    if (minutes.value >= 60) {
      minutes.value = 0;
      hour.value = hour.value + 1;
    }
    recordTime.value =
      (hour.value < 10 ? "0" + hour.value : hour.value) +
      ":" +
      (minutes.value < 10 ? "0" + minutes.value : minutes.value) +
      ":" +
      (seconds.value < 10 ? "0" + seconds.value : seconds.value);
  };
  // 点击录屏按钮
  const recordIng = () => {
    isShow.value = !isShow.value;
    recordTime.value = "00:00:00";
    hour.value = 0;
    minutes.value = 0;
    seconds.value = 0;
    clearInterval(timer.value);
    timer.value = setInterval(startTimer, 1000);
  };
  const canvasSign = (mycanvas, ctx) => {
    mycanvas.onclick = null;
    mycanvas.onmousedown = function (e) {
      tempPos.value = [];
      e.preventDefault();
      const mousedown = windowToCanvas(e, mycanvas);
      mycanvas.onmousemove = function (e) {
        e.preventDefault();
        showLastHistory(ctx, history); // 每次绘制先清除上一次
        const point = windowToCanvas(e, mycanvas);
        const w = Math.abs(point.x - mousedown.x);
        const h = Math.abs(point.y - mousedown.y);
        const left = point.x > mousedown.x ? mousedown.x : point.x;
        const top = point.y > mousedown.y ? mousedown.y : point.y;
        const pos = drawerRect(ctx, left, top, w, h);
        tempPos.value.push(pos);
      };
      mycanvas.onmouseup = function (e) {
        e.preventDefault();
        addHistoy(history, ctx, mycanvas); // 保存上一次数据
        mycanvas.onmousemove = null;
        posArray.push(tempPos.value[tempPos.value.length - 1]);
        console.log(posArray, "这是截取的图片坐标");
      };
    };
    addHistoy(history, ctx, mycanvas); // 添加一张默认的数据
  };

  // 视频流相关--------------------------------------------------------
  const photograph = () => {
    // 把当前视频帧内容渲染到canvas上
    const img = document.getElementById("myVideo9"); //获取标签
    const width = img.clientWidth; //获取屏幕尺寸
    const height = img.clientHeight;
    canvasWidth.value = width;
    canvasHeight.value = height;
    //getContext方法返回一个用于在画布上绘图的环境。
    const canvas = document.getElementById("myCanvas");
    const ctx = canvas.getContext("2d");
    nextTick(() => {
    	//flv直接为img  m3u8为img.childNodes[0]
      ctx.drawImage(img.childNodes[0], 0, 0, width, height); //在画布上定位图像
      canvasSign(canvas, ctx);
      //以上代码可能运行报错截取帧跨域问题,则需要通过后端返回截取的帧,img.src为后端截取的帧进行更改
      const img = new Image();
      img.crossOrigin = "";
      img.onload = function () {
        ctx.drawImage(img, 0, 0, width, height); //在画布上定位图像
        canvasSign(canvas, ctx);
      };
      img.src = "https://avatars3.githubusercontent.com/u/496048?s=120&v=4";
    });
  };
  // 截屏
  const getCurPic = () => {
    // 裁剪图片,打开弹窗
    cropperModel.value = true;
    //需要使用nextTick 不然DOM渲染过快导致初次无法进行渲染-或者使用SetTomeOut(()=>{},200)
    nextTick(() => {
      // 打开截屏弹出
      photograph();
    });
  };
  const getVideo = () => {
  	 //以下实现为flv格式使用了flv.js 
    // if (flvjs.isSupported()) {
    //   const url = "http://192.168.3.160:28323/demo/stream-1.live.flv";
    //   //检查当前浏览器是否支持 flv.js
    //index因为有多个信号源所以使用了遍历组件的方式拼起来的id
    //   const videoElement = document.getElementById("myVideo" + index);
    //   //创建一个 flv.js 播放器实例
    //   playervideo.value = flvjs.createPlayer(
    //     {
    //       type: "flv", //指定视频类型
    //       isLive: true, //标记视频流是否为直播流
    //       hasAudio: menu, //标记视频流是否包含音频轨道
    //       url //视频流的 URL 地址
    //     },
    //     {
    //       autoCleanupSourceBuffer: false, //自动清除缓存
    //       enableWorker: false, //不启用分离线程
    //       enableStashBuffer: false //关闭io缓冲区
    //     }
    //   );
    //   //使用 `attachMediaElement()` 方法将视频元素与 flv.js 播放器关联起来
    //   playervideo.value.attachMediaElement(videoElement);
    //   //加载视频流
    //   playervideo.value.load();
    //   //开始播放视频
    //   playervideo.value.play();
    //   console.log("支持flv", playervideo, playervideo.value.attachMediaElement);
    // }
    //以下实现为m3u8格式使用的videojs
      //index因为有多个信号源所以使用了遍历组件的方式拼起来的id
    playervideo.value = videojs("myVideo" + index, {
      controls: menu, // 是否显示控制条
      bigPlayButton: menu, //在视频上显示大播放按钮
      textTrackDisplay: menu,
      posterImage: menu, // 是否显示封面
      errorDisplay: menu,
      controlBar: {
        playToggle: false, //播放按钮
        progressControl: false, //进度条
        volumePanel: menu, //音量
        pictureInPictureToggle: false, //画中画
        remainingTimeDisplay: false, //时长
        fullscreenToggle: menu //全屏按钮
      },
      userActions: {
        click: false
      },
      poster:
        "https://img2.baidu.com/it/u=289789983,1298381275&fm=253&fmt=auto&app=120&f=JPEG?w=863&h=500"
    });
  };
  return {
    posArray,
    submit,
    isShow,
    recordTime,
    getVideo,
    playervideo,
    getCurPic,
    cropperModel,
    recordIng,
    subtitleSwitch,
    videoSrc,
    canvasWidth,
    canvasHeight
  };
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值