使用flv.js和Jessibuca方式播放流媒体 rtmp/rtsp

目录

flvjs与FLV有什么区别和联系?

vue中使用flvjs

封装一个flv函数

H265格式请使用jessibuca

flvjs与FLV有什么区别和联系?

flv.js
是 HTML5 Flash 视频(FLV)播放器,纯原生 JavaScript 开发,没有用到 Flash。由 bilibili 网站开源(Github)。

概览:

一个实现了在 HTML5 视频中播放 FLV 格式视频的 JavaScript 库。它的工作原理是将 FLV 文件流转码复用成 ISO BMFF(MP4 碎片)片段,然后通过 Media Source Extensions 将 MP4 片段喂进浏览器。

flv.js 是使用 ECMAScript 6 编写的,然后通过 Babel Compiler 编译成 ECMAScript 5,使用 Browserify 打包。

功能:

FLV 容器,具有 H.264 + AAC 编解码器播放功能
多部分分段视频播放
HTTP FLV 低延迟实时流播放
FLV 通过 WebSocket 实时流播放
兼容 Chrome, FireFox, Safari 10, IE11 和 Edge
十分低开销,并且通过你的浏览器进行硬件加速
FLV
HTTP FLV则是将RTMP封装在HTTP协议之上的,可以更好的穿透防火墙等。rtmp和http-flv的视频格式都是flv格式的,只是传输协议而不同。rtmp是tcp的传输协议,而http-flv是http长链接的传输协议。

总结
flvjs是一个H5播放器。FLV是一种协议。flvjs可以用于播放FLV格式的视频。

几种视频流比较。

协议http-flv  rtmp hls
传输方式 http流 tcp流 http流
视频封装格式 flv flv    Ts文件
延迟   低   低
数据分段连续流连续流 切片文件
h5播放 flv.js video.js hls.js


vue中使用flvjs


1.使用npm安装flv.js

npm install --save flv.js

2.新建FlvLive.vue文件,在文件中引入

import flvjs from 'flv.js'

<script>
            if (flvjs.isSupported()) { // 判断当前浏览器是否支持flv。
                var videoElement = document.getElementById('videoElement'); 
                var flvPlayer = flvjs.createPlayer({
                    type: 'flv',
                    // isLive: true,
                    // hasAudio: false,
                    url:'http://127.0.0.1:8000/live/abc.flv'
                });
                flvPlayer.attachMediaElement(videoElement); // 挂载video标签。
                flvPlayer.load(); 
                flvPlayer.play(); // 播放
            }
        </script>

封装一个flv函数

function playVideo(demo, url) {
  demo = document.getElementById(demo);
  if (demo) {
    demo.pause()
    demo.unload()
    demo.detachMediaElement()
    demo.destroy()
    demo = null
  }
  if (flvjs.isSupported()) {
    var flvPlayer = flvjs.createPlayer({
      type: 'flv',
      hasAudio: false,
      url: url
    });
    flvPlayer.attachMediaElement(demo);
    flvPlayer.load(); //加载
  }
  demo.play();


//断开流链接,若不断开会一直占用带宽
function destoryVideo() {
        this.flvPlayer.pause();
        this.flvPlayer.unload();
        this.flvPlayer.detachMediaElement();
        this.flvPlayer.destroy();
        this.flvPlayer = null;
      },

H265格式请使用Jessibuca

支持h265和h264格式

GitHub - langhuihui/jessibuca: Jessibuca是一款开源的纯H5直播流播放器

前台框架: GitHub - 648540858/wvp-GB28181-pro: WEB VIDEO PLATFORM是一个基于GB28181-2016标准实现的网络视频平台,支持NAT穿透,支持海康、大华、宇视等品牌的IPC、NVR、DVR接入。支持国标级联,支持rtsp/rtmp等视频流转发到国标平台,支持rtsp/rtmp等推流转发到国标平台。

配置项:

let isShow = true
  window.flvPlayer = new Jessibuca({
    container: demo,
    autoWasm: true,
    background: "",
    controlAutoHide: false,
    debug: false,
    decoder: "static/js/jessibuca/decoder.js",
    forceNoOffscreen: true,
    hasAudio: false,
    hasVideo: true,
    heartTimeout: 5,
    heartTimeoutReplay: true,
    heartTimeoutReplayTimes: 3,
    hiddenAutoPause: false,
    hotKey: false,
    isFlv: false,
    isFullResize: false,
    isNotMute: false,
    isResize: false,
    keepScreenOn: false,
    loadingText: "请稍等, 视频加载中......",
    loadingTimeout: 10,
    loadingTimeoutReplay: true,
    loadingTimeoutReplayTimes: 3,
    openWebglAlignment: false,
    operateBtns: {
      fullscreen: isShow,
      screenshot: isShow,
      play: isShow,
      audio: isShow,
      record: false
    },
    recordType: "webm",
    rotate: 0,
    showBandwidth: false,
    supportDblclickFullscreen: false,
    timeout: 10,
    useMSE: true, //pc端true 渲染为canvas标签,false为video标签;移动端与之相反
    useOffscreen: false,
    useWCS: true,
    useWebFullScreen: false,
    videoBuffer: 0,
    wasmDecodeAudioSyncVideo: true,
    wasmDecodeErrorReplay: true,
    wcsUseVideoRender: true
  },);

Android端webView灰色按钮(默认的播放按钮)问题

android端自动起播在首帧出来之前会有一个灰色的播放按钮闪现,不同的手机或者android版本会略有不同,这个是webview中video内置的poster导致,前端无法隐藏

方案:

1.先设置useMSE:false保证是video标签渲染

1.设置Video的poster属性为一个透明的图片 推荐使用poster="https://via.placeholder.com/1x1"

document.querySelector('video').poster="https://via.placeholder.com/1x1"

// 透明 base64
<video poster="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" />
// or 
<video poster="https://via.placeholder.com/1x1" />
// or
<video poster="noposter" />

poster="" 直接给空字符串会被忽略,所以需要设置一个透明的图片或者noposter

封装多画面组件

效果图:

cameraPlays.vue

<!-- 多画面播放视频 -->
<template>
  <div style="height: 100%; width: 100%" class="video_box">
    <transition name="modal" tag="div">
      <div style="height: 100%; width: 100%" v-if="visible">
        <div class="" style="height: 100%; width: 100%">
          <div
            class="user_skills comm-item"
            @mouseenter="onHover"
            @mouseleave="onLeave"
            style="
              width: 100%;
              padding-top: 0px;
              height: 103%;
              position: relative;
            "
          >
            <div
              style="width: 100%; height: 96%; z-index: 9"
              class="cameraPlay avatar video-avatar"
            ></div>
            <slot></slot>
          </div>
        </div>
      </div>
    </transition>
    <!-- 全屏视频监控 -->
    <transition name="modal" tag="div">
      <div id="center" style="z-index: 99999" v-if="hasShowVideo">
        <div class="video_dialog fullBox" :style="{ left: left, top: top }">
          <div id="dialog_title" style="z-index: 99">
            <div
              class="dialog_name"
              style="
                margin-top: -3px;
                line-height: 98px;
                text-align: center;
                font-size: 46px;
              "
            >
              视频监控
            </div>
            <div class="close" @click="closeFullVideo">X</div>
          </div>
          <div
            class="user_skills comm-item"
            style="width: 100%; padding-top: 0px; height: 103%"
          >
            <div
              style="width: 100%; height: 96%"
              id=""
              class="cameraPlayFull avatar video-avatar"
            ></div>
            <div class="video-control cameraPop">
              <div
                class="top"
                @mousedown="cameraControlDiversion('up')"
                @mouseup="cameraControlDiversion('up', 0)"
              ></div>
              <div
                class="right"
                @mousedown="cameraControlDiversion('right')"
                @mouseup="cameraControlDiversion('right', 0)"
              ></div>
              <div class="content"></div>
              <div
                class="bottom"
                @mousedown="cameraControlDiversion('down')"
                @mouseup="cameraControlDiversion('down', 0)"
              ></div>
              <div
                class="left"
                @mousedown="cameraControlDiversion('left')"
                @mouseup="cameraControlDiversion('left', 0)"
              ></div>
            </div>
          </div>
        </div>
      </div>
    </transition>
  </div>
</template>

<script type="text/ecmascript-6">
import qxUtils from "@baseJs/utils";
import url from "@baseJs/interface";
import qxParams from "@baseJs/params";
import axios from "axios";
let reg = /^ws(s)?:\/\/(.*?)\//,
zxCamera = null;
function initData() {
  cameraId = qxParams.cameraObj.id;
  deviceId = qxParams.cameraObj.deviceId;
  channelNo = qxParams.cameraObj.channelNo;
  liveUrl = qxParams.cameraObj.liveUrl;
  sourceType = qxParams.cameraObj.sourceType;
}
function create(demo) {
  let isShow = true
  let flvPlayer = "flvPlayer_" + demo;
  window[flvPlayer] = new Jessibuca({
    container: demo,
    autoWasm: true,
    background: "",
    controlAutoHide: false,
    debug: false,
    decoder: "static/js/jessibuca/decoder.js",
    forceNoOffscreen: true,
    hasAudio: false,
    hasVideo: true,
    heartTimeout: 5,
    heartTimeoutReplay: true,
    heartTimeoutReplayTimes: 3,
    hiddenAutoPause: false,
    hotKey: false,
    isFlv: false,
    isFullResize: false,
    isNotMute: false,
    isResize: false,
    keepScreenOn: false,
    loadingText: "请稍等, 视频加载中......",
    loadingTimeout: 10,
    loadingTimeoutReplay: true,
    loadingTimeoutReplayTimes: 3,
    openWebglAlignment: false,
    operateBtns: {
      fullscreen: isShow,
      screenshot: isShow,
      play: isShow,
      audio: isShow,
      record: false,
    },
    recordType: "webm",
    rotate: 0,
    showBandwidth: false,
    supportDblclickFullscreen: true,
    timeout: 10,
    useMSE: true,
    useOffscreen: false,
    useWCS: true,
    useWebFullScreen: false,
    videoBuffer: 0,
    wasmDecodeAudioSyncVideo: true,
    wasmDecodeErrorReplay: true,
    wcsUseVideoRender: true,
  });

  [flvPlayer].onLog = (msg) => console.error(msg);
  [flvPlayer].onRecord = (status) => console.log("onRecord", status);
  [flvPlayer].onPause = () => console.log("onPause");
  [flvPlayer].onPlay = () => console.log("onPlay");
  [flvPlayer].onFullscreen = (msg) => console.log("onFullscreen", msg);
  [flvPlayer].onMute = (msg) => console.log("onMute", msg);
}
class ZxControlDiversion {
  constructor(...args) {
    let { id: cameraId, deviceId, channelNo, liveUrl, sourceType } = args[0]
    this.deviceId = deviceId
    this.channelNo = channelNo
    this.liveUrl = liveUrl
    this.sourceType = sourceType
    this.cameraId = cameraId
  }
  loginSPPT() {
    return new Promise((resolve, reject) => {
      axios
        .get(url.ZX_USER_LOGIN, {
          params: {
            password: "52cad73a70f28ce0bc5858be2283e415",
            username: "zxsl",
            // username: "admin"
          },
        })
        .then((res) => {
          if (res.data.code === 0) {
            resolve(res);
          }
        });
    });
  }
  playStart() {
    return new Promise((resolve, reject) => {

      if (qxParams.cameraObj.accessToken) {
        // 设置请求头信息
        const headers = {
          "Content-Type": "application/json",
          "Access-Token": qxParams.cameraObj.accessToken,
        };

        // 创建 axios 实例并设置默认的请求头
        const instance = axios.create({
          baseURL: `${url.ZX_PLAT_START}/${this.deviceId}/${this.channelNo}`,
          timeout: 5000,
          headers: headers,
        });

        instance
          .get("")
          .then((re) => {
            if (re.data.code == 0) {
              this.liveUrl = re.data.data.ws_flv;
              // console.log(qxParams.cameraObj)
              resolve(re);
            }
            reject(re.data.msg);
          })
          .catch((err) => {
            reject(err.msg);
          });
      } else {
        this.loginSPPT().then((res) => {
          qxParams.cameraObj.accessToken = res.data.data.accessToken;
          // 设置请求头信息
          const headers = {
            "Content-Type": "application/json",
            "Access-Token": qxParams.cameraObj.accessToken,
          };

          // 创建 axios 实例并设置默认的请求头
          const instance = axios.create({
            baseURL: `${url.ZX_PLAT_START}/${this.deviceId}/${this.channelNo}`,
            timeout: 5000,
            headers: headers,
          });

          instance
            .get("")
            .then((re) => {
              if (re.data.code == 0) {
                this.liveUrl = re.data.data.ws_flv;
                console.log(`this.deviceId ==>${this.deviceId}/${this.channelNo}`, this.liveUrl);
                resolve(re);
              }
              reject(re.data.msg);
            })
            .catch((err) => {
              reject(err.msg);
            });
        });
      }
    });
  }
  playStop() {
    // 设置请求头信息
    const headers = {
      "Content-Type": "application/json",
      "Access-Token": qxParams.cameraObj.accessToken,
    };

    // 创建 axios 实例并设置默认的请求头
    const instance = axios.create({
      baseURL: `${url.ZX_PLAT_STOP}/${this.deviceId}/${this.channelNo}`,
      timeout: 1500,
      headers: headers,
    });
    instance.get("").then((r) => { });
  }
  // command八项控制指令,允许值: left, right, up, down, upleft, upright, downleft, downright, zoomin, zoomout, stop
  static zxControl(type) {
    let data = {
      command: type,
    };

    axios.post(
      `${url.ZX_CONTROL}/${this.deviceId}/${this.channelNo}`,
      toFormData(Object.assign(data, controlData)),
      {
        headers: {
          "Content-Type": "application/x-www-form-urlencoded",
          "Access-Token": qxParams.cameraObj.accessToken,
        },
      }
    );
  }
  // 预置点控制
  static zxPresetControl(type, index) {
    let code;
    let data = {};
    switch (type) {
      case "rename": //设置
        code = 129;
        data = {
          cmdCode: code,
          parameter1: 0,
          parameter2: index,
          combindCode2: 0,
        };
        break;
      case "move":
        code = 130;
        data = {
          cmdCode: code,
          parameter1: 0,
          parameter2: index,
          combindCode2: 0,
        };
        break;
      case "del":
        params.$this.$message({
          message: "暂不支持删除",
          offset: 100,
        });
        return;
        code = 131;
        break;
      default:
        break;
    }
    // let data = {
    //   command: code,
    //   parameter2: index,
    // }
    // let data1 = Object.assign(data, presetControlData)
    axios.post(
      `${url.ZX_FRONT_END_COMMAND}/${deviceId}/${channelNo}`,
      toFormData(data),
      {
        headers: {
          "Content-Type": "application/x-www-form-urlencoded",
          "Access-Token": qxParams.cameraObj.accessToken,
        },
      }
    );
  }
  // 巡航控制
  static zxCruiseControl(type, index) {
    let data = {};
    switch (type) {
      case "moveCruise":
        data = {
          cmdCode: 136,
          parameter1: index,
          parameter2: 0,
          combindCode2: 0,
        };
        break;
      default:
        break;
    }
    axios.post(
      `${url.ZX_FRONT_END_COMMAND}/${deviceId}/${channelNo}`,
      toFormData(data),
      {
        headers: {
          "Content-Type": "application/x-www-form-urlencoded",
          "Access-Token": qxParams.cameraObj.accessToken,
        },
      }
    );
  }
  static zxGetCruisePath(fun) {
    qxParams.cameraObj.presetList = [{ presetID: 1 }];
    fun(qxParams.cameraObj.presetList);
  }
}

// 点播/发送心跳
function guohandleEventOther(...args) {
  return new Promise((resolve, reject) => {
    if (window.flvPlayer && params.playType == "jessibuca") {
      jessDestroy();
    }
    let { id: cameraId, deviceId, channelNo, liveUrl, sourceType } = args[0]

    if (sourceType * 1 === 1) {
      zxCamera
        .playStart()
        .then((res) => {
          console.log("res===>", res);

          resolve(res);
        })
        .catch((err) => {
          // showError('点播超时')
        });
    }
    if (sourceType * 1 === 2) {
      setInterval(() => {
        // let newUrl = replaceCameraUrl(url.OUTSIDE_V1_CAMERA_HOLDING)
        let newUrl = url.OUTSIDE_V1_CAMERA_HOLDING;
        axios.get(newUrl, {
          params: {
            cameraId,
          },
        });
      }, 10000);
      // promise(1)
      resolve(1);
    }
  });
}
function getInternet() {
  if (params.isInternet !== null) return;
  return new Promise((resolve, reject) => {
    const instance = axios.create({
      baseURL:
        "https://cdn.staticfile.org/twitter-bootstrap/3.3.7/css/bootstrap.min.css",
      timeout: 1500,
    });
    instance
      .get("")
      .then((res) => {
        params.isInternet = true;
        resolve(true);
      })
      .catch((err) => {
        params.isInternet = false;
        // params.isInternet = true
        reject(false);
      });
  });
}
function getCameraReplace() {
  if (params.cameraReplaceUrl !== null) return;
  return new Promise(async (resolve, reject) => {
    let res = await axios.get(url.GET_CONFIG_BY_CODE, {
      params: {
        code: "replacePreLiveUrl",
      },
    });
    if (res.data.code == "0") {
      if (res.data.data !== null) {
        params.cameraReplaceUrl = res.data.data.value;
      } else {
        params.cameraReplaceUrl = "";
      }
    }
    console.log("object :>> ", res.data);
    resolve(params.cameraReplaceUrl);
  });
}
function jessDestroy() {
  // liveUrl = null
  // window.flvPlayer.destroy()
  // window.flvPlayer = null
  zxCamera.playStop();
}
function toFormData(data) {
  let tmp = new FormData();
  for (var key in data) {
    tmp.append(key, data[key])
  }
  // tmp.append("regionIds","");
  return tmp
}
export default {
  name: "cameraPaly",
  props: {
    boxId: {
      required: true,
      type: String,
    },
    left: {
      type: String,
      default: "7%",
    },
    top: {
      type: String,
      default: "50px",
    },
  },
  data() {
    return {
      isShowControl: true,
      visible: false,
      cameraInfo: {},
      fullData: {},
      hasShowVideo: false,
      isCloseVideo: false,
    };
  },

  components: {},
  created() { },
  mounted() { },
  methods: {
    showFullVideo(data) {
      console.log("data==>", data);
      this.hasShowVideo = true;
      if (data) {
        this.cameraInfo = data;
        this.isCloseVideo = true;
        console.log("playVideo", this.cameraInfo);
        this.$nextTick(() => {
          let dom = document.querySelector(".cameraPlayFull");
          dom.setAttribute("id", this.boxId + "Full");
          if (this.cameraInfo) {
            let { id, channelNo, deviceId, liveUrl, lng, lat, sourceType } =
              this.cameraInfo;

            qxParams.cameraObj.id = id;
            qxParams.cameraObj.channelNo = channelNo;
            qxParams.cameraObj.deviceId = deviceId;
            qxParams.cameraObj.liveUrl = liveUrl;
            qxParams.cameraObj.sourceType = sourceType;
            qxParams.cameraObj.lng = lng;
            qxParams.cameraObj.lat = lat;
          }
          qxUtils.guohandleEventOther().then((res) => {
            let dom = document.getElementById(this.boxId + "Full");
            if (dom) {
              dom.style.background = "none";
              qxUtils.playLive(this.boxId + "Full");
            }
          });
        });
      } else {
        this.$nextTick(() => {
          let dom = document.querySelector(".cameraPlayFull");
          dom.setAttribute("id", this.boxId + "Full");
          console.log(dom);
          qxUtils.guohandleEventOther().then((res) => {
            let dom = document.getElementById(this.boxId + "Full");
            if (dom) {
              console.log(dom);
              dom.style.background = "none";
              qxUtils.playLive(this.boxId + "Full");
            }
          });
        });
      }
    },
    closeVideo() {
      this.hasShowVideo = false;
      this.visible = true;
      this.$emit("closeFullVideoBox");
    },
    closeFullVideo() {
      if (!this.isCloseVideo) {
        this.hasShowVideo = false;
        this.visible = true;
        this.$nextTick(() => {
          qxUtils.guohandleEventOther().then((res) => {
            let dom = document.getElementById(this.boxId);
            if (dom) {
              dom.style.background = "none";
              qxUtils.playLive(this.boxId);
            }
          });
        });
        return;
      }
      this.closeVideo();
    },
    playVideo(cameraInfo,parentName) {
      this.fullData = cameraInfo;
      this.visible = true;
      this.$nextTick(() => {
        let dom = document.querySelector(`${parentName} .cameraPlay`);
        // let dom = document.querySelector(`.cameraPlay`);
        // console.log('this.boxId==>',this.boxId)
        dom.setAttribute("id", this.boxId);
        if (cameraInfo) {
          zxCamera = new ZxControlDiversion(cameraInfo)
          guohandleEventOther(cameraInfo).then((res) => {
            let dom = document.getElementById(this.boxId);
            // console.log('dom==>',dom,res.data.data.flv)
            if (dom) {
              this.playLive(this.boxId, res.data.data.flv);
            }
          });
        }


      });
    },
    playLive(boxId,liveUrl ) {
      let demo = document.getElementById(boxId);
      // if (window.flvPlayer) {
      //   this.jessDestroy(demo);
      // }

      // 调用播放
      create(demo);
      let flvPlayer = "flvPlayer_" + demo;
      window[flvPlayer].play(liveUrl);
    },
    cameraControlDiversion(...args) {
      let { channelNo, deviceId } = args[0];
      let { type, status } = args[1];
      let data = {
        command: type,
      };
      let controlData = {
        horizonSpeed: 100,
        verticalSpeed: 100,
        zoomSpeed: 30
      }
      if (status === 0) {
        data.command ='stop'
        axios.post(
          `${url.ZX_CONTROL}/${deviceId}/${channelNo}`,
          toFormData(Object.assign(data, controlData)),
          {
            headers: {
              "Content-Type": "application/x-www-form-urlencoded",
              "Access-Token": qxParams.cameraObj.accessToken,
            },
          }
        );
      }else {
        axios.post(
          `${url.ZX_CONTROL}/${deviceId}/${channelNo}`,
          toFormData(Object.assign(data, controlData)),
          {
            headers: {
              "Content-Type": "application/x-www-form-urlencoded",
              "Access-Token": qxParams.cameraObj.accessToken,
            },
          }
        );
      }


    },
    onHover() {
      this.isShowControl = true;
    },
    onLeave() {
      // this.isShowControl = false;
    },
  },
};
</script>

<style scoped>
.fullBox {
  padding: 0 20px 20px;
}

.top,
.left,
.right,
.bottom {
  z-index: 99991;
}
</style>

index.vue

<template>
  <div>
    <div class="item_con" style="padding:0">
      <div
        id="videoPlay1"
        style="width: 456px;height: 231px;margin: 20px;"
        @mouseenter="onHover"
        @mouseleave="onLeave"
      >
        <cameraPlay :boxId="'video_x1'" ref="cameraPalyRef1">
          <div class="video-control cameraPop" v-if="isShowControl">
            <div
              class="top"
              @mousedown="cameraControlDiversion1('up', 1)"
              @mouseup="cameraControlDiversion1('up', 0)"
            ></div>
            <div
              class="right"
              @mousedown="cameraControlDiversion1('right', 1)"
              @mouseup="cameraControlDiversion1('right', 0)"
            ></div>
            <div class="content"></div>
            <div
              class="bottom"
              @mousedown="cameraControlDiversion1('down', 1)"
              @mouseup="cameraControlDiversion1('down', 0)"
            ></div>
            <div
              class="left"
              @mousedown="cameraControlDiversion1('left', 1)"
              @mouseup="cameraControlDiversion1('left', 0)"
            ></div>
          </div>
        </cameraPlay>
      </div>
    </div>
  </div>
</template>

<script type="text/ecmascript-6">
import cameraPlay from "./cameraPlays";
export default {
  data() {
    return {
      isShowControl:false,
      cameraInfo2: {
        id: 0,
        channelNo: "34020000001320000067",
      cameraInfo1: {
        id: 0,
        channelNo: "34020000001320000067",
        deviceId: "44010200492000000067",
        liveUrl:
          "http://58.144.221.11:30101/rtp/44010200492000000067_34020000001320000067.live.flv",
        lng: "",
        lat: "",
        sourceType: 1
      }

    }
  },
  components: {
    cameraPlay,
  },
  methods: {
    onHover() {
      this.isShowControl = true;
    },
    onLeave() {
      this.isShowControl = false;
    },
    onHover2() {
      this.isShowControl2 = true;
    },
    onLeave2() {
      this.isShowControl2 = false;
    },
    cameraControlDiversion1(type, status) {
      let data = {
        type: type,
        status: status
      };
      this.$refs.cameraPalyRef1.cameraControlDiversion(this.cameraInfo1, data);
    },
    cameraControlDiversion2(type, status) {
      let data = {
        type: type,
        status: status
      };
      this.$refs.cameraPalyRef2.cameraControlDiversion(this.cameraInfo2, data);
    },
  }
}
</script>

<style lang="stylesheet/stylus"></style>

`flv.js`是一个JavaScript库,专为现代浏览器设计,用于在Web页面上播放RTMP (Real Time Messaging Protocol) 流。RTMP是一种常用于实时音视频传输的技术,比如在线直播。`flv.js`并不是直接处理RTMP流,但它提供了一种间接的方式来解析显示从RTMP服务器流下来的FLV (Flash Video) 格式的数据,即使用户的浏览器可能不支持原生的HTML5 Media Source Extensions (MSE) 或者其他的RTMP解码技术。 当你想要在Web上播放RTMP流时,通常的流程包括以下几个步骤: 1. **服务端设置**:需要一个RTMP服务器(如Adobe Flash Media Server、Wowza或nginx-rtmp等),将直播内容推送到服务器上,并以FLV格式接收。 2. **转码到HTTP Live Streaming (HLS)**:由于大多数现代浏览器对RTMP的支持有限,服务端可能会选择先将RTMP流转换成更常见的HLS或MP4等HTTP分段式流格式,然后通过HTTP供客户端访问。 3. **使用`flv.js`**:在前端,开发者会链接`flv.js`库到HTML页面,这个库能够处理来自HLS或转码后的FLV文件流,通过JavaScript API动态加载并播放。 4. **事件监听**:`flv.js`提供了事件系统,可以响应加载、错误、缓冲进度等用户交互,开发者可以根据这些事件调整播放体验。 5. **兼容性优化**:虽然现代浏览器支持越来越多的媒体源API,但为了确保所有用户都能看到直播,可能需要进行一些回退方案(例如使用video标签的`flash`属性或HTML5 `<object>`元素)。 **注意事项**: - 要确保服务端配置正确,因为`flv.js`不会处理原始的RTMP连接,它依赖于已经转换过的流。 - `flv.js`在某些老旧或非主流浏览器上可能存在兼容性问题,测试是必要的。 - 对于更好的性能用户体验,推荐使用WebSocket或其他长连接协议替代频繁的短连接RTMP请求。
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

流氓也是种气质 _Cookie

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值