提交: 视频管理-分屏展示 功能 *使用 livePlayer 播放器。

This commit is contained in:
23688nl 2022-09-14 18:05:56 +08:00
parent 52b5d81481
commit 3d1e5a545b
24 changed files with 1591 additions and 11 deletions

View File

@ -36,6 +36,7 @@
"url": "https://github.com/histroniot/smart-power-ui.git" "url": "https://github.com/histroniot/smart-power-ui.git"
}, },
"dependencies": { "dependencies": {
"@liveqing/liveplayer": "^2.7.0",
"@riophae/vue-treeselect": "0.4.0", "@riophae/vue-treeselect": "0.4.0",
"axios": "0.21.0", "axios": "0.21.0",
"clipboard": "2.0.6", "clipboard": "2.0.6",

1
public/cdn/js/jswebrtc.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<cross-domain-policy>
<allow-access-from domain="*"/>
</cross-domain-policy>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Binary file not shown.

BIN
public/images/16gg.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 180 B

BIN
public/images/16gg_bc.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 183 B

BIN
public/images/video.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

@ -11,7 +11,7 @@
<link rel="stylesheet" href="//at.alicdn.com/t/font_2506643_9w119og75cs.css"> <link rel="stylesheet" href="//at.alicdn.com/t/font_2506643_9w119og75cs.css">
<link rel="stylesheet" href="//at.alicdn.com/t/c/font_2506643_ncb9eqoon2o.css"> <link rel="stylesheet" href="//at.alicdn.com/t/c/font_2506643_5jmnx3lcnpu.css">
<script type="text/javascript" src="http://webapi.amap.com/maps?v=1.4.15&key=4b483d227a9c7dd12e46faae5a8c8af8"></script> <script type="text/javascript" src="http://webapi.amap.com/maps?v=1.4.15&key=4b483d227a9c7dd12e46faae5a8c8af8"></script>
<script src="<%= BASE_URL %>js/config.js"></script> <script src="<%= BASE_URL %>js/config.js"></script>
@ -20,6 +20,7 @@
<script src="https://a.amap.com/jsapi_demos/static/demo-center/js/demoutils.js"></script> <script src="https://a.amap.com/jsapi_demos/static/demo-center/js/demoutils.js"></script>
<script type="text/javascript" src="https://webapi.amap.com/maps?v=1.4.15&key=4b483d227a9c7dd12e46faae5a8c8af8&plugin=AMap.Geocoder"></script> <script type="text/javascript" src="https://webapi.amap.com/maps?v=1.4.15&key=4b483d227a9c7dd12e46faae5a8c8af8&plugin=AMap.Geocoder"></script>
<script type="text/javascript" src="<%= BASE_URL %>cdn/js/jessibuca/jessibuca.js"></script> <script type="text/javascript" src="<%= BASE_URL %>cdn/js/jessibuca/jessibuca.js"></script>
<script src="<%= BASE_URL %>cdn/js/liveplayer/liveplayer-lib.min.js"></script>
<title><%= webpackConfig.name %></title> <title><%= webpackConfig.name %></title>
<style> <style>
html, html,

View File

@ -80,3 +80,12 @@ export function screenshotList(query) {
params: query params: query
}) })
} }
// 查询未绑定 摄像头列表
export function unbindVideoDev(query) {
return request({
url: '/monitor/video/unbindVideoList',
method: 'get',
params: query
})
}

View File

@ -0,0 +1,19 @@
import request from '@/utils/request'
// 查询视频回放列表
export function listVideo(query) {
return request({
url: '/iot/replay/list',
method: 'get',
params: query
})
}
// 查询视频回放列表
export function splitScreenTree(query) {
return request({
url: '/monitor/splitScreenDisplay/treeList',
method: 'get',
params: query
})
}

56
src/api/video/site.js Normal file
View File

@ -0,0 +1,56 @@
import request from '@/utils/request'
import { preventRepeatFu } from "@/utils/hciot";
// 查询站点监控列表
export function listSite(query) {
return request({
url: '/iot/site/list',
method: 'get',
params: query
})
}
// 查询站点监控详细
export function getSite(siteId) {
return request({
url: '/iot/site/detail/' + siteId,
method: 'get'
})
}
// 新增站点监控
export function addSite(data) {
preventRepeatFu(true)
return request({
url: '/iot/site/add',
method: 'post',
data: data
})
}
// 修改站点监控
export function updateSite(data) {
preventRepeatFu(true)
return request({
url: '/iot/site/edit',
method: 'put',
data: data
})
}
// 删除站点监控
export function delSite(siteId) {
return request({
url: '/iot/site/del/' + siteId,
method: 'delete'
})
}
// 实时监控树形
export function siteTree(data) {
return request({
url: '/iot/site/tree',
method: 'get',
params: data
})
}

View File

@ -0,0 +1 @@
<svg t="1631782689827" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="7221" width="128" height="128"><path d="M1024.2 480L861.9 710c-9.3 13.2-21.5 23.7-35.4 30.9-13.9 7.2-29.5 11.1-45.7 11.1-18.7 0-37-5.3-52.8-15.2L98.4 341.5C87.8 334.8 79.2 325.6 73.3 315c-5.9-10.7-9.1-22.8-9.1-35.3 0-15.3 4.8-30.3 13.8-42.8l117.6-163c9.3-12.9 21.4-23.1 35.1-30.1 13.7-7 29.1-10.8 45-10.8 18.4 0 36.4 5.1 52.1 14.8L1024.2 480zM754.1 768c-11.1 0-22-1.5-32.6-4.5-10.6-3-20.7-7.4-30.2-13.2l-611.8-373c-6.7-4.1-15.3 0.7-15.3 8.6v22.6c0 14.6 7.6 28.2 20 35.8l182.5 112.3C241.2 572 224.2 600 224.2 632c0 20.2 6.8 38.8 18.3 53.7a62.518 62.518 0 0 1-44.2 18.3H64.2v-46.8c0-29-19.5-54.4-47.6-61.9L0.2 591l-0.2 369 28.8-14.3c21.7-10.8 35.5-33 35.5-57.3V768h135.5c25.9 0 50.8-10.3 69.2-28.7l3.1-3.1c10.6-10.6 25-16.3 40-16.3h0.2c46.4 0 84.4-35.9 87.8-81.4L674.2 807.2c9.2 5.7 19.9 8.7 30.7 8.7 9.8 0 19.3-2.5 27.7-6.9 8.4-4.5 15.7-11 21.1-19.2l6.8-10.1c3.2-5-0.4-11.7-6.4-11.7zM312.2 656c-13.2 0-24-10.8-24-24s10.8-24 24-24 24 10.8 24 24-10.8 24-24 24z" p-id="7222"></path></svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -0,0 +1 @@
<svg t="1631782749568" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="7516" width="128" height="128"><path d="M969.4 532.9c-16.2 10.3-26.8 27.3-29 46.4-22.6 197-205.2 350.6-427.6 350.6-221.6 0-403.7-152.7-427.3-349-2.3-18.7-12.7-35.6-28.5-45.8-2.2-1.4-4.4-2.8-6.5-4.3C20.1 510 1.9 475.5 1.4 438.6c-0.6-44.5 0.1-104.1 0.7-150 0.6-47.1 27.1-90.1 69-111.8 206.5-106.9 673-108.3 882.4-0.4 42.5 21.9 69.4 65.7 69.4 113.6v146.2c0 38.4-19.1 74.4-51.3 95.3-0.8 0.5-1.5 0.9-2.2 1.4z m-617 251.4c22 37.2 91.1 65.7 160.4 65.7s136.9-26.9 158.8-64l-0.7-210c-14.8-73-81.1-126.1-158.1-126.1s-145.7 53.7-160.4 126.8v207.6z m574.8-533.8C767 157.4 298 150.8 100 248.5c-10.8 5.3-17.6 16.3-17.8 28.3-0.3 32.8-0.9 92.2 1.5 115.6 151.5-95.3 703.3-93.9 859.4 0V278.1c0.1-11.4-6-21.9-15.9-27.6zM512.8 609.9c43.9 0 79.7 39.4 79.7 88.1 0 48.7-35.7 88.1-79.7 88.1s-79.7-39.4-79.7-88.1c-0.1-48.7 35.7-88.1 79.7-88.1z" p-id="7517"></path></svg>

After

Width:  |  Height:  |  Size: 961 B

View File

@ -0,0 +1,190 @@
<template>
<div class="cloud-control-wrap">
<div
class="direction-new-wrap"
:style="{
width: width,
height: width
}">
<div class="direction-new-item">
<el-button
type="text"
icon="el-icon-caret-top"
style="font-size: 35px;transform: rotate(-45deg);"
:disabled="disabledState"
@mousedown.native="handleCloudSend('left')"
@mouseup.native="handleCloudSend('stop')"
/>
</div>
<div class="direction-new-item">
<el-button
type="text"
style="font-size: 35px;transform: rotate(-45deg);"
:disabled="disabledState"
@mousedown.native="handleCloudSend('up')"
@mouseup.native="handleCloudSend('stop')"
icon="el-icon-caret-right"
/>
</div>
<div class="direction-new-item">
<el-button
type="text"
style="font-size: 35px;transform: rotate(-45deg);"
:disabled="disabledState"
@mousedown.native="handleCloudSend('down')"
@mouseup.native="handleCloudSend('stop')"
icon="el-icon-caret-left"
/>
</div>
<div class="direction-new-item">
<el-button
type="text"
style="font-size: 35px;transform: rotate(-45deg);"
:disabled="disabledState"
@mousedown.native="handleCloudSend('right')"
@mouseup.native="handleCloudSend('stop')"
icon="el-icon-caret-bottom"
/>
</div>
</div>
<!-- <div class="direction-wrap">
<div class="direction-item">
<el-button type="text" icon="" />
</div>
<div class="direction-item">
<el-button
type="text"
icon="el-icon-caret-right"
style="font-size: 35px;transform: rotate(-45deg);"
:disabled="disabledState"
@mousedown.native="handleCloudSend('left')"
@mouseup.native="handleCloudSend('stop')"
/>
</div>
<div class="direction-item">
<el-button type="text" icon="" />
</div>
<div class="direction-item">
<el-button
type="text"
style="font-size: 35px;transform: rotate(-45deg);"
:disabled="disabledState"
@mousedown.native="handleCloudSend('up')"
@mouseup.native="handleCloudSend('stop')"
icon="el-icon-caret-left"
/>
</div>
<div class="direction-item">
<el-button type="text" icon="" />
</div>
<div class="direction-item">
<el-button
type="text"
style="font-size: 35px;transform: rotate(-45deg);"
:disabled="disabledState"
@mousedown.native="handleCloudSend('down')"
@mouseup.native="handleCloudSend('stop')"
icon="el-icon-caret-right"
/>
</div>
<div class="direction-item">
<el-button type="text" icon="" />
</div>
<div class="direction-item">
<el-button
type="text"
style="font-size: 35px;transform: rotate(-45deg);"
:disabled="disabledState"
@mousedown.native="handleCloudSend('right')"
@mouseup.native="handleCloudSend('stop')"
icon="el-icon-caret-bottom"
/>
</div>
<div class="direction-item">
<el-button type="text" icon="" />
</div>
</div>
<div class=""></div> -->
</div>
</template>
<script>
export default {
name: "CloudControl",
props: {
disabledState: {
type: Boolean,
default: true,
},
width: {
type: String,
default: '120px'
}
},
methods: {
handleCloudSend(type) {
this.$emit("sendCloud", type);
},
handleClick() {},
},
};
</script>
<style scoped>
.cloud-control-wrap {
width: 100%;
height: 100%;
display: flex;
flex-wrap: wrap;
justify-content: center;
align-items: center;
min-height: 120px;
min-width: 120px;
}
.direction-wrap {
width: 180px;
height: 200px;
display: flex;
flex-wrap: wrap;
flex-wrap: wrap;
justify-content: space-evenly;
align-items: center;
}
.direction-item {
width: 50px;
height: 50px;
display: flex;
justify-content: center;
align-items: center;
}
.direction-new-wrap {
width: 100%;
height: 100%;
min-height: 120px;
min-width: 120px;
border-radius: 50%;
overflow: hidden;
display: flex;
flex-wrap: wrap;
background: url(~@/assets/images/cloudBg1.png?);
background-size: cover;
background-position: center;
transform: rotate(-45deg);
}
.direction-new-item {
/* border: 1px solid #8d8d8d; */
width: 50%;
height: 50%;
display: flex;
justify-content: center;
align-items: center;
}
.direction-new-item:hover {
/* box-shadow: 1px 1px 1px 1px slategrey; */
}
</style>

View File

@ -1,6 +1,7 @@
import axios from 'axios'
import { serviceApi } from '@/config/dvr.config' import { serviceApi } from '@/config/dvr.config'
import { getPTZCmd, PTZ_TYPE } from './ptz-cmd.js' import { getPTZCmd, PTZ_TYPE } from './ptz-cmd.js'
import axios from 'axios'
/** /**
* 获取gbs 指令接口 api * 获取gbs 指令接口 api
* @param {*} v 使用 m7s 版本 * @param {*} v 使用 m7s 版本
@ -141,8 +142,8 @@ export const handleStopBye = (device, channel) => {
axiosGet(url, params) axiosGet(url, params)
} }
export const handleSendDirect = (reqUrl, params, type, ) => { export const handleSendDirect = (reqUrl, version, params, type ) => {
let url = `${reqUrl}control` let url = `${reqUrl}${getServiceApi(version)}control`
axiosGet(url, Object.assign(params, { axiosGet(url, Object.assign(params, {
ptzcmd: getPTZCmd({ ptzcmd: getPTZCmd({
type: PTZ_TYPE[type] type: PTZ_TYPE[type]

View File

@ -555,7 +555,7 @@
</template> </template>
<script> <script>
import { listInfo, getInfo, delInfo, addInfo, updateInfo } from "@/api/video/camera"; import { listInfo, getInfo, delInfo, addInfo, updateInfo, unbindVideoDev } from "@/api/video/camera";
import { getUser } from "@/api/system/user"; import { getUser } from "@/api/system/user";
import { listSite } from "@/api/iot/site"; import { listSite } from "@/api/iot/site";
import { initMap, gjzCode } from "@/utils/latlngFromAddress"; import { initMap, gjzCode } from "@/utils/latlngFromAddress";
@ -565,9 +565,9 @@ import SelectTableWrap from "@/components/SelectTable/index";
import ShopLocation from "@/components/Amap/components/shopLocation/index"; import ShopLocation from "@/components/Amap/components/shopLocation/index";
import SelectInput from "../profile/SelectInput/index"; import SelectInput from "../profile/SelectInput/index";
import { getToken } from "@/utils/auth"; import { getToken } from "@/utils/auth";
import { // import {
listDevice, // listDevice,
} from "@/api/iot/device"; // } from "@/api/iot/device";
const joinTypeOpt = { const joinTypeOpt = {
CAMERA: "摄像头直连", CAMERA: "摄像头直连",
@ -972,7 +972,7 @@ export default {
}, },
// //
getDeviceChildList(data) { getDeviceChildList(data) {
listDevice(Object.assign(JSON.parse(JSON.stringify(data.page)), JSON.parse(JSON.stringify(data.param)))).then(res => { unbindVideoDev(Object.assign(JSON.parse(JSON.stringify(data.page)), JSON.parse(JSON.stringify(data.param)))).then(res => {
this.tableSelectOption.tableList = res.rows; this.tableSelectOption.tableList = res.rows;
this.tableSelectOption.queryOpt.page.total = Number(res.total); this.tableSelectOption.queryOpt.page.total = Number(res.total);
}) })
@ -1092,7 +1092,7 @@ export default {
/** 新增按钮操作 */ /** 新增按钮操作 */
handleAdd() { handleAdd() {
this.reset(); this.reset();
this.getTenantList(); // this.getTenantList();
this.open = true; this.open = true;
this.tempType = "create"; this.tempType = "create";
this.title = "添加监控设备"; this.title = "添加监控设备";
@ -1101,7 +1101,7 @@ export default {
handleUpdate(row) { handleUpdate(row) {
this.tempType = "update"; this.tempType = "update";
this.reset(); this.reset();
this.getTenantList(); // this.getTenantList();
const recordId = row.recordId || this.ids; const recordId = row.recordId || this.ids;
getInfo(recordId).then((response) => { getInfo(recordId).then((response) => {
this.form = response.data; this.form = response.data;

View File

@ -0,0 +1,588 @@
<template>
<div
class="gbs-screen-view"
style="background: #f4f4f4; height: calc(100vh - 85px); padding: 10px"
>
<el-row :gutter="20">
<el-col :span="5" style="overflow: hidden">
<div>
<span
style="
display: block;
height: 40px;
width: 100%;
background-color: #2b2f3a;
color: #fff;
line-height: 42px;
padding-left: 12px;
margin-bottom: 5px;
"
>监控资源</span
>
<el-tree
style="
height: 100%;
padding-top: 10px;
height: calc(100vh - 370px);
overflow: auto;
"
class="zzjg-video-tree"
:data="deptOptions"
:props="defaultProps"
node-key="treeId"
:default-expanded-keys="expandedList"
:expand-on-click-node="true"
@node-expand="handleNodeExpand"
@node-collapse="handleNodeCollapse"
:filter-node-method="filterNode"
ref="entityTreeRef"
>
<div
class="custom-tree-node"
style="display: flex; width: 100%; align-items: baseline"
slot-scope="{ node, data }"
>
<i class="el-icon-folder-opened" v-if="node.level !== 3"></i>
<svg-icon
v-if="data.videoType === 'GUN_MACHINE'"
icon-class="A_qjsxt"
class="svg-icon"
style="position: relative; top: 3px"
/>
<svg-icon
v-else-if="data.videoType === 'BALL_MACHINE'"
icon-class="A_yjsxt"
class="svg-icon"
style="position: relative; top: 3px"
/>
<span
:title="data.playing ? '在线' : '离线'"
class="palying-span"
:style="computeIsPlaying(data) || data.playing ? 'background: #27e874;' : ''"
v-if="node.level === 3"
></span>
<span
style="font-size: 13px; margin-left: 5px; line-height: 18px"
@click="handlePaly(node, data)"
>{{ node.label }}</span
>
<el-button
type="text"
v-if="node.level === 3"
@click="handlePaly(node, data)"
>
<i
:class="
computeIsPlaying(data) || node.data.playing
? 'el-icon-video-camera-solid'
: 'el-icon-video-play'
"
:style="
computeIsPlaying(data) || node.data.playing
? 'margin-left: 5px; color: #37dc11;'
: 'margin-left: 5px; '
"
></i>
<span v-if="!computeIsPlaying(data) && !data.playing"
>播放</span
>
</el-button>
</div>
</el-tree>
</div>
<div class="video-right">
<cloud-control
:disabledState="false"
width="200px"
@sendCloud="sendCloud"
></cloud-control>
</div>
</el-col>
<el-col :span="19" id="videoView">
<div
:class="
fullscreen
? 'third-party-right-view fullscreen'
: 'third-party-right-view'
"
v-loading="splitScreenLoading"
element-loading-text="拼命加载中"
element-loading-spinner="el-icon-loading"
element-loading-background="rgba(0, 0, 0, 0.8)"
>
<!-- <split-screen
v-if="!splitScreenLoading && !isWebRTC"
ref="splitScreen"
:scale="scale"
:serviceUrl="getServiceUrl()"
:split="splitType"
/> -->
<e-other-screen
v-if="!splitScreenLoading"
ref="splitScreen"
:scale="scale"
:serviceUrl="getServiceUrl()"
:split="splitType"
@handleTreeNodes="handleTreeNodes"
/>
</div>
<div
class="third-rarty-right-option"
:style="isWebRTC ? 'justify-content: right' : ''"
>
<div
class="ptz-attribute"
v-show="!isWebRTC"
style="
padding-left: 5px;
display: flex;
justify-content: left;
width: 300px;
align-items: center;
"
>
<el-radio-group v-model="scale" size="mini" @change="scaleChange">
<el-radio-button :label="0">拉伸填充</el-radio-button>
<el-radio-button :label="1">等比缩放</el-radio-button>
<el-radio-button :label="2">完全填充</el-radio-button>
</el-radio-group>
</div>
<div>
<!-- <el-button
type="text"
icon="el-icon-menu"
size="medium"
:style="splitType === 1 ? 'color: #1890ff' : ''"
@click="radioScreenChange(1)"
title="一宫格"
></el-button> -->
<el-button
type="text"
icon="el-icon-menu"
size="medium"
:style="splitType === 4 ? 'color: #1890ff' : ''"
@click="radioScreenChange(4)"
title="四宫格"
></el-button>
<el-button
:style="splitType === 9 ? 'color: #1890ff' : ''"
type="text"
icon="el-icon-s-grid"
size="medium"
@click="radioScreenChange(9)"
title="九宫格"
></el-button>
<el-button
type="text"
size="medium"
@click="radioScreenChange(16)"
title="十六宫格"
>
<img
:src="
splitType === 16 ? '/images/16gg_bc.png' : '/images/16gg.png'
"
style="width: 12px; margin-top: 1px"
/>
</el-button>
<el-button
type="text"
icon="el-icon-full-screen"
@click="screen"
size="medium"
title="全屏"
></el-button>
</div>
</div>
</el-col>
</el-row>
</div>
</template>
<script>
import CloudControl from "@/components/JessibucaPlay/CloudControl";
import {
getServiceUrlWS,
handleStartInviteLive,
getServiceUrl,
getServiceApi,
} from "@/utils/gbsLive";
import { splitScreenTree } from "@/api/video/playVideo";
import {
setRecordIdx,
getType,
setType,
init,
getScreenIsWebRTC,
setScreenIsWebRTC,
} from "./modules/screenRecord";
import EOtherScreen from "./modules/EOtherScreen";
export default {
name: "GbsScreen",
components: {
CloudControl,
EOtherScreen,
},
data() {
return {
splitType: 4,
fullscreen: false,
isWebRTC: true, // webRTC cookies true
splitOption: {
alone: 1,
four: 4,
nine: 9,
sixteen: 16,
},
defaultProps: {
children: "children",
label: "label",
key: "key",
},
deptOptions: [],
expandedList: [],
queryVideo1: {
siteId: "",
},
selectSite: {},
eventSource: null,
scale: 0,
splitScreenLoading: false,
};
},
created() {
let that = this;
let isWebRTCStart = getScreenIsWebRTC();
if (!isWebRTCStart) {
setScreenIsWebRTC(true);
this.isWebRTC = true;
} else {
this.isWebRTC = getScreenIsWebRTC() == "false" ? false : true;
}
window.onresize = function () {
if (!that.checkFull()) {
that.fullscreen = false;
}
};
this.splitType = Number(getType());
this.getTreeselect();
this.$forceUpdate();
},
computed: {},
methods: {
handleNodeExpand(e) {
this.expandedList.push(e.treeId);
},
handleNodeCollapse(e) {
this.expandedList = this.expandedList.filter((v) => v !== e.treeId);
},
scaleChange(e) {
this.scale = e;
this.$forceUpdate();
// this.jessibuca.setScaleMode(this.scale)
},
/** 查询部门下拉树结构 */
getTreeselect() {
this.deptOptions = [];
this.splitScreenLoading = true;
splitScreenTree({ serverType: "M7S" }).then((res) => {
if (res.code === 200) {
this.deptOptions = res.data;
this.splitScreenLoading = init(this.deptOptions);
}
//
// if (res.data && res.data.length > 0) {
// this.eventResult({ val: res.data[0].id });
// }
});
},
checkFull() {
var isFull =
document.mozFullScreen ||
document.fullScreen ||
//Webkit
document.webkitIsFullScreen ||
document.webkitRequestFullScreen ||
document.mozRequestFullScreen ||
document.msFullscreenEnabled;
if (isFull === undefined) {
isFull = false;
}
return isFull;
},
computeIsPlaying(e) {
return this.$refs.splitScreen.getIsPlaying(e);
},
computeIsDisabled() {
return this.$refs.splitScreen.getIsDisabled();
},
sendCloud(e) {
this.$refs.splitScreen.sendCloud(e);
},
getServiceUrl() {
return `${getServiceUrl(
this.selectSite["https"] === true ? "https://" : "http://",
this.selectSite.m7sHost,
this.selectSite.m7sPort
)}${getServiceApi(this.selectSite["m7sVersion"])}`;
},
//
filterNode(value, data) {
if (!value) return true;
return data.label.indexOf(value) !== -1;
},
radioScreenChange(e) {
if (this.$refs.splitScreen) {
this.$refs.splitScreen.batchDestroyed();
}
setType(e);
this.splitType = e;
},
/**
* 2022-09-14 放弃 list 接口获取实时 状态
initEventSource() {
if (this.selectSite["serverType"] === "M7S") {
let url = `${getServiceUrl(
this.selectSite["https"] === true ? "https://" : "http://",
this.selectSite.m7sHost,
this.selectSite.m7sPort
)}${getServiceApi(this.selectSite["m7sVersion"])}list`;
if (this.eventSource) {
this.eventSource.close();
}
this.eventSource = new EventSource(url);
this.eventSource.onmessage = this.eventSourceMessage;
}
},
*/
/**
* 2022-09-14 放弃 list 接口获取实时 状态
eventResult(data) {
if (data.val) {
this.queryVideo1 = {
siteId: data["val"],
};
getSite(this.queryVideo1.siteId).then((response) => {
this.selectSite = response.data;
this.initEventSource();
});
}
},
*/
//
eventSourceMessage(event) {
var _this = this;
if (event.data !== "null" && _this.deptOptions) {
var newList = [...JSON.parse(event.data)];
var depTree = [..._this.deptOptions];
newList.forEach((camera) => {
if (camera["Channels"] && camera["Channels"].length > 0) {
camera["Channels"].forEach((cameraItem) => {
depTree.forEach((treeNode) => {
if (treeNode["children"] && treeNode["children"].length > 0) {
treeNode["children"].forEach((childNode) => {
if (
childNode.devChannel === cameraItem.DeviceID &&
camera.ID === childNode.devId
) {
childNode = Object.assign(childNode, cameraItem);
}
});
}
});
});
}
});
this.deptOptions = [...depTree];
this.deptOptions = this.deptOptions.concat();
this.$forceUpdate();
}
},
handleOpenInviteLive(node, data) {
var _this = this;
if (!this.computeIsPlaying(data)) {
this.$confirm("连接设备,并且开始播放直播!", "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning",
}).then(function () {
let selectIdx = _this.$refs.splitScreen.getSelectIdx();
setRecordIdx(data, selectIdx);
_this.$nextTick(() =>
_this.$refs.splitScreen.handleOpenInviteLive(
Object.assign(data, {
devChannel: data["devChannel"] || data.devChannel,
}),
selectIdx
)
);
_this.handleUpdatePlayStatus(node, data);
});
} else {
this.$refs.splitScreen.setPlayingIdx(row.data);
}
},
handleTreeNodes(data) {
let { getNode } = this.$refs.entityTreeRef;
if (this.$refs.entityTreeRef) {
let node = getNode(data.treeId);
if (node) {
this.handleUpdatePlayStatus(node, data, false);
}
}
},
/**
* 热更新 播放摄像头 状态
*/
handleUpdatePlayStatus(node, data, status) {
let { updateKeyChildren } = this.$refs.entityTreeRef;
let parentId = node.parent.data.treeId;
let parentChildNodes = node.parent.data.children || [];
parentChildNodes = parentChildNodes.map((v) => {
if (v["treeId"] === data["treeId"]) {
v["playing"] = status;
}
return v;
});
updateKeyChildren(parentId, [...parentChildNodes]);
},
handlePaly(node, data) {
if (node.level === 3) {
if (!this.computeIsPlaying(node.data)) {
if (data.LivePublisher) {
this.onTreeSelect(node);
} else {
this.handleOpenInviteLive(node, data);
}
} else {
this.$refs.splitScreen.setPlayingIdx(node.data);
}
this.$forceUpdate();
}
},
onTreeSelect(row) {
this.treeSelectKey = row["key"];
if (!row.data.LivePublisher) {
let siteInfo = {
https: row.data.https,
devId: row.data.devId,
m7sHost: row.data.siteM7sHost,
m7sPort: row.data.siteM7sPort,
m7sVersion: row.data.siteM7sVersion,
};
handleStartInviteLive(siteInfo, row.data);
} else {
const playUrl = `${getServiceUrlWS(
row.data["https"] === true ? "https://" : "http://",
row.data.siteM7sHost,
row.data.siteM7sPort
)}/jessica/${row.data.LivePublisher.StreamPath}`;
setRecordIdx(row.data, this.$refs.splitScreen.getSelectIdx());
this.$nextTick(() =>
this.$refs.splitScreen.setPassagewayByIdx(row.data, playUrl)
);
}
},
//
screen() {
let element = document.getElementById("videoView");
if (this.fullscreen) {
if (document.exitFullscreen) {
document.exitFullscreen();
} else if (document.webkitCancelFullScreen) {
document.webkitCancelFullScreen();
} else if (document.mozCancelFullScreen) {
document.mozCancelFullScreen();
} else if (document.msExitFullscreen) {
document.msExitFullscreen();
}
} else {
if (element.requestFullscreen) {
element.requestFullscreen();
} else if (element.webkitRequestFullScreen) {
element.webkitRequestFullScreen();
} else if (element.mozRequestFullScreen) {
element.mozRequestFullScreen();
} else if (element.msRequestFullscreen) {
// IE11
element.msRequestFullscreen();
}
}
this.fullscreen = !this.fullscreen;
// this.changeLayout();
},
},
};
</script>
<style lang="scss">
.gbs-screen-view {
.zzjg-video-tree .el-tree-node__content:hover {
background: #e2d170f1 !important;
}
.el-card__title {
width: 100px;
display: flex;
align-items: center;
.el-card__ts {
display: block;
width: 5px;
height: 19px;
background-color: #0bb2d4;
border-radius: 15px;
}
.el-card__t {
font-weight: 600;
margin-left: 7px;
margin-top: 2px;
}
}
.third-party-right-view {
width: calc(100% - 0px);
height: 100%;
border: 1px solid rgb(121, 120, 120);
height: calc(100vh - 140px);
}
.third-rarty-right-option {
height: 35px;
width: 100%;
background-color: #2b2f3a;
margin-top: 2px;
display: flex;
justify-content: flex-end;
padding-right: 10px;
justify-content: space-between;
.el-button--text {
color: #f0f0f1;
}
}
.video-right {
width: 100%;
height: 100%;
background-color: #e1e4e7;
min-height: 220px;
display: flex;
justify-content: center;
align-items: center;
}
.palying-span {
display: block;
width: 5px;
height: 5px;
border-radius: 50%;
position: relative;
left: -7px;
top: 3px;
}
.fullscreen {
height: calc(100vh - 37px);
}
.ptz-attribute .el-radio-button--mini .el-radio-button__inner {
background: #1a1d23;
color: #fff;
}
.ptz-attribute
.el-radio-button__orig-radio:checked
+ .el-radio-button__inner {
background-color: #f56c6c;
}
}
</style>

View File

@ -0,0 +1,112 @@
<template>
<div class="e-live-player">
<LivePlayer
ref="livePlayerRef"
aspect="fullscreen"
alt="无信号"
:autofocus="true"
:stretch="true"
:live="true"
controls
:autoplay="true"
v-loading="bLoading"
element-loading-text="加载中..."
element-loading-background="#000"
:loading.sync="bLoading"
:hide-big-play-button="true"
@error="EventError"
@pause="videoPause"
@ended="videoEnded"
:videoUrl="LoadingNum <= LoadingMax ? videoUrl : ''"
></LivePlayer>
</div>
</template>
<script>
import LivePlayer from "@liveqing/liveplayer";
import { handleStartInviteLive } from "@/utils/gbsLive";
export default {
name: "ELivePlayer",
props: {
videoUrl: {
type: String,
require: true,
default: () => {
return "";
},
},
videoItem: {
type: Object,
require: true,
default: () => {
return {};
},
},
},
components: {
LivePlayer,
},
data() {
return {
bLoading: true,
LoadingMax: 30,
LoadingNum: 0,
timeoutHeartbeat: 60,
isHeartbeat: true,
};
},
watch: {
videoUrl(val) {
this.LoadingNum = 0;
this.bLoading = true;
},
},
// created() {
// this.restartLive()
// },
methods: {
//
restartLive() {
this.$refs.livePlayerRef.play()
let siteInfo = {
https: this.videoItem.https,
devId: this.videoItem.devId,
m7sHost: this.videoItem.siteM7sHost,
m7sPort: this.videoItem.siteM7sPort,
m7sVersion: this.videoItem.siteM7sVersion,
};
var _this = this;
if (this.isHeartbeat) {
this.isHeartbeat = false
handleStartInviteLive(siteInfo, this.videoItem);
setTimeout(() => {
_this.isHeartbeat = true;
}, _this.timeoutHeartbeat * 1000);
}
},
videoEnded(e) {
console.log("video-videoEnded--:");
this.restartLive();
},
videoPause(e) {
console.log("video-pause--:");
this.restartLive();
},
EventError(e) {
if (this.LoadingNum <= this.LoadingMax - 1) {
this.bLoading = true;
}
this.LoadingNum += 1;
},
},
};
</script>
<style lang="scss" >
.e-live-player {
width: 100%;
height: 100%;
position: relative;
.el-loading-parent--relative {
position: initial !important;
}
}
</style>

View File

@ -0,0 +1,301 @@
<template>
<div class="e-webrtc-screen">
<div class="video-list">
<div
v-for="(item, index) in list"
@click="videoViewClick(item, index)"
:style="{
height: compVideoWidth(),
width: compVideoWidth(),
margin: '1px',
overflow: 'hidden',
}"
:class="selectionRow === index ? 'checked-video' : ''"
:key="index"
>
<div
class="item-option"
:style="selectionRow === index ? 'display: flex;' : ''"
ref="itemOption"
>
<el-button
v-if="split === 1"
type="text"
icon="el-icon-full-screen"
size="medium"
title="全屏"
@click="fullScreenVideo(index)"
></el-button>
<el-button
type="text"
icon="el-icon-circle-close"
@click="closeVideo(index)"
size="medium"
title="关闭"
></el-button>
</div>
<div
v-if="index < split"
ref="videoDiv"
:class="
selectionRow === index
? 'item-video-div video-div-hover'
: 'item-video-div '
"
>
<!-- <e-webrtc-video
v-if="item"
:videoUrl="`webrtc://localhost:9527/${item.devId}/${item.devChannel}`"
:params="{
'streamPath': `${item.devId}/${item.devChannel}`
}"
:serviceHttps="item.https"
ref="webrtcVideo"
/> -->
<div v-if="item" style="width: 100%; height: 100%">
<e-live-player
:videoUrl="`${item.https == true ? 'https' : 'http'}://${
item.siteM7sHost
}:${item.siteM7sPort}/hdl/${item.devId}/${item.devChannel}.flv`"
:videoItem="item"
/>
</div>
<img
v-else
src="/images/video.png"
style="
width: 180px;
height: 80px;
opacity: 0.3;
filter: alpha(opacity=30);
"
/>
</div>
</div>
</div>
</div>
</template>
<script>
import { getRecord, setRecordIdx } from "./screenRecord";
import {
handleSendDirect,
handleStartInviteLive,
getServiceUrl
} from "@/utils/gbsLive";
import EWebrtcVideo from "./EWebrtcVideo";
import ELivePlayer from "./ELivePlayer";
export default {
name: "EWebrtcScreen",
components: {
EWebrtcVideo,
ELivePlayer,
},
props: {
showList: {
type: Array,
require: true,
},
scale: {
type: Number,
require: true,
},
split: {
type: Number,
require: true,
},
serviceUrl: {
type: String,
require: true,
},
},
data() {
return {
list: [],
selectionRow: 0,
videoItemStyle: {
width: "calc((100% - 15px) / 2)",
height: "",
margin: "1px",
},
};
},
created() {
this.handleTypeDom(Number(this.split));
},
watch: {
split(val) {
this.handleTypeDom(Number(val));
},
},
methods: {
EventPaly(e) {
console.log("play--Event", e);
},
setPlayingIdx(e) {
this.list.forEach((v, idx) => {
if ( v && (v['devChannel'] === e['devChannel'] && v["devId"] === e['devId'])) {
this.selectionRow = idx;
this.$forceUpdate();
}
});
},
sendCloud(e) {
let videoObj = this.list[this.selectionRow];
if (videoObj) {
let serviceUrl = `${getServiceUrl(
videoObj.https ? "https://" : "http://",
videoObj.siteM7sHost,
videoObj.siteM7sPort
)}`;
console.log('http-url:', serviceUrl)
handleSendDirect(
serviceUrl,
videoObj.siteM7sVersion || 'v3',
{
id: videoObj.devId,
channel: videoObj.devChannel,
},
e
);
}
},
//
closeVideo(index) {
this.$emit('handleTreeNodes', this.list[index]);
this.list[index] = null;
setRecordIdx(null, index);
this.$forceUpdate();
},
compVideoWidth() {
if (this.split === 1) {
return "100%";
} else if (this.split === 4) {
return `calc((100% - ${1 * 4}px) / 2)`;
} else if (this.split === 9) {
return `calc((100% - ${1 * 6}px) / 3)`;
} else if (this.split === 16) {
return `calc((100% - ${1 * 8}px) / 4)`;
}
},
// dom
handleTypeDom(type) {
this.list = [];
const record = getRecord();
for (let i = 0; i < type; i++) {
if (record && record[i]) {
//
this.handleOpenInviteLive(record[i], i);
} else {
this.list.push(null);
}
// this.list.push(null)
}
},
//
handleOpenInviteLive(row, idx) {
this.list[idx] = Object.assign(row, {
bLoading: true,
});
handleStartInviteLive(
{
https: row.https,
devId: row.devId,
m7sHost: row.siteM7sHost,
m7sPort: row.siteM7sPort,
m7sVersion: row.siteM7sVersion,
},
{
devChannel: row.devChannel,
}
);
this.$forceUpdate();
},
setPassagewayByIdx(row, playUrl) {
if (this.selectionRow >= 0) {
this.list[this.selectionRow] = row;
this.$forceUpdate();
}
},
getSelectIdx() {
return this.selectionRow;
},
getIsPlaying(e) {
let list = this.list.filter((v) => v && (v["devChannel"] === e['devChannel'] && v["devId"] === e['devId']));
return list && list.length > 0;
},
//
videoViewClick(item, index) {
this.selectionRow = index;
},
batchDestroyed() {},
changeLayout() {
var num = Math.sqrt(this.split);
this.videoItemStyle["width"] = `calc((100% - ${num * 2}px) / ${num})`;
if (this.fullscreen) {
this.videoItemStyle["height"] = `calc((100vh - 40px - ${
num * 2
}px) / ${num})`;
} else {
this.videoItemStyle["height"] = `calc((100vh - 151px - ${
num * 2
}px) / ${num})`;
}
this.$forceUpdate();
},
},
};
</script>
<style lang="scss">
.e-webrtc-screen {
width: calc(100% - 0px);
height: 100%;
border: 1px solid rgb(121, 120, 120);
.video-list {
height: auto;
width: 100%;
display: flex;
flex-wrap: wrap;
background-color: #1e1e1e;
padding: 0px;
height: 100%;
// justify-content: space-between;
justify-content: flex-start;
.item-option {
display: none;
justify-content: flex-end;
padding-right: 20px;
background: -webkit-gradient(
linear,
left top,
left bottom,
color-stop(0%, #080808),
color-stop(20%, #080808c2),
color-stop(100%, #66676700)
);
position: relative;
top: 0;
z-index: 2001;
.el-button--text {
color: #f0f0f1;
}
}
.item-video-div {
height: 100%;
width: 100%;
background: rgb(86, 86, 86);
display: flex;
justify-content: center;
align-items: center;
margin-top: 0px;
overflow: hidden;
}
.video-div-hover {
margin-top: -34px;
}
}
.checked-video {
border-radius: 3px;
border: 2px solid #078fef;
}
}
</style>

View File

@ -0,0 +1,163 @@
<template>
<section style="display: block; width: 100%; height: 100%; overflow: hidden">
<video
class="full-height full-width"
controls
preload="auto"
autoplay="true"
muted
style="width: 100%; height: 100%; object-fit: fill"
ref="video"
@click="handleVideo"
></video>
<div class="refresh-button" v-show="showRefresh">
<i class="el-icon-video-play" @click="handleRefresh"></i>
</div>
</section>
</template>
<script>
import { getToken } from "@/utils/auth";
export default {
data() {
return {
pc: null,
};
},
props: {
params: {
type: Object,
},
videoUrl: {
type: String,
default: "",
},
serverType: {
type: String,
default: "SRS",
},
serviceHttps: {
type: Boolean,
},
overtime: {
type: Number,
default: () => {
return 5;
},
},
},
mounted() {
this.getStream();
},
watch: {
videoUrl(val) {
if (val) {
this.getStream();
}
},
},
data() {
return {
timedRefresh: null,
showRefresh: false,
};
},
methods: {
handleVideo(event) {
event.preventDefault()
},
//
handleTimedRefresh() {
var _this = this;
this.showRefresh = false;
this.timedRefresh = setInterval(function () {
_this.showRefresh = true;
}, this.overtime * 1000);
},
videoPause() {},
handleRefresh() {
if (this.timedRefresh) {
clearInterval(this.timedRefresh)
this.showRefresh = true;
}
this.getStream()
},
getStream() {
if (this.videoUrl) {
if (this.pc) {
this.pc.destroy();
}
let option = {
video: this.$refs.video,
autoplay: true,
params: this.params || null,
type: "offer",
onPlay: (obj) => {
if (this.timedRefresh) {
clearInterval(this.timedRefresh)
this.showRefresh = false;
}
},
};
if (!this.serverType || this.serverType === "SRS") {
option.clientip = getToken();
}
option.crossType = this.serviceHttps == true ? "https" : "http";
this.handleTimedRefresh();
console.log(this.videoUrl)
this.pc = new JSWebrtc.Player(this.videoUrl, option);
}
},
videoFullScreen() {
var ele = this.$refs.video;
if (ele.requestFullscreen) {
ele.requestFullscreen();
} else if (ele.mozRequestFullScreen) {
ele.mozRequestFullScreen();
} else if (ele.webkitRequestFullScreen) {
ele.webkitRequestFullScreen();
}
},
videoExitFullscreen() {
var de = document;
if (de.exitFullscreen) {
de.exitFullscreen();
} else if (de.mozCancelFullScreen) {
de.mozCancelFullScreen();
} else if (de.webkitCancelFullScreen) {
de.webkitCancelFullScreen();
}
},
},
beforeDestroy() {
if (this.pc) {
this.pc.destroy();
}
},
};
</script>
<style lang="scss">
.full-width {
height: 100%;
transform: matrix(1, 0, 0, 1, 0, 0);
}
.refresh-button {
height: 100%;
width: 100%;
background: -webkit-gradient(
linear,
left top,
left bottom,
color-stop(0%, #080808),
color-stop(20%, #080808c2),
color-stop(100%, #0c0c0c73)
);
position: relative;
top: -102%;
display: flex;
justify-content: center;
align-items: center;
font-size: 50px;
color: #9b9b9bd1;
}
</style>

View File

@ -0,0 +1,122 @@
import Cookies from 'js-cookie'
const gonggeKey = 'screen-record'
const typeKey = 'screen-type'
const isWebRTC = 'is-webRTC'
// 初始左侧列表数据 和 记录数据
export function init(list = []) {
// 二级 Tree 结构
let cookiesList = JSON.parse( Cookies.get(gonggeKey) || '[]')
let treeChildList = []
var newResult = []
if (list && cookiesList && cookiesList.length > 0) {
list.forEach(v => {
if (v.children && v.children.length > 0) {
v.children.forEach(i => {
treeChildList = treeChildList.concat(i.children || [])
})
}
})
cookiesList.forEach((cook,idx) => {
if (cook) {
for(let i in treeChildList) {
if (treeChildList[i].devChannel === cook['devChannel'] && treeChildList[i].devId === cook['devId']) {
newResult[idx] = cook
}
}
} else {
newResult[idx] = null
}
})
setRecord(newResult)
}
return false
}
export function getRecord(index) {
let record = Cookies.get(gonggeKey)
let list = record ? JSON.parse(record): []
var newList = []
if (!list || list.length < 16) {
for (let i = 0; i < 16; i++) {
newList.push(null)
}
setRecord(newList)
return newList
} else {
if (index !== undefined && index !== null) {
return list[index]
} else {
return list
}
}
}
export function getType() {
const type = Cookies.get(typeKey)
if (!type) {
setType('4')
return '4'
} else {
return type
}
}
export function setRecordIdx(record, idx) {
if (idx !== undefined && idx !== null) {
let list = JSON.parse(Cookies.get(gonggeKey) || '[]')
if (record) {
filterRecordVideo(list, record)
list[idx] = {
devChannel: record.devChannel || record.devChannel,
devId: record.devId,
https: record.https,
siteM7sHost: record.siteM7sHost,
siteM7sPort: record.siteM7sPort,
siteM7sVersion: record.siteM7sVersion,
// label: record.label
}
} else {
list[idx] = record
}
setRecord(list)
}
}
// 判断 记录列表中是否已有记录当前 播放设备
function filterRecordVideo(list, record) {
const newList = [...list]
newList.forEach((v, idx) => {
if (v) {
if (v['devChannel'] === record.devChannel && v['devId'] === record.devId) {
list[idx] = null
}
}
})
}
export function setRecord(list) {
return Cookies.set(gonggeKey, JSON.stringify(list))
}
export function setType(type) {
return Cookies.set(typeKey, type)
}
/**
* 设置分屏模式 是否 webRTC
* @param {*} value 设置的新值
* @returns
*/
export function setScreenIsWebRTC(value) {
return Cookies.set(isWebRTC, value)
}
/**
* 获取分屏模式 是否 webRTC
* @returns
*/
export function getScreenIsWebRTC() {
return Cookies.get(isWebRTC)
}

View File

@ -1,6 +1,7 @@
'use strict' 'use strict'
const path = require('path') const path = require('path')
const defaultSettings = require('./src/settings.js') const defaultSettings = require('./src/settings.js')
const CopyWebpackPlugin = require('copy-webpack-plugin');
function resolve(dir) { function resolve(dir) {
return path.join(__dirname, dir) return path.join(__dirname, dir)
@ -47,6 +48,13 @@ module.exports = {
externals: { externals: {
AMap: 'AMap' AMap: 'AMap'
}, },
plugins: [
new CopyWebpackPlugin([
{ from: 'node_modules/@liveqing/liveplayer/dist/component/crossdomain.xml'},
{ from: 'node_modules/@liveqing/liveplayer/dist/component/liveplayer.swf'},
{ from: 'node_modules/@liveqing/liveplayer/dist/component/liveplayer-lib.min.js', to: 'cdn/js/liveplayer/'},
]),
],
name: name, name: name,
resolve: { resolve: {
alias: { alias: {