2025-09-30 18:38:56 +08:00

647 lines
17 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div id="player-main">
<div id="player"></div>
<!-- 为每个窗口添加控制按钮容器 -->
<div class="video-controls" v-for="i in videoInfo.maxWindows" :key="i" :id="'controls-' + i" :style="{ display: 'none' }">
<div class="controls-top">
<div></div>
<div @click="stopPlay(i)" class="top-close">
关闭
<i class="el-icon-close"></i>
</div>
</div>
<div class="controls-bottom">
<div></div>
<div>
<el-tooltip class="item" effect="dark" :content="`${videoInfo.recordingBegin ? '结束' : '开始'}录制`" placement="top">
<div class="bgImage transcribe" @click="isTranscribe(i)"></div>
</el-tooltip>
<el-tooltip class="item" effect="dark" :content="`抓图`" placement="top">
<div class="bgImage screenshot" @click="capture('JPEG', i)"></div>
</el-tooltip>
<!-- <div>流畅</div> -->
<el-tooltip class="item" effect="dark" :content="`${muted ? '关闭' : '开启'}音量`" placement="top">
<div @click="handleVolume(i)" :class="videoInfo.muted ? 'openVolume' : 'disableVolume'" class="bgImage"></div>
</el-tooltip>
<el-tooltip class="item" effect="dark" :content="`${isFullScreen ? '退出' : '进入'}全屏模式`" placement="top">
<div
class="bgImage"
:class="videoInfo.isFullScreen ? 'exitFullScreen' : 'fullScreen'"
@click="singleFullScreen(i)"
></div>
</el-tooltip>
</div>
</div>
</div>
<div class="player-tool">
<el-tooltip class="item" effect="dark" :content="`默认`" placement="top">
<div class="bgImage splitscreen1" @click="onTwoSubmit(1)"></div>
</el-tooltip>
<el-tooltip class="item" effect="dark" :content="`2x2`" placement="top">
<div class="bgImage splitscreen2" @click="onTwoSubmit(2)"></div>
</el-tooltip>
<el-tooltip class="item" effect="dark" :content="`4x4`" placement="top">
<div class="bgImage splitscreen3" @click="onTwoSubmit(4)"></div>
</el-tooltip>
<el-tooltip class="item" effect="dark" :content="`停止所有播放`" placement="top">
<div class="bgImage stopAll" @click="stopAllPlay"></div>
</el-tooltip>
<el-tooltip class="item" effect="dark" :content="`${videoInfo.isFullScreen ? '退出' : '进入'}全屏模式`" placement="top">
<div class="bgImage" :class="videoInfo.isFullScreen ? 'exitFullScreen' : 'fullScreen'" @click="wholeFullScreen(i)"></div>
</el-tooltip>
</div>
</div>
</template>
<script setup>
// 请求工具
// import http from "@/http/http2.js";
import { getVideoItemInfo } from "@/api/modules/mapConfig";
import moment from "moment";
import dayjs from "dayjs";
import { reactive, watch, onMounted } from "vue";
import { ElMessage } from "element-plus";
import loadingGif from "@/assets/images/iscImage/loading.gif";
import errorPng from "@/assets/images/iscImage/text-to-image.png";
const props = defineProps({
devList: {
type: Array,
default: () => []
}
});
const videoInfo = reactive({
// 监控点编码
code: "",
// 播放器对象
player: null,
devH5List: [],
numCount: 1,
iWndIndex: 0,
maxWindows: 16, // 最大窗口数
recordingBegin: false,
muted: true,
playback: {
startTime: "2023-08-16T00:00:00",
endTime: "2023-08-16T23:00:00",
valueFormat: moment.HTML5_FMT.DATETIME_LOCAL_SECONDS,
seekStart: "2023-08-16T10:00:00",
rate: ""
},
isFullScreen: false,
isFullScreenAll: false
});
// 关闭所有视频
const stopAllPlay = () => {
videoInfo.player.JS_StopRealPlayAll().then(
() => {
videoInfo.playback.rate = 0;
console.log("stopAllPlay success");
videoInfo.devH5List.forEach((item, index) => {
const wnd = document.querySelector(`#player #player-container-${index}`);
if (wnd) {
const controls = document.getElementById(`controls-${index + 1}`);
if (controls) {
controls.classList.remove("video-controls_flex");
}
}
});
videoInfo.devH5List = [];
},
e => {
console.error(e);
}
);
};
// 关闭单个
const stopPlay = (currentWindowIndex, type) => {
videoInfo.player.JS_Stop(currentWindowIndex - 1).then(
() => {
videoInfo.playback.rate = 0;
console.log("stop realplay success");
const wnd = document.querySelector(`#player #player-container-${currentWindowIndex - 1}`);
if (wnd) {
const controls = document.getElementById(`controls-${currentWindowIndex}`);
if (controls) {
controls.classList.remove("video-controls_flex");
}
if (type == "delete") return;
videoInfo.devH5List.splice(currentWindowIndex - 1, 1);
}
},
e => {
console.error(e);
}
);
};
const handleVolume = currentWindowIndex => {
if (videoInfo.muted) {
openSound(currentWindowIndex);
} else {
closeSound(currentWindowIndex);
}
};
/* 声音、抓图、录像 */
const openSound = currentWindowIndex => {
videoInfo.player.JS_OpenSound(currentWindowIndex - 1).then(
() => {
console.log("openSound success");
videoInfo.muted = false;
},
e => {
console.error(e);
}
);
};
const closeSound = currentWindowIndex => {
videoInfo.player.JS_CloseSound(currentWindowIndex - 1).then(
() => {
console.log("closeSound success");
videoInfo.muted = true;
},
e => {
console.error(e);
}
);
};
// 单个窗口全屏
const singleFullScreen = currentWindowIndex => {
if (videoInfo.isFullScreenAll) {
videoInfo.wholeFullScreen();
return;
}
videoInfo.player.JS_FullScreenSingle(currentWindowIndex - 1).then(
() => {
console.log(`singleFullScreen success`);
},
e => {
console.error(e);
}
);
};
// 整体窗口全屏
const wholeFullScreen = () => {
videoInfo.player.JS_FullScreenDisplay(videoInfo.isFullScreenAll).then(
() => {
console.log(`wholeFullScreen success`);
videoInfo.isFullScreenAll = !videoInfo.isFullScreenAll;
},
e => {
console.error(e);
}
);
};
const isTranscribe = currentWindowIndex => {
if (videoInfo.recordingBegin) {
recordStop(currentWindowIndex);
} else {
recordStart("MP4", currentWindowIndex);
}
};
// 开始录制
const recordStart = (type, currentWindowIndex) => {
const codeMap = { MP4: 5, PS: 2 };
//let options = {irecordType: 2, cbStreamCB: streamcb}
let player = videoInfo.player,
index = currentWindowIndex - 1,
fileName = `${moment().format("YYYYMMDDHHmmss")}.mp4`,
typeCode = codeMap[type];
player
.JS_StartSaveEx(index, fileName, typeCode, {
irecordType: 1,
cbStreamCB: videoInfo.streamcb
})
.then(
() => {
ElMessage.success("开始录制");
videoInfo.recordingBegin = true;
console.log("record start ...");
},
e => {
console.error(e);
}
);
};
// 结束录制
const recordStop = currentWindowIndex => {
let player = videoInfo.player,
index = currentWindowIndex - 1;
player.JS_StopSave(index).then(
res => {
console.log("record stoped, saving ...", res);
videoInfo.recordingBegin = false;
},
e => {
console.error(e);
}
);
};
// 抓图
const capture = (imageType, currentWindowIndex) => {
let player = videoInfo.player,
index = currentWindowIndex - 1;
player.JS_CapturePicture(index, "img", imageType).then(
() => {
console.log("capture success", imageType);
},
e => {
console.error(e);
}
);
};
/**
* 初始化播放器
*/
const initPlayer = () => {
videoInfo.player = new JSPlugin({
// 需要英文字母开头 必填
szId: "player",
// 必填,引用H5player.min.js的js相对路径
szBasePath: "/public/bin",
iMaxSplit: 4,
openDebug: true,
mseWorkerEnable: false, //是否开启多线程解码分辨率大于1080P建议开启否则可能卡顿
bSupporDoubleClickFull: true, //是否支持双击全屏true-双击是全屏false-双击无响应
// 分屏播放默认最大分屏4*4
// iMaxSplit: 16,
iCurrentSplit: 1,
// 样式
oStyle: {
border: "#343434",
borderSelect: "#FFCC00",
background: "#000"
}
});
// 事件回调绑定
videoInfo.player.JS_SetWindowControlCallback({
windowEventSelect: function (iWndIndex) {
//插件选中窗口回调
console.log("windowSelect callback: ", iWndIndex);
videoInfo.iWndIndex = iWndIndex;
},
pluginErrorHandler: function (iWndIndex, iErrorCode, oError) {
//插件错误回调
console.log("pluginError callback: ", iWndIndex, iErrorCode, oError);
},
windowEventOver: function (iWndIndex) {
//鼠标移过回调
console.log(iWndIndex);
// 使用选中事件来定位控制按钮
const wnd = document.querySelector(`#player #player-container-${iWndIndex}`);
console.log(1111222, wnd);
if (wnd) {
const controls = document.getElementById(`controls-${iWndIndex + 1}`);
console.log(controls, wnd);
if (controls && !wnd.contains(controls)) {
// 确保控制按钮只添加一次
if (!wnd.querySelector(".video-controls")) {
wnd.appendChild(controls);
// controls.style.display = "flex";
}
}
if (controls) {
// if (iWndIndex > videoInfo.devH5List.length - 1) return;
// controls.classList.add("video-controls_flex");
const player_playVideo = wnd.querySelector(`#player_playVideo${iWndIndex}`);
if (player_playVideo.src) {
controls.classList.add("video-controls_flex");
}
}
}
},
windowEventOut: function (iWndIndex) {
//鼠标移出回调
console.log(iWndIndex);
const wnd = document.querySelector(`#player #player-container-${iWndIndex}`);
if (wnd) {
const controls = document.getElementById(`controls-${iWndIndex + 1}`);
if (controls) {
controls.classList.remove("video-controls_flex");
}
}
},
windowEventUp: function (iWndIndex) {
//鼠标mouseup事件回调
//console.log(iWndIndex);
},
windowFullCcreenChange: function (bFull) {
//全屏切换回调
console.log("fullScreen callback: ", bFull);
videoInfo.isFullScreen = bFull;
videoInfo.isFullScreenAll = false;
},
firstFrameDisplay: function (iWndIndex, iWidth, iHeight) {
//首帧显示回调
console.log("firstFrame loaded callback: ", iWndIndex, iWidth, iHeight);
},
performanceLack: function (iWndIndex) {
//性能不足回调
console.log("performanceLack callback: ", iWndIndex);
},
StreamEnd: function (iWndIndex) {
//性能不足回调
console.log("recv StreamEnd: ", iWndIndex);
},
StreamHeadChanged: function (iWndIndex) {
console.log("recv StreamHeadChanged: ", iWndIndex);
},
ThumbnailsEvent: (iWndIndex, eventType, eventCode) => {
console.log("recv ThumbnailsEvent: " + iWndIndex + ", eventType:" + eventType + ", eventCode:" + eventCode);
},
InterruptStream: (iWndIndex, iTime) => {
console.log("recv InterruptStream: " + iWndIndex + ", iTime:" + iTime);
},
ElementChanged: (iWndIndex, szElementType) => {
//回调采用的是video还是canvas
console.log("recv ElementChanged: " + iWndIndex + ", szElementType:" + szElementType);
}
});
play(videoInfo.devH5List[0], videoInfo.iWndIndex);
};
/**
* 获取取流连接
* @returns {*}
*/
const getPreviewUrl = row => {
let tempCode = row.serialNumber;
const param = {
cameraIndexCode: tempCode,
streamType: row.defaultStreamType == 2 ? 0 : row.defaultStreamType,
type: window.location.protocol.includes("https") ? "wss" : "ws",
transmode: 1,
itemId: row.itemId
};
// 这里
return getVideoItemInfo(param);
};
/**
* 播放
*/
const play = (row, index) => {
stopPlay(index + 1, "delete");
getPreviewUrl({
...row
}).then(res => {
if (res.code !== 200 && !res.result.videoInfo) {
ElMessage.warning("获取视频流失败!");
return;
}
// 视频添加加载中图片
const id = "player-container-" + index;
var d1 = document.getElementById(id); //获取div元素
d1.childNodes.forEach(item => {
if (item.nodeName == "" || item.nodeName == "IMG") {
d1.removeChild(item);
}
});
var im = document.createElement("img"); //创建图片
im.src = loadingGif;
//图片设置成和div一样大小
const divHeightNum = d1.style.height.slice(0, d1.style.height.length - 2);
// const imgHeightNum = divHeightNum * 0.2
const imgHeightNum = 256;
im.style.width = imgHeightNum + "px";
im.style.height = imgHeightNum + "px";
im.style.position = "absolute";
im.style.top = "50%";
im.style.left = "50%";
const positionNum = imgHeightNum / 2 - imgHeightNum;
im.style.marginLeft = positionNum + "px";
im.style.marginTop = positionNum + "px";
d1.appendChild(im); //图片挂载到div上
let preUrl = res.result.videoInfo.url;
const param = {
playURL: preUrl,
// 1高级模式 0普通模式高级模式支持所有
mode: 0
};
// 索引默认0
if (!index) {
index = 0;
}
videoInfo.player.JS_Play(preUrl, param, index).then(
() => {
// 播放成功回调
d1.removeChild(d1.childNodes[d1.childNodes.length - 1]);
console.log("播放成功");
},
err => {
console.log("播放失败");
im.src = errorPng;
const imgHeightNum = 20;
const imgWidthNum = 150;
im.style.width = imgWidthNum + "px";
im.style.height = imgHeightNum + "px";
const positionNum = imgHeightNum / 2 - imgHeightNum;
const positionWidthNum = imgWidthNum / 2 - imgWidthNum;
im.style.marginLeft = positionWidthNum + "px";
im.style.marginTop = positionNum + "px";
}
);
videoInfo.player.JS_SetConnectTimeOut(index, 60).then(
() => {
console.info("JS_SetConnectTimeOut success");
// do you want...
},
err => {
console.info("JS_SetConnectTimeOut failed", err);
// do you want...
}
);
});
};
/**
* 分屏,这里我太懒了,就循环了一个视频流
*/
const onTwoSubmit = num => {
// 这里的分屏是以列来算的如果这里参数2那么就是横竖两列就是4格
videoInfo.numCount = num;
videoInfo.player.JS_ArrangeWindow(num).then(
() => {
// 循环取流
// for (let i = 0; i < num * num; i++) {
// videoInfo.play(i);
// }
},
e => {
console.error(e);
}
);
};
watch(
() => props.devList,
(a, b) => {
console.log("isc_plugin.vue获取到设备列表", a, b, videoInfo.devH5List);
//a是value的新值b是旧值
a.forEach((item, index) => {
if (videoInfo.numCount == 1) {
play(item, videoInfo.iWndIndex);
} else if (a.length == 1) {
play(item, videoInfo.iWndIndex);
} else {
play(item, videoInfo.index);
}
});
videoInfo.devH5List = a;
},
{ deep: true }
);
onMounted(() => {
videoInfo.devH5List = props.devList;
console.log("我进来了", props.devList);
// 页面加载初始化`
initPlayer();
});
</script>
<style lang="scss" scoped>
.bgImage {
width: 18px;
height: 18px;
background-repeat: no-repeat;
background-size: 100% 100%;
cursor: pointer;
}
.transcribe {
width: 20px;
height: 20px;
background-image: url("@/assets/images/iscImage/transcribe.png");
}
.screenshot {
background-image: url("@/assets/images/iscImage/screenshot.png");
}
.disableVolume {
background-image: url("@/assets/images/iscImage/disableVolume.png");
}
.fullScreen {
background-image: url("@/assets/images/iscImage/fullScreen.png");
}
.openVolume {
background-image: url("@/assets/images/iscImage/openVolume.png");
}
.exitFullScreen {
background-image: url("@/assets/images/iscImage/exitFullScreen.png");
}
.splitscreen1 {
background-image: url("@/assets/images/iscImage/splitscreen1.png");
}
.splitscreen2 {
background-image: url("@/assets/images/iscImage/splitscreen2.png");
}
.splitscreen3 {
background-image: url("@/assets/images/iscImage/splitscreen3.png");
}
.stopAll {
background-image: url("@/assets/images/iscImage/stopAll.png");
}
.video-controls_flex {
display: flex !important;
}
/* 添加控制按钮样式 */
.video-controls {
width: 100%;
height: 100%;
position: absolute;
z-index: 100;
// background: rgba(0, 0, 0, 0.7);
display: none;
flex-direction: column;
justify-content: space-between;
.controls-bottom {
height: 35px;
display: flex;
justify-content: space-between;
align-items: center;
background: rgba(0, 0, 0, 0.7);
color: white;
padding: 0 16px;
> div {
display: flex;
justify-content: space-between;
align-items: center;
> div:not(:first-child) {
margin-left: 15px;
}
}
}
.controls-top {
padding: 16px;
display: flex;
justify-content: space-between;
align-items: center;
.top-close {
width: 84px;
height: 27px;
background: rgba(0, 0, 0, 0.7);
border-radius: 41px;
font-size: 15px;
color: #ffffff;
display: flex;
justify-content: center;
align-items: center;
> i {
margin-left: 5px;
}
}
}
}
#player-main {
width: 100%;
height: 100%;
}
#player {
width: 100%;
height: calc(100% - 40px);
}
.player-tool {
display: flex;
align-items: center;
justify-content: flex-end;
background-color: #3d3d3d;
height: 40px;
padding: 0 16px;
> div:not(:first-child) {
margin-left: 16px;
}
}
.hikvision-player {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
}
#video-container {
width: 100%;
height: 500px;
background-color: #000;
}
.control-bar {
padding: 10px;
background: #f5f5f5;
display: flex;
align-items: center;
gap: 10px;
}
.video-source {
padding: 10px;
background: #f5f5f5;
display: flex;
gap: 10px;
}
.volume-control {
display: flex;
align-items: center;
gap: 10px;
}
</style>