需求:
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的支持也蛮不错的,有兴趣可以移步看看
西瓜播放器