vue flv和hls兼容的播放器

需求:

1、一个播放器能支持以下两个渠道的视频流播放
2、支持多个视频播放
3、样式统一
在这里插入图片描述

本地插件播放器客户不喜欢,浏览器能直接播放的不能支持多路负载(卡顿)

多种播放格式里最终选择了hls和flv作为网页播放的取流格式

使用到的包有 flv.js,hls.js

npm install flv.js hls.js

flv播放器

flvVideo.vue

<template>
  <div id="flvVideo" class="flv-video">
    <video
      :id="this.videoData.camera_id"
      :style="'width: 100%; height:100%;object-fit: fill'"
      controls
      autoplay
      muted
    ></video>
  </div>
</template>

<script>
//=> 引入flv.js
import flvjs from "flv.js";
export default {
  name: "flvVideo",
  data() {
    return {
      videoData: {},
      player: null,
      videoElement: null,
    };
  },
  props: {
    item: Object,
  },
  watch: {
    item: {
      // 每个属性值发生变化就会调用这个函数
      handler(newVal, oldVal) {
        console.log(oldVal);
        this.videoData = newVal;
        // 这里延迟只是为了等待页面内容加载,另外做点卡顿效果,实际上根据需求优化这里就好了
        setTimeout(() => {
          this.startPlay();
        }, 1000);
      },
      // 立即处理 进入页面就触发
      immediate: true,
      // 深度监听 属性的变化
      deep: true,
    },
  },
  methods: {
    //视频浏览
    startPlay() {
      if (flvjs.isSupported()) {
        this.videoElement = document.getElementById(this.videoData.camera_id);
        this.player = flvjs.createPlayer({
          type: "flv", //=> 媒体类型 flv 或 mp4
          isLive: true, //=> 是否为直播流
          hasAudio: false, //=> 是否开启声音
          url: this.videoData.flv_address, //=> 视频流地址
        });
        this.player.attachMediaElement(this.videoElement); //=> 绑DOM
        this.player.load();
        this.player.play();
      } else {
        console.log("flvjs不支持");
      }
    },
  },
};
</script>

<style>
.flv-video {
  object-fit: fill;
  display: inline-block;
}
</style>

hls播放器

hlsVideo.vue

<template>
  <div id="app-container" class="hls-video" style="display: inline-block">
    <video
      ref="videoElement"
      :src="videoData.flv_address"
      :id="videoData.camera_id"
      controls
      muted
      :style="'width: 100%; height:100%;object-fit: fill'"
    ></video>
  </div>
</template>

<script>
import hlsjs from "hls.js";
export default {
  name: "hlsVideo",
  components: {},
  data() {
    return {
      videoUrl: "", //这里书写路径,例
      videoData: {},
      video: null,
      hlsjs: null,
    };
  },
  props: {
    item: Object,
  },
  watch: {
    item: {
      // 每个属性值发生变化就会调用这个函数
      handler(newVal, oldVal) {
        console.log(oldVal);
        this.videoData = newVal;
        // 这里延迟只是为了等待页面内容加载,另外做点卡顿效果,实际上根据需求优化这里就好了
        setTimeout(() => {
          this.show();
        }, 1000);
      },
      // 立即处理 进入页面就触发
      immediate: true,
      // 深度监听 属性的变化
      deep: true,
    },
  },
  methods: {
    //播放
    show() {
      this.video = document.getElementById(this.videoData.camera_id); //定义挂载点
      console.log(this.video);
      if (hlsjs.isSupported()) {
        this.hlsjs = new hlsjs();
        this.hlsjs.loadSource(this.videoData.flv_address); //设置播放路径
        this.hlsjs.attachMedia(this.video); //解析到video标签上
        console.log(this.hlsjs);
        this.hlsjs.on(hlsjs.Events.MANIFEST_PARSED, () => {
          this.video.play();
          console.log("加载成功");
        });
        this.hlsjs.on(hlsjs.Events.ERROR, (event, data) => {
          console.log(event, data);
          // 监听出错事件
          console.log("加载失败");
        });
      } else {
        this.$message.error("不支持的格式");
        return;
      }
    },
    //停止播放
    closeVideo() {
      if (this.hlsjs) {
        this.video.pause();
        this.hlsjs.destroy();
        this.hlsjs = null;
        this.$emit("closeVideo");
      }
    },
  },
};
</script>

<style>
.hls-video {
  object-fit: fill;
  display: inline-block;
}
</style>

整合两个播放器

index.vue

<template>
  <div id="videoPage">
    <div class="list">
      <div class="title">Flv列表</div>
      <div class="item">
        <div class="item-title">
          唯一Key:<input type="text" v-model="flvObj.camera_id" />
        </div>
        <div class="item-content">
          视频地址:<input type="text" v-model="flvObj.flv_address" />
        </div>
        <button @click="flvAddFn">添加</button>
      </div>
      <div class="item scrollbar" v-for="(item, index) in itemFlvList" :key="index">
        <div class="item-title">唯一Key:{{ item.camera_id }}</div>
        <div class="item-content">视频地址:{{ item.flv_address }}</div>
      </div>
    </div>
    <div class="list">
      <div class="title">Hls列表</div>
      <div class="item">
        <div class="item-title">
          唯一Key:<input type="text" v-model="hlsObj.camera_id" />
        </div>
        <div class="item-content">
          视频地址:<input type="text" v-model="hlsObj.flv_address" />
        </div>
        <button @click="hlsAddFn">添加</button>
      </div>
      <div class="item scrollbar" v-for="(item, index) in itemHlsList" :key="index">
        <div class="item-title">唯一Key:{{ item.camera_id }}</div>
        <div class="item-content">视频地址:{{ item.flv_address }}</div>
      </div>
    </div>
    <!-- 通过 v-if 会移出 DOM 实现刷新部分组件 -->
    <div v-if="loading" :class="'video-card ' + isInRanges(itemFlvList.length + itemHlsList.length)">
      <template v-for="(item, index) in itemFlvList">
        <flv-video :item="item" :key="'flv' + index" class="video-item-card"></flv-video>
      </template>
      <template v-for="(item, index) in itemHlsList">
        <hls-video :item="item" :key="'hls' + index"  class="video-item-card"></hls-video>
      </template>
      <div class="video-item-card" v-for="item in (isInRangesCount(itemFlvList.length + itemHlsList.length) - itemFlvList.length - itemHlsList.length)" :key="item"></div>
    </div>
    <!-- <follower></follower> -->
  </div>
</template>

<script>
import flvVideo from "./components/flvVideo";
import hlsVideo from "./components/hlsVideo";
export default {
  name: "videoPage",
  data() {
    return {
      flvAddBool: false,
      hlsAddBool: false,
      flvObj: {
        camera_id: "flv",
        flv_address: "",
      },
      hlsObj: {
        camera_id: "hls",
        flv_address: "",
      },
      itemFlvList: [
        // {
        //   camera_id: "flv0",
        //   flv_address: "http://ivi.bupt.edu.cn/flv/7182741-1.flv",
        // },
      ],
      itemHlsList: [
        // {
        //   camera_id: "hls0",
        //   flv_address: "https://sf1-cdn-tos.huoshanstatic.com/obj/media-fe/xgplayer_doc_video/hls/xgplayer-demo.m3u8",
        // },
      ],
      loading: true,
    };
  },
  components: {
    flvVideo,
    hlsVideo
  },
  methods: {
    flvAddFn() {
      if (this.flvObj.flv_address != "") {
        this.flvObj.camera_id = "flv" + this.itemFlvList.length;
        this.itemFlvList.push({ ...this.flvObj });
      }
    },
    hlsAddFn() {
      if (this.hlsObj.flv_address != "") {
        this.hlsObj.camera_id = "hls" + this.itemHlsList.length;
        this.itemHlsList.push({ ...this.hlsObj });
      }
    },
    // 自定义分屏,支持多个播放
    isInRanges(num) {
      if (num === 0) {
        return 'video-count-1';
      } else if (num === 1) {
        return 'video-count-1';
      } else if (num >= 2 && num <= 4) {
        return 'video-count-4';
      } else if (num >= 5 && num <= 6) {
        return 'video-count-6';
      } else if (num >= 7 && num <= 9) {
        return 'video-count-9';
      } else if (num >= 10 && num <= 16) {
        return 'video-count-16';
      } else if (num >= 17 && num <= 25) {
        return 'video-count-25';
      } else {
        return 'video-count-25';
      }
    },
    isInRangesCount(num) {
      if (num === 0) {
        return 1
      } else if (num === 1) {
        return 1
      } else if (num >= 2 && num <= 4) {
        return 4
      } else if (num >= 5 && num <= 6) {
        return 6
      } else if (num >= 7 && num <= 9) {
        return 9
      } else if (num >= 10 && num <= 16) {
        return 16
      } else if (num >= 17 && num <= 25) {
        return 25
      } else {
        return false
      }
    },
  },
};
</script>

<style scoped>
.video-card {
  width: 1500px;
  height: 900px;
  background: #000;
  margin: 20px auto;
  display: flex;
  flex-wrap: wrap;
}
.list {
  width: 100%;
  max-height: 200px;
  overflow-y: auto;
  padding: 10px;
  border-radius: 6px;
  box-shadow: 0px 0px 6px 2px #858585;
  margin-bottom: 10px;
  box-sizing: border-box;
}
.list .title {
  font-weight: bold;
  line-height: 2;
}
.list .item {
  display: flex;
}
.list .item .item-title {
  min-width: 200px;
  margin-right: 40px;
}
.video-item-card {
  border: 1px solid #858585;
  box-sizing: border-box;
}
.video-count-1 .video-item-card {
  width: 100%;
  height: 100%;
}
.video-count-4 .video-item-card {
  width: 50%;
  height: 50%;
}
.video-count-6 .video-item-card {
  width: calc(100% / 3);
  height: calc(100% / 2);
}
.video-count-9 .video-item-card {
  width: calc(100% / 3);
  height: calc(100% / 3);
}
.video-count-16 .video-item-card {
  width: 25%;
  height: 25%;
}
.video-count-25 .video-item-card {
  width: 20%;
  height: 20%;
}
.scrollbar::-webkit-scrollbar {
  /*滚动条整体样式*/
  width: 2px; /*高宽分别对应横竖滚动条的尺寸*/
  height: 2px;
}
.scrollbar::-webkit-scrollbar-thumb {
  /*滚动条里面小方块*/
  border-radius: 2px;
  -webkit-box-shadow: inset 0 0 2px rgba(101, 183, 255, 0.5);
  background: rgba(101, 183, 255, 0.5);
}
.scrollbar::-webkit-scrollbar-track {
  /*滚动条里面轨道*/
  -webkit-box-shadow: inset 0 0 2px rgba(101, 183, 255, 0.5);
  border-radius: 2px;
  background: transparent;
}
</style>

效果

在这里插入图片描述

这个已经是能直接用的,这边要一个demo,做完后因部分原因不需要了,啧…

2024/4/19

遇到一个西瓜播放器,对hls和flv的支持也蛮不错的,有兴趣可以移步看看
西瓜播放器

### Vue 视频播放器支持 FLV M3U8 格式 为了在基于 Vue 的项目中实现对 FLV M3U8 流媒体格式的支持,可以采用 `flv.js` `hls.js` 这两个库来处理不同类型的视频流。这两个库分别针对 FLV HLS (HTTP Live Streaming, 常见文件扩展名为 .m3u8) 提供了解码支持功能[^1]。 #### 安装依赖包 通过 NPM 来安装必要的 JavaScript 库: ```bash npm install flv.js hls.js ``` #### 创建自定义组件用于加载控制视频播放 创建一个新的 Vue 组件,比如命名为 `VideoPlayer.vue`,在这个组件里引入并初始化上述提到的 JS 库实例,并根据传入的数据源动态切换解码方式。 ```javascript // VideoPlayer.vue <template> <div class="video-container"> <video ref="videoElement" controls></video> </div> </template> <script> import Hls from 'hls.js'; import FlvJs from 'flv.js'; export default { props: ['src', 'type'], mounted() { const videoEl = this.$refs.videoElement; if (this.type === 'application/x-mpegURL') { // 对于HLS(.m3u8) if (Hls.isSupported()) { let hls = new Hls(); hls.loadSource(this.src); hls.attachMedia(videoEl); } else if (videoEl.canPlayType('application/vnd.apple.mpegurl')) { videoEl.src = this.src; } } if (this.type === 'video/flv') { // 针对FLV if (FlvJs.isSupported()) { let flvPlayer = FlvJs.createPlayer({ type: 'flv', url: this.src }); flvPlayer.attachMediaElement(videoEl); flvPlayer.play(); } } }, }; </script> ``` 此代码片段展示了如何在一个简单的 Vue 单文件组件内集成这两种不同的播放技术。当接收到 `.m3u8` 或者 `.flv` 类型的 URL 地址时,会自动选择合适的解析方法来进行播放操作[^2]。 对于更复杂的应用场景或是希望获得更好的用户体验,还可以考虑使用一些成熟的第三方开源框架或插件,它们通常提供了更加丰富的特性良好的跨平台兼容性。例如,在某些情况下可能还需要考虑到浏览器的安全策略以及网络状况等因素的影响[^3]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值