记一次业务监控流flv播放的封装

发布时间 2023-11-27 20:36:43作者: 邪妖怪
[2023年11月27日20:31:09]

记一次业务监控流flv播放的封装

vue3封装flvjs,用于监控流。包括:判断可播放、推流、停流、播放、销毁功能。

/** useRender.ts */
import flvjs from "flv.js";
import type FlvJs from "flv.js";

const host = import.meta.env.VITE_APP_BASE_API;
let player: Flvjs.Player | undefined;
let timer: number | undefined;

/**
 * 判断是否可用flv
 * @returns 
 */
function canUseFlv(): boolean {
  return flvjs.isSupported();
}

/**
 * 初始化
 * @param camereInfo 
 * @param videoElement 
 */
function _init(camereInfo: any, videoElement: HTMLVideoElement) {
  play(camereInfo, videoElement);
}

/**
 * 销毁
 * @param flvPlayer 
 * @returns 
 */
function destroyPlayer(flvPlayer: FlvJs.Player | undefined) {
  if (!flvPlayer) return;
  flvPlayer?.pause();
  flvPlayer?.unload();
  flvPlayer?.detachMediaElement();
  flvPlayer?.destroy();
  flvPlayer = undefined;
}

/**
 * 补帧追帧
 * @param flvPlayer
 * @param videoElement
 */
function increaseFrame(
  flvPlayer: FlvJs.Player,
  videoElement: HTMLVideoElement
) {
  let end = flvPlayer.buffered.end(0);
  let delta = end - flvPlayer.currentTime;
  if (delta > 10 || delta < 0) {
    flvPlayer.currentTime = flvPlayer.buffered.end(0) - 1;
  } else if (delta > 1) {
    videoElement.playbackRate = 1.1;
  } else {
    videoElement.playbackRate = 1;
  }
}

/**
 * 播放
 * @param cameraInfo 
 * @param videoElement 
 */
function play(cameraInfo: any, videoElement: HTMLVideoElement) {
  let flvPlayer = flvjs.createPlayer(
    {
      type: "flv",
      isLive: true,
      hasAudio: false,
      url: cameraInfo.streamAddress,
    },
    {
      enableStashBuffer: false,
      stashInitialSize: 128,
    }
  );
  flvPlayer.mediaElement = videoElement
  flvPlayer.attachMediaElement(videoElement);
  flvPlayer.load();
  player = flvPlayer;

  flvPlayer.on(flvjs.Events.METADATA_ARRIVED, function () {
      console.log("获取视频流...");
      /** 监听进度补追帧 */
      videoElement.addEventListener("progress", function () {
        try {
          increaseFrame(flvPlayer, videoElement);
        } catch {};
      });
      /** 监听重回浏览器画面触发补追帧 */
      videoElement.addEventListener("visibilitychange", function () {
        try {
          increaseFrame(flvPlayer, videoElement);
        } catch {};
      });
      flvPlayer.play();
  });

  /** 断流重连 */
  flvPlayer.on(
    flvjs.Events.ERROR,
    function (errorType: FlvJs.ErrorTypes, errorDetails: FlvJs.ErrorDetails) {
      if(timer) return;
      timer = window.setTimeout(() => {
        console.log("播放时发生了一个错误");
        videoElement.removeEventListener("progress", function () {
          increaseFrame(flvPlayer, videoElement);
        });
        videoElement.removeEventListener("visibilitychange", function () {
          increaseFrame(flvPlayer, videoElement);
        });
        if(player) player = undefined;
        destroyPlayer(flvPlayer);
        _init(cameraInfo, videoElement);
        clearTimeout(timer);
        timer = undefined;
      }, 3000);
    }
  );
}

function killPlayer() {
  if (!player) return;
  player.mediaElement.removeEventListener("progress", function () {
    increaseFrame(player, player.mediaElement);
  })
  player.mediaElement.removeEventListener("visibilitychange", function () {
    increaseFrame(player, player.mediaElement);
  })
  player?.pause();
  player?.unload();
  player?.detachMediaElement();
  player?.destroy();
  player = undefined;
  clearTimeout(timer);
  timer = undefined;
}

/**
 * 开始推流
 * @param cameraInfo
 */
async function startPush(cameraInfo: any) {
  await fetch(host + "/api/Device/Camera/PushCamera", {
    method: "POST",
    headers: new Headers({
      "Content-Type": "application/json",
      "Authorization": userStore.token
    } as HeadersInit),
    body: JSON.stringify([
      {
        bizType: "HKMQTT",
        id: cameraInfo.id,
        cameraName: cameraInfo.cameraName,
        rtspPath: cameraInfo.rtspPath,
        rtmpPath: cameraInfo.rtmpPath,
        streamAddress: cameraInfo.streamAddress,
        no: cameraInfo.no
      }
    ]),
  })
}

/**
 * 停止推流
 * @param cameraInfo
 */
async function stopPush(cameraInfo: any) {
  await fetch(host + "/api/Device/Camera/StopCamera", {
    method: "POST",
    headers: new Headers({
      "Content-Type": "application/json",
      "Authorization": userStore.token
    } as HeadersInit),
    body: JSON.stringify([
      {
        bizType: "HKMQTT",
        id: cameraInfo.id,
        cameraName: cameraInfo.cameraName,
        rtspPath: cameraInfo.rtspPath,
        rtmpPath: cameraInfo.rtmpPath,
        streamAddress: cameraInfo.streamAddress,
        no: cameraInfo.no
      }
    ]),
  });
}

export default function useRender() {
  return {
    startPush,
    stopPush,
    play,
    killPlayer,
    canUseFlv
  };
}