开源版本,视频直播功能

This commit is contained in:
gx_ma
2024-07-08 09:30:11 +08:00
parent 55a3b95a97
commit b2ea3dec75
167 changed files with 16329 additions and 21 deletions

View File

@@ -0,0 +1,183 @@
<template>
<div id="DeviceTree" style="width: 100%; height: 100%; background-color: #ffffff; overflow: auto">
<div style="line-height: 3vh;margin-bottom: 10px;font-size: 17px;">设备列表</div>
<el-tree ref="tree" :props="defaultProps" :current-node-key="selectchannelId" :default-expanded-keys="expandIds"
:highlight-current="true" @node-click="handleNodeClick" :load="loadNode" lazy node-key="id"
style="min-width: 100%; display: inline-block !important">
<span class="custom-tree-node" slot-scope="{ node, data }" style="width: 100%">
<span v-if="node.data.type === 0 && node.data.online" title="在线设备"
class="device-online iconfont icon-jiedianleizhukongzhongxin2"></span>
<span v-if="node.data.type === 0 && !node.data.online" title="离线设备"
class="device-offline iconfont icon-jiedianleizhukongzhongxin2"></span>
<span v-if="node.data.type === 3 && node.data.online" title="在线通道"
class="device-online iconfont icon-shebeileijiankongdian"></span>
<span v-if="node.data.type === 3 && !node.data.online" title="离线通道"
class="device-offline iconfont icon-shebeileijiankongdian"></span>
<span v-if="node.data.type === 4 && node.data.online" title="在线通道-球机"
class="device-online iconfont icon-shebeileiqiuji"></span>
<span v-if="node.data.type === 4 && !node.data.online" title="离线通道-球机"
class="device-offline iconfont icon-shebeileiqiuji"></span>
<span v-if="node.data.type === 5 && node.data.online" title="在线通道-半球"
class="device-online iconfont icon-shebeileibanqiu"></span>
<span v-if="node.data.type === 5 && !node.data.online" title="离线通道-半球"
class="device-offline iconfont icon-shebeileibanqiu"></span>
<span v-if="node.data.type === 6 && node.data.online" title="在线通道-枪机"
class="device-online iconfont icon-shebeileiqiangjitongdao"></span>
<span v-if="node.data.type === 6 && !node.data.online" title="离线通道-枪机"
class="device-offline iconfont icon-shebeileiqiangjitongdao"></span>
<span v-if="node.data.online" style="padding-left: 1px" class="device-online">{{ node.label }}</span>
<span v-if="!node.data.online" style="padding-left: 1px" class="device-offline">{{ node.label }}</span>
</span>
</el-tree>
</div>
</template>
<script>
import { listSipDeviceChannel } from '@/api/iot/sipdevice';
import { listDeviceShort } from '@/api/iot/device';
export default {
name: 'DeviceTree',
data() {
return {
// 总条数
total: 0,
// 监控设备通道信息表格数据
channelList: [],
DeviceData: [],
expandIds: [],
selectData: {},
selectchannelId: '',
defaultProps: {
children: 'children',
label: 'name',
isLeaf: 'isLeaf',
},
queryParams: {
pageNum: 1,
pageSize: 100,
status: 3,
deviceType: 3,
},
};
},
props: ['onlyCatalog', 'clickEvent'],
mounted() {
this.selectchannelId = '';
this.expandIds = ['0'];
},
methods: {
handleNodeClick(data, node, element) {
this.selectData = node.data;
this.selectchannelId = node.data.value;
if (node.level !== 0) {
let deviceNode = this.$refs.tree.getNode(data.userData.channelSipId);
if (typeof this.clickEvent == 'function' && node.level > 1) {
this.clickEvent(deviceNode.data.userData);
}
}
},
loadNode: function (node, resolve) {
if (node.level === 0) {
listDeviceShort(this.queryParams).then((response) => {
const data = response.rows;
if (data.length > 0) {
let nodeList = [];
for (let i = 0; i < data.length; i++) {
let node = {
name: data[i].deviceName,
isLeaf: false,
id: data[i].serialNumber,
type: 0,
online: data[i].status === 3,
userData: data[i],
};
nodeList.push(node);
}
resolve(nodeList);
} else {
resolve([]);
}
});
} else {
let channelArray = [];
listSipDeviceChannel(node.data.userData.serialNumber).then((res) => {
if (res.data != null) {
channelArray = channelArray.concat(res.data);
this.channelDataHandler(channelArray, resolve);
} else {
resolve([]);
}
});
}
},
channelDataHandler: function (data, resolve) {
if (data.length > 0) {
let nodeList = [];
for (let i = 0; i < data.length; i++) {
let item = data[i];
let channelType = item.id.substring(10, 13);
console.log('channelType: ' + channelType);
let type = 3;
if (item.id.length <= 10) {
type = 2;
} else {
if (item.id.length > 14) {
let channelType = item.id.substring(10, 13);
// 111-DVR编码;112-视频服务器编码;118-网络视频录像机(NVR)编码;131-摄像机编码;132-网络摄像机(IPC)编码
if (channelType !== '111' && channelType !== '112' && channelType !== '118' && channelType !== '131' && channelType !== '132') {
type = -1;
// 1-球机;2-半球;3-固定枪机;4-遥控枪机
} else if (item.basicData.ptztype === 1) {
type = 4;
} else if (item.basicData.ptztype === 2) {
type = 5;
} else if (item.basicData.ptztype === 3 || item.basicData.ptztype === 4) {
type = 6;
}
} else {
if (item.basicData.subCount > 0 || item.basicData.parental === 1) {
type = 2;
}
}
}
let node = {
name: item.name || item.id,
isLeaf: true,
id: item.id,
deviceId: item.deviceId,
type: type,
online: item.status === 2,
userData: item.basicData,
};
if (channelType === '111' || channelType === '112' || channelType === '118' || channelType === '131' || channelType === '132') {
nodeList.push(node);
}
}
resolve(nodeList);
} else {
resolve([]);
}
},
reset: function () {
this.$forceUpdate();
},
},
destroyed() { },
};
</script>
<style>
.device-tree-main-box {
text-align: left;
}
.device-online {
color: #252525;
}
.device-offline {
color: #727272;
}
</style>

View File

@@ -0,0 +1,203 @@
<template>
<div>
<el-row>
<span style="margin-left: 10px" prop="channelName">通道名称</span>
<el-select v-model="channelId" placeholder="请选择" @change="changeChannel()" size="small">
<el-option v-for="option in channelList" :key="option.value" :label="option.label" :value="option.value"></el-option>
</el-select>
<span style="margin: 10px 10px 10px 30px">开启拉流:</span>
<el-switch v-model="pushStream" active-color="#13ce66" inactive-color="#c4c6c9" style="border-radius: 10px" :disabled="channelId === ''" @change="startPushStream"></el-switch>
<span style="margin: 10px 10px 10px 30px">开启直播录像:</span>
<el-switch v-model="playrecord" active-color="#13ce66" inactive-color="#c4c6c9" style="border-radius: 10px" :disabled="channelId === ''" @change="startPlayRecord"></el-switch>
</el-row>
<player ref="player" :playerinfo="playinfo" class="components-container"></player>
</div>
</template>
<script>
import player from '@/views/components/player/player.vue';
import { startPlay, closeStream, listChannel } from '@/api/iot/channel';
import { startPlayRecord } from '@/api/iot/record';
export default {
name: 'device-live-stream',
components: {
player,
},
props: {
device: {
type: Object,
default: null,
},
},
watch: {
// 获取到父组件传递的device后
device: function (newVal, oldVal) {
this.deviceInfo = newVal;
if (this.deviceInfo.channelId) {
this.channelId = this.deviceInfo.channelId;
this.changeChannel();
}
if (this.deviceInfo && this.deviceInfo.deviceId !== 0) {
this.queryParams.deviceSipId = this.deviceInfo.serialNumber;
this.deviceId = this.device.serialNumber;
}
},
},
data() {
return {
deviceInfo: {},
deviceId: '',
channelId: '',
streamId: '',
ssrc: '',
playurl: '',
playinfo: {},
playrecord: false,
playrecording: false,
playing: false,
pushStream: false,
retrycount: 0,
channelList: [],
// 查询参数
queryParams: {
pageNum: 1,
pageSize: 10,
deviceSipId: null,
channelSipId: null,
},
};
},
created() {
this.queryParams.deviceSipId = this.device.serialNumber;
this.deviceId = this.device.serialNumber;
this.getList();
this.playinfo = {
playtype: 'play',
deviceId: this.device.serialNumber,
};
},
destroyed() {
console.log("destroyed");
this.closeDestroy(false);
},
methods: {
/** 查询监控设备通道信息列表 */
getList() {
this.loading = true;
listChannel(this.queryParams).then((response) => {
this.channelList = response.rows.map((item) => {
return { value: item.channelSipId, label: item.channelName };
});
console.log(this.channelList);
});
},
changeChannel() {
this.playinfo.channelId = this.channelId;
this.startPlayer();
},
// 直播播放
TimeoutCallback() {
this.closeDestroy(false);
this.retrycount = 0;
setTimeout(() => {
this.startPlayer();
}, 1000);
},
startPushStream() {
if (!this.channelId) {
console.log('开始通道: [' + this.channelId + ']');
return;
}
console.log('推流状态: [' + this.pushStream + ']');
if (this.pushStream) {
this.startPlayer();
} else {
this.closeDestroy(true);
}
},
startPlayRecord() {
console.log('推流状态: [' + this.pushStream + ']');
this.closeDestroy(true);
setTimeout(() => {
this.startPlayer();
}, 500);
},
// 开启直播播放器
startPlayer() {
if (!this.channelId) {
console.log('直播录像通道: [' + this.channelId + ']');
return;
}
this.deviceId = this.queryParams.deviceSipId;
if (this.playing) {
this.closeDestroy(false);
}
if (!this.$refs.player.isInit) {
this.$refs.player.init();
}
this.$refs.player.registercallback('loadingTimeout', this.TimeoutCallback);
this.$refs.player.registercallback('delayTimeout', this.TimeoutCallback);
if (this.playrecord) {
startPlayRecord(this.deviceId, this.channelId).then((response) => {
console.log('开始录像:' + this.deviceId + ' : ' + this.channelId);
const res = response.data;
this.streamId = res.streamId;
this.playurl = res.playurl;
this.$refs.player.play(res.playurl);
this.playing = true;
this.playrecording = true;
this.pushStream = true;
});
} else {
startPlay(this.deviceId, this.channelId).then((response) => {
console.log('开始推流: [' + this.streamId + ']');
const res = response.data;
this.streamId = res.streamId;
this.playurl = res.playurl;
this.$refs.player.play(res.playurl);
this.playing = true;
this.playrecording = false;
this.pushStream = true;
});;
}
},
closeStream(force) {
if (force) {
if (this.playing && this.streamId) {
console.log('关闭推流3: [' + this.streamId + ']');
closeStream(this.deviceId, this.channelId, this.streamId).then((res) => {
this.streamId = '';
this.ssrc = '';
this.playurl = '';
this.pushStream = false;
});
this.playing = false;
this.playrecording = false;
}
} else {
if (this.playrecording === true) {
return;
}
if (this.playing && this.streamId) {
console.log('关闭推流3: [' + this.streamId + ']');
closeStream(this.deviceId, this.channelId, this.streamId).then((res) => {
this.streamId = '';
this.ssrc = '';
this.playurl = '';
this.pushStream = false;
});
this.playing = false;
this.playrecording = false;
}
}
},
closeDestroy(force) {
this.closeStream(force);
this.$refs.player.destroy();
},
destroy() {
this.$refs.player.destroy();
},
},
};
</script>

View File

@@ -0,0 +1,291 @@
<template>
<div style="display: block; width: 1000px">
<div style="display: flex">
<el-row>
<span style="margin-left: 10px" prop="channelName">通道</span>
<el-select v-model="channelId" placeholder="请选择" @change="changeChannel" style="width: 200px; margin-right: 10px" size="small">
<el-option v-for="option in channelList" :key="option.value" :label="option.label" :value="option.value"></el-option>
</el-select>
<span style="overflow: auto; margin-left: 10px">日期</span>
<el-date-picker v-model="queryDate" type="date" size="small" value-format="yyyy-MM-dd" clearable placeholder="选择日期" style="width: 180px; margin-right: 10px" />
<el-button-group style="margin: 0">
<el-button size="mini" type="success" title="查看录像" @click="loadDevRecord()" :disabled="channelId === '' || !queryDate">
<i class="el-icon-video-camera" />
查看
</el-button>
</el-button-group>
<span style="margin-left: 82px; overflow: auto">时间</span>
<el-button-group>
<el-time-picker
size="small"
is-range
align="left"
v-model="timeRange"
value-format="yyyy-MM-dd HH:mm:ss"
range-separator=""
start-placeholder="开始时间"
end-placeholder="结束时间"
placeholder="选择时间范围"
@change="timePickerChange"
style="width: 200px"
:disabled="channelId === '' || !queryDate"
></el-time-picker>
</el-button-group>
<el-button-group style="margin: 0 0 0 10px">
<el-button size="mini" type="primary" title="下载选定录像" @click="downloadRecord()" :disabled="channelId === '' || !timeRange">
<i class="el-icon-download" />
转存
</el-button>
</el-button-group>
</el-row>
</div>
<player ref="playbacker" :playerinfo="playinfo" class="components-container"></player>
</div>
</template>
<script>
import player from '@/views/components/player/player.vue';
import { listChannel, playback, closeStream, playbackSeek } from '@/api/iot/channel';
import { getDevRecord, startDownloadRecord } from '@/api/iot/record';
export default {
name: 'DeviceVideo',
components: {
player,
},
data() {
return {
deviceId: '',
channelId: '',
streamId: '',
ssrc: '',
playurl: '',
queryDate: '',
playing: false,
vodData: {},
hisData: [],
playinfo: {},
channelList: [],
playbackinfo: {},
timeRange: null,
startTime: null,
endTime: null,
// 查询参数
queryParams: {
pageNum: 1,
pageSize: 10,
deviceSipId: null,
channelSipId: null,
},
};
},
props: {
device: {
type: Object,
default: null,
},
},
watch: {
// 获取到父组件传递的device后
device: function (newVal, oldVal) {
this.deviceInfo = newVal;
if (this.deviceInfo && this.deviceInfo.deviceId !== 0) {
this.queryParams.deviceSipId = this.deviceInfo.serialNumber;
this.deviceId = this.device.serialNumber;
}
},
},
created() {
this.queryParams.deviceSipId = this.device.serialNumber;
this.deviceId = this.device.serialNumber;
this.getList();
this.playinfo = {
playtype: 'playback',
deviceId: this.device.serialNumber,
};
},
beforeDestroy() {},
destroyed() {
this.closeStream();
},
methods: {
/** 查询监控设备通道信息列表 */
getList() {
this.loading = true;
listChannel(this.queryParams).then((response) => {
this.channelList = response.rows.map((item) => {
return { value: item.channelSipId, label: item.channelName };
});
});
},
// 改变通道
changeChannel() {
this.playinfo.channelId = this.channelId;
},
initUrl(data) {
if (data) {
this.streamId = data.ssrc;
this.ssrc = data.ssrc;
this.playurl = data.playurl;
} else {
this.streamId = '';
this.ssrc = '';
this.playurl = '';
}
},
getBeijingTime(queryDate) {
// 计算与UTC的时区差对于北京时间来说是8小时
let offset = 8 * 60 * 60 * 1000;
// 加上时区差,得到北京时间
let beijingTime = new Date(new Date(queryDate).getTime() - offset);
return beijingTime.getTime();
},
// 录像控制
loadDevRecord() {
this.$refs.playbacker.registercallback('playbackSeek', this.seekPlay);
if (this.queryDate === '' || this.queryDate === null){
this.$message.error('请选择日期');
return;
}
if (this.deviceId && this.channelId) {
const date = this.getBeijingTime(this.queryDate);
const start = date / 1000;
const end = Math.floor((date + 24 * 60 * 60 * 1000 - 1) / 1000);
const query = {
start: start,
end: end,
};
this.vodData = {
start: start,
end: end,
base: start,
};
this.setTime(this.queryDate + ' 00:00:00', this.queryDate + ' 23:59:59');
getDevRecord(this.deviceId, this.channelId, query).then((res) => {
this.hisData = res.data.recordItems;
if (res.data.recordItems) {
const len = this.hisData.length;
if (len > 0) {
if (this.hisData[0].start < start) {
this.hisData[0].start = start;
this.vodData.start = start;
} else {
this.vodData.start = this.hisData[0].start;
}
if (this.hisData[0].end !== 0 &&this.hisData[0].end < end) {
this.vodData.end = this.hisData[0].end;
}
this.playback();
} else {
this.$message({
type: 'warning',
message: '请确认设备是否支持录像或者设备SD卡是否正确插入',
});
}
} else {
this.$message({
type: 'warning',
message: '请确认设备是否支持录像或者设备SD卡是否正确插入',
});
}
});
}
},
/**录像播放*/
playback() {
const query = {
start: this.vodData.start,
end: this.vodData.end,
};
if (this.ssrc) {
closeStream(this.deviceId, this.channelId, this.ssrc).then((res) => {
playback(this.deviceId, this.channelId, query)
.then((res) => {
this.initUrl(res.data);
})
.finally(() => {
this.triggerPlay(this.hisData);
});
});
} else {
playback(this.deviceId, this.channelId, query)
.then((res) => {
this.initUrl(res.data);
})
.finally(() => {
this.triggerPlay(this.hisData);
});
}
},
/**触发播放*/
triggerPlay(playTimes) {
this.$refs.playbacker.playback(this.playurl, playTimes);
this.playing = true;
},
/**录像播放*/
seekPlay(s) {
const curTime = this.vodData.base + s.hour * 3600 + s.min * 60 + s.second;
const seekRange = curTime - this.vodData.start;
if (this.ssrc) {
const query = {
seek: seekRange,
};
const _this = this;
playbackSeek(this.deviceId, this.channelId, this.streamId, query).then((res) => {
_this.$refs.playbacker.setPlaybackStartTime(curTime);
});
}
},
/**关闭播放流*/
closeStream() {
if (this.playing && this.streamId) {
closeStream(this.deviceId, this.channelId, this.streamId).then((res) => {
this.streamId = '';
this.ssrc = '';
this.playurl = '';
this.playing = false;
});
// this.$refs.playbacker.destroy();
}
},
/**销毁录像播放器*/
destroy() {
if (this.playing && this.streamId) {
this.$refs.playbacker.destroy();
}
},
closeDestroy() {
this.closeStream();
this.destroy();
},
/**设置时间*/
timePickerChange: function (val) {
this.setTime(val[0], val[1]);
},
setTime: function (startTime, endTime) {
this.startTime = startTime;
this.endTime = endTime;
this.timeRange = [startTime, endTime];
},
/**下载录像*/
downloadRecord: function () {
const start = new Date(this.startTime).getTime() / 1000;
const end = new Date(this.endTime).getTime() / 1000;
const query = {
startTime: start,
endTime: end,
speed: '4',
};
startDownloadRecord(this.deviceId, this.channelId, query).then((res) => {
console.log('开始转存到流服务器:' + this.deviceId + ' : ' + this.channelId);
if (res.code === 200 ) {
this.$message({
type: 'success',
message: '转存到流服务器,请前往视频中心->录像管理查看!',
});
}
});
},
},
};
</script>

View File

@@ -0,0 +1,62 @@
<template>
<div id="easyplayer"></div>
</template>
<script>
export default {
name: 'player',
data() {
return {
easyPlayer: null
};
},
props: ['videoUrl', 'error', 'hasaudio', 'height'],
mounted () {
let paramUrl = decodeURIComponent(this.$route.params.url)
this.$nextTick(() =>{
if (typeof (this.videoUrl) == "undefined") {
this.videoUrl = paramUrl;
}
console.log("初始化时的地址为: " + this.videoUrl)
this.play(this.videoUrl)
})
},
watch:{
videoUrl(newData, oldData){
this.play(newData)
},
immediate:true
},
methods: {
play: function (url) {
console.log(this.height)
if (this.easyPlayer != null) {
this.easyPlayer.destroy();
}
if (typeof (this.height) == "undefined") {
this.height = false
}
this.easyPlayer = new WasmPlayer(null, 'easyplayer', this.eventcallbacK, {Height: this.height})
this.easyPlayer.play(url, 1)
},
pause: function () {
this.easyPlayer.destroy();
this.easyPlayer = null
},
},
destroyed() {
this.easyPlayer.destroy();
},
}
</script>
<style>
.LodingTitle {
min-width: 70px;
}
/* 隐藏logo */
.iconqingxiLOGO {
display: none !important;
}
</style>

View File

@@ -0,0 +1,363 @@
<template>
<div ref="container" @dblclick="fullscreenSwich"
style="width:100%;height:100%;background-color: #000000;margin:0 auto;">
<div class="buttons-box" id="buttonsBox">
<div class="buttons-box-left">
<i v-if="!playing" class="iconfont icon-play jessibuca-btn" @click="playBtnClick"></i>
<i v-if="playing" class="iconfont icon-pause jessibuca-btn" @click="pause"></i>
<i class="iconfont icon-stop jessibuca-btn" @click="destroy"></i>
<i v-if="isNotMute" class="iconfont icon-audio-high jessibuca-btn" @click="mute()"></i>
<i v-if="!isNotMute" class="iconfont icon-audio-mute jessibuca-btn" @click="cancelMute()"></i>
</div>
<div class="buttons-box-right">
<span class="jessibuca-btn">{{ kBps }} kb/s</span>
<i class="iconfont icon-camera1196054easyiconnet jessibuca-btn" @click="screenshot"
style="font-size: 1rem !important"></i>
<i class="iconfont icon-shuaxin11 jessibuca-btn" @click="playBtnClick"></i>
<i v-if="!fullscreen" class="iconfont icon-weibiaoti10 jessibuca-btn" @click="fullscreenSwich"></i>
<i v-if="fullscreen" class="iconfont icon-weibiaoti11 jessibuca-btn" @click="fullscreenSwich"></i>
</div>
</div>
</div>
</template>
<script>
let jessibucaPlayer = {};
export default {
name: 'jessibuca',
data() {
return {
playing: false,
isNotMute: false,
quieting: false,
fullscreen: false,
loaded: false, // mute
speed: 0,
performance: "", // 工作情况
kBps: 0,
btnDom: null,
videoInfo: null,
volume: 1,
rotate: 0,
vod: true, // 点播
forceNoOffscreen: false,
};
},
props: ['videoUrl', 'error', 'hasAudio', 'height'],
mounted() {
window.onerror = (msg) => {
// console.error(msg)
};
console.log(this._uid)
let paramUrl = decodeURIComponent(this.$route.params.url)
this.$nextTick(() => {
this.updatePlayerDomSize()
window.onresize = () => {
this.updatePlayerDomSize()
}
if (typeof (this.videoUrl) == "undefined") {
this.videoUrl = paramUrl;
}
this.btnDom = document.getElementById("buttonsBox");
console.log("初始化时的地址为: " + this.videoUrl)
this.play(this.videoUrl)
})
},
watch: {
videoUrl(newData, oldData) {
this.play(newData)
},
immediate: true
},
methods: {
updatePlayerDomSize() {
let dom = this.$refs.container;
let width = dom.parentNode.clientWidth
let height = (9 / 16) * width
const clientHeight = Math.min(document.body.clientHeight, document.documentElement.clientHeight)
if (height > clientHeight) {
height = clientHeight
width = (16 / 9) * height
}
dom.style.width = width + 'px';
dom.style.height = height + "px";
},
create() {
let options = {};
console.log("hasAudio " + this.hasAudio)
jessibucaPlayer[this._uid] = new window.JessibucaPro(Object.assign(
{
container: this.$refs.container,
autoWasm: true,
background: "",
controlAutoHide: false,
debug: false,
decoder: "/js/jessibuca-pro/decoder-pro.js",
forceNoOffscreen: true,
hasAudio: typeof (this.hasAudio) == "undefined" ? true : this.hasAudio,
hasVideo: true,
heartTimeout: 5,
heartTimeoutReplay: true,
heartTimeoutReplayTimes: 3,
hiddenAutoPause: false,
hotKey: false,
isFlv: false,
isFullResize: false,
isNotMute: this.isNotMute,
isResize: false,
keepScreenOn: false,
loadingText: "请稍等, 视频加载中......",
loadingTimeout: 10,
loadingTimeoutReplay: true,
loadingTimeoutReplayTimes: 3,
openWebglAlignment: false,
operateBtns: {
fullscreen: false,
screenshot: false,
play: false,
audio: false,
record: false
},
recordType: "webm",
rotate: 0,
showBandwidth: false,
supportDblclickFullscreen: false,
timeout: 10,
useMSE: location.hostname !== "localhost" && location.protocol !== "https:",
useOffscreen: false,
useWCS: location.hostname === "localhost" || location.protocol === "https",
useWebFullScreen: false,
videoBuffer: 0,
wasmDecodeAudioSyncVideo: true,
wasmDecodeErrorReplay: true,
wcsUseVideoRender: true
},
options
));
let jessibuca = jessibucaPlayer[this._uid];
let _this = this;
jessibuca.on("load", function () {
console.log("on load init");
});
jessibuca.on("log", function (msg) {
console.log("on log", msg);
});
jessibuca.on("record", function (msg) {
console.log("on record:", msg);
});
jessibuca.on("pause", function () {
_this.playing = false;
});
jessibuca.on("play", function () {
_this.playing = true;
});
jessibuca.on("fullscreen", function (msg) {
console.log("on fullscreen", msg);
_this.fullscreen = msg
});
jessibuca.on("mute", function (msg) {
console.log("on mute", msg);
_this.isNotMute = !msg;
});
jessibuca.on("audioInfo", function (msg) {
console.log("audioInfo", msg);
});
jessibuca.on("bps", function (bps) {
// console.log('bps', bps);
});
let _ts = 0;
jessibuca.on("timeUpdate", function (ts) {
// console.log('timeUpdate,old,new,timestamp', _ts, ts, ts - _ts);
_ts = ts;
});
jessibuca.on("videoInfo", function (info) {
console.log("videoInfo", info);
});
jessibuca.on("error", function (error) {
console.log("error", error);
});
jessibuca.on("timeout", function () {
console.log("timeout");
});
jessibuca.on('start', function () {
console.log('start');
})
jessibuca.on("performance", function (performance) {
let show = "卡顿";
if (performance === 2) {
show = "非常流畅";
} else if (performance === 1) {
show = "流畅";
}
_this.performance = show;
});
jessibuca.on('buffer', function (buffer) {
// console.log('buffer', buffer);
})
jessibuca.on('stats', function (stats) {
// console.log('stats', stats);
})
jessibuca.on('kBps', function (kBps) {
_this.kBps = Math.round(kBps);
});
// 显示时间戳 PTS
jessibuca.on('videoFrame', function () {
})
//
jessibuca.on('metadata', function () {
});
},
playBtnClick: function (event) {
this.play(this.videoUrl)
},
play: function (url) {
if (jessibucaPlayer[this._uid]) {
this.destroy().then(() => {
jessibucaPlayer[this._uid].on("play", () => {
this.playing = true;
this.loaded = true;
});
if (jessibucaPlayer[this._uid].hasLoaded()) {
jessibucaPlayer[this._uid].play(url);
} else {
jessibucaPlayer[this._uid].on("load", () => {
console.log("load 播放")
jessibucaPlayer[this._uid].play(url);
});
}
});
} else {
this.create();
jessibucaPlayer[this._uid].on("play", () => {
this.playing = true;
this.loaded = true;
});
if (jessibucaPlayer[this._uid].hasLoaded()) {
jessibucaPlayer[this._uid].play(url);
} else {
jessibucaPlayer[this._uid].on("load", () => {
console.log("load 播放")
jessibucaPlayer[this._uid].play(url);
});
}
}
},
pause: function () {
if (jessibucaPlayer[this._uid]) {
jessibucaPlayer[this._uid].pause();
}
this.playing = false;
this.err = "";
this.performance = "";
},
screenshot: function () {
if (jessibucaPlayer[this._uid]) {
jessibucaPlayer[this._uid].screenshot();
}
},
mute: function () {
if (jessibucaPlayer[this._uid]) {
jessibucaPlayer[this._uid].mute();
}
},
cancelMute: function () {
if (jessibucaPlayer[this._uid]) {
jessibucaPlayer[this._uid].cancelMute();
}
},
destroy: async function () {
if (jessibucaPlayer[this._uid]) {
await jessibucaPlayer[this._uid].destroy().then(() => {
this.create();
});
} else {
this.create();
}
if (document.getElementById("buttonsBox") == null) {
this.$refs.container.appendChild(this.btnDom)
}
jessibucaPlayer[this._uid] = null;
this.playing = false;
this.err = "";
this.performance = "";
},
eventcallbacK: function (type, message) {
// console.log("player 事件回调")
// console.log(type)
// console.log(message)
},
fullscreenSwich: function () {
let isFull = this.isFullscreen()
jessibucaPlayer[this._uid].setFullscreen(!isFull)
this.fullscreen = !isFull;
},
isFullscreen: function () {
return document.fullscreenElement ||
document.msFullscreenElement ||
document.mozFullScreenElement ||
document.webkitFullscreenElement || false;
}
},
destroyed() {
if (jessibucaPlayer[this._uid]) {
jessibucaPlayer[this._uid].destroy();
}
this.playing = false;
this.loaded = false;
this.performance = "";
},
}
</script>
<style>
@import '../css/iconfont.css';
.buttons-box {
width: 100%;
height: 28px;
background-color: rgba(43, 51, 63, 0.7);
position: absolute;
display: -webkit-box;
display: -ms-flexbox;
display: flex;
left: 0;
bottom: 0;
user-select: none;
z-index: 10;
}
.jessibuca-btn {
width: 20px;
color: rgb(255, 255, 255);
line-height: 27px;
margin: 0px 10px;
padding: 0px 2px;
cursor: pointer;
text-align: center;
font-size: 0.8rem !important;
}
.buttons-box-right {
position: absolute;
right: 0;
}
</style>

View File

@@ -0,0 +1,347 @@
<template>
<div class="root">
<div class="container-shell">
<div id="container" ref="container"></div>
</div>
</div>
</template>
<script>
let jessibucaPlayer = {};
import { ptzdirection, ptzscale } from '@/api/iot/sipdevice';
export default {
name: 'player',
props: {
playerinfo: {
type: Object,
default: null,
},
},
mounted() {
console.log(this._uid);
},
watch: {
playerinfo: function (newVal, oldVal) {
console.log('playerinfo 发生变化');
this.playinfo = newVal;
if (this.playinfo && this.playinfo.playtype !== '') {
this.playtype = this.playinfo.playtype;
}
},
},
jessibuca: null,
data() {
return {
isPlaybackPause: false,
useWebGPU: false,
isInit: false,
playinfo: {},
playtype: 'play',
operateBtns: {},
};
},
beforeDestroy() {},
created() {
this.playinfo = this.playerinfo;
if (this.playinfo && this.playinfo.playtype !== '') {
this.playtype = this.playinfo.playtype;
}
this.init();
},
methods: {
init() {
var isSupportWebgpu = 'gpu' in navigator;
if (isSupportWebgpu) {
console.log('支持webGPU');
this.useWebGPU = true;
} else {
console.log('暂不支持webGPU降级到webgl渲染');
this.useWebGPU = false;
}
const useVconsole = this.isMobile() || this.isPad();
if (useVconsole && window.VConsole) {
new window.VConsole();
}
this.$nextTick(() => {
this.initplayer();
});
},
initplayer() {
this.isPlaybackPause = false;
this.initconf();
jessibucaPlayer[this._uid] = new window.JessibucaPro({
container: this.$refs.container,
decoder: '/js/jessibuca-pro/decoder-pro.js',
videoBuffer: Number(0.2), // 缓存时长
isResize: false,
useWCS: false,
useMSE: false,
useSIMD: true,
wcsUseVideoRender: false,
loadingText: '加载中',
debug: false,
debugLevel: "debug",
showBandwidth: true, // 显示网速
showPlaybackOperate: true,
operateBtns: this.operateBtns,
forceNoOffscreen: true,
isNotMute: false,
showPerformance: false,
// playFailedAndReplay: true,
// networkDelayTimeoutReplay: true,
playbackForwardMaxRateDecodeIFrame: 4,
useWebGPU: this.useWebGPU, // 使用WebGPU
});
let jessibuca = jessibucaPlayer[this._uid];
this.initcallback(jessibuca);
this.isInit = true;
},
initconf() {
if (this.playtype === 'play') {
//直播按钮配置
this.operateBtns = {
fullscreen: true,
zoom: true,
ptz: true,
play: true,
};
} else {
//录像回放按钮配置
this.operateBtns = {
fullscreen: true,
zoom: true,
play: true,
ptz: false,
};
}
},
initcallback(jessibuca) {
const _this = this;
jessibuca.on('error', function (error) {
console.log('jessibuca error');
console.log(error);
//_this.destroy();
});
jessibuca.on("playFailedAndPaused", function (reason, lastFrameInfo, msg) {
console.log('playFailedAndPaused', reason, msg);
// lastFrameInfo 是最后一帧的画面,可以用来重播的时候,显示最后一帧画面。
// msg 具体的错误信息。
});
jessibuca.on('visibilityChange', (value) => {
if (value === true) {
// 窗口显示
console.log('visibilityChange true');
} else {
// 窗口隐藏
console.log('visibilityChange false');
}
});
jessibuca.on('pause', function (pause) {
console.log('pause success!');
console.log(pause);
});
jessibuca.on('loading', function (load) {
console.log('loading success!');
console.log(load);
});
jessibuca.on('stats', function (s) {
console.log('stats is', s);
});
jessibuca.on('timeout', function (error) {
console.log('timeout:', error);
});
jessibuca.on('playbackPreRateChange', (rate) => {
jessibuca.forward(rate);
});
let pre = 0;
let cur = 0;
jessibuca.on('timeUpdate', function (ts) {
cur = parseInt(ts / 60000);
if (pre !== cur) {
pre++;
}
});
jessibuca.on(JessibucaPro.EVENTS.ptz, (arrow) => {
console.log('ptz arrow', arrow);
_this.handlePtz(arrow);
});
jessibuca.on('crashLog', (data) => {
console.log('crashLog is', data);
});
},
registercallback(events, func) {
if (jessibucaPlayer[this._uid]) {
jessibucaPlayer[this._uid].on(events, func);
}
},
isMobile() {
return /iphone|ipad|android.*mobile|windows.*phone|blackberry.*mobile/i.test(window.navigator.userAgent.toLowerCase());
},
isPad() {
return /ipad|android(?!.*mobile)|tablet|kindle|silk/i.test(window.navigator.userAgent.toLowerCase());
},
play(url) {
if (jessibucaPlayer[this._uid]) {
jessibucaPlayer[this._uid].play(url);
}
},
pause() {
if (jessibucaPlayer[this._uid]) {
jessibucaPlayer[this._uid].pause();
}
},
replay(url) {
if (jessibucaPlayer[this._uid]) {
jessibucaPlayer[this._uid].destroy().then(() => {
this.initplayer();
this.play(url);
});
} else {
this.initplayer();
this.play(url);
}
},
handlePtz(arrow) {
let leftRight = 0;
let upDown = 0;
if (arrow === 'left') {
leftRight = 2;
} else if (arrow === 'right') {
leftRight = 1;
} else if (arrow === 'up') {
upDown = 1;
} else if (arrow === 'down') {
upDown = 2;
}
var data = {
leftRight: leftRight,
upDown: upDown,
moveSpeed: 125,
};
if (this.playinfo && this.playinfo.playtype !== '') {
ptzdirection(this.playinfo.deviceId, this.playinfo.channelId, data).then(async (response) => {
//console.log("云台方向控制:" + JSON.stringify(response));
});
}
},
playback(url, playTimes) {
if (jessibucaPlayer[this._uid]) {
jessibucaPlayer[this._uid].playback(url, {
playList: playTimes,
fps: 25, //FPS定频(本地设置)生效)
showControl: true,
showRateBtn: true,
isUseFpsRender: true, // 是否使用固定的fps渲染如果设置的fps小于流推过来的会造成内存堆积甚至溢出
isCacheBeforeDecodeForFpsRender: false, // rfs渲染时是否在解码前缓存数据
supportWheel: true, // 是否支持滚动轴切换精度。
rateConfig: [
{ label: '正常', value: 1 },
{ label: '2倍', value: 2 },
{ label: '4倍', value: 4 },
{ label: '8倍', value: 8 },
],
});
this.isPlaybackPause = false;
}
},
playbackPause() {
if (jessibucaPlayer[this._uid]) {
jessibucaPlayer[this._uid].playbackPause();
this.isPlaybackPause = true;
}
},
replayback(url, playTimes) {
if (jessibucaPlayer[this._uid]) {
jessibucaPlayer[this._uid].destroy().then(() => {
this.initplayer();
this.playback(url, playTimes);
});
} else {
this.initplayer();
this.playback(url, playTimes);
}
},
setPlaybackStartTime(curTime) {
if (jessibucaPlayer[this._uid]) {
jessibucaPlayer[this._uid].setPlaybackStartTime(curTime);
}
},
destroy() {
if (jessibucaPlayer[this._uid]) {
jessibucaPlayer[this._uid].destroy().then(() => {
this.initplayer();
});
}
},
close() {
if (jessibucaPlayer[this._uid]) {
jessibucaPlayer[this._uid].close();
}
},
},
};
</script>
<style scoped lang="scss">
.root {
display: flex;
margin-right: 3rem;
}
.container-shell {
backdrop-filter: blur(5px);
// background: hsla(0, 0%, 50%, 0.5);
//background: #fff;
//padding: 10px 4px 10px 4px;
/* border: 2px solid black; */
// width: auto;
position: relative;
border-radius: 10px;
// box-shadow: 0 5px 5px;
}
.container-shell:before {
//content: "设备播放器";
position: absolute;
color: darkgray;
//top: 4px;
left: 10px;
//text-shadow: 1px 1px black;
}
#container {
background: rgba(13, 14, 27, 0.7);
width: 1000px;
height: 630px;
border-radius: 5px;
}
.err {
position: absolute;
top: 40px;
left: 10px;
color: red;
}
.option {
position: absolute;
top: 4px;
right: 10px;
display: flex;
place-content: center;
font-size: 12px;
}
.option span {
color: white;
}
@media (max-width: 720px) {
#container {
width: 90vw;
height: 52.7vw;
}
}
</style>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long