mirror of
https://gitee.com/beecue/fastbee.git
synced 2025-12-17 16:36:03 +08:00
开源版本,视频直播功能
This commit is contained in:
105
vue/src/api/iot/channel.js
Normal file
105
vue/src/api/iot/channel.js
Normal file
@@ -0,0 +1,105 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
// 查询监控设备通道信息列表
|
||||
export function listChannel(query) {
|
||||
return request({
|
||||
url: '/sip/channel/list',
|
||||
method: 'get',
|
||||
params: query
|
||||
})
|
||||
}
|
||||
|
||||
// 查询监控设备通道信息详细
|
||||
export function getChannel(channelId) {
|
||||
return request({
|
||||
url: '/sip/channel/' + channelId,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
// 新增监控设备通道信息
|
||||
export function addChannel(createNum, data) {
|
||||
return request({
|
||||
url: '/sip/channel/' + createNum,
|
||||
method: 'post',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
// 修改监控设备通道信息
|
||||
export function updateChannel(data) {
|
||||
return request({
|
||||
url: '/sip/channel',
|
||||
method: 'put',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
// 删除监控设备通道信息
|
||||
export function delChannel(channelId) {
|
||||
return request({
|
||||
url: '/sip/channel/' + channelId,
|
||||
method: 'delete'
|
||||
})
|
||||
}
|
||||
|
||||
// 开始播放
|
||||
export function startPlay(deviceId, channelId) {
|
||||
return request({
|
||||
url: '/sip/player/play/' + deviceId + "/" + channelId,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
// 获取流信息
|
||||
export function getStreaminfo(deviceId, channelId) {
|
||||
return request({
|
||||
url: '/sip/player/playstream/' + deviceId + "/" + channelId,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
export function playback(deviceId, channelId, query) {
|
||||
return request({
|
||||
url: '/sip/player/playback/' + deviceId + "/" + channelId,
|
||||
method: 'get',
|
||||
params: query
|
||||
})
|
||||
}
|
||||
|
||||
export function closeStream(deviceId, channelId, streamId){
|
||||
return request({
|
||||
url: '/sip/player/closeStream/' + deviceId + "/" + channelId + "/" + streamId,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
export function playbackPause(deviceId, channelId, streamId) {
|
||||
return request({
|
||||
url: '/sip/player/playbackPause/' + deviceId + "/" + channelId + "/" + streamId,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
export function playbackReplay(deviceId, channelId, streamId) {
|
||||
return request({
|
||||
url: '/sip/player/playbackReplay/' + deviceId + "/" + channelId + "/" + streamId,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
export function playbackSeek(deviceId, channelId, streamId, query) {
|
||||
return request({
|
||||
url: '/sip/player/playbackSeek/' + deviceId + "/" + channelId + "/" + streamId,
|
||||
method: 'get',
|
||||
params: query
|
||||
})
|
||||
}
|
||||
|
||||
export function playbackSpeed(deviceId, channelId, streamId, query) {
|
||||
return request({
|
||||
url: '/sip/player/playbackSpeed/' + deviceId + "/" + channelId + "/" + streamId,
|
||||
method: 'get',
|
||||
params: query
|
||||
})
|
||||
}
|
||||
52
vue/src/api/iot/mediaServer.js
Normal file
52
vue/src/api/iot/mediaServer.js
Normal file
@@ -0,0 +1,52 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
// 查询流媒体服务器配置列表
|
||||
export function listmediaServer(query) {
|
||||
return request({
|
||||
url: '/sip/mediaserver/list',
|
||||
method: 'get',
|
||||
params: query
|
||||
})
|
||||
}
|
||||
|
||||
// 查询流媒体服务器配置详细
|
||||
export function getmediaServer() {
|
||||
return request({
|
||||
url: '/sip/mediaserver/',
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
// 新增流媒体服务器配置
|
||||
export function addmediaServer(data) {
|
||||
return request({
|
||||
url: '/sip/mediaserver',
|
||||
method: 'post',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
// 修改流媒体服务器配置
|
||||
export function updatemediaServer(data) {
|
||||
return request({
|
||||
url: '/sip/mediaserver',
|
||||
method: 'put',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
// 删除流媒体服务器配置
|
||||
export function delmediaServer(id) {
|
||||
return request({
|
||||
url: '/sip/mediaserver/' + id,
|
||||
method: 'delete'
|
||||
})
|
||||
}
|
||||
|
||||
export function checkmediaServer(query) {
|
||||
return request({
|
||||
url: '/sip/mediaserver/check' ,
|
||||
method: 'get',
|
||||
params: query
|
||||
})
|
||||
}
|
||||
88
vue/src/api/iot/record.js
Normal file
88
vue/src/api/iot/record.js
Normal file
@@ -0,0 +1,88 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
export function getDevRecord(deviceId,channelId,query) {
|
||||
return request({
|
||||
url: '/sip/record/devquery/' + deviceId + "/" + channelId,
|
||||
method: 'get',
|
||||
params: query
|
||||
})
|
||||
}
|
||||
|
||||
export function getRecord(channelId,sn) {
|
||||
return request({
|
||||
url: '/sip/record/query/' + channelId + "/" + sn,
|
||||
method: 'get',
|
||||
})
|
||||
}
|
||||
|
||||
export function getServerRecord(query) {
|
||||
return request({
|
||||
url: '/sip/record/serverRecord/list',
|
||||
method: 'get',
|
||||
params: query
|
||||
})
|
||||
}
|
||||
|
||||
export function getServerRecordByDate(query) {
|
||||
return request({
|
||||
url: '/sip/record/serverRecord/date/list',
|
||||
method: 'get',
|
||||
params: query
|
||||
})
|
||||
}
|
||||
|
||||
export function getServerRecordByStream(query) {
|
||||
return request({
|
||||
url: '/sip/record/serverRecord/stream/list',
|
||||
method: 'get',
|
||||
params: query
|
||||
})
|
||||
}
|
||||
|
||||
export function getServerRecordByApp(query) {
|
||||
return request({
|
||||
url: '/sip/record/serverRecord/app/list',
|
||||
method: 'get',
|
||||
params: query
|
||||
})
|
||||
}
|
||||
|
||||
export function getServerRecordByFile(query) {
|
||||
return request({
|
||||
url: '/sip/record/serverRecord/file/list',
|
||||
method: 'get',
|
||||
params: query
|
||||
})
|
||||
}
|
||||
|
||||
export function getServerRecordByDevice(query) {
|
||||
return request({
|
||||
url: '/sip/record/serverRecord/device/list',
|
||||
method: 'get',
|
||||
params: query
|
||||
})
|
||||
}
|
||||
|
||||
export function startPlayRecord(deviceId, channelId) {
|
||||
return request({
|
||||
url: '/sip/record/play/' + deviceId + "/" + channelId,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
export function startDownloadRecord(deviceId, channelId, query) {
|
||||
return request({
|
||||
url: '/sip/record/download/' + deviceId + "/" + channelId,
|
||||
method: 'get',
|
||||
params: query
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
export function uploadRecord(query) {
|
||||
return request({
|
||||
url: '/sip/record/upload',
|
||||
method: 'get',
|
||||
params: query
|
||||
})
|
||||
}
|
||||
34
vue/src/api/iot/sipConfig.js
Normal file
34
vue/src/api/iot/sipConfig.js
Normal file
@@ -0,0 +1,34 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
// 查询sip系统配置详细
|
||||
export function getSipconfig(productId,isDefault) {
|
||||
return request({
|
||||
url: '/sip/sipconfig/' + productId+'/'+isDefault,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
// 新增sip系统配置
|
||||
export function addSipconfig(data) {
|
||||
return request({
|
||||
url: '/sip/sipconfig',
|
||||
method: 'post',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
// 修改sip系统配置
|
||||
export function updateSipconfig(data) {
|
||||
return request({
|
||||
url: '/sip/sipconfig',
|
||||
method: 'put',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
export function delSipconfigByProductId(productId) {
|
||||
return request({
|
||||
url: '/sip/sipconfig/product/' + productId,
|
||||
method: 'delete'
|
||||
})
|
||||
}
|
||||
74
vue/src/api/iot/sipdevice.js
Normal file
74
vue/src/api/iot/sipdevice.js
Normal file
@@ -0,0 +1,74 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
// 查询监控设备列表
|
||||
export function listSipDevice(query) {
|
||||
return request({
|
||||
url: '/sip/device/list',
|
||||
method: 'get',
|
||||
params: query
|
||||
})
|
||||
}
|
||||
|
||||
export function listSipDeviceChannel(deviceId) {
|
||||
return request({
|
||||
url: '/sip/device/listchannel/'+ deviceId,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
// 查询监控设备详细
|
||||
export function getSipDevice(deviceId) {
|
||||
return request({
|
||||
url: '/sip/device/' + deviceId,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
// 新增监控设备
|
||||
export function addSipDevice(data) {
|
||||
return request({
|
||||
url: '/sip/device',
|
||||
method: 'post',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
// 修改监控设备
|
||||
export function updateSipDevice(data) {
|
||||
return request({
|
||||
url: '/sip/device',
|
||||
method: 'put',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
// 删除监控设备
|
||||
export function delSipDevice(deviceId) {
|
||||
return request({
|
||||
url: '/sip/device/' + deviceId,
|
||||
method: 'delete'
|
||||
})
|
||||
}
|
||||
|
||||
export function delSipDeviceBySipId(sipId) {
|
||||
return request({
|
||||
url: '/sip/device/sipid/' + sipId,
|
||||
method: 'delete'
|
||||
})
|
||||
}
|
||||
|
||||
export function ptzdirection(deviceId,channelId,data) {
|
||||
return request({
|
||||
url: '/sip/ptz/direction/'+ deviceId + "/" + channelId,
|
||||
method: 'post',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
export function ptzscale(deviceId,channelId,data) {
|
||||
return request({
|
||||
url: '/sip/ptz/scale/'+ deviceId + "/" + channelId,
|
||||
method: 'post',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
183
vue/src/views/components/player/DeviceTree.vue
Normal file
183
vue/src/views/components/player/DeviceTree.vue
Normal 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>
|
||||
203
vue/src/views/components/player/deviceLiveStream.vue
Normal file
203
vue/src/views/components/player/deviceLiveStream.vue
Normal 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>
|
||||
291
vue/src/views/components/player/deviceVideo.vue
Normal file
291
vue/src/views/components/player/deviceVideo.vue
Normal 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>
|
||||
62
vue/src/views/components/player/easyplayer.vue
Normal file
62
vue/src/views/components/player/easyplayer.vue
Normal 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>
|
||||
363
vue/src/views/components/player/jessibuca.vue
Normal file
363
vue/src/views/components/player/jessibuca.vue
Normal 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>
|
||||
347
vue/src/views/components/player/player.vue
Normal file
347
vue/src/views/components/player/player.vue
Normal 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
1
vue/src/views/components/player/public/web-player-pro.js
Normal file
1
vue/src/views/components/player/public/web-player-pro.js
Normal file
File diff suppressed because one or more lines are too long
@@ -26,8 +26,13 @@
|
||||
设备编号
|
||||
</template>
|
||||
<el-input v-model="form.serialNumber" placeholder="请输入设备编号" :disabled="form.status !== 1" maxlength="32">
|
||||
<el-button slot="append" @click="generateNum" :loading="genDisabled"
|
||||
:disabled="form.status !== 1">生成</el-button>
|
||||
<!-- <el-button slot="append" @click="generateNum" :loading="genDisabled"
|
||||
:disabled="form.status !== 1">生成</el-button> -->
|
||||
<el-button v-if="form.deviceType !== 3" slot="append" @click="generateNum"
|
||||
:loading="genDisabled" :disabled="form.status != 1"
|
||||
v-hasPermi="['iot:device:add']">生成</el-button>
|
||||
<el-button v-if="form.deviceType === 3" slot="append" @click="genSipID()"
|
||||
:disabled="form.status != 1" v-hasPermi="['iot:device:add']">生成</el-button>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="openServerTip">
|
||||
@@ -123,6 +128,8 @@
|
||||
|
||||
<!-- 选择产品 -->
|
||||
<product-list ref="productList" :productId="form.productId" @productEvent="getProductData($event)" />
|
||||
|
||||
<sipid ref="sipidGen" :product="form" @addGenEvent="getSipIDData($event)" />
|
||||
</el-tab-pane>
|
||||
|
||||
<el-tab-pane name="runningStatus" v-if="form.deviceType !== 3 && !isSubDev">
|
||||
@@ -140,10 +147,14 @@
|
||||
<business ref="business"/>
|
||||
</el-tab-pane>
|
||||
|
||||
<el-tab-pane :disabled="form.deviceId === 0" v-if="form.deviceType === 3" name="sipPlayer">
|
||||
<!-- <el-tab-pane :disabled="form.deviceId === 0" v-if="form.deviceType === 3" name="sipPlayer">
|
||||
<span slot="label"><span style="color:red;">¥ </span>设备直播</span>
|
||||
<business ref="business"/>
|
||||
</el-tab-pane>
|
||||
</el-tab-pane> -->
|
||||
<el-tab-pane name="videoLive" :disabled="form.deviceId == 0" v-if="form.deviceType === 3" >
|
||||
<span slot="label">设备直播</span>
|
||||
<device-live-stream ref="deviceLiveStream" :device="form" />
|
||||
</el-tab-pane>
|
||||
|
||||
<el-tab-pane :disabled="form.deviceId === 0" v-if="form.deviceType === 3" name="sipVideo">
|
||||
<span slot="label"><span style="color:red;">¥ </span>直播录像</span>
|
||||
@@ -267,6 +278,13 @@ import { deviceSynchronization, getDevice, addDevice, updateDevice, generatorDev
|
||||
import { getDeviceRunningStatus } from '@/api/iot/device';
|
||||
import { cacheJsonThingsModel } from '@/api/iot/model';
|
||||
import { getDeviceTemp } from '@/api/iot/temp';
|
||||
import deviceVideo from '@/views/components/player/deviceVideo.vue';
|
||||
import deviceLiveStream from '@/views/components/player/deviceLiveStream';
|
||||
import sipid from '../sip/sipidGen.vue';
|
||||
import player from '@/views/components/player/player.vue';
|
||||
import channel from '../sip/channel';
|
||||
|
||||
|
||||
|
||||
export default {
|
||||
name: 'DeviceEdit',
|
||||
@@ -283,6 +301,11 @@ export default {
|
||||
deviceTimer,
|
||||
JsonViewer,
|
||||
vueQr,
|
||||
deviceVideo,
|
||||
deviceLiveStream,
|
||||
player,
|
||||
channel,
|
||||
sipid,
|
||||
},
|
||||
watch: {
|
||||
activeName(val) {
|
||||
@@ -512,21 +535,76 @@ export default {
|
||||
},
|
||||
|
||||
// 获取子组件订阅的设备状态
|
||||
getDeviceStatusData(status) {
|
||||
this.form.status = status;
|
||||
},
|
||||
// getDeviceStatusData(status) {
|
||||
// this.form.status = status;
|
||||
// },
|
||||
getPlayerData(data) {
|
||||
this.activeName = data.tabName;
|
||||
this.channelId = data.channelId;
|
||||
// this.$set(this.form, 'channelId', this.channelId);
|
||||
if (this.channelId) {
|
||||
this.$refs.deviceLiveStream.channelId = this.channelId;
|
||||
this.$refs.deviceLiveStream.changeChannel();
|
||||
}
|
||||
},
|
||||
|
||||
/** 选项卡改变事件*/
|
||||
// tabChange(panel) {
|
||||
// this.$nextTick(() => {
|
||||
// // 获取监测统计数据
|
||||
// if (panel.name === 'deviceStastic' && !this.isSubDev) {
|
||||
// this.$refs.deviceStatistic.getListHistory();
|
||||
// } else if (panel.name === 'deviceTimer'&& !this.isSubDev) {
|
||||
// this.$refs.deviceTimer.getList();
|
||||
// }
|
||||
// });
|
||||
// },
|
||||
tabChange(panel) {
|
||||
this.$nextTick(() => {
|
||||
// 获取监测统计数据
|
||||
if (panel.name === 'deviceStastic' && !this.isSubDev) {
|
||||
this.$refs.deviceStatistic.getListHistory();
|
||||
} else if (panel.name === 'deviceTimer'&& !this.isSubDev) {
|
||||
this.$refs.deviceTimer.getList();
|
||||
}
|
||||
});
|
||||
},
|
||||
if (this.form.deviceType == 3 && panel.name != 'deviceReturn') {
|
||||
if (panel.name === 'videoLive') {
|
||||
this.$refs.deviceVideo.destroy();
|
||||
if (this.channelId) {
|
||||
this.$refs.deviceLiveStream.channelId = this.channelId;
|
||||
this.$refs.deviceLiveStream.changeChannel();
|
||||
}
|
||||
if (this.$refs.deviceLiveStream.channelId) {
|
||||
this.$refs.deviceLiveStream.changeChannel();
|
||||
}
|
||||
} else if (panel.name === 'deviceVideo') {
|
||||
this.$refs.deviceLiveStream.destroy();
|
||||
if (this.$refs.deviceVideo.channelId && this.$refs.deviceVideo.queryDate) {
|
||||
this.$refs.deviceVideo.loadDevRecord();
|
||||
}
|
||||
} else if (panel.name === 'sipChannel') {
|
||||
this.$refs.deviceChannel.getList();
|
||||
}
|
||||
//关闭直播流
|
||||
if (panel.name !== 'sipVideo') {
|
||||
if(this.$refs.deviceLiveStream.playing) {
|
||||
this.$refs.deviceLiveStream.closeDestroy(false);
|
||||
}
|
||||
}
|
||||
//关闭录像流
|
||||
if (panel.name !== 'deviceVideo') {
|
||||
if(this.$refs.deviceVideo.playing) {
|
||||
this.$refs.deviceVideo.closeDestroy();
|
||||
}
|
||||
}
|
||||
}
|
||||
this.$nextTick(() => {
|
||||
// 获取监测统计数据
|
||||
if (panel.name === 'deviceStastic') {
|
||||
this.$refs.deviceStatistic.getListHistory();
|
||||
} else if (panel.name === 'deviceTimer') {
|
||||
this.$refs.deviceTimer.getList();
|
||||
} else if (panel.name === 'deviceSub') {
|
||||
if (this.form.serialNumber) {
|
||||
this.$refs.deviceSub.queryParams.gwDevCode = this.form.serialNumber;
|
||||
this.$refs.deviceSub.getList();
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
/** 数据同步*/
|
||||
deviceSynchronization() {
|
||||
deviceSynchronization(this.form.serialNumber).then(async (response) => {
|
||||
@@ -843,6 +921,9 @@ export default {
|
||||
this.serverType = 1;
|
||||
}
|
||||
},
|
||||
getSipIDData(devsipid) {
|
||||
this.form.serialNumber = devsipid;
|
||||
},
|
||||
// 获取选中的用户
|
||||
getUserData(user) { },
|
||||
/**关闭物模型 */
|
||||
|
||||
@@ -136,6 +136,13 @@
|
||||
<product-authorize ref="productAuthorize" :product="form" />
|
||||
</el-tab-pane>
|
||||
|
||||
<el-tab-pane label="" name="sipConfig" :disabled="form.productId == 0" v-if="form.deviceType === 3">
|
||||
<span slot="label">SIP配置</span>
|
||||
<config-sip ref="configSip" :product="form" />
|
||||
</el-tab-pane>
|
||||
|
||||
<div style="margin-top: 200px"></div>
|
||||
|
||||
<el-tab-pane label="" name="alert" :disabled="form.productId == 0" v-if="form.deviceType !== 3">
|
||||
<span slot="label"><span style="color:red;">¥ </span>告警配置</span>
|
||||
<business ref="business"/>
|
||||
@@ -175,10 +182,11 @@
|
||||
|
||||
<script>
|
||||
import productThingsModel from "./product-things-model";
|
||||
import productApp from "./product-app"
|
||||
import productAuthorize from "./product-authorize"
|
||||
import imageUpload from "../../../components/ImageUpload/index"
|
||||
import business from "../business/index"
|
||||
import productApp from "./product-app";
|
||||
import productAuthorize from "./product-authorize";
|
||||
import imageUpload from "../../../components/ImageUpload/index";
|
||||
import business from "../business/index";
|
||||
import configSip from '../sip/sipconfig.vue';
|
||||
import {
|
||||
listProtocol
|
||||
} from "@/api/iot/protocol";
|
||||
@@ -207,6 +215,7 @@ export default {
|
||||
productAuthorize,
|
||||
imageUpload,
|
||||
business,
|
||||
configSip,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
||||
206
vue/src/views/iot/sip/channel.vue
Normal file
206
vue/src/views/iot/sip/channel.vue
Normal file
@@ -0,0 +1,206 @@
|
||||
<template>
|
||||
<div style="padding-left: 20px">
|
||||
<el-row :gutter="10" class="mb8">
|
||||
<el-col :span="1.5">
|
||||
<el-button type="warning" plain icon="el-icon-refresh" size="mini" @click="getList">刷新</el-button>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-table v-loading="loading" :data="channelList" size="mini">
|
||||
<el-table-column label="设备ID" align="center" prop="deviceSipId" />
|
||||
<el-table-column label="通道ID" align="center" prop="channelSipId" />
|
||||
<el-table-column label="快照" min-width="120">
|
||||
<template v-slot:default="scope">
|
||||
<el-image v-if="isVideoChannel(scope.row)" :src="getSnap(scope.row)" :preview-src-list="getBigSnap(scope.row)" :fit="'contain'" style="width: 60px">
|
||||
<div slot="error" class="image-slot">
|
||||
<i class="el-icon-picture-outline"></i>
|
||||
</div>
|
||||
</el-image>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="通道名称" align="center" prop="channelName" />
|
||||
<el-table-column label="产品型号" align="center" prop="model" />
|
||||
<el-table-column label="推流状态" align="center" prop="streamPush" >
|
||||
<template slot-scope="scope">
|
||||
<el-tag type="warning" v-if="scope.row.streamPush === 0">无</el-tag>
|
||||
<el-tag type="success" v-if="scope.row.streamPush === 1">推流中</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="直播录像状态" align="center" prop="streamRecord" >
|
||||
<template slot-scope="scope">
|
||||
<el-tag type="warning" v-if="scope.row.streamRecord === 0">无</el-tag>
|
||||
<el-tag type="success" v-if="scope.row.streamRecord === 1">录像中</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="录像转存状态" align="center" prop="videoRecord" >
|
||||
<template slot-scope="scope">
|
||||
<el-tag type="warning" v-if="scope.row.videoRecord === 0">无</el-tag>
|
||||
<el-tag type="success" v-if="scope.row.videoRecord === 1">转存中</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="状态" align="center" prop="status" width="80">
|
||||
<template slot-scope="scope">
|
||||
<dict-tag :options="dict.type.sip_gen_status" :value="scope.row.status" size="mini" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" align="center" width="120" class-name="small-padding fixed-width">
|
||||
<template slot-scope="scope">
|
||||
<el-button size="small" type="success" icon="el-icon-video-play" style="padding: 5px" :disabled="scope.row.status !== 2" @click="sendDevicePush(scope.row)">查看直播</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<pagination v-show="total > 0" :total="total" :page.sync="queryParams.pageNum" :limit.sync="queryParams.pageSize" @pagination="getList" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { listChannel, getChannel, delChannel } from '@/api/iot/channel';
|
||||
export default {
|
||||
name: 'Channel',
|
||||
dicts: ['sip_gen_status', 'video_type', 'channel_type'],
|
||||
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;
|
||||
}
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
loadSnap: {},
|
||||
// 设备信息
|
||||
deviceInfo: {},
|
||||
// 遮罩层
|
||||
loading: true,
|
||||
// 选中数组
|
||||
ids: [],
|
||||
// 非单个禁用
|
||||
single: true,
|
||||
// 非多个禁用
|
||||
multiple: true,
|
||||
// 显示搜索条件
|
||||
showSearch: true,
|
||||
// 总条数
|
||||
total: 0,
|
||||
// 监控设备通道信息表格数据
|
||||
channelList: [],
|
||||
// 弹出层标题
|
||||
title: '',
|
||||
// 是否显示弹出层
|
||||
open: false,
|
||||
// 查询参数
|
||||
queryParams: {
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
deviceSipId: null,
|
||||
},
|
||||
// 表单参数
|
||||
form: {},
|
||||
};
|
||||
},
|
||||
created() {
|
||||
this.queryParams.deviceSipId = this.device.serialNumber;
|
||||
this.getList();
|
||||
},
|
||||
methods: {
|
||||
//通知设备上传媒体流
|
||||
sendDevicePush: function (itemData) {
|
||||
var data = { tabName: 'videoLive', channelId: itemData.channelSipId };
|
||||
this.$emit('playerEvent', data);
|
||||
console.log('通知设备推流:' + itemData.deviceSipId + ' : ' + itemData.channelSipId);
|
||||
},
|
||||
/** 查询监控设备通道信息列表 */
|
||||
getList() {
|
||||
this.loading = true;
|
||||
listChannel(this.queryParams).then((response) => {
|
||||
console.log(response);
|
||||
this.channelList = response.rows;
|
||||
this.total = response.total;
|
||||
this.loading = false;
|
||||
});
|
||||
},
|
||||
// 取消按钮
|
||||
cancel() {
|
||||
this.open = false;
|
||||
this.reset();
|
||||
},
|
||||
// 表单重置
|
||||
reset() {
|
||||
this.form = {
|
||||
channelId: null,
|
||||
channelSipId: null,
|
||||
deviceSipId: null,
|
||||
channelName: null,
|
||||
manufacture: null,
|
||||
model: null,
|
||||
owner: null,
|
||||
civilcode: null,
|
||||
block: null,
|
||||
address: null,
|
||||
parentid: null,
|
||||
ipaddress: null,
|
||||
port: null,
|
||||
password: null,
|
||||
ptztype: null,
|
||||
ptztypetext: null,
|
||||
status: 0,
|
||||
longitude: null,
|
||||
latitude: null,
|
||||
streamid: null,
|
||||
subcount: null,
|
||||
parental: 1,
|
||||
hasaudio: 1,
|
||||
};
|
||||
this.resetForm('form');
|
||||
},
|
||||
/** 搜索按钮操作 */
|
||||
handleQuery() {
|
||||
this.queryParams.pageNum = 1;
|
||||
this.getList();
|
||||
},
|
||||
/** 修改按钮操作 */
|
||||
handleUpdate(row) {
|
||||
this.reset();
|
||||
const channelId = row.channelId || this.ids;
|
||||
getChannel(channelId).then((response) => {
|
||||
this.form = response.data;
|
||||
this.open = true;
|
||||
this.title = '修改监控设备通道信息';
|
||||
});
|
||||
},
|
||||
/** 删除按钮操作 */
|
||||
handleDelete(row) {
|
||||
const channelIds = row.channelId || this.ids;
|
||||
this.$modal
|
||||
.confirm('是否确认删除监控设备通道信息编号为"' + channelIds + '"的数据项?')
|
||||
.then(function () {
|
||||
return delChannel(channelIds);
|
||||
})
|
||||
.then(() => {
|
||||
this.getList();
|
||||
this.$modal.msgSuccess('删除成功');
|
||||
})
|
||||
.catch(() => {});
|
||||
},
|
||||
getSnap: function (row) {
|
||||
console.log('getSnap:' + process.env.VUE_APP_BASE_API + '/profile/snap/' + row.deviceSipId + '_' + row.channelSipId + '.jpg');
|
||||
return process.env.VUE_APP_BASE_API + '/profile/snap/' + row.deviceSipId + '_' + row.channelSipId + '.jpg';
|
||||
},
|
||||
getBigSnap: function (row) {
|
||||
return [this.getSnap(row)];
|
||||
},
|
||||
isVideoChannel: function (row) {
|
||||
let channelType = row.channelSipId.substring(10, 13);
|
||||
// 111-DVR编码;112-视频服务器编码;118-网络视频录像机(NVR)编码;131-摄像机编码;132-网络摄像机(IPC)编码
|
||||
return !(channelType !== '111' && channelType !== '112' && channelType !== '118' && channelType !== '131' && channelType !== '132');
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
474
vue/src/views/iot/sip/index.vue
Normal file
474
vue/src/views/iot/sip/index.vue
Normal file
@@ -0,0 +1,474 @@
|
||||
<template>
|
||||
<div style="padding: 6px">
|
||||
<el-card style="margin-bottom: 6px">
|
||||
<el-form :model="queryParams" ref="queryForm" :inline="true" label-width="60px" style="margin-bottom: -20px">
|
||||
<el-form-item label="设备ID" prop="deviceSipId">
|
||||
<el-input v-model="queryParams.deviceSipId" placeholder="请输入设备编号" clearable size="small"
|
||||
@keyup.enter.native="handleQuery" />
|
||||
</el-form-item>
|
||||
<el-form-item label="通道ID" prop="channelSipId">
|
||||
<el-input v-model="queryParams.channelSipId" placeholder="请输入通道ID" clearable size="small"
|
||||
@keyup.enter.native="handleQuery" />
|
||||
</el-form-item>
|
||||
<el-form-item label="状态" prop="status">
|
||||
<el-select v-model="queryParams.status" placeholder="请选择状态" clearable size="small">
|
||||
<el-option v-for="dict in dict.type.sip_gen_status" :key="dict.value" :label="dict.label"
|
||||
:value="dict.value" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
|
||||
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
|
||||
</el-form-item>
|
||||
<el-form-item style="float: right">
|
||||
<el-button type="primary" plain icon="el-icon-plus" size="mini" @click="handleAdd"
|
||||
v-hasPermi="['iot:video:add']" :disabled="isGeneralUser">批量生成</el-button>
|
||||
<el-button type="danger" plain icon="el-icon-delete" size="mini" :disabled="multiple || isGeneralUser"
|
||||
@click="handleDelete" v-hasPermi="['iot:video:remove']">批量删除</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
|
||||
<el-card style="margin-bottom: 5px">
|
||||
<el-table v-loading="loading" :data="sipidList" @selection-change="handleSelectionChange"
|
||||
@cell-dblclick="celldblclick" size="">
|
||||
<el-table-column type="selection" :selectable="selectable" width="55" align="center" />
|
||||
<el-table-column label="设备编号" align="center" prop="deviceSipId">
|
||||
<template slot-scope="scope">
|
||||
<el-link :underline="false" type="primary" @click="handleViewDevice(scope.row.deviceSipId)">{{
|
||||
scope.row.deviceSipId }}</el-link>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="通道ID" align="center" prop="channelSipId" />
|
||||
<el-table-column label="状态" align="center" prop="status" width="80">
|
||||
<template slot-scope="scope">
|
||||
<dict-tag :options="dict.type.sip_gen_status" :value="scope.row.status" size="mini" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="所属产品" align="center" prop="productName" />
|
||||
<el-table-column label="设备类型" align="center" prop="deviceType">
|
||||
<template slot-scope="scope">
|
||||
<dict-tag :options="dict.type.video_type" :value="scope.row.deviceType" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="通道类型" align="center" prop="channelType">
|
||||
<template slot-scope="scope">
|
||||
<dict-tag :options="dict.type.channel_type" :value="scope.row.channelType" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="行政区域" align="center" prop="citycode" />
|
||||
<el-table-column label="注册时间" align="center" prop="registerTime" width="180">
|
||||
<template slot-scope="scope">
|
||||
<span>{{ parseTime(scope.row.registerTime, '{y}-{m}-{d} {h}:{m}:{s}') }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<!-- <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
|
||||
<template slot-scope="scope">
|
||||
<el-button size="mini" type="text" icon="el-icon-edit" @click="handleUpdate(scope.row,'remark')" v-hasPermi="['iot:video:edit']">修改
|
||||
</el-button>
|
||||
<el-button size="mini" type="text" icon="el-icon-delete" @click="handleDelete(scope.row)" v-hasPermi="['iot:video:remove']" v-if="!scope.row.deviceSipId">删除
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column> -->
|
||||
</el-table>
|
||||
<pagination v-show="total > 0" :total="total" :page.sync="queryParams.pageNum" :limit.sync="queryParams.pageSize"
|
||||
@pagination="getList" />
|
||||
</el-card>
|
||||
|
||||
<el-dialog :title="title" :visible.sync="open" width="450px" append-to-body>
|
||||
<el-form :model="createForm" label-width="80px" ref="createForm">
|
||||
<el-form-item label="行政区划">
|
||||
<el-cascader :options="cityOptions" v-model="createForm.city" @change="changeProvince"
|
||||
change-on-select></el-cascader>
|
||||
</el-form-item>
|
||||
<el-form-item label="设备类型" prop="deviceType">
|
||||
<el-select v-model="createForm.deviceType" placeholder="请选择设备类型">
|
||||
<el-option v-for="dict in dict.type.video_type" :key="dict.value" :label="dict.label"
|
||||
:value="dict.value"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="通道类型" prop="channelType">
|
||||
<el-select v-model="createForm.channelType" placeholder="请选择设备类型">
|
||||
<el-option v-for="dict in dict.type.channel_type" :key="dict.value" :label="dict.label"
|
||||
:value="dict.value"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="所属产品" prop="productName">
|
||||
<el-input readonly v-model="createForm.productName" placeholder="请选择产品">
|
||||
<el-button slot="append" @click="selectProduct()">选择</el-button>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="通道数量" prop="createNum">
|
||||
<el-input-number controls-position="right" v-model="createForm.createNum" :max="10" placeholder="请输入生成通道数量"
|
||||
type="number" style="width: 330px" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div slot="footer" class="dialog-footer">
|
||||
<el-button type="primary" @click="submitForm">生 成</el-button>
|
||||
<el-button @click="cancel">取 消</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 选择产品 -->
|
||||
<product-list ref="productList" @productEvent="getProductData($event)" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
.createNum {
|
||||
width: 300px;
|
||||
}
|
||||
|
||||
.createNum input {
|
||||
width: 260px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import { getDeviceBySerialNumber } from '@/api/iot/device';
|
||||
import { regionData, CodeToText } from 'element-china-area-data';
|
||||
import { listChannel, getChannel, delChannel, addChannel } from '@/api/iot/channel';
|
||||
import productList from './product-list.vue';
|
||||
|
||||
export default {
|
||||
name: 'Sip',
|
||||
dicts: ['sip_gen_status', 'video_type', 'channel_type'],
|
||||
components: {
|
||||
productList,
|
||||
},
|
||||
props: {
|
||||
product: {
|
||||
type: Object,
|
||||
default: null,
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
// 获取到父组件传递的productId后,刷新列表
|
||||
product: function (newVal, oldVal) {
|
||||
this.productInfo = newVal;
|
||||
if (this.productInfo && this.productInfo.productId != 0) {
|
||||
this.queryParams.productId = this.productInfo.productId;
|
||||
this.deviceParams.productId = this.productInfo.productId;
|
||||
this.getList();
|
||||
}
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
// 是否普通用户
|
||||
isGeneralUser: true,
|
||||
// 遮罩层
|
||||
loading: true,
|
||||
// 选中数组
|
||||
ids: [],
|
||||
// 非多个禁用
|
||||
multiple: true,
|
||||
// 总条数
|
||||
total: 0,
|
||||
// sipid表格数据
|
||||
sipidList: [],
|
||||
// 弹出层标题
|
||||
title: '',
|
||||
// 是否显示弹出层
|
||||
open: false,
|
||||
// 查询参数
|
||||
queryParams: {
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
deviceSipId: null,
|
||||
deviceChannelId: null,
|
||||
status: null,
|
||||
},
|
||||
// 表单参数
|
||||
createForm: {
|
||||
city: '',
|
||||
deviceType: '',
|
||||
channelType: '',
|
||||
createNum: 1,
|
||||
remark: '',
|
||||
area: '',
|
||||
},
|
||||
form: {},
|
||||
// 产品
|
||||
productInfo: {},
|
||||
// 城市
|
||||
cityOptions: regionData,
|
||||
city: '',
|
||||
// 表单校验
|
||||
rules: {
|
||||
protocol: [
|
||||
{
|
||||
required: true,
|
||||
message: '默认播放协议不能为空',
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
ip: [
|
||||
{
|
||||
required: true,
|
||||
message: '服务器ip不能为空',
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
domain: [
|
||||
{
|
||||
required: true,
|
||||
message: '服务器域名不能为空',
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
secret: [
|
||||
{
|
||||
required: true,
|
||||
message: '流媒体密钥不能为空',
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
portHttp: [
|
||||
{
|
||||
required: true,
|
||||
message: 'http端口不能为空',
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
portHttps: [
|
||||
{
|
||||
required: true,
|
||||
message: 'https端口不能为空',
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
portRtmp: [
|
||||
{
|
||||
required: true,
|
||||
message: 'rtmp端口不能为空',
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
portRtsp: [
|
||||
{
|
||||
required: true,
|
||||
message: 'rtsp端口不能为空',
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
rtpPortRange: [
|
||||
{
|
||||
required: true,
|
||||
message: 'rtp端口范围不能为空',
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
delFlag: [
|
||||
{
|
||||
required: true,
|
||||
message: '删除标志不能为空',
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
createBy: [
|
||||
{
|
||||
required: true,
|
||||
message: '创建者不能为空',
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
createTime: [
|
||||
{
|
||||
required: true,
|
||||
message: '创建时间不能为空',
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
},
|
||||
created() {
|
||||
// 普通用户只能查看自己的通道
|
||||
if (this.$store.state.user.roles.indexOf('general') === -1) {
|
||||
this.isGeneralUser = false;
|
||||
}
|
||||
this.getList();
|
||||
},
|
||||
methods: {
|
||||
/** 查看设备操作 */
|
||||
handleViewDevice(serialNumber) {
|
||||
this.$router.push({
|
||||
path: '/iot/device',
|
||||
query: {
|
||||
t: Date.now(),
|
||||
sn: serialNumber,
|
||||
},
|
||||
});
|
||||
},
|
||||
/**选择产品 */
|
||||
selectProduct() {
|
||||
this.open = false;
|
||||
this.$refs.productList.open = true;
|
||||
this.$refs.productList.getList();
|
||||
},
|
||||
/**获取选中的产品 */
|
||||
getProductData(product) {
|
||||
this.open = true;
|
||||
this.createForm.productId = product.productId;
|
||||
this.createForm.productName = product.productName;
|
||||
this.createForm.tenantId = product.tenantId;
|
||||
this.createForm.tenantName = product.tenantName;
|
||||
},
|
||||
/** 行政区划改变 **/
|
||||
changeProvince(data) {
|
||||
if (data && data[0] != null && data[1] != null && data[2] != null) {
|
||||
const str = CodeToText[data[0]] + '/' + CodeToText[data[1]] + '/' + CodeToText[data[2]];
|
||||
this.createForm.citycode = str;
|
||||
}
|
||||
},
|
||||
/**获取设备详情*/
|
||||
getDeviceBySerialNumber(serialNumber) {
|
||||
this.openDevice = true;
|
||||
getDeviceBySerialNumber(serialNumber).then((response) => {
|
||||
this.device = response.data;
|
||||
});
|
||||
},
|
||||
/** 查询通道列表 */
|
||||
getList() {
|
||||
listChannel(this.queryParams).then((response) => {
|
||||
this.sipidList = response.rows;
|
||||
this.total = response.total;
|
||||
this.loading = false;
|
||||
});
|
||||
},
|
||||
// 取消按钮
|
||||
cancel() {
|
||||
this.open = false;
|
||||
this.reset();
|
||||
},
|
||||
// 表单重置
|
||||
reset() {
|
||||
this.createForm = {
|
||||
id: null,
|
||||
deviceSipId: null,
|
||||
channelSipId: null,
|
||||
status: 0,
|
||||
registertime: null,
|
||||
createBy: null,
|
||||
createTime: null,
|
||||
updateBy: null,
|
||||
updateTime: null,
|
||||
remark: null,
|
||||
createNum: 1,
|
||||
};
|
||||
this.resetForm('createForm');
|
||||
},
|
||||
/** 搜索按钮操作 */
|
||||
handleQuery() {
|
||||
this.loading = true;
|
||||
this.queryParams.pageNum = 1;
|
||||
this.getList();
|
||||
},
|
||||
/** 重置按钮操作 */
|
||||
resetQuery() {
|
||||
this.resetForm('queryForm');
|
||||
this.handleQuery();
|
||||
},
|
||||
// 多选框选中数据
|
||||
handleSelectionChange(selection) {
|
||||
this.ids = selection.map((item) => item.id);
|
||||
this.multiple = !selection.length;
|
||||
},
|
||||
/** 批量新增按钮操作 */
|
||||
handleAdd() {
|
||||
this.reset();
|
||||
this.open = true;
|
||||
this.title = '生成设备通道';
|
||||
},
|
||||
/** 修改按钮操作 */
|
||||
handleUpdate(row) {
|
||||
this.reset();
|
||||
const id = row.id || this.ids;
|
||||
console.log(row);
|
||||
getChannel(id).then((response) => {
|
||||
this.createForm = response.data;
|
||||
this.open = true;
|
||||
this.title = '修改产品分类';
|
||||
});
|
||||
},
|
||||
/** 提交按钮 */
|
||||
submitForm() {
|
||||
if (this.createForm.createNum < 1) {
|
||||
this.$modal.alertError('通道数量至少一个');
|
||||
return;
|
||||
}
|
||||
if (!this.createForm.productId || this.createForm.productId == 0) {
|
||||
this.$modal.alertError('请选择所属产品');
|
||||
return;
|
||||
}
|
||||
this.createForm.deviceSipId = this.createForm.city[2] + '0000' + this.createForm.deviceType + '0';
|
||||
this.createForm.channelSipId = this.createForm.city[2] + '0000' + this.createForm.channelType + '0';
|
||||
if (this.createForm.deviceType !== '' && this.createForm.channelType !== '' && this.createForm.city.length === 3) {
|
||||
console.log(this.createForm);
|
||||
addChannel(this.createForm.createNum, this.createForm).then((response) => {
|
||||
this.$modal.msgSuccess('新增成功');
|
||||
this.open = false;
|
||||
this.getList();
|
||||
});
|
||||
} else {
|
||||
this.$message({
|
||||
type: 'error',
|
||||
message: '请选择地区,设备类型,通道类型!!',
|
||||
});
|
||||
}
|
||||
},
|
||||
/** 删除按钮操作 */
|
||||
handleDelete(row) {
|
||||
const sipIds = row.id || this.ids;
|
||||
this.$modal
|
||||
.confirm('是否确认删除ID为"' + sipIds + '"的数据项?')
|
||||
.then(function () {
|
||||
return delChannel(sipIds);
|
||||
})
|
||||
.then(() => {
|
||||
this.getList();
|
||||
this.$modal.msgSuccess('删除成功');
|
||||
})
|
||||
.catch(() => { });
|
||||
},
|
||||
|
||||
//禁用有绑定设备的复选框,status:1=未使用,2=已使用
|
||||
selectable(row) {
|
||||
if (row.status == 2 || this.isGeneralUser) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
//表格增加复制功能
|
||||
celldblclick(row, column, cell, event) {
|
||||
this.$copyText(row[column.property]).then(
|
||||
(e) => {
|
||||
this.onCopy();
|
||||
},
|
||||
function (e) {
|
||||
this.onError();
|
||||
}
|
||||
);
|
||||
},
|
||||
onCopy() {
|
||||
this.$notify({
|
||||
title: '成功',
|
||||
message: '复制成功!',
|
||||
type: 'success',
|
||||
offset: 50,
|
||||
duration: 2000,
|
||||
});
|
||||
},
|
||||
onError() {
|
||||
this.$notify({
|
||||
title: '失败',
|
||||
message: '复制失败!',
|
||||
type: 'error',
|
||||
offset: 50,
|
||||
duration: 2000,
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
364
vue/src/views/iot/sip/mediaServer-edit.vue
Normal file
364
vue/src/views/iot/sip/mediaServer-edit.vue
Normal file
@@ -0,0 +1,364 @@
|
||||
<template>
|
||||
<div id="mediaServerEdit" v-loading="isLoging">
|
||||
<el-dialog title="流媒体服务器节点" :width="dialogWidth" top="2rem" :close-on-click-modal="false" :visible.sync="showDialog" :destroy-on-close="true" @close="close()">
|
||||
<div id="formStep" style="margin-top: 1rem; margin-right: 20px">
|
||||
<el-form v-if="currentStep == 1" ref="mediaServerForm" :rules="rules" :model="mediaServerForm" label-width="280px" style="width: 70%">
|
||||
<!-- <el-form-item label="所属租户" prop="productName">-->
|
||||
<!-- <el-input readonly v-model="mediaServerForm.tenantName" placeholder="请选择所属租户">-->
|
||||
<!-- <el-button slot="append" @click="selectUser()">选择</el-button>-->
|
||||
<!-- </el-input>-->
|
||||
<!-- </el-form-item>-->
|
||||
<el-form-item label="服务器IP" prop="ip">
|
||||
<el-input v-model="mediaServerForm.ip" placeholder="媒体服务IP" clearable></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="Http端口" prop="portHttp">
|
||||
<el-input v-model="mediaServerForm.portHttp" placeholder="媒体服务HTTP端口" clearable></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="服务密钥" prop="secret">
|
||||
<el-input v-model="mediaServerForm.secret" placeholder="媒体服务SECRET" clearable></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<div style="float: right; font-size: 28px">
|
||||
<el-button @click="close">取消</el-button>
|
||||
<el-button type="success" @click="checkServer" :loading="btnLoading">测试</el-button>
|
||||
<el-button type="primary" v-if="currentStep === 1 && serverCheck === 1" @click="next">下一步</el-button>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<el-row :gutter="24">
|
||||
<el-col :span="12">
|
||||
<el-form v-if="currentStep === 2 || currentStep === 3" ref="mediaServerForm1" :rules="rules" :model="mediaServerForm" label-width="140px" :disabled="!editFlag">
|
||||
<el-form-item label="配置名称" prop="serverId">
|
||||
<el-input v-model="mediaServerForm.serverId" placeholder="配置名称" clearable></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="服务器IP" prop="ip">
|
||||
<el-input v-if="currentStep === 2" v-model="mediaServerForm.ip" disabled></el-input>
|
||||
<el-input v-if="currentStep === 3" v-model="mediaServerForm.ip"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="播放协议" prop="protocol">
|
||||
<el-select v-model="mediaServerForm.protocol" style="width: 100%">
|
||||
<el-option key="http" label="http" value="http"></el-option>
|
||||
<el-option key="https" label="https" value="https"></el-option>
|
||||
<el-option key="ws" label="ws" value="ws"></el-option>
|
||||
<el-option key="rtmp" label="rtmp" value="rtmp"></el-option>
|
||||
<el-option key="rtsp" label="rtsp" value="rtsp"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="HookUrl" prop="hookurl">
|
||||
<el-input v-model="mediaServerForm.hookurl" placeholder="HookUrl" clearable></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="Http端口" prop="portHttp">
|
||||
<el-input v-if="currentStep === 2" v-model="mediaServerForm.portHttp" disabled></el-input>
|
||||
<el-input v-if="currentStep === 3" v-model="mediaServerForm.portHttp"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="Https端口" prop="portHttps">
|
||||
<el-input v-model="mediaServerForm.portHttps" placeholder="Https端口" clearable></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="Rtsp端口" prop="portRtsp">
|
||||
<el-input v-model="mediaServerForm.portRtsp" placeholder="Rtsp端口" clearable></el-input>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form v-if="currentStep === 2 || currentStep === 3" ref="mediaServerForm2" :rules="rules" :model="mediaServerForm" label-width="180px" :disabled="!editFlag">
|
||||
<el-form-item label="流媒体密钥" prop="secret">
|
||||
<el-input v-if="currentStep === 2" v-model="mediaServerForm.secret" disabled></el-input>
|
||||
<el-input v-if="currentStep === 3" v-model="mediaServerForm.secret"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="服务器域名" prop="domain">
|
||||
<el-input v-model="mediaServerForm.domain" placeholder="服务器域名" clearable></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="自动配置">
|
||||
<el-switch v-model="mediaServerForm.autoConfig"></el-switch>
|
||||
</el-form-item>
|
||||
<el-form-item label="收流模式">
|
||||
<el-switch active-text="多端口" inactive-text="单端口" @change="portRangeChange" v-model="mediaServerForm.rtpEnable"></el-switch>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="!mediaServerForm.rtpEnable" label="收流端口" prop="rtpProxyPort">
|
||||
<el-input v-model.number="mediaServerForm.rtpProxyPort" clearable></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="mediaServerForm.rtpEnable" label="收流端口">
|
||||
<el-input v-model="rtpPortRange1" placeholder="起始" @change="portRangeChange" clearable style="width: 100px" prop="rtpPortRange1"></el-input>
|
||||
<el-input v-model="rtpPortRange2" placeholder="终止" @change="portRangeChange" clearable style="width: 100px" prop="rtpPortRange2" ></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="Rtmp端口" prop="portRtmp">
|
||||
<el-input v-model="mediaServerForm.portRtmp" placeholder="Rtmp端口" clearable></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="录像管理端口" prop="recordPort">
|
||||
<el-input v-model.number="mediaServerForm.recordPort" :disabled="!editFlag">
|
||||
<el-button v-if="mediaServerForm.recordPort > 0" class="el-icon-check" slot="append" type="primary" @click="checkRecordServer"></el-button>
|
||||
</el-input>
|
||||
<i v-if="recordServerCheck === 1" class="el-icon-success" style="color: #3caf36; position: absolute; top: 14px"></i>
|
||||
<i v-if="recordServerCheck === 2" class="el-icon-loading" style="color: #3caf36; position: absolute; top: 14px"></i>
|
||||
<i v-if="recordServerCheck === -1" class="el-icon-error" style="color: #c80000; position: absolute; top: 14px"></i>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<div style="float: right">
|
||||
<el-button type="primary" @click="onSubmit" v-if="editFlag">提交</el-button>
|
||||
<el-button @click="close" v-if="editFlag">关闭</el-button>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</el-dialog>
|
||||
<!-- 选择用户 -->
|
||||
<user-list ref="userList" @userEvent="getUserData($event)" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { addmediaServer, checkmediaServer, updatemediaServer } from '@/api/iot/mediaServer';
|
||||
import userList from '@/views/iot/sip/user-list.vue';
|
||||
|
||||
export default {
|
||||
name: 'MediaServerEdit',
|
||||
components: { userList },
|
||||
props: {
|
||||
editFlag: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
|
||||
},
|
||||
},
|
||||
data() {
|
||||
const isValidIp = (rule, value, callback) => {
|
||||
// 校验IP是否符合规则
|
||||
var reg = /^(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])$/;
|
||||
if (!reg.test(value)) {
|
||||
return callback(new Error('请输入有效的IP地址'));
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
return true;
|
||||
};
|
||||
const isValidPort = (rule, value, callback) => {
|
||||
// 校验IP是否符合规则
|
||||
var reg = /^(([0-9]|[1-9]\d{1,3}|[1-5]\d{4}|6[0-5]{2}[0-3][0-5]))$/;
|
||||
if (!reg.test(value)) {
|
||||
return callback(new Error('请输入有效的端口号'));
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
return true;
|
||||
};
|
||||
return {
|
||||
tempTenantId: '',
|
||||
tempTenantName: '',
|
||||
btnLoading: false,
|
||||
dialogWidth: '',
|
||||
defaultWidth: 1000,
|
||||
listChangeCallback: null,
|
||||
showDialog: false,
|
||||
isLoging: false,
|
||||
dialogLoading: false,
|
||||
currentStep: 1,
|
||||
platformList: [],
|
||||
serverCheck: 0,
|
||||
recordServerCheck: 0,
|
||||
mediaServerForm: {
|
||||
serverId: '',
|
||||
ip: '',
|
||||
domain: '',
|
||||
productId: '',
|
||||
productName: '',
|
||||
tenantId: '',
|
||||
tenantName: '',
|
||||
autoConfig: true,
|
||||
hookurl: '',
|
||||
secret: '',
|
||||
portHttp: '',
|
||||
portHttps: '',
|
||||
recordPort: '',
|
||||
portRtmp: '',
|
||||
portRtsp: '',
|
||||
rtpEnable: true,
|
||||
rtpPortRange: '',
|
||||
rtpProxyPort: '',
|
||||
},
|
||||
rtpPortRange1: 30000,
|
||||
rtpPortRange2: 30100,
|
||||
rules: {
|
||||
ip: [{ required: true, validator: isValidIp, message: '请输入有效的IP地址', trigger: 'blur' }],
|
||||
portHttp: [{ required: true, validator: isValidPort, message: '请输入有效的端口号', trigger: 'blur' }],
|
||||
portHttps: [{ required: true, validator: isValidPort, message: '请输入有效的端口号', trigger: 'blur' }],
|
||||
recordPort: [{ required: true, validator: isValidPort, message: '请输入有效的端口号', trigger: 'blur' }],
|
||||
portRtmp: [{ required: true, validator: isValidPort, message: '请输入有效的端口号', trigger: 'blur' }],
|
||||
portRtsp: [{ required: true, validator: isValidPort, message: '请输入有效的端口号', trigger: 'blur' }],
|
||||
rtpPortRange1: [{ required: true, validator: isValidPort, message: '请输入有效的端口号', trigger: 'blur' }],
|
||||
rtpPortRange2: [{ required: true, validator: isValidPort, message: '请输入有效的端口号', trigger: 'blur' }],
|
||||
rtpProxyPort: [{ required: true, validator: isValidPort, message: '请输入有效的端口号', trigger: 'blur' }],
|
||||
secret: [{ required: true, message: '请输入secret', trigger: 'blur' }],
|
||||
},
|
||||
};
|
||||
},
|
||||
computed: {},
|
||||
created() {
|
||||
this.setDialogWidth();
|
||||
},
|
||||
methods: {
|
||||
setDialogWidth() {
|
||||
let val = document.body.clientWidth;
|
||||
if (val < this.defaultWidth) {
|
||||
this.dialogWidth = '100%';
|
||||
} else {
|
||||
this.dialogWidth = this.defaultWidth + 'px';
|
||||
}
|
||||
},
|
||||
openDialog: function (param, callback) {
|
||||
this.showDialog = true;
|
||||
this.listChangeCallback = callback;
|
||||
if (param != null) {
|
||||
if (param.autoConfig === 1) {
|
||||
param.autoConfig = param.autoConfig === 1;
|
||||
} else if (param.autoConfig === 0) {
|
||||
param.autoConfig = param.autoConfig === 1;
|
||||
}
|
||||
|
||||
if (param.rtpEnable === 1) {
|
||||
param.rtpEnable = param.rtpEnable === 1;
|
||||
} else if (param.rtpEnable === 0) {
|
||||
param.rtpEnable = param.rtpEnable === 1;
|
||||
}
|
||||
|
||||
this.mediaServerForm = param;
|
||||
this.currentStep = 3;
|
||||
if (param.rtpPortRange) {
|
||||
let rtpPortRange = this.mediaServerForm.rtpPortRange.split(',');
|
||||
if (rtpPortRange.length > 0) {
|
||||
this.rtpPortRange1 = rtpPortRange[0];
|
||||
this.rtpPortRange2 = rtpPortRange[1];
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
checkServer: function () {
|
||||
this.$refs.mediaServerForm.validate((valid) => {
|
||||
if (valid) {
|
||||
this.btnLoading = true;
|
||||
this.serverCheck = 0;
|
||||
let query = {
|
||||
ip: this.mediaServerForm.ip,
|
||||
port: this.mediaServerForm.portHttp,
|
||||
secret: this.mediaServerForm.secret,
|
||||
};
|
||||
checkmediaServer(query)
|
||||
.then((response) => {
|
||||
this.btnLoading = false;
|
||||
if (response.data != null) {
|
||||
this.mediaServerForm = response.data;
|
||||
this.mediaServerForm.autoConfig = true;
|
||||
this.mediaServerForm.rtpEnable = true;
|
||||
this.mediaServerForm.protocol = 'http';
|
||||
this.mediaServerForm.domain = 'fastbee.com';
|
||||
this.mediaServerForm.enabled = 1;
|
||||
this.mediaServerForm.tenantId = this.tempTenantId;
|
||||
this.mediaServerForm.tenantName = this.tempTenantName;
|
||||
this.mediaServerForm.serverId = 'fastbee';
|
||||
this.mediaServerForm.hookurl = 'java:8080';
|
||||
this.mediaServerForm.portHttps = 8443;
|
||||
this.mediaServerForm.recordPort = 18081;
|
||||
this.mediaServerForm.portRtmp = 1935;
|
||||
this.mediaServerForm.portRtsp = 554;
|
||||
this.mediaServerForm.rtpProxyPort = '';
|
||||
this.rtpPortRange1 = 30000;
|
||||
this.rtpPortRange2 = 30100;
|
||||
this.serverCheck = 1;
|
||||
this.$modal.alertSuccess('配置地址连接成功');
|
||||
} else {
|
||||
this.serverCheck = -1;
|
||||
this.$modal.alertError('配置地址无法连接');
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
this.serverCheck = -1;
|
||||
this.$message({
|
||||
showClose: true,
|
||||
message: error,
|
||||
type: 'error',
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
next: function () {
|
||||
this.currentStep = 2;
|
||||
this.defaultWidth = 900;
|
||||
this.setDialogWidth();
|
||||
},
|
||||
checkRecordServer: function () {
|
||||
let that = this;
|
||||
that.recordServerCheck = 2;
|
||||
if (that.mediaServerForm.recordPort <= 0 || that.mediaServerForm.recordPort > 65535) {
|
||||
that.recordServerCheck = -1;
|
||||
that.$message({
|
||||
showClose: true,
|
||||
message: '端口号应该在-65535之间',
|
||||
type: 'error',
|
||||
});
|
||||
return;
|
||||
}
|
||||
},
|
||||
onSubmit: function () {
|
||||
this.dialogLoading = true;
|
||||
this.mediaServerForm.rtpEnable = this.mediaServerForm.rtpEnable ? 1 : 0;
|
||||
this.mediaServerForm.autoConfig = this.mediaServerForm.autoConfig ? 1 : 0;
|
||||
if (this.mediaServerForm.id != null) {
|
||||
updatemediaServer(this.mediaServerForm).then((response) => {
|
||||
this.$modal.msgSuccess('修改成功');
|
||||
this.showDialog = false;
|
||||
});
|
||||
} else {
|
||||
this.portRangeChange();
|
||||
addmediaServer(this.mediaServerForm).then((response) => {
|
||||
this.$modal.msgSuccess('新增成功');
|
||||
this.showDialog = false;
|
||||
});
|
||||
}
|
||||
this.$parent.getServerList();
|
||||
this.$parent.delay();
|
||||
},
|
||||
close: function () {
|
||||
this.showDialog = false;
|
||||
this.dialogLoading = false;
|
||||
this.mediaServerForm = {
|
||||
serverId: '',
|
||||
ip: '',
|
||||
domain: '',
|
||||
autoConfig: true,
|
||||
hookurl: '',
|
||||
secret: '',
|
||||
portHttp: '',
|
||||
portHttps: '',
|
||||
recordPort: '',
|
||||
portRtmp: '',
|
||||
portRtsp: '',
|
||||
rtpEnable: true,
|
||||
rtpPortRange: '',
|
||||
rtpProxyPort: '',
|
||||
};
|
||||
this.rtpPortRange1 = 30000;
|
||||
this.rtpPortRange2 = 30100;
|
||||
this.listChangeCallback = null;
|
||||
this.currentStep = 1;
|
||||
},
|
||||
portRangeChange: function () {
|
||||
if (this.mediaServerForm.rtpEnable) {
|
||||
this.mediaServerForm.rtpPortRange = this.rtpPortRange1 + ',' + this.rtpPortRange2;
|
||||
console.log(this.mediaServerForm.rtpPortRange);
|
||||
}
|
||||
},
|
||||
selectUser() {
|
||||
this.$refs.userList.open = true;
|
||||
this.$refs.userList.getList();
|
||||
},
|
||||
getUserData(user) {
|
||||
this.tempTenantId = user.userId;
|
||||
this.tempTenantName = user.userName;
|
||||
this.mediaServerForm.tenantId = user.userId;
|
||||
this.mediaServerForm.tenantName = user.userName;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
188
vue/src/views/iot/sip/mediaServer.vue
Normal file
188
vue/src/views/iot/sip/mediaServer.vue
Normal file
@@ -0,0 +1,188 @@
|
||||
<template>
|
||||
<div id="mediaServer" style="padding: 6px">
|
||||
<el-card style="margin-bottom: 6px">
|
||||
<el-form :model="queryParams" ref="queryForm" :inline="true" label-width="75px" style="margin-bottom: -20px">
|
||||
<el-form-item>
|
||||
<el-button type="primary" plain icon="el-icon-plus" size="mini" @click="add"
|
||||
v-hasPermi="['iot:video:add']">新增节点</el-button>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="warning" plain icon="el-icon-refresh" size="mini" @click="getServerList">刷新</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
|
||||
<el-card style="padding-bottom: 100px" v-loading="loading">
|
||||
<el-row :gutter="30">
|
||||
<el-col :xs="24" :sm="12" :md="12" :lg="8" :xl="6" v-for="(item, index) in mediaServerList" :key="index"
|
||||
style="margin-bottom: 30px; text-align: center">
|
||||
<el-card shadow="always" class="card-item">
|
||||
<el-row :gutter="10">
|
||||
<el-col :span="15">
|
||||
<el-descriptions :column="1" size="mini" style="white-space: nowrap">
|
||||
<el-descriptions-item label="配置名称">
|
||||
{{ item.serverId }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="服务器IP">
|
||||
{{ item.ip }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="播放协议">
|
||||
{{ item.protocol }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="创建时间">
|
||||
{{ parseTime(item.createTime, '{y}-{m}-{d}') }}
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<div style="margin-top: 10px">
|
||||
<el-image :src="require('@/assets/images/zlm-logo.png')" fit="fit"></el-image>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-button-group style="margin-top: 10px">
|
||||
<el-button type="danger" size="mini" style="padding: 5px 10px" icon="el-icon-delete"
|
||||
v-hasPermi="['iot:video:remove']" @click="del(item)">删除</el-button>
|
||||
<el-button type="primary" size="mini" style="padding: 5px 15px" icon="el-icon-view" @click="view(item)"
|
||||
v-hasPermi="['iot:video:query']">查看</el-button>
|
||||
<el-button v-if="!istrue" type="success" size="mini" style="padding: 5px 15px" icon="el-icon-odometer"
|
||||
@click.native.prevent="edit(item)" v-hasPermi="['iot:video:edit']">编辑
|
||||
</el-button>
|
||||
<el-button v-else type="success" size="mini" style="padding: 5px 15px" icon="el-icon-odometer"
|
||||
:loading="true" disabled>重启中...
|
||||
</el-button>
|
||||
</el-button-group>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-empty description="暂无数据,请添加流媒体服务器节点" v-if="total == 0"></el-empty>
|
||||
<pagination v-show="total > 0" :total="total" :page.sync="queryParams.pageNum" :limit.sync="queryParams.pageSize"
|
||||
:pageSizes="[12, 24, 36, 60]" @pagination="getServerList" />
|
||||
</el-card>
|
||||
|
||||
<mediaServerEdit ref="mediaServerEdit" :edit-flag="editFlag"> </mediaServerEdit>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import mediaServerEdit from '@/views/iot/sip/mediaServer-edit.vue';
|
||||
import { delmediaServer, listmediaServer } from '@/api/iot/mediaServer';
|
||||
export default {
|
||||
name: 'MediaServer',
|
||||
components: {
|
||||
mediaServerEdit,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
// 遮罩层
|
||||
loading: true,
|
||||
editFlag: false,
|
||||
istrue: false,
|
||||
mediaServerList: [], //设备列表
|
||||
winHeight: window.innerHeight - 200,
|
||||
updateLooper: false,
|
||||
currentPage: 1,
|
||||
count: 15,
|
||||
num: this.getNumberByWidth(),
|
||||
total: 0,
|
||||
queryParams: {
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
},
|
||||
};
|
||||
},
|
||||
computed: {},
|
||||
mounted() {
|
||||
this.initData();
|
||||
//this.updateLooper = setInterval(this.initData, 2000);
|
||||
},
|
||||
destroyed() {
|
||||
clearTimeout(this.updateLooper);
|
||||
},
|
||||
methods: {
|
||||
initData: function () {
|
||||
this.getServerList();
|
||||
},
|
||||
pageNumChange: function (val) {
|
||||
this.queryParams.pageNum = val;
|
||||
this.getServerList();
|
||||
},
|
||||
pageSizeChange: function (val) {
|
||||
this.queryParams.pageSize = val;
|
||||
this.getServerList();
|
||||
},
|
||||
getServerList: function () {
|
||||
this.loading = true;
|
||||
listmediaServer(this.queryParams).then((response) => {
|
||||
this.mediaServerList = response.rows;
|
||||
this.total = response.total;
|
||||
this.loading = false;
|
||||
});
|
||||
},
|
||||
add: function () {
|
||||
this.$refs.mediaServerEdit.openDialog(null, this.initData, this.editFlag);
|
||||
this.editFlag = true;
|
||||
},
|
||||
view: function (row) {
|
||||
this.$refs.mediaServerEdit.openDialog(row, this.initData, this.editFlag);
|
||||
this.editFlag = false;
|
||||
},
|
||||
edit: function (row) {
|
||||
this.$refs.mediaServerEdit.openDialog(row, this.initData, this.editFlag);
|
||||
this.editFlag = true;
|
||||
},
|
||||
delay: function () {
|
||||
let n = 5;
|
||||
// 定义定时器time
|
||||
const time = setInterval(() => {
|
||||
this.istrue = true;
|
||||
n--;
|
||||
// 如果n<0,清除定时器,禁用状态取消,文字提示为空(不显示)
|
||||
if (n < 0) {
|
||||
this.istrue = false;
|
||||
clearInterval(time);
|
||||
}
|
||||
}, 1000);
|
||||
},
|
||||
del: function (row) {
|
||||
const ids = row.id || this.ids;
|
||||
this.$modal
|
||||
.confirm('是否确认删除流媒体服务器配置编号为"' + ids + '"的数据项?')
|
||||
.then(function () {
|
||||
delmediaServer(ids);
|
||||
})
|
||||
.then(() => {
|
||||
this.getServerList();
|
||||
this.$modal.msgSuccess('删除成功');
|
||||
})
|
||||
.catch(() => { });
|
||||
},
|
||||
getNumberByWidth() {
|
||||
let candidateNums = [1, 2, 3, 4, 6, 8, 12, 24];
|
||||
let clientWidth = window.innerWidth - 30;
|
||||
let interval = 20;
|
||||
let itemWidth = 360;
|
||||
let num = (clientWidth + interval) / (itemWidth + interval);
|
||||
let result = Math.ceil(24 / num);
|
||||
let resultVal = 24;
|
||||
for (let i = 0; i < candidateNums.length; i++) {
|
||||
let value = candidateNums[i];
|
||||
if (i + 1 >= candidateNums.length) {
|
||||
return 24;
|
||||
}
|
||||
if (value <= result && candidateNums[i + 1] > result) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
return resultVal;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.card-item {
|
||||
border-radius: 10px;
|
||||
padding: 15px 0px;
|
||||
}
|
||||
</style>
|
||||
145
vue/src/views/iot/sip/product-list.vue
Normal file
145
vue/src/views/iot/sip/product-list.vue
Normal file
@@ -0,0 +1,145 @@
|
||||
<template>
|
||||
<el-dialog title="选择产品" :visible.sync="open" width="600px" append-to-body>
|
||||
<div style="margin-top:-55px;">
|
||||
<el-divider style="margin-top:-30px;"></el-divider>
|
||||
<el-form :model="queryParams" ref="queryForm" :inline="true" label-width="68px">
|
||||
<el-form-item label="产品名称" prop="productName">
|
||||
<el-input v-model="queryParams.productName" placeholder="请输入产品名称" clearable size="small" @keyup.enter.native="handleQuery" />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
|
||||
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<el-table v-loading="loading" ref="singleTable" :data="productList" @row-click="rowClick" highlight-current-row size="mini">
|
||||
<el-table-column label="选择" width="50" align="center">
|
||||
<template slot-scope="scope">
|
||||
<input type="radio" :checked="scope.row.isSelect" name="product" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="产品名称" align="center" prop="productName" />
|
||||
<el-table-column label="分类名称" align="center" prop="categoryName" />
|
||||
<el-table-column label="租户名称" align="center" prop="tenantName" />
|
||||
<el-table-column label="联网方式" align="center" prop="networkMethod">
|
||||
<template slot-scope="scope">
|
||||
<dict-tag :options="dict.type.iot_network_method" :value="scope.row.networkMethod" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="创建时间" align="center" prop="createTime" width="100">
|
||||
<template slot-scope="scope">
|
||||
<span>{{ parseTime(scope.row.createTime, '{y}-{m}-{d}') }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<pagination v-show="total>0" :total="total" :page.sync="queryParams.pageNum" :limit.sync="queryParams.pageSize" @pagination="getList" />
|
||||
</div>
|
||||
<div slot="footer" class="dialog-footer">
|
||||
<el-button @click="confirmSelectProduct" type="primary">确定</el-button>
|
||||
<el-button @click="closeDialog" type="info">关 闭</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {
|
||||
listProduct,
|
||||
} from "@/api/iot/product";
|
||||
|
||||
export default {
|
||||
name: "SipProductList",
|
||||
dicts: ['iot_vertificate_method', 'iot_network_method'],
|
||||
props: {
|
||||
productId: {
|
||||
type: Number,
|
||||
default: 0
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
// 遮罩层
|
||||
loading: true,
|
||||
// 总条数
|
||||
total: 0,
|
||||
// 打开选择产品对话框
|
||||
open: false,
|
||||
// 产品列表
|
||||
productList: [],
|
||||
// 选中的产品
|
||||
product: {},
|
||||
// 查询参数
|
||||
queryParams: {
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
productName: null,
|
||||
categoryId: null,
|
||||
categoryName: null,
|
||||
tenantId: null,
|
||||
tenantName: null,
|
||||
isSys: null,
|
||||
status: 2, //已发布
|
||||
deviceType: 3, // 监控设备
|
||||
networkMethod: null,
|
||||
},
|
||||
};
|
||||
},
|
||||
created() {
|
||||
|
||||
},
|
||||
methods: {
|
||||
/** 查询产品列表 */
|
||||
getList() {
|
||||
this.loading = true;
|
||||
listProduct(this.queryParams).then(response => {
|
||||
//产品列表初始化isSelect值,用于单选
|
||||
for (let i = 0; i < response.rows.length; i++) {
|
||||
response.rows[i].isSelect = false;
|
||||
}
|
||||
this.productList = response.rows;
|
||||
this.total = response.total;
|
||||
if (this.productId != 0) {
|
||||
this.setRadioSelected(this.productId);
|
||||
}
|
||||
this.loading = false;
|
||||
});
|
||||
},
|
||||
/** 搜索按钮操作 */
|
||||
handleQuery() {
|
||||
this.queryParams.pageNum = 1;
|
||||
this.getList();
|
||||
},
|
||||
/** 重置按钮操作 */
|
||||
resetQuery() {
|
||||
this.resetForm("queryForm");
|
||||
this.handleQuery();
|
||||
},
|
||||
/** 单选数据 */
|
||||
rowClick(product) {
|
||||
if (product != null) {
|
||||
this.setRadioSelected(product.productId);
|
||||
this.product = product;
|
||||
}
|
||||
},
|
||||
/** 设置单选按钮选中 */
|
||||
setRadioSelected(productId) {
|
||||
for (let i = 0; i < this.productList.length; i++) {
|
||||
if (this.productList[i].productId == productId) {
|
||||
this.productList[i].isSelect = true;
|
||||
} else {
|
||||
this.productList[i].isSelect = false;
|
||||
}
|
||||
}
|
||||
},
|
||||
/**确定选择产品,产品传递给父组件 */
|
||||
confirmSelectProduct() {
|
||||
this.$emit('productEvent', this.product);
|
||||
this.open = false;
|
||||
},
|
||||
/**关闭对话框 */
|
||||
closeDialog() {
|
||||
this.open = false;
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
160
vue/src/views/iot/sip/sipconfig.vue
Normal file
160
vue/src/views/iot/sip/sipconfig.vue
Normal file
@@ -0,0 +1,160 @@
|
||||
<template>
|
||||
<div style="padding:6px;">
|
||||
<el-form ref="form" :model="form" label-width="100px">
|
||||
<el-row :gutter="100">
|
||||
<el-col :xs="24" :sm="24" :md="12" :lg="12" :xl="8">
|
||||
<el-form-item label="默认配置" prop="isdefault">
|
||||
<el-switch v-model="form.isdefault" :active-value="1" :inactive-value="0"/>
|
||||
</el-form-item>
|
||||
<el-form-item label="服务器地址" prop="ip">
|
||||
<el-input v-model="form.ip" disabled />
|
||||
</el-form-item>
|
||||
<el-form-item label="服务器域" prop="domain">
|
||||
<el-input v-model="form.domain" />
|
||||
</el-form-item>
|
||||
<el-form-item label="认证密码" prop="password">
|
||||
<el-input v-model="form.password" placeholder="请输入认证密码" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :xs="24" :sm="24" :md="12" :lg="12" :xl="8">
|
||||
<el-form-item label="接入方式">
|
||||
<el-input v-model="accessWay" disabled>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="服务器端口" prop="port">
|
||||
<el-input v-model="form.port" type="number" disabled />
|
||||
</el-form-item>
|
||||
<el-form-item label="服务器ID" prop="serverSipid">
|
||||
<el-input v-model="form.serverSipid" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :xs="23" :sm="23" :md="23" :lg="23" :xl="15">
|
||||
<el-form-item style="text-align:center;margin-top:20px;">
|
||||
<el-button v-show="form.id && productInfo.status != 2" v-hasPermi="['iot:video:edit']" type="primary"
|
||||
@click="submitForm">修 改</el-button>
|
||||
<el-button v-show="!form.id && productInfo.status != 2" v-hasPermi="['iot:video:add']" type="primary"
|
||||
@click="submitForm">新 增</el-button>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
.specsColor {
|
||||
background-color: #fcfcfc;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import {
|
||||
getSipconfig,
|
||||
addSipconfig,
|
||||
updateSipconfig,
|
||||
} from '@/api/iot/sipConfig';
|
||||
|
||||
export default {
|
||||
name: 'ConfigSip',
|
||||
props: {
|
||||
product: {
|
||||
type: Object,
|
||||
default: null,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
// 接入方式
|
||||
accessWay: '国标GB28181',
|
||||
// 遮罩层
|
||||
loading: true,
|
||||
// 选中数组
|
||||
ids: [],
|
||||
// 总条数
|
||||
total: 0,
|
||||
// sip系统配置表格数据
|
||||
sipconfigList: [],
|
||||
// 弹出层标题
|
||||
title: '',
|
||||
// 是否显示弹出层
|
||||
open: false,
|
||||
// 查询参数
|
||||
queryParams: {
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
productId: null,
|
||||
},
|
||||
// 表单参数
|
||||
form: {},
|
||||
// 表单校验
|
||||
rules: {
|
||||
domain: [{
|
||||
required: true,
|
||||
message: '服务器域不能为空',
|
||||
trigger: 'blur',
|
||||
}],
|
||||
serverSipid: [{
|
||||
required: true,
|
||||
message: '服务器sipid不能为空',
|
||||
trigger: 'blur',
|
||||
}],
|
||||
password: [{
|
||||
required: true,
|
||||
message: 'sip认证密码不能为空',
|
||||
trigger: 'blur',
|
||||
}],
|
||||
},
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
// 获取到父组件传递的productId后,刷新列表
|
||||
product: function (newVal, oldVal) {
|
||||
this.productInfo = newVal;
|
||||
if (this.productInfo && this.productInfo.productId != 0) {
|
||||
// 表单没有数据则获取默认配置
|
||||
if (!this.form.id) {
|
||||
this.getSipconfig(true);
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
created() {
|
||||
this.productInfo = this.product;
|
||||
if (this.productInfo && this.productInfo.productId != 0) {
|
||||
this.getSipconfig(false);
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
/** 获取产品下第一条SIP配置 */
|
||||
getSipconfig(isDefault) {
|
||||
getSipconfig(this.productInfo.productId, isDefault).then(response => {
|
||||
this.form = response.data;
|
||||
if (isDefault) {
|
||||
this.submitForm();
|
||||
}
|
||||
});
|
||||
},
|
||||
/** 提交按钮 */
|
||||
submitForm() {
|
||||
this.$refs['form'].validate(valid => {
|
||||
if (valid) {
|
||||
this.form.productId = this.product.productId;
|
||||
if (this.form.isdefault == null) {
|
||||
this.form.isdefault = 0;
|
||||
}
|
||||
if (this.form.id != null) {
|
||||
updateSipconfig(this.form).then(response => {
|
||||
this.$modal.msgSuccess('修改成功');
|
||||
});
|
||||
} else {
|
||||
addSipconfig(this.form).then(response => {
|
||||
this.$modal.msgSuccess('新增成功');
|
||||
this.getSipconfig(false);
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
119
vue/src/views/iot/sip/sipidGen.vue
Normal file
119
vue/src/views/iot/sip/sipidGen.vue
Normal file
@@ -0,0 +1,119 @@
|
||||
<template>
|
||||
<el-dialog :title="title" :visible.sync="open" width="400px" append-to-body>
|
||||
<el-form ref="createForm" :model="createForm" label-width="100px">
|
||||
<el-form-item label="行政区划">
|
||||
<el-cascader :options="cityOptions" v-model="createForm.city" @change="changeProvince" change-on-select>
|
||||
</el-cascader>
|
||||
</el-form-item>
|
||||
<el-form-item label="设备类型" prop="deviceType">
|
||||
<el-select v-model="createForm.deviceType" placeholder="请选择设备类型">
|
||||
<el-option v-for="dict in dict.type.video_type" :key="dict.value" :label="dict.label" :value="dict.value"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="通道类型" prop="channelType">
|
||||
<el-select v-model="createForm.channelType" placeholder="请选择设备类型">
|
||||
<el-option v-for="dict in dict.type.channel_type" :key="dict.value" :label="dict.label" :value="dict.value"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="通道数量" prop="createNum">
|
||||
<el-input-number controls-position="right" v-model="createForm.createNum" placeholder="请输入生成通道数量" type="number" style="width:220px;" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div slot="footer" class="dialog-footer">
|
||||
<el-button type="primary" @click="submitForm">生 成</el-button>
|
||||
<el-button @click="closeDialog">取 消</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {
|
||||
regionData,
|
||||
CodeToText
|
||||
} from 'element-china-area-data'
|
||||
|
||||
import {addChannel} from "@/api/iot/channel";
|
||||
|
||||
export default {
|
||||
name: "SipidDialog",
|
||||
dicts: ['video_type', 'channel_type'],
|
||||
props: {
|
||||
product: {
|
||||
type: Object,
|
||||
default: null
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
// 遮罩层
|
||||
loading: true,
|
||||
title: "生成设备编号和通道",
|
||||
// 总条数
|
||||
total: 0,
|
||||
// 打开选择产品对话框
|
||||
open: false,
|
||||
devsipid: "",
|
||||
createForm: {
|
||||
city: '',
|
||||
deviceType: '',
|
||||
channelType: '',
|
||||
createNum: 1,
|
||||
},
|
||||
// 城市
|
||||
cityOptions: regionData,
|
||||
city: '',
|
||||
cityCode: '',
|
||||
};
|
||||
},
|
||||
created() {
|
||||
|
||||
},
|
||||
methods: {
|
||||
/** 行政区划改变 **/
|
||||
changeProvince(data) {
|
||||
if (data && data[0] != null && data[1] != null && data[2] != null) {
|
||||
const str = CodeToText[data[0]] + '/' + CodeToText[data[1]] + '/' + CodeToText[data[2]];
|
||||
this.createForm.citycode = str;
|
||||
}
|
||||
},
|
||||
/** 提交按钮 */
|
||||
submitForm() {
|
||||
if (this.createForm.createNum < 1) {
|
||||
this.$modal.alertError("通道数量至少一个");
|
||||
return;
|
||||
}
|
||||
this.createForm.productId = this.product.productId;
|
||||
this.createForm.productName = this.product.productName;
|
||||
this.createForm.tenantId = this.product.tenantId;
|
||||
this.createForm.tenantName = this.product.tenantName;
|
||||
this.createForm.deviceSipId = this.createForm.city[2] + "0000" + this.createForm.deviceType + "0"
|
||||
this.createForm.channelSipId = this.createForm.city[2] + "0000" + this.createForm.channelType + "0"
|
||||
if (this.createForm.deviceType !== "" && this.createForm.channelType !== "" && this.createForm.city.length === 3) {
|
||||
addChannel(this.createForm.createNum, this.createForm).then(response => {
|
||||
this.$modal.msgSuccess("已生成设备编号和通道");
|
||||
this.devsipid = response.data;
|
||||
this.confirmSelectProduct();
|
||||
});
|
||||
} else {
|
||||
this.$message({
|
||||
type: 'error',
|
||||
message: '请选择地区,设备类型,通道类型!!'
|
||||
});
|
||||
}
|
||||
|
||||
},
|
||||
confirmSelectProduct() {
|
||||
this.open = false;
|
||||
this.$emit('addGenEvent', this.devsipid);
|
||||
},
|
||||
/**关闭对话框 */
|
||||
closeDialog() {
|
||||
this.open = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
221
vue/src/views/iot/sip/splitview.vue
Normal file
221
vue/src/views/iot/sip/splitview.vue
Normal file
@@ -0,0 +1,221 @@
|
||||
<template>
|
||||
<div style="padding: 6px">
|
||||
<el-card id="devicePosition" style="width: 100vw; height: 91vh" :body-style="{ padding: '0px' }">
|
||||
<el-container v-loading="loading" style="height: 91vh; " element-loading-text="拼命加载中">
|
||||
<el-aside width="250px" style="background-color: #ffffff;">
|
||||
<DeviceTree :clickEvent="clickEvent"></DeviceTree>
|
||||
</el-aside>
|
||||
<el-main style="padding: 0">
|
||||
<div height="5vh" style="text-align: left; font-size: 17px; line-height: 5vh;margin-bottom: 10px;">
|
||||
分屏:
|
||||
<el-button type="success" style="margin-left: 10px;" :class="{ active: spilt == 1 }" @click="spilt = 1"
|
||||
plain icon='el-icon-full-screen' size="mini">单屏 </el-button>
|
||||
<el-button type="info" style="margin-left: 10px;" :class="{ active: spilt == 4 }" @click="spilt = 4"
|
||||
icon="el-icon-menu" plain size="mini">四屏</el-button>
|
||||
<el-button type="warning" style="margin-left: 10px;" :class="{ active: spilt == 9 }" @click="spilt = 9"
|
||||
plain icon="el-icon-s-grid" size="mini">九屏</el-button>
|
||||
</div>
|
||||
<div style="height: 85vh; display: flex; flex-wrap: wrap">
|
||||
<div v-for="i in spilt" :key="i" class="play-box" :style="liveStyle"
|
||||
:class="{ redborder: playerIdx == i - 1 }" @click="playerIdx = i - 1">
|
||||
<div v-if="!videoUrl[i - 1]" style="color: #ffffff; font-size: 30px; font-weight: bold">{{ i }}</div>
|
||||
<player ref="player" v-else :videoUrl="videoUrl[i - 1]" fluent autoplay @screenshot="shot"
|
||||
@destroy="destroy" class="player-wrap" />
|
||||
</div>
|
||||
</div>
|
||||
</el-main>
|
||||
</el-container>
|
||||
</el-card>
|
||||
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import player from '@/views/components/player/jessibuca.vue';
|
||||
import DeviceTree from '@/views/components/player/DeviceTree.vue';
|
||||
import { startPlay } from '@/api/iot/channel';
|
||||
|
||||
export default {
|
||||
name: 'SplitView',
|
||||
components: {
|
||||
player,
|
||||
DeviceTree,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
videoUrl: [''],
|
||||
spilt: 1, //分屏
|
||||
playerIdx: 0, //激活播放器
|
||||
updateLooper: 0, //数据刷新轮训标志
|
||||
count: 15,
|
||||
total: 0,
|
||||
loading: false,
|
||||
};
|
||||
},
|
||||
mounted() { },
|
||||
created() {
|
||||
this.checkPlayByParam();
|
||||
},
|
||||
|
||||
computed: {
|
||||
liveStyle() {
|
||||
let style = { width: '81%', height: '99%' };
|
||||
switch (this.spilt) {
|
||||
case 4:
|
||||
style = { width: '40%', height: '49%' };
|
||||
break;
|
||||
case 9:
|
||||
style = { width: '27%', height: '32%' };
|
||||
break;
|
||||
}
|
||||
this.$nextTick(() => {
|
||||
for (let i = 0; i < this.spilt; i++) {
|
||||
const player = this.$refs.player;
|
||||
player && player[i] && player[i].updatePlayerDomSize();
|
||||
}
|
||||
});
|
||||
return style;
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
spilt(newValue) {
|
||||
console.log('切换画幅;' + newValue);
|
||||
let that = this;
|
||||
for (let i = 1; i <= newValue; i++) {
|
||||
if (!that.$refs['player' + i]) {
|
||||
continue;
|
||||
}
|
||||
this.$nextTick(() => {
|
||||
if (that.$refs['player' + i] instanceof Array) {
|
||||
that.$refs['player' + i][0].resize();
|
||||
} else {
|
||||
that.$refs['player' + i].resize();
|
||||
}
|
||||
});
|
||||
}
|
||||
window.localStorage.setItem('split', newValue);
|
||||
},
|
||||
'$route.fullPath': 'checkPlayByParam',
|
||||
},
|
||||
destroyed() {
|
||||
clearTimeout(this.updateLooper);
|
||||
},
|
||||
methods: {
|
||||
destroy(idx) {
|
||||
console.log(idx);
|
||||
this.clear(idx.substring(idx.length - 1));
|
||||
},
|
||||
clickEvent: function (data) {
|
||||
if (data.channelSipId) {
|
||||
this.sendDevicePush(data);
|
||||
}
|
||||
},
|
||||
//通知设备上传媒体流
|
||||
sendDevicePush: function (itemData) {
|
||||
this.save(itemData);
|
||||
let deviceId = itemData.deviceSipId;
|
||||
let channelId = itemData.channelSipId;
|
||||
console.log('通知设备推流1:' + deviceId + ' : ' + channelId);
|
||||
let idxTmp = this.playerIdx;
|
||||
let that = this;
|
||||
this.loading = true;
|
||||
startPlay(deviceId, channelId)
|
||||
.then((response) => {
|
||||
console.log('开始播放:' + this.deviceId + ' : ' + this.channelId);
|
||||
console.log('流媒体信息:' + response.data);
|
||||
let res = response.data;
|
||||
console.log('playurl:' + res.playurl);
|
||||
itemData.playUrl = res.playurl;
|
||||
itemData.streamId = res.streamId;
|
||||
that.setPlayUrl(itemData.playUrl, idxTmp);
|
||||
})
|
||||
.finally(() => {
|
||||
that.loading = false;
|
||||
});
|
||||
},
|
||||
setPlayUrl(url, idx) {
|
||||
this.$set(this.videoUrl, idx, url);
|
||||
let _this = this;
|
||||
setTimeout(() => {
|
||||
window.localStorage.setItem('videoUrl', JSON.stringify(_this.videoUrl));
|
||||
}, 100);
|
||||
},
|
||||
checkPlayByParam() {
|
||||
let { deviceId, channelId } = this.$route.query;
|
||||
if (deviceId && channelId) {
|
||||
this.sendDevicePush({ deviceId, channelId });
|
||||
}
|
||||
},
|
||||
shot(e) {
|
||||
var base64ToBlob = function (code) {
|
||||
let parts = code.split(';base64,');
|
||||
let contentType = parts[0].split(':')[1];
|
||||
let raw = window.atob(parts[1]);
|
||||
let rawLength = raw.length;
|
||||
let uInt8Array = new Uint8Array(rawLength);
|
||||
for (let i = 0; i < rawLength; ++i) {
|
||||
uInt8Array[i] = raw.charCodeAt(i);
|
||||
}
|
||||
return new Blob([uInt8Array], {
|
||||
type: contentType,
|
||||
});
|
||||
};
|
||||
let aLink = document.createElement('a');
|
||||
let blob = base64ToBlob(e); //new Blob([content]);
|
||||
let evt = document.createEvent('HTMLEvents');
|
||||
evt.initEvent('click', true, true); //initEvent 不加后两个参数在FF下会报错 事件类型,是否冒泡,是否阻止浏览器的默认行为
|
||||
aLink.download = '截图';
|
||||
aLink.href = URL.createObjectURL(blob);
|
||||
aLink.click();
|
||||
},
|
||||
save(item) {
|
||||
let dataStr = window.localStorage.getItem('playData') || '[]';
|
||||
let data = JSON.parse(dataStr);
|
||||
data[this.playerIdx] = item;
|
||||
window.localStorage.setItem('playData', JSON.stringify(data));
|
||||
},
|
||||
clear(idx) {
|
||||
let dataStr = window.localStorage.getItem('playData') || '[]';
|
||||
let data = JSON.parse(dataStr);
|
||||
data[idx - 1] = null;
|
||||
console.log(data);
|
||||
window.localStorage.setItem('playData', JSON.stringify(data));
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<style>
|
||||
.btn {
|
||||
margin: 0 10px;
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
color: #409eff;
|
||||
}
|
||||
|
||||
.btn.active {
|
||||
color: #409eff;
|
||||
}
|
||||
|
||||
.redborder {
|
||||
border: 2px solid red !important;
|
||||
}
|
||||
|
||||
.play-box {
|
||||
background-color: #000000;
|
||||
border: 1px solid #505050;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-right: 10px;
|
||||
position: relative;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.player-wrap {
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
height: 100% !important;
|
||||
}
|
||||
</style>
|
||||
154
vue/src/views/iot/sip/user-list.vue
Normal file
154
vue/src/views/iot/sip/user-list.vue
Normal file
@@ -0,0 +1,154 @@
|
||||
<template>
|
||||
<el-dialog title="选择用户" :visible.sync="open" width="800px">
|
||||
<div style="margin-top:-50px;">
|
||||
<el-divider></el-divider>
|
||||
</div>
|
||||
<!--用户数据-->
|
||||
<el-form :model="queryParams" ref="queryForm" :rules="rules" :inline="true" label-width="80px">
|
||||
<el-form-item label="手机号码" prop="phonenumber">
|
||||
<el-input type="text" placeholder="请输入用户手机号码" v-model="queryParams.phonenumber" minlength="10" clearable size="small" show-word-limit style="width: 240px" @keyup.enter.native="handleQuery"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">查询</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<el-table v-loading="loading" :data="userList" highlight-current-row size="mini" @current-change="handleCurrentChange" border>
|
||||
<el-table-column label="选择" width="50" align="center">
|
||||
<template slot-scope="scope">
|
||||
<input type="radio" :checked="scope.row.isSelect" name="user" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="用户编号" align="center" key="userId" prop="userId" width="120" />
|
||||
<el-table-column label="用户名称" align="center" key="userName" prop="userName" />
|
||||
<el-table-column label="用户昵称" align="center" key="nickName" prop="nickName" />
|
||||
<el-table-column label="手机号码" align="center" key="phonenumber" prop="phonenumber" width="120" />
|
||||
<el-table-column label="创建时间" align="center" prop="createTime" width="160">
|
||||
<template slot-scope="scope">
|
||||
<span>{{ parseTime(scope.row.createTime) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
</el-table>
|
||||
<div slot="footer" class="dialog-footer">
|
||||
<el-button type="primary" @click="confirmSelectUser">确定</el-button>
|
||||
<el-button @click="closeSelectUser">关 闭</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {
|
||||
listUser
|
||||
} from "@/api/iot/tool";
|
||||
|
||||
export default {
|
||||
name: "user-list",
|
||||
props: {
|
||||
device: {
|
||||
type: Array,
|
||||
default: null
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
// 获取到父组件传递的device
|
||||
device: function (newVal, oldVal) {
|
||||
this.deviceInfo = newVal;
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
// 遮罩层
|
||||
loading: false,
|
||||
// 选中数组
|
||||
ids: [],
|
||||
// 弹出层标题
|
||||
title: "",
|
||||
// 用户列表
|
||||
userList: [],
|
||||
// 选中的用户
|
||||
user: {},
|
||||
// 设备信息
|
||||
deviceInfo: {},
|
||||
// 是否显示选择用户弹出层
|
||||
open: false,
|
||||
// 查询参数
|
||||
queryParams: {
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
userName: undefined,
|
||||
phonenumber: undefined,
|
||||
status: 0,
|
||||
deptId: undefined
|
||||
},
|
||||
// 表单校验
|
||||
rules: {
|
||||
phonenumber: [{
|
||||
required: true,
|
||||
message: "手机号码不能为空",
|
||||
trigger: "blur"
|
||||
}, {
|
||||
min: 11,
|
||||
max: 11,
|
||||
message: '手机号码长度为11位',
|
||||
trigger: 'blur'
|
||||
}],
|
||||
},
|
||||
};
|
||||
},
|
||||
created() {},
|
||||
methods: {
|
||||
/** 查询用户列表 */
|
||||
getList() {
|
||||
this.loading = true;
|
||||
listUser(this.addDateRange(this.queryParams, this.dateRange)).then(response => {
|
||||
this.userList = response.rows;
|
||||
this.total = response.total;
|
||||
this.loading = false;
|
||||
});
|
||||
},
|
||||
/** 搜索按钮操作 */
|
||||
handleQuery() {
|
||||
this.$refs["queryForm"].validate(valid => {
|
||||
if (valid) {
|
||||
this.queryParams.pageNum = 1;
|
||||
this.getList();
|
||||
}
|
||||
});
|
||||
|
||||
},
|
||||
// 重置查询
|
||||
resetQuery() {
|
||||
this.$refs["queryForm"].resetFields();
|
||||
this.userList = [];
|
||||
},
|
||||
//设置单选按钮选中
|
||||
setRadioSelected(userId) {
|
||||
for (let i = 0; i < this.userList.length; i++) {
|
||||
if (this.userList[i].userId === userId) {
|
||||
this.userList[i].isSelect = true;
|
||||
this.user = this.userList[i];
|
||||
} else {
|
||||
this.userList[i].isSelect = false;
|
||||
}
|
||||
}
|
||||
},
|
||||
// 单选数据
|
||||
handleCurrentChange(user) {
|
||||
if (user != null) {
|
||||
this.setRadioSelected(user.userId);
|
||||
this.user = user;
|
||||
}
|
||||
},
|
||||
confirmSelectUser() {
|
||||
this.$emit('userEvent', this.user);
|
||||
this.open = false;
|
||||
},
|
||||
// 关闭选择用户
|
||||
closeSelectUser() {
|
||||
this.open = false;
|
||||
this.resetQuery();
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
Reference in New Issue
Block a user