废话不多说
下载并引用
import videojs from "video.js";
import "video.js/dist/video-js.css";
import "videojs-contrib-hls";
video js插件是跟据他来插入dom进行视频的渲染
<video
:id="'video' + item.channelId"
ref="videos"
class="video video-js vjs-big-play-centered"
>
<source
:id="'video' + item.channelId"
src=""
type="application/x-mpegURL"
/>
</video>
data() {
return {
timeMU: null,
videoOptions: {
autoplay: true,
muted: true, //默认情况下消除任何音频
controls: true,
poster: "",
sources: [
{
src: "",
type: "application/x-mpegURL",
},
],
},
player: [], //存储video 实列
vldeoArr: [],
channelIds: [], //id 用来获取视频
imgUrl: require("@/assets/no-photo.png"),
};
},
下面我把我的代码全部贴出,说一下我页面的逻辑我这个也页面是用的组件写法,获取父组件获取来的videoList视频列表,根据视频列表的channelId来进行mutiRtspAndM3u8接口请求返回过来的是视频的url地址
但是后端有问题mutiRtspAndM3u8这个接口咩有处理好,就是说我传入9个channelId按道理他应该给我9个视频的url.....但是重点来了他返回来的有一下集中可能,一,返回来data里面只有3个,还有6个没有返回来..并且data里面的3个里面不一定都有url.可能有,可能没有,如果没有的话就接着发送这个请求,直到获取到,或者就是跳转到下一页就取消定时器...
下面是我视频播放的方法
// 视频播放
async getMuVideo(ids) {
let _this = this;
await mutiRtspAndM3u8(ids, this).then((res) => {
this.$nextTick(async () => {
// 清除m3u8定时器
if (this.timeMU) {
clearInterval(this.timeMU);
this.timeMU = null
}
let arr = res.data.data;
let bool = true; //用来判断 url 是否全部有值
let urlArr = [];
arr.forEach((item, index) => {
if (item.visitUrl) {
// 拿到所有的url去和视频列表里面判断,如果视频列表里面的是离线OFFLINE就改为在线ONLINE状态
_this.videoList.forEach((v) => {
// && v.status == "OFFLINE"
if (item.channelId == v.channelId) {
// v.status = "ONLINE";
v.loading = false;
if(v.status != "OFFLINE"){
}
// this.getVideoList()
// 因为上一步有了url视频地址,虽然这次判断更改了dom的显示,但是还是无法赋值url
}
});
this.$nextTick(() => {
let videoOptions = JSON.parse(
JSON.stringify(this.videoOptions)
);
videoOptions.sources[0].src =
_this.GLOBAL.BASE_URL + item.visitUrl;
let dom =
"<video id=" +
"video" +
item.channelId +
' class="video-js vjs-default-skin" controls style="width:100%;height:100%"></video>';
this.$refs["videoDiv" + item.channelId][0].innerHTML = dom;
this.$forceUpdate();
this.player.push(
videojs("video" + item.channelId, videoOptions)
);
});
} else {
bool = false;
urlArr.push(Number(item.channelId));
}
});
// channelIds 与 返回来的视频不匹对。那就把剩余的id再去发送请求
if (this.channelIds.length != arr.length) {
let muidArr = arr.map((item) => Number(item.channelId));
let idArr = this.getArrDifference(this.channelIds, muidArr);
urlArr = [...idArr, ...urlArr];
// console.log(urlArr);
}
if (!bool || this.channelIds.length != arr.length) {
let arr = urlArr.map((item) => {
return {
channelId: item,
};
});
// console.log(arr);
this.channelIds = urlArr;
this.timeMU = null
this.timeMU = setInterval(async () => {
await this.getMuVideo(arr);
}, 3000);
}
});
});
},
然后那又有一个需求,在videoList视频列表有离线和在线状态,html上根据videoList视频列表返回来的data判断显示视频和不显示视频
<div style="position: relative">
<!-- 在线 -->
<div
@click="lookVideo(item.channelId, item.status, item.channelName)"
element-loading-background="rgba(0, 0, 0, 0.1)"
v-loading="item.loading"
:style="height"
:ref="'videoDiv' + item.channelId"
v-show="item.status == 'ONLINE'"
>
<video
:id="'video' + item.channelId"
ref="videos"
class="video video-js vjs-big-play-centered"
>
<source
:id="'video' + item.channelId"
src=""
type="application/x-mpegURL"
/>
</video>
</div>
<!-- 离线 -->
<div
@click.self="
lookVideo(item.channelId, item.status, item.channelName)
"
:style="height"
v-show="item.status == 'OFFLINE'"
class="backgc-content"
>
<el-image
style="width: 50%; height: 50%"
:src="imgUrl"
fit="fill"
></el-image>
</div>
<!-- 最下方 -->
<div class="videoTitle" v-if="cameraname">
<span style="color: #ffffff"
>[{{ item.channelId }}] {{ item.channelName }}</span
>
<div style="position: absolute; left: 80%">
<div>
<i
:class="{
'el-icon-success': item.status == 'ONLINE',
'el-icon-error': item.status == 'OFFLINE',
}"
></i
><span
:class="{
online: item.status == 'ONLINE',
offline: item.status == 'OFFLINE',
}"
>{{ item.status == "ONLINE" ? "在线" : "离线" }}</span
>
</div>
</div>
</div>
</div>
离线状态里没有cideo标签就无法展示视频
意思是我9个channelId里面有离线的和没离线的都去请求了,渲染的时候肯定是离线是没办法渲染上的,然后我根据判断假如离线的channelId为5,5的url回来了,我就把videoList的离线状态改成在线,然后在线的html就有video标签了,...现在问题来了,这个判断是在有这个5的url时候进行判断的,所以他已经有url了已经用video js插入dom进行渲染了
就算if了也是无法显示,所以逻辑有问题,没法进行.搞不懂为啥离线的也要发送请求,接通视频后撒撒刷新页面不就可以了
下面我把带吗全部贴出看你们可以看的懂吗,然后销毁这个video js标签也有问题,解决是插入daom元素
<template>
<div>
<el-row style="margin-top: 20px">
<el-col
v-for="(item, index) of vldeoArr"
:key="index"
:span="span"
style="border: 2px solid #344563"
>
<div style="position: relative">
<!-- 在线 -->
<div
@click="lookVideo(item.channelId, item.status, item.channelName)"
element-loading-background="rgba(0, 0, 0, 0.1)"
v-loading="item.loading"
:style="height"
:ref="'videoDiv' + item.channelId"
v-show="item.status == 'ONLINE'"
>
<video
:id="'video' + item.channelId"
ref="videos"
class="video video-js vjs-big-play-centered"
>
<source
:id="'video' + item.channelId"
src=""
type="application/x-mpegURL"
/>
</video>
</div>
<!-- 离线 -->
<div
@click.self="
lookVideo(item.channelId, item.status, item.channelName)
"
:style="height"
v-show="item.status == 'OFFLINE'"
class="backgc-content"
>
<el-image
style="width: 50%; height: 50%"
:src="imgUrl"
fit="fill"
></el-image>
</div>
<!-- 最下方 -->
<div class="videoTitle" v-if="cameraname">
<span style="color: #ffffff"
>[{{ item.channelId }}] {{ item.channelName }}</span
>
<div style="position: absolute; left: 80%">
<div>
<i
:class="{
'el-icon-success': item.status == 'ONLINE',
'el-icon-error': item.status == 'OFFLINE',
}"
></i
><span
:class="{
online: item.status == 'ONLINE',
offline: item.status == 'OFFLINE',
}"
>{{ item.status == "ONLINE" ? "在线" : "离线" }}</span
>
</div>
</div>
</div>
</div>
</el-col>
</el-row>
</div>
</template>
<script>
import {
rtspAndM3u8,
getRtspAndM3u8,
mutiRtspAndM3u8,
} from "@/api/videoSquare.js";
import videojs from "video.js";
import "video.js/dist/video-js.css";
import "videojs-contrib-hls";
export default {
name: "videps",
props: {
// 宫格
spanNum: {
type: Number,
default: 1,
},
//多少页
currentPage: {
type: Number,
default: 1,
},
//获取的id 根据id用来请求视频流
videoList: {
type: Array,
default: () => [],
},
// 摄像头是否显示
cameraname: {
type: Boolean,
default: true,
},
// 父组件的方法
getVideoList: {
type: Function,
default: null,
},
},
computed: {
height() {
let num = Math.sqrt(this.spanNum);
let h = "550px";
if (this.spanNum == 12) {
num = 3;
}
if (this.$store.getters.flagName) {
h = "85vh";
}
return {
height: `calc(${h} / ${num})`,
width: "100%",
};
},
span() {
let num = Math.sqrt(this.spanNum);
if (this.spanNum == 12) {
num = 4;
}
return 24 / num;
},
},
data() {
return {
timeMU: null,
videoOptions: {
autoplay: true,
muted: true, //默认情况下消除任何音频
controls: true,
poster: "",
sources: [
{
src: "",
type: "application/x-mpegURL",
},
],
},
player: [], //存储video 实列
vldeoArr: [],
channelIds: [], //id 用来获取视频
imgUrl: require("@/assets/no-photo.png"),
};
},
mounted() {},
methods: {
//点击播放视频
lookVideo(channelId, status, channelName) {
// return;
const loading = this.$loading({
lock: true,
text: "Loading",
spinner: "el-icon-loading",
background: "rgba(0, 0, 0, 0.3)",
target: document.querySelector(".click_" + channelId),
});
let data = {
channelId: channelId,
};
rtspAndM3u8(data)
.then((res) => {
if (res.data.code == 200) {
let visitUrl = res.data.data.visitUrl;
//请求地址不为空
if (visitUrl != null) {
this.visitUrl = this.GLOBAL.BASE_URL + visitUrl;
// this.visitUrl = res.data.data.visitUrl;
loading.close();
this.$router.push({
name: "videoPlay",
query: {
channelId: channelId,
channelName: channelName,
visitUrl: this.visitUrl,
},
});
} else {
//请求地址为空
let _this = this;
_this.playTimer = setInterval(function () {
//获取m3u8播放地址
let params = {
channelId: channelId,
};
getRtspAndM3u8(params)
.then((res) => {
if (res.data.code == 200) {
let visitUrl = res.data.data;
if (visitUrl != null) {
_this.visitUrl = _this.GLOBAL.BASE_URL + visitUrl;
// loading.close();
clearInterval(_this.playTimer);
_this.playTimer = null;
_this.$router.push({
name: "videoPlay",
query: {
channelId: channelId,
channelName: channelName,
visitUrl: _this.visitUrl,
},
});
} else {
_this.$message.error(res.data.msg);
return false;
}
} else {
// _this.$message.error(res.data.msg);
//清除延迟定时器
/*clearInterval(this.playTimer);
this.playTimer = null;
return false;*/
}
if (status == "OFFLINE") {
// _this.$message.error(res.data.msg);
loading.close();
return false;
}
})
.catch((error) => {});
}, 5000);
}
} else {
this.$message.error(res.data.msg);
return false;
}
})
.catch((error) => {});
},
Destroy() {
// 销毁视频实例
if (this.player.length) {
this.player.forEach((item) => {
item.dispose();
});
this.player = [];
}
},
// 父组件keepalive钩子函数离开页面调用
keepDestory(){
if (this.timeMU) {
clearInterval(this.timeMU);
this.timeMU = null
}
this.Destroy(); //销毁视频实例
},
getArrDifference(arr1, arr2) {
return arr1.concat(arr2).filter(function (v, i, arr) {
return arr.indexOf(v) === arr.lastIndexOf(v);
});
},
// 取消请求
cancelQuest() {
if (typeof this.source === "function") {
// console.log("取消上一次请求");
this.source("终止请求"); //取消请求
}
},
// 视频播放
async getMuVideo(ids) {
let _this = this;
await mutiRtspAndM3u8(ids, this).then((res) => {
this.$nextTick(async () => {
// 清除m3u8定时器
if (this.timeMU) {
clearInterval(this.timeMU);
this.timeMU = null
}
let arr = res.data.data;
let bool = true; //用来判断 url 是否全部有值
let urlArr = [];
arr.forEach((item, index) => {
if (item.visitUrl) {
// 拿到所有的url去和视频列表里面判断,如果视频列表里面的是离线OFFLINE就改为在线ONLINE状态
_this.videoList.forEach((v) => {
// && v.status == "OFFLINE"
if (item.channelId == v.channelId) {
// v.status = "ONLINE";
v.loading = false;
if(v.status != "OFFLINE"){
}
// this.getVideoList()
// 因为上一步有了url视频地址,虽然这次判断更改了dom的显示,但是还是无法赋值url
}
});
this.$nextTick(() => {
let videoOptions = JSON.parse(
JSON.stringify(this.videoOptions)
);
videoOptions.sources[0].src =
_this.GLOBAL.BASE_URL + item.visitUrl;
let dom =
"<video id=" +
"video" +
item.channelId +
' class="video-js vjs-default-skin" controls style="width:100%;height:100%"></video>';
this.$refs["videoDiv" + item.channelId][0].innerHTML = dom;
this.$forceUpdate();
this.player.push(
videojs("video" + item.channelId, videoOptions)
);
});
} else {
bool = false;
urlArr.push(Number(item.channelId));
}
});
// channelIds 与 返回来的视频不匹对。那就把剩余的id再去发送请求
if (this.channelIds.length != arr.length) {
let muidArr = arr.map((item) => Number(item.channelId));
let idArr = this.getArrDifference(this.channelIds, muidArr);
urlArr = [...idArr, ...urlArr];
// console.log(urlArr);
}
if (!bool || this.channelIds.length != arr.length) {
let arr = urlArr.map((item) => {
return {
channelId: item,
};
});
// console.log(arr);
this.channelIds = urlArr;
this.timeMU = null
this.timeMU = setInterval(async () => {
await this.getMuVideo(arr);
}, 3000);
}
});
});
},
},
beforeDestroy() {
if (this.timeMU) {
clearInterval(this.timeMU);
this.timeMU = null
}
this.Destroy();
},
watch: {
videoList: {
async handler(val) {
this.cancelQuest(); // 请求发送前调用 切换栅格时 把之前的接口清空
await this.Destroy();
this.channelIds = [];
this.vldeoArr = val;
let arrId = [];
// 判断是否离线 只取在线 改为不判断离线和不离线都发请求
this.vldeoArr.forEach((item) => {
// console.log(this.$refs["videoDiv" + item.channelId]);
item.loading = true;
arrId.push({ channelId: item.channelId });
this.channelIds.push(Number(item.channelId));
});
this.$forceUpdate();
if (arrId.length) {
this.getMuVideo(arrId);
}
},
deep: true,
},
},
};
</script>
<style scoped lang="scss">
.video {
width: 100%;
height: 100%;
}
.backgc-content {
display: flex;
justify-content: center;
align-items: center;
background: #000000;
// height: 306px;
text-align: center;
}
/*标题样式*/
.videoTitle {
display: flex;
justify-content: space-between;
align-items: center;
width: 93%;
position: absolute;
bottom: 15px;
left: 20px;
span {
font-size: 14px;
font-family: Microsoft YaHei;
font-weight: 400;
color: rgba(0, 0, 0, 0.85);
}
div {
/*在线icon样式*/
.el-icon-success:before {
font-size: 12px;
color: #4cd964;
margin-right: 5px;
}
.el-icon-error:before {
font-size: 12px;
color: #ff3a3a;
margin-right: 5px;
}
span.online {
font-size: 12px;
color: #4cd964;
}
span.offline {
font-size: 12px;
color: #ff3a3a;
}
}
}
</style>
不喜勿喷,多交流多探讨