feat:视频预览分页
This commit is contained in:
1 parent
f1bcaef4cd
commit
ac2085eb4c
11 files changed
+474
-423
No files matched your search
@@ -1,5 +1,6 @@
|
||||
import request from "/@/utils/request"
|
||||
|
||||
// 通过deviceCode查询通道列表
|
||||
export function list(query: any) {
|
||||
return request({
|
||||
url: '/admin/deviceChannel/list',
|
||||
@@ -8,6 +9,19 @@ export function list(query: any) {
|
||||
})
|
||||
}
|
||||
|
||||
// 通过deviceCode查询通道列表
|
||||
export const getChannelPage = (query:any) => {
|
||||
return request({
|
||||
url: '/admin/deviceChannel/panel/getPage',
|
||||
method: 'post',
|
||||
data:query,
|
||||
params: {
|
||||
current: query.current,
|
||||
size: query.size,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export function getReplayList(data: any) {
|
||||
return request({
|
||||
url: '/admin/deviceChannel/getReplayList',
|
||||
|
||||
@@ -0,0 +1,126 @@
|
||||
const pageDivId = "ws-video-page"
|
||||
|
||||
const rendererPage = (page = 1, pageSize = 10, total = 0, onChange = () => { }) => {
|
||||
const tpl = document.createElement('template')
|
||||
tpl.innerHTML = `
|
||||
<div id="${pageDivId}" style="margin-left: 10px;flex:1;justify-content:center;height:100%;display:flex;flex-direction:column;">
|
||||
</div>
|
||||
`.trim()
|
||||
const root = tpl.content.firstElementChild
|
||||
// 初始化分页栏
|
||||
mountPagination(root, { total, pageSize, current: page, onChange })
|
||||
return root
|
||||
}
|
||||
|
||||
const createPagination = ({ total = 0, pageSize = 10, current = 1, onChange = (page, pageSize) => { } }) => {
|
||||
const totalPages = Math.max(1, Math.ceil(total / pageSize))
|
||||
const bar = document.createElement('div')
|
||||
bar.className = 'ws-pagination-bar'
|
||||
bar.id = 'ws-pagination-bar'
|
||||
bar.style.cssText = 'display:flex;gap:4px;align-items:center;flex-wrap:wrap;color:#e5e7eb;font-size:11px;'
|
||||
// 将当前分页状态写入 dataset,事件从 dataset 读取最新值,避免闭包旧值
|
||||
bar.dataset.current = String(current)
|
||||
bar.dataset.pageSize = String(pageSize)
|
||||
bar.dataset.total = String(total)
|
||||
bar.dataset.totalPages = String(totalPages)
|
||||
|
||||
const makeBtn = (text, disabled, handler) => {
|
||||
const btn = document.createElement('button')
|
||||
btn.textContent = text
|
||||
btn.disabled = !!disabled
|
||||
btn.style.cssText = 'padding:2px 6px;border:1px solid #B4BAC2;background:transparent;color:#B4BAC2;border-radius:3px;cursor:pointer;font-size:14px;'
|
||||
btn.addEventListener('click', handler)
|
||||
return btn
|
||||
}
|
||||
|
||||
const prev = makeBtn('上一页', current <= 1, () => {
|
||||
const cur = Number(bar.dataset.current || 1)
|
||||
const size = Number(bar.dataset.pageSize || 10)
|
||||
onChange(Math.max(1, cur - 1), size)
|
||||
})
|
||||
prev.className = 'ws-pagination-prev'
|
||||
const next = makeBtn('下一页', current >= totalPages, () => {
|
||||
const cur = Number(bar.dataset.current || 1)
|
||||
const size = Number(bar.dataset.pageSize || 10)
|
||||
const tp = Number(bar.dataset.totalPages || 1)
|
||||
onChange(Math.min(tp, cur + 1), size)
|
||||
})
|
||||
next.className = 'ws-pagination-next'
|
||||
|
||||
const info = document.createElement('span')
|
||||
info.className = 'ws-pagination-info'
|
||||
info.textContent = `第${current}/${totalPages}页,共${total}项`
|
||||
info.style = "font-size:14px;color:#B4BAC2;"
|
||||
|
||||
bar.append(prev, info, next)
|
||||
return bar
|
||||
}
|
||||
|
||||
const mountPagination = ($container, { total, pageSize, current, onChange }) => {
|
||||
// 就地更新,避免闪烁与重复 append
|
||||
const updateUI = (bar, total, size, cur) => {
|
||||
const tp = Math.max(1, Math.ceil(total / size))
|
||||
// 写入最新状态
|
||||
bar.dataset.current = String(cur)
|
||||
bar.dataset.pageSize = String(size)
|
||||
bar.dataset.total = String(total)
|
||||
bar.dataset.totalPages = String(tp)
|
||||
// 更新文案与禁用状态
|
||||
const prevBtn = bar.querySelector('.ws-pagination-prev')
|
||||
const nextBtn = bar.querySelector('.ws-pagination-next')
|
||||
const info = bar.querySelector('.ws-pagination-info')
|
||||
if (prevBtn) prevBtn.disabled = cur <= 1
|
||||
if (nextBtn) nextBtn.disabled = cur >= tp
|
||||
if (info) info.textContent = `第${cur}/${tp}页,共${total}项`
|
||||
}
|
||||
|
||||
let bar = $container.querySelector('#ws-pagination-bar')
|
||||
|
||||
const handleChange = (page, size = pageSize) => {
|
||||
updateUI(bar, total, size, page)
|
||||
// 向外派发事件,便于宿主接收分页变更
|
||||
$container.dispatchEvent(new CustomEvent('pagechange', { detail: { page, pageSize: size, total } }))
|
||||
// 执行外部回调(透传 page 与 pageSize)
|
||||
if (typeof onChange === 'function') {
|
||||
onChange(page, size)
|
||||
}
|
||||
}
|
||||
|
||||
if (!bar) {
|
||||
bar = createPagination({ total, pageSize, current, onChange: handleChange })
|
||||
$container.appendChild(bar)
|
||||
}
|
||||
updateUI(bar, total, pageSize, current)
|
||||
}
|
||||
|
||||
const clearClss = ($wsControl) => {
|
||||
const $wsControlEnd = $wsControl.querySelector(".ws-flex.ws-flex-end")
|
||||
if ($wsControlEnd) {
|
||||
$wsControlEnd.style = "width: initial;"
|
||||
}
|
||||
// 删除$wsControl下面style为flex:1的元素
|
||||
const $flex1 = $wsControl.querySelector("[style*='flex: 1']")
|
||||
if ($flex1) $flex1.remove()
|
||||
}
|
||||
|
||||
export const rendererVideoPage = (opts = {}) => {
|
||||
const $wsControl = document.querySelector(".ws-control")
|
||||
if (!$wsControl) return
|
||||
clearClss($wsControl)
|
||||
// 先查找有没有 video-page 元素
|
||||
let $page = $wsControl.querySelector(`#${pageDivId}`)
|
||||
const defaults = { total: 0, pageSize: 10, current: 1, onChange: () => { } }
|
||||
const params = { ...defaults, ...opts }
|
||||
if (!$page) {
|
||||
const node = rendererPage(params.current, params.pageSize, params.total, params.onChange)
|
||||
if (typeof $wsControl.prepend === 'function') {
|
||||
$wsControl.prepend(node)
|
||||
} else {
|
||||
$wsControl.insertBefore(node, $wsControl.firstChild)
|
||||
}
|
||||
$page = node
|
||||
} else {
|
||||
// 如果容器已存在,确保分页挂载
|
||||
mountPagination($page, params)
|
||||
}
|
||||
}
|
||||
@@ -5,31 +5,37 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="deviceGroupPlay">
|
||||
import { useMessage, useMessageBox } from "/@/hooks/message";
|
||||
import { useMessage } from "/@/hooks/message";
|
||||
|
||||
import * as DeviceCameraApi from "/@/api/device/deviceCamera"
|
||||
import * as DeviceChannelApi from "/@/api/device/deviceChannel"
|
||||
|
||||
// @ts-ignore
|
||||
import PlayerManager from "../../components/lib/icc/PlayerManager";
|
||||
// @ts-ignore
|
||||
import { rendererVideoPage } from "../lib/custom/video-custom"
|
||||
|
||||
let realPlayer: any = null;
|
||||
|
||||
const videoState = reactive({
|
||||
dialogVisible: false,
|
||||
loading: false,
|
||||
treeLoading: false,
|
||||
videoNum: 0,
|
||||
numList: [1, 4, 9, 16, 25],
|
||||
const state = reactive<any>({
|
||||
numList: [1, 4, 9, 16],
|
||||
num: 16,
|
||||
maxNum: 16,
|
||||
channelList: [],
|
||||
/**
|
||||
* 码流类型:
|
||||
* 1=主码流
|
||||
* 2=辅码流
|
||||
* 3=辅码流2
|
||||
*/
|
||||
streamType: 1,
|
||||
current: 1,
|
||||
total: 100,
|
||||
})
|
||||
|
||||
const channel = reactive<any>({
|
||||
playList: [],
|
||||
deviceChannelList: []
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
|
||||
const query = reactive({
|
||||
list: [],
|
||||
deviceCode: null,
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
@@ -37,84 +43,126 @@ onUnmounted(() => {
|
||||
})
|
||||
|
||||
// 视频预览初始化
|
||||
async function init(list: any) {
|
||||
async function init(channelCodelist: any, deviceCode: any) {
|
||||
try {
|
||||
destroy()
|
||||
if (list && list.length) {
|
||||
// 查询设备通道
|
||||
const { code: deviceChannelCode, data: deviceChannelList } = await DeviceChannelApi.list({ deviceChannelCodeList: list })
|
||||
if (deviceChannelCode != 0) throw new Error()
|
||||
if (deviceChannelList == null || deviceChannelList.length == 0) throw new Error()
|
||||
channel.deviceChannelList = deviceChannelList
|
||||
// 查询视频
|
||||
const { code: cameraCode, data: channelList } = await DeviceCameraApi.getRtspVideoList({
|
||||
channelIdList: list
|
||||
})
|
||||
if (cameraCode != 0) throw new Error()
|
||||
channel.playList = channelList ?? []
|
||||
initVideo()
|
||||
if (channelCodelist) {
|
||||
query.list = channelCodelist
|
||||
}
|
||||
if (deviceCode) {
|
||||
query.deviceCode = deviceCode
|
||||
}
|
||||
state.current = 1
|
||||
state.num = 16
|
||||
initVideo()
|
||||
} catch (error: any) {
|
||||
console.error("视频预览初始化失败", error);
|
||||
console.error("|视频预览初始化|,意外的错误", error);
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化视频
|
||||
function initVideo() {
|
||||
async function initVideo() {
|
||||
const _params = {
|
||||
deviceChannelCodeList: query.list ?? [],
|
||||
deviceCode: query.deviceCode ?? null,
|
||||
current: state.current,
|
||||
size: state.num,
|
||||
};
|
||||
try {
|
||||
let { code, data } = await DeviceChannelApi.getChannelPage(_params);
|
||||
if (code != 0) throw new Error();
|
||||
let { records, total } = data
|
||||
if (!records || !records.length) records = []
|
||||
state.total = total
|
||||
let suitableNum = state.num
|
||||
if (state.total < state.numList[state.numList.length - 1]) {
|
||||
suitableNum = findSuitableNum(records.length, state.numList);
|
||||
state.maxNum = suitableNum
|
||||
}
|
||||
state.num = suitableNum;
|
||||
state.channelList = records;
|
||||
console.log(`查询参数:${JSON.stringify(_params)}原始通道数: ${records.length}, 调整后的显示路数: ${suitableNum}`);
|
||||
if (records.length >= 50) return
|
||||
// 初始化播放器
|
||||
initPlayerManager();
|
||||
} catch (error) {
|
||||
console.error("|初始化视频|,意外的错误", error);
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化播放器
|
||||
function initPlayerManager() {
|
||||
if (realPlayer) realPlayer = null;
|
||||
videoState.videoNum = findSuitableNum(channel.playList.length, videoState.numList)
|
||||
realPlayer = new PlayerManager({
|
||||
el: "ws-real-player" /** 实时预览容器id,创建多个播放器,传入不同的容器id即可 **/,
|
||||
type: "real" /** real - 实时预览 record - 录像回放 **/,
|
||||
maxNum: 16 /** 一个播放器上限能播放的路数,可根据实际情况设置,支持 1 4 9 16 25 **/,
|
||||
num: videoState.videoNum /** 初始化,页面显示的路数 **/,
|
||||
showControl: true /** 是否显示工具栏,默认显示 **/,
|
||||
prefixUrl: "lib" /** 解码库所在位置的前缀 **/,
|
||||
isRTPStream: true /** true-标准码流拉流 false-私有码流拉流 **/,
|
||||
// 实时预览容器id,创建多个播放器,传入不同的容器id即可
|
||||
el: "ws-real-player",
|
||||
// real - 实时预览 record - 录像回放
|
||||
type: "real",
|
||||
// 一个播放器上限能播放的路数,可根据实际情况设置,支持 1 4 9 16 25
|
||||
maxNum: state.maxNum,
|
||||
// 初始化,页面显示的路数
|
||||
num: state.num,
|
||||
// 是否显示工具栏,默认显示
|
||||
showControl: true,
|
||||
// 解码库所在位置的前缀
|
||||
prefixUrl: "lib",
|
||||
// true-标准码流拉流 false-私有码流拉流
|
||||
isRTPStream: true,
|
||||
receiveMessageFromWSPlayer: (methods: any, data: any, err: any) => {
|
||||
/* 回调函数,可以在以下回调函数里面做监听 */
|
||||
switch (methods) {
|
||||
case "initializationCompleted":
|
||||
console.log("初始化完成");
|
||||
realPlayer.setPlayerAdapter("stretching");
|
||||
// 初始化完成,可调用播放方法(适用于动态加载解码库)
|
||||
// 若回调未触发时就使用实时预览/录像回放,则无法播放。
|
||||
// 此时我们可以调用一个
|
||||
if (realPlayer) {
|
||||
realPlayer.setPlayerAdapter("stretching");
|
||||
}
|
||||
playReal();
|
||||
refreshPage()
|
||||
break;
|
||||
case "changeStreamType": // 主/辅码流切换回调
|
||||
console.log(data.channelData); // 通道数据
|
||||
console.log(data.streamType); // 码流类型 0-主码流 1-辅码流1 2-辅码流2
|
||||
console.log(data.selectIndex); // 窗口序号,从 0
|
||||
// 主/辅码流切换回调
|
||||
case "changeStreamType":
|
||||
if (state.streamType != data.streamType) {
|
||||
console.log("主/辅码流切换回调", data);
|
||||
state.streamType = data.streamType;
|
||||
destroy();
|
||||
initVideo();
|
||||
}
|
||||
break;
|
||||
case "realSuccess": // 实时预览成功
|
||||
// 实时预览成功
|
||||
case "realSuccess":
|
||||
console.log("实时预览成功");
|
||||
break;
|
||||
case "realError": // 实时预览失败
|
||||
console.log("实时预览失败", err);
|
||||
// 实时预览失败
|
||||
case "realError":
|
||||
console.log("实时预览失败", data, err);
|
||||
break;
|
||||
// 点击窗口上的对讲按钮触发的事件回调
|
||||
case "notifyTalk":
|
||||
// 点击窗口上的对讲按钮触发的事件回调
|
||||
console.log("notifyTalk", data);
|
||||
case "talkError": // 对讲失败
|
||||
console.log("对讲失败");
|
||||
break;
|
||||
case "selectWindowChanged": // 选中的窗口发生改变
|
||||
// 选中的窗口发生改变
|
||||
case "selectWindowChanged":
|
||||
console.log(data, "返回窗口信息");
|
||||
break;
|
||||
case "windowNumChanged": // 播放器显示的路数发生改变
|
||||
console.log(data, "返回显示的窗口数量");
|
||||
// 播放器显示的路数发生改变
|
||||
case "windowNumChanged":
|
||||
console.log("每页", data);
|
||||
if (data == state.num) return
|
||||
state.num = data
|
||||
refreshPage()
|
||||
break;
|
||||
case "closeVideo": // 视频关闭回调
|
||||
// 点击关闭按钮引发的视频关闭进行提示
|
||||
// 切换视频引发的视频关闭不进行提示
|
||||
// 视频关闭回调
|
||||
case "closeVideo":
|
||||
if (!data.changeVideoFlag) {
|
||||
console.log(`窗口${data.selectIndex}的视频已关闭`);
|
||||
// console.log(`窗口${data.selectIndex}的视频已关闭`);
|
||||
}
|
||||
break;
|
||||
case "statusChanged": // 视频状态发生改变
|
||||
// 视频状态发生改变
|
||||
case "statusChanged":
|
||||
break;
|
||||
case "errorInfo": // 错误信息汇总
|
||||
// 错误信息汇总
|
||||
case "errorInfo":
|
||||
console.log(data, "可打印查看错误消息");
|
||||
}
|
||||
},
|
||||
@@ -123,45 +171,73 @@ function initVideo() {
|
||||
|
||||
// 预览
|
||||
async function playReal() {
|
||||
if (!channel.playList || !channel.playList.length) return
|
||||
try {
|
||||
for (let index = 0; index < channel.playList.length; index++) {
|
||||
const element = channel.playList[index]
|
||||
const targetChannel = channel.deviceChannelList.find((v: any) => v.channelCode == element.channelId)
|
||||
// 请求
|
||||
let errorIndexList = []
|
||||
for (let index = 0; index < state.channelList.length; index++) {
|
||||
const element = state.channelList[index];
|
||||
// 获取通道数据
|
||||
const _params = {
|
||||
channelId: element.channelCode,
|
||||
streamType: state.streamType,
|
||||
};
|
||||
try {
|
||||
const { code, data } = await DeviceCameraApi.getRtspVideo(_params);
|
||||
if (code != 0 || !data) throw new Error();
|
||||
realPlayer.realByUrl({
|
||||
rtspURL: element.fullUrl, // 必传
|
||||
wsURL: element.wsURL, // 必传
|
||||
selectIndex: index, // 必传, 播放窗口号
|
||||
channelId: element.channelId, // 必传, 通道id
|
||||
streamType: element.streamType, // 必传, 码流类型,
|
||||
playerAdapter: "stretching", // 选传, selfAdaption 自适应 | stretching 拉伸
|
||||
rtspURL: data.fullUrl,
|
||||
wsURL: data.wsURL,
|
||||
selectIndex: index,
|
||||
channelId: data.channelId,
|
||||
streamType: data.streamType,
|
||||
playerAdapter: "stretching",
|
||||
channelData: {
|
||||
// 建议传, 通道树信息
|
||||
id: targetChannel.channelCode, // {String} 通道编码
|
||||
deviceCode: targetChannel.deviceCode, // {String} 设备编码
|
||||
deviceType: targetChannel.deviceType, // {String} 设备类型
|
||||
channelSeq: targetChannel.channelSeq, // {String|Number} 通道序号
|
||||
}, // 选传
|
||||
id: element.channelCode,
|
||||
deviceCode: element.deviceCode,
|
||||
deviceType: element.deviceType,
|
||||
channelSeq: element.channelSeq,
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("|预览|,意外的错误", error);
|
||||
if (realPlayer) {
|
||||
// realPlayer.close(index);
|
||||
}
|
||||
errorIndexList.push(index)
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.error(error);
|
||||
useMessage().error(error?.msg ?? "视频播放错误");
|
||||
}
|
||||
if (errorIndexList.length > 0) {
|
||||
useMessage().error(`监控${errorIndexList.map(v => v + 1).join(',')}视频加载错误`);
|
||||
}
|
||||
}
|
||||
|
||||
function refreshPage() {
|
||||
rendererVideoPage({
|
||||
current: state.current,
|
||||
pageSize: state.num,
|
||||
total: state.total,
|
||||
onChange: (page: any, pageSize: any) => {
|
||||
console.log("分页", page, pageSize);
|
||||
if (page == state.current && pageSize == state.num) {
|
||||
return
|
||||
}
|
||||
state.current = page;
|
||||
state.num = pageSize;
|
||||
destroy()
|
||||
initVideo();
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 销毁
|
||||
function destroy() {
|
||||
console.log("摄像头预览销毁");
|
||||
if (realPlayer) {
|
||||
if (realPlayer) {
|
||||
for (let index = 0; index < videoState.videoNum; index++) {
|
||||
realPlayer.close(index)
|
||||
}
|
||||
}
|
||||
realPlayer.destroy()
|
||||
realPlayer = null;
|
||||
if (!realPlayer) return
|
||||
console.log("销毁的数量", state.num);
|
||||
for (let index = 0; index < state.num; index++) {
|
||||
realPlayer.close(index)
|
||||
}
|
||||
realPlayer.destroy()
|
||||
realPlayer = null;
|
||||
console.log("监控视频销毁成功");
|
||||
}
|
||||
|
||||
// 根据当前数量在numList中找到合适的值(大于等于当前值的最小值)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<el-dialog v-model="state.dialogVisible" title="预览" width="60%" append-to-body :before-close="handleClose">
|
||||
<el-dialog v-model="state.dialogVisible" title="预览" width="80%" append-to-body :before-close="handleClose">
|
||||
<div class="flex justify-center items-start">
|
||||
<div class="min-w-[20%]">
|
||||
<el-tree :data="channel.treeList" :props="{
|
||||
@@ -14,7 +14,7 @@
|
||||
</el-tree>
|
||||
</div>
|
||||
<div class="flex-1 overflow-hidden video-container">
|
||||
<div id="ws-real-player" style="height: 500px; margin-bottom: 20px" />
|
||||
<preview-video ref="previewVideoRef" style="height: 500px; margin-bottom: 20px" />
|
||||
</div>
|
||||
</div>
|
||||
<template #footer>
|
||||
@@ -31,25 +31,19 @@ import { useMessage } from "/@/hooks/message";
|
||||
|
||||
import * as DeviceManageApi from "/@/api/device/deviceManage"
|
||||
import * as DeviceChannelApi from "/@/api/device/deviceChannel"
|
||||
import * as DeviceCameraApi from "/@/api/device/deviceCamera"
|
||||
|
||||
// @ts-ignore
|
||||
import PlayerManager from "../lib/icc/PlayerManager";
|
||||
const PreviewVideo = defineAsyncComponent(() => import('./index.vue'));
|
||||
|
||||
let realPlayer: any = null;
|
||||
const previewVideoRef = ref()
|
||||
|
||||
const state = reactive({
|
||||
dialogVisible: false,
|
||||
channelList: [],
|
||||
loading: false,
|
||||
treeLoading: false,
|
||||
videoNum: 0,
|
||||
numList: [1, 4, 9, 16, 25],
|
||||
})
|
||||
|
||||
const detail = reactive<any>({
|
||||
|
||||
})
|
||||
const detail = reactive<any>({})
|
||||
|
||||
const channel = reactive<any>({
|
||||
treeList: [],
|
||||
@@ -59,7 +53,7 @@ const channel = reactive<any>({
|
||||
|
||||
const handleClose = () => {
|
||||
state.dialogVisible = false
|
||||
destroy()
|
||||
previewVideoRef.value.destroy()
|
||||
}
|
||||
|
||||
// 打开弹窗
|
||||
@@ -73,15 +67,11 @@ const openDialog = async (id: string) => {
|
||||
|
||||
// 点击树
|
||||
const handleNodeClick = (e: any) => {
|
||||
destroy()
|
||||
if (e.id == -1) {
|
||||
state.videoNum = findSuitableNum(channel.list.length, state.numList);
|
||||
initVideo()
|
||||
playReal()
|
||||
previewVideoRef.value.init([], detail.deviceCode)
|
||||
return
|
||||
}
|
||||
state.videoNum = 1
|
||||
initVideo(e.channelCode)
|
||||
previewVideoRef.value.init([e.channelCode], detail.deviceCode)
|
||||
}
|
||||
|
||||
// 初始化表单数据
|
||||
@@ -100,7 +90,7 @@ const getdeviceManageData = async (id: string) => {
|
||||
}
|
||||
};
|
||||
|
||||
// 查询通道列表
|
||||
// 查询通道树
|
||||
const getDeviceChannelList = async () => {
|
||||
state.treeLoading = true
|
||||
try {
|
||||
@@ -108,7 +98,6 @@ const getDeviceChannelList = async () => {
|
||||
if (code != 0) throw new Error()
|
||||
channel.treeList = data
|
||||
channel.list = data
|
||||
state.videoNum = findSuitableNum(channel.list.length, state.numList);
|
||||
// 处理树
|
||||
if (data && data.length) {
|
||||
const tree = data.map((v: any) => {
|
||||
@@ -127,144 +116,15 @@ const getDeviceChannelList = async () => {
|
||||
]
|
||||
}
|
||||
// 播放视频
|
||||
initVideo()
|
||||
previewVideoRef.value.init([], detail.deviceCode)
|
||||
} catch (error: any) {
|
||||
console.error(error);
|
||||
console.error("|查询通道树|,意外的错误", error);
|
||||
useMessage().error(error?.msg ?? "意外的错误");
|
||||
} finally {
|
||||
state.treeLoading = false
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化视频
|
||||
function initVideo(channelCode?: string) {
|
||||
if (realPlayer) {
|
||||
realPlayer = null;
|
||||
}
|
||||
realPlayer = new PlayerManager({
|
||||
el: "ws-real-player" /** 实时预览容器id,创建多个播放器,传入不同的容器id即可 **/,
|
||||
type: "real" /** real - 实时预览 record - 录像回放 **/,
|
||||
maxNum: 16 /** 一个播放器上限能播放的路数,可根据实际情况设置,支持 1 4 9 16 25 **/,
|
||||
num: state.videoNum /** 初始化,页面显示的路数 **/,
|
||||
showControl: true /** 是否显示工具栏,默认显示 **/,
|
||||
prefixUrl: "lib" /** 解码库所在位置的前缀 **/,
|
||||
isRTPStream: true /** true-标准码流拉流 false-私有码流拉流 **/,
|
||||
receiveMessageFromWSPlayer: (methods: any, data: any, err: any) => {
|
||||
/* 回调函数,可以在以下回调函数里面做监听 */
|
||||
switch (methods) {
|
||||
case "initializationCompleted":
|
||||
console.log("初始化完成");
|
||||
realPlayer.setPlayerAdapter("stretching");
|
||||
// 初始化完成,可调用播放方法(适用于动态加载解码库)
|
||||
// 若回调未触发时就使用实时预览/录像回放,则无法播放。
|
||||
// 此时我们可以调用一个
|
||||
playReal(channelCode);
|
||||
break;
|
||||
case "changeStreamType": // 主/辅码流切换回调
|
||||
console.log(data.channelData); // 通道数据
|
||||
console.log(data.streamType); // 码流类型 0-主码流 1-辅码流1 2-辅码流2
|
||||
console.log(data.selectIndex); // 窗口序号,从 0
|
||||
break;
|
||||
case "realSuccess": // 实时预览成功
|
||||
console.log("实时预览成功");
|
||||
break;
|
||||
case "realError": // 实时预览失败
|
||||
console.log("实时预览失败", err);
|
||||
break;
|
||||
case "notifyTalk":
|
||||
// 点击窗口上的对讲按钮触发的事件回调
|
||||
console.log("notifyTalk", data);
|
||||
case "talkError": // 对讲失败
|
||||
console.log("对讲失败");
|
||||
break;
|
||||
case "selectWindowChanged": // 选中的窗口发生改变
|
||||
console.log(data, "返回窗口信息");
|
||||
break;
|
||||
case "windowNumChanged": // 播放器显示的路数发生改变
|
||||
console.log(data, "返回显示的窗口数量");
|
||||
break;
|
||||
case "closeVideo": // 视频关闭回调
|
||||
// 点击关闭按钮引发的视频关闭进行提示
|
||||
// 切换视频引发的视频关闭不进行提示
|
||||
if (!data.changeVideoFlag) {
|
||||
console.log(`窗口${data.selectIndex}的视频已关闭`);
|
||||
}
|
||||
break;
|
||||
case "statusChanged": // 视频状态发生改变
|
||||
break;
|
||||
case "errorInfo": // 错误信息汇总
|
||||
console.log(data, "可打印查看错误消息");
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// 预览
|
||||
async function playReal(channelCode?: string) {
|
||||
if (!channel.list || !channel.list.length) return
|
||||
try {
|
||||
// 获取流地址
|
||||
const _data = {
|
||||
channelIdList: channel.list.map((v: any) => v.channelCode)
|
||||
}
|
||||
if (channelCode) {
|
||||
_data.channelIdList = [channelCode]
|
||||
}
|
||||
const { code, data: channelList } = await DeviceCameraApi.getRtspVideoList({
|
||||
..._data
|
||||
})
|
||||
if (code != 0) throw new Error()
|
||||
if (!channel.list || !channel.list.length) return
|
||||
state.videoNum = channel.list.length
|
||||
for (let index = 0; index < channelList.length; index++) {
|
||||
const element = channelList[index]
|
||||
const targetChannel = channel.list.find((v: any) => v.channelCode == element.channelId)
|
||||
realPlayer.realByUrl({
|
||||
rtspURL: element.fullUrl, // 必传
|
||||
wsURL: element.wsURL, // 必传
|
||||
selectIndex: index, // 必传, 播放窗口号
|
||||
channelId: element.channelId, // 必传, 通道id
|
||||
streamType: element.streamType, // 必传, 码流类型,
|
||||
playerAdapter: "stretching", // 选传, selfAdaption 自适应 | stretching 拉伸
|
||||
channelData: {
|
||||
// 建议传, 通道树信息
|
||||
id: targetChannel.channelCode, // {String} 通道编码
|
||||
deviceCode: targetChannel.deviceCode, // {String} 设备编码
|
||||
deviceType: targetChannel.deviceType, // {String} 设备类型
|
||||
channelSeq: targetChannel.channelSeq, // {String|Number} 通道序号
|
||||
}, // 选传
|
||||
});
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.error(error);
|
||||
useMessage().error(error?.msg ?? "视频播放错误");
|
||||
}
|
||||
}
|
||||
|
||||
// 销毁
|
||||
function destroy() {
|
||||
console.log("摄像头预览销毁");
|
||||
if (realPlayer) {
|
||||
if (realPlayer) {
|
||||
for (let index = 0; index < state.videoNum; index++) {
|
||||
realPlayer.close(index)
|
||||
}
|
||||
}
|
||||
realPlayer.destroy()
|
||||
realPlayer = null;
|
||||
}
|
||||
}
|
||||
|
||||
// 根据当前数量在numList中找到合适的值(大于等于当前值的最小值)
|
||||
function findSuitableNum(currentNum: any, numList: any) {
|
||||
const suitableNums = numList.filter((num: any) => num >= currentNum);
|
||||
if (suitableNums.length > 0) {
|
||||
return Math.min(...suitableNums);
|
||||
} else {
|
||||
return Math.max(...numList);
|
||||
}
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
openDialog
|
||||
});
|
||||
|
||||
@@ -5,10 +5,12 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="ReplayVideo">
|
||||
import * as AlarmReplayVideoListApi from "/@/api/third/deviceCameraReplay"
|
||||
import { formatDateRange, getDateRangeTimestamps } from "/@/utils/formatTime"
|
||||
import { useMessage } from "/@/hooks/message";
|
||||
import { formatDateRange } from "/@/utils/formatTime"
|
||||
|
||||
import * as AlarmReplayVideoListApi from "/@/api/third/deviceCameraReplay"
|
||||
import * as DeviceChannelApi from "/@/api/device/deviceChannel"
|
||||
|
||||
const emit = defineEmits(['mounted'])
|
||||
|
||||
// @ts-ignore
|
||||
import PlayerManager from "../lib/icc/PlayerManager";
|
||||
@@ -16,78 +18,129 @@ import PlayerManager from "../lib/icc/PlayerManager";
|
||||
let replayVideo: any = null
|
||||
|
||||
const state = reactive<any>({
|
||||
channelList: [],
|
||||
startTimestamp: null,
|
||||
endTimestamp: null,
|
||||
queryForm: {},
|
||||
dialogVisible: false,
|
||||
channelList: [],
|
||||
loading: false,
|
||||
treeLoading: false,
|
||||
currentNum: 4,
|
||||
numList: [1, 4, 9, 16, 25],
|
||||
maxCurrentNum: 16,
|
||||
numList: [1, 4, 9, 16],
|
||||
type: "device",
|
||||
code: null,
|
||||
current: 1,
|
||||
total: 100,
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
emit("mounted")
|
||||
const query = reactive({
|
||||
list: [],
|
||||
deviceCode: null,
|
||||
})
|
||||
|
||||
|
||||
onUnmounted(() => {
|
||||
destroy()
|
||||
})
|
||||
|
||||
// 初始化播放器
|
||||
const init = (dataList: any, queryForm: any) => {
|
||||
destroy()
|
||||
state.queryForm = queryForm
|
||||
if (dataList && dataList.length) {
|
||||
state.channelList = dataList
|
||||
const init = (queryForm: any, replayForm: any) => {
|
||||
if (queryForm) {
|
||||
if (queryForm.list) {
|
||||
query.list = queryForm.list
|
||||
}
|
||||
if (queryForm.deviceCode) {
|
||||
query.deviceCode = queryForm.deviceCode
|
||||
}
|
||||
}
|
||||
|
||||
state.queryForm = replayForm
|
||||
// 处理日期范围
|
||||
if (queryForm.videoDateList && queryForm.videoDateList.length == 2) {
|
||||
const dateRange = formatDateRange(queryForm.videoDateList)
|
||||
state.startTimestamp = dateRange.startTimestamp
|
||||
state.endTimestamp = dateRange.endTimestamp
|
||||
if (replayForm.videoDateList) {
|
||||
if (replayForm.videoDateList.length == 2) {
|
||||
const dateRange = formatDateRange(replayForm.videoDateList)
|
||||
state.startTimestamp = dateRange.startTimestamp
|
||||
state.endTimestamp = dateRange.endTimestamp
|
||||
}
|
||||
}
|
||||
state.currentNum = findSuitableNum(state.channelList.length, state.numList)
|
||||
destroy()
|
||||
initVideo()
|
||||
}
|
||||
|
||||
// 初始化视频
|
||||
async function initVideo() {
|
||||
const _params = {
|
||||
deviceChannelCodeList: query.list ?? [],
|
||||
deviceCode: query.deviceCode ?? null,
|
||||
current: state.current,
|
||||
size: state.currentNum,
|
||||
};
|
||||
try {
|
||||
let { code, data } = await DeviceChannelApi.getChannelPage(_params);
|
||||
if (code != 0) throw new Error();
|
||||
let { records, total } = data
|
||||
if (!records || !records.length) records = []
|
||||
state.total = total
|
||||
let suitableNum = state.num
|
||||
if (state.total < state.numList[state.numList.length - 1]) {
|
||||
suitableNum = findSuitableNum(records.length, state.numList);
|
||||
state.maxCurrentNum = suitableNum
|
||||
}
|
||||
state.currentNum = suitableNum;
|
||||
state.channelList = records;
|
||||
console.log(`原始通道数: ${records.length}, 调整后的显示路数: ${suitableNum}`);
|
||||
// 初始化播放器
|
||||
initPlayerManager();
|
||||
} catch (error) {
|
||||
console.error("|初始化视频|,意外的错误", error);
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化视频
|
||||
function initPlayerManager() {
|
||||
if (replayVideo) replayVideo = null;
|
||||
replayVideo = new PlayerManager({
|
||||
el: "ws-record-player", /** 实时预览容器id,创建多个播放器,传入不同的容器id即可 **/
|
||||
type: 'record', /** real - 实时预览 record - 录像回放 **/
|
||||
maxNum: 16, /** 一个播放器上限能播放的路数,可根据实际情况设置,支持 1 4 9 16 25 **/
|
||||
num: state.currentNum, /** 初始化,页面显示的路数 **/
|
||||
showControl: true, /** 是否显示工具栏,默认显示 **/
|
||||
prefixUrl: 'lib', /** 解码库所在位置的前缀 **/
|
||||
isRTPStream: true, /** true-标准码流拉流 false-私有码流拉流 **/
|
||||
receiveMessageFromWSPlayer: (methods: any, data: any, err: any) => { /* 回调函数,可以在以下回调函数里面做监听 */
|
||||
// 实时预览容器id,创建多个播放器,传入不同的容器id即可
|
||||
el: "ws-record-player",
|
||||
// real - 实时预览 record - 录像回放
|
||||
type: 'record',
|
||||
// 一个播放器上限能播放的路数,可根据实际情况设置,支持 1 4 9 16 25
|
||||
maxNum: state.maxCurrentNum,
|
||||
// 初始化,页面显示的路数
|
||||
num: state.currentNum,
|
||||
// 是否显示工具栏,默认显示
|
||||
showControl: true,
|
||||
// 解码库所在位置的前缀
|
||||
prefixUrl: 'lib',
|
||||
// true-标准码流拉流 false-私有码流拉流
|
||||
isRTPStream: true,
|
||||
receiveMessageFromWSPlayer: (methods: any, data: any, err: any) => {
|
||||
switch (methods) {
|
||||
// 初始化完成,可以调用拉流方法,实现播放器加载后立即显示画面
|
||||
case 'initializationCompleted':
|
||||
// 初始化完成,可以调用拉流方法,实现播放器加载后立即显示画面
|
||||
playRecord()
|
||||
break;
|
||||
// 录像回放成功
|
||||
case "recordSuccess":
|
||||
// 录像回放成功
|
||||
console.log("录像回放成功")
|
||||
break;
|
||||
// 录像回放失败
|
||||
case "recordError":
|
||||
// 录像回放失败
|
||||
console.log("录像回放失败", err)
|
||||
break;
|
||||
// 录像回放完成
|
||||
case "recordFinish":
|
||||
// 录像回放完成
|
||||
console.log("录像回放完成");
|
||||
break;
|
||||
// 拖拽时间戳的回调
|
||||
case 'switchStartTime':
|
||||
console.log("拖拽时间戳的回调:", data);
|
||||
break;
|
||||
// 选中的窗口发生改变
|
||||
case 'selectWindowChanged':
|
||||
// 选中的窗口发生改变
|
||||
console.log(data, "返回窗口信息")
|
||||
break;
|
||||
// 播放器显示的路数发生改变
|
||||
case 'windowNumChanged':
|
||||
// 播放器显示的路数发生改变
|
||||
console.log(data, "返回显示的窗口数量")
|
||||
if (state.currentNum == data) return
|
||||
state.currentNum = data
|
||||
if (replayVideo) {
|
||||
@@ -97,9 +150,8 @@ const init = (dataList: any, queryForm: any) => {
|
||||
}
|
||||
playRecord()
|
||||
break;
|
||||
case 'closeVideo': // 视频关闭回调
|
||||
// 点击关闭按钮引发的视频关闭进行提示
|
||||
// 切换视频引发的视频关闭不进行提示
|
||||
// 视频关闭回调
|
||||
case 'closeVideo':
|
||||
if (!data.changeVideoFlag) {
|
||||
console.log(`窗口${data.selectIndex}的视频已关闭`)
|
||||
}
|
||||
@@ -117,45 +169,51 @@ const init = (dataList: any, queryForm: any) => {
|
||||
|
||||
// 流地址方式播放录像回放
|
||||
const playRecord = async () => {
|
||||
// 根据maxNum获取
|
||||
if (!state.channelList || !state.channelList.length) return
|
||||
const playList = state.channelList.slice(0, state.currentNum)
|
||||
for (let index = 0; index < playList.length; index++) {
|
||||
const element: any = playList[index];
|
||||
// 根据maxNum获取
|
||||
let list = state.channelList
|
||||
if (!list || !list.length) return
|
||||
let errorIndexList = []
|
||||
for (let index = 0; index < list.length; index++) {
|
||||
const element = list[index];
|
||||
const _data = {
|
||||
...state.queryForm,
|
||||
channelId: element.channelCode
|
||||
}
|
||||
// 获取回放列表
|
||||
const recordRes = await AlarmReplayVideoListApi.defaultReplayVideoList(_data)
|
||||
if (recordRes.code != 0) continue
|
||||
const videoRes = await AlarmReplayVideoListApi.getRtspReplayVideo(_data)
|
||||
if (videoRes.code != 0) continue
|
||||
console.log("data:", videoRes.data);
|
||||
replayVideo.recordByUrl({
|
||||
wsURL: videoRes.data.wsURL,
|
||||
rtspURL: videoRes.data.fullUrl,
|
||||
channelId: videoRes.data.channelId, // 必传, 通道id
|
||||
records: recordRes.data || [], // 录像列表
|
||||
selectIndex: index, // 必传
|
||||
playRecordByTime: true, // 默认传true,拖拽异常时请传入false
|
||||
startTime: state.startTimestamp, // 开始时间戳(秒)
|
||||
endTime: state.endTimestamp, // 结束时间戳(秒)
|
||||
// playerAdapter: opt.playerAdapter, // 选传, , selfAdaption 自适应 | stretching 拉伸
|
||||
channelData: {
|
||||
records: recordRes.data || []
|
||||
}, // 选传, 通道信息
|
||||
})
|
||||
try {
|
||||
const recordRes = await AlarmReplayVideoListApi.defaultReplayVideoList(_data)
|
||||
if (recordRes.code != 0 || !recordRes.data) throw new Error();
|
||||
const videoRes = await AlarmReplayVideoListApi.getRtspReplayVideo(_data)
|
||||
if (videoRes.code != 0 || !videoRes.data) throw new Error();
|
||||
replayVideo.recordByUrl({
|
||||
wsURL: videoRes.data.wsURL,
|
||||
rtspURL: videoRes.data.fullUrl,
|
||||
channelId: videoRes.data.channelId,
|
||||
records: recordRes.data || [],
|
||||
selectIndex: index,
|
||||
playRecordByTime: true,
|
||||
startTime: state.startTimestamp,
|
||||
endTime: state.endTimestamp,
|
||||
channelData: {
|
||||
records: recordRes.data || []
|
||||
},
|
||||
})
|
||||
} catch (error) {
|
||||
console.error("|流地址方式播放录像回放|,意外的错误", error);
|
||||
errorIndexList.push(index)
|
||||
}
|
||||
if (errorIndexList.length > 0) {
|
||||
useMessage().error(`监控${errorIndexList.map(v => v + 1).join(',')}视频加载错误`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 销毁
|
||||
function destroy() {
|
||||
if (!replayVideo) return
|
||||
if (replayVideo) {
|
||||
for (let index = 0; index < state.currentNum; index++) {
|
||||
replayVideo.close(index)
|
||||
}
|
||||
for (let index = 0; index < state.currentNum; index++) {
|
||||
replayVideo.close(index)
|
||||
}
|
||||
replayVideo.destroy()
|
||||
replayVideo = null;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<el-dialog v-model="state.dialogVisible" title="回放" width="75%" :before-close="handleClose">
|
||||
<el-dialog v-model="state.dialogVisible" title="回放" width="80%" :before-close="handleClose">
|
||||
<div class="flex justify-center items-start">
|
||||
<div v-if="state.type == 'device'" class="min-w-[20%]">
|
||||
<el-tree :data="channel.treeList" :props="{
|
||||
@@ -34,7 +34,7 @@
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div id="ws-record-player" style="height: 500px; margin-bottom: 20px" />
|
||||
<ReplayVideo ref="replayVideoRef" style="height: 500px; margin-bottom: 20px" />
|
||||
</div>
|
||||
</div>
|
||||
<template #footer>
|
||||
@@ -54,14 +54,15 @@ import * as AlarmReplayVideoListApi from "/@/api/third/deviceCameraReplay"
|
||||
import * as DeviceManageApi from "/@/api/device/deviceManage"
|
||||
import * as DeviceChannelApi from "/@/api/device/deviceChannel"
|
||||
|
||||
const ReplayVideo = defineAsyncComponent(() => import("./index.vue"));
|
||||
|
||||
// 定义查询字典
|
||||
const { dh_replay_source, dh_video_stream_type } = useDict("dh_replay_source", "dh_video_stream_type")
|
||||
|
||||
// @ts-ignore
|
||||
import PlayerManager from "../lib/icc/PlayerManager";
|
||||
|
||||
let replayVideo: any = null
|
||||
|
||||
const replayVideoRef = ref()
|
||||
|
||||
const state = reactive<any>({
|
||||
startTimestamp: null,
|
||||
endTimestamp: null,
|
||||
@@ -92,13 +93,11 @@ const channel = reactive<any>({
|
||||
|
||||
// 打开弹窗
|
||||
const openDialog = async (type: string, code: string, timeRange?: { startTime: string, endTime: string }) => {
|
||||
console.log("type:", type, "code:", code, "timeRange:", timeRange);
|
||||
if (!type || !code) return
|
||||
state.type = type
|
||||
state.code = code
|
||||
state.dialogVisible = true
|
||||
if (!code) return
|
||||
|
||||
// 如果传入了时间范围,则设置查询表单的时间
|
||||
if (timeRange) {
|
||||
queryForm.videoDateList = [timeRange.startTime, timeRange.endTime]
|
||||
@@ -119,21 +118,24 @@ const doSearch = async () => {
|
||||
}
|
||||
|
||||
const handleClose = () => {
|
||||
replayVideoRef.value.destroy()
|
||||
state.dialogVisible = false
|
||||
destroy()
|
||||
}
|
||||
|
||||
// 点击树
|
||||
const handleNodeClick = (e: any) => {
|
||||
console.log("点击树:", e);
|
||||
destroy()
|
||||
if (e.id == -1) {
|
||||
state.currentNum = findSuitableNum(channel.list.length, state.numList);
|
||||
initVideo()
|
||||
replayVideoRef.value.init(
|
||||
{ list: [], deviceCode: state.code },
|
||||
{}
|
||||
)
|
||||
return
|
||||
}
|
||||
state.currentNum = 1
|
||||
initVideo(e.channelCode)
|
||||
replayVideoRef.value.init(
|
||||
{ list: [e.channelCode], deviceCode: state.code },
|
||||
{}
|
||||
)
|
||||
}
|
||||
|
||||
// 初始化表单数据
|
||||
@@ -168,15 +170,12 @@ const getDeviceChannelList = async (codeStr: any) => {
|
||||
}
|
||||
const { data, code } = await DeviceChannelApi.list(_data)
|
||||
if (code != 0) throw new Error()
|
||||
|
||||
if (!data || !data.length) {
|
||||
useMessage().error("查询不到设备通道")
|
||||
return
|
||||
}
|
||||
|
||||
channel.treeList = data
|
||||
channel.list = data
|
||||
state.currentNum = findSuitableNum(channel.list.length, state.numList);
|
||||
// 处理树
|
||||
if (data && data.length) {
|
||||
const tree = data.map((v: any) => {
|
||||
@@ -194,8 +193,11 @@ const getDeviceChannelList = async (codeStr: any) => {
|
||||
}
|
||||
]
|
||||
}
|
||||
// 播放回放
|
||||
initVideo()
|
||||
console.log("replayVideoRef:", replayVideoRef);
|
||||
replayVideoRef.value.init(
|
||||
{ list: [], deviceCode: state.code },
|
||||
{}
|
||||
)
|
||||
} catch (error: any) {
|
||||
console.error(error);
|
||||
useMessage().error(error?.msg ?? "意外的错误");
|
||||
@@ -242,84 +244,6 @@ const playRecord = async (channelCode?: string) => {
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化视频
|
||||
function initVideo(channelCode?: string) {
|
||||
if (replayVideo) replayVideo = null;
|
||||
replayVideo = new PlayerManager({
|
||||
el: "ws-record-player", /** 实时预览容器id,创建多个播放器,传入不同的容器id即可 **/
|
||||
type: 'record', /** real - 实时预览 record - 录像回放 **/
|
||||
maxNum: 16, /** 一个播放器上限能播放的路数,可根据实际情况设置,支持 1 4 9 16 25 **/
|
||||
num: state.currentNum, /** 初始化,页面显示的路数 **/
|
||||
showControl: true, /** 是否显示工具栏,默认显示 **/
|
||||
prefixUrl: 'lib', /** 解码库所在位置的前缀 **/
|
||||
isRTPStream: true, /** true-标准码流拉流 false-私有码流拉流 **/
|
||||
receiveMessageFromWSPlayer: (methods: any, data: any, err: any) => { /* 回调函数,可以在以下回调函数里面做监听 */
|
||||
switch (methods) {
|
||||
case 'initializationCompleted':
|
||||
// 初始化完成,可以调用拉流方法,实现播放器加载后立即显示画面
|
||||
playRecord(channelCode)
|
||||
break;
|
||||
case "recordSuccess":
|
||||
// 录像回放成功
|
||||
console.log("录像回放成功")
|
||||
break;
|
||||
case "recordError":
|
||||
// 录像回放失败
|
||||
console.log("录像回放失败", err)
|
||||
break;
|
||||
case "recordFinish":
|
||||
// 录像回放完成
|
||||
console.log("录像回放完成");
|
||||
break;
|
||||
// 拖拽时间戳的回调
|
||||
case 'switchStartTime':
|
||||
console.log("拖拽时间戳的回调:", data);
|
||||
break;
|
||||
case 'selectWindowChanged':
|
||||
// 选中的窗口发生改变
|
||||
console.log(data, "返回窗口信息")
|
||||
break;
|
||||
case 'windowNumChanged':
|
||||
// 播放器显示的路数发生改变
|
||||
console.log(data, "返回显示的窗口数量")
|
||||
if (state.currentNum == data) return
|
||||
state.currentNum = data
|
||||
if (replayVideo) {
|
||||
for (let index = 0; index < state.currentNum; index++) {
|
||||
replayVideo.close(index)
|
||||
}
|
||||
}
|
||||
playRecord()
|
||||
break;
|
||||
case 'closeVideo': // 视频关闭回调
|
||||
// 点击关闭按钮引发的视频关闭进行提示
|
||||
// 切换视频引发的视频关闭不进行提示
|
||||
if (!data.changeVideoFlag) {
|
||||
console.log(`窗口${data.selectIndex}的视频已关闭`)
|
||||
}
|
||||
break;
|
||||
case 'statusChanged':
|
||||
// 视频状态发生改变
|
||||
break;
|
||||
case 'errorInfo':
|
||||
// 错误信息汇总
|
||||
console.log(data, "可打印查看错误消息");
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 销毁
|
||||
function destroy() {
|
||||
if (!replayVideo) return
|
||||
if (replayVideo) {
|
||||
for (let index = 0; index < state.currentNum; index++) {
|
||||
replayVideo.close(index)
|
||||
}
|
||||
}
|
||||
replayVideo.destroy()
|
||||
replayVideo = null;
|
||||
}
|
||||
|
||||
// 获取默认日期范围(前三天)
|
||||
function getDefaultDateRange() {
|
||||
@@ -378,16 +302,6 @@ const disabledDate = (time: Date) => {
|
||||
return false
|
||||
}
|
||||
|
||||
// 根据当前数量在numList中找到合适的值(大于等于当前值的最小值)
|
||||
function findSuitableNum(currentNum: any, numList: any) {
|
||||
const suitableNums = numList.filter((num: any) => num >= currentNum);
|
||||
if (suitableNums.length > 0) {
|
||||
return Math.min(...suitableNums);
|
||||
} else {
|
||||
return Math.max(...numList);
|
||||
}
|
||||
}
|
||||
|
||||
// 暴露变量
|
||||
defineExpose({
|
||||
openDialog
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<el-row v-if="false">
|
||||
<div class="mb8" style="width: 100%">
|
||||
<el-button icon="folder-add" type="primary" class="ml10" @click="formDialogRef.openDialog()"
|
||||
v-auth="'admin_deviceManage_add'">
|
||||
@@ -80,11 +80,17 @@
|
||||
<el-button icon="VideoPlay" text type="primary"
|
||||
@click="previewVideoRef.openDialog(row.id)">预览</el-button>
|
||||
<el-button icon="VideoCamera" text type="primary"
|
||||
@click="replayVideoDialogRef.openDialog('device', row.deviceCode)">回放</el-button>
|
||||
<el-button icon="edit-pen" text type="primary" v-auth="'admin_deviceManage_edit'"
|
||||
@click="formDialogRef.openDialog(row.id)">编辑</el-button>
|
||||
<el-button icon="delete" text type="primary" v-auth="'admin_deviceManage_del'"
|
||||
@click="handleDelete([row.id])">删除</el-button>
|
||||
@click="replayVideoDialogRef.openDialog('device', row.deviceCode)">
|
||||
回放
|
||||
</el-button>
|
||||
<el-button v-if="false" icon="edit-pen" text type="primary"
|
||||
v-auth="'admin_deviceManage_edit'" @click="formDialogRef.openDialog(row.id)">
|
||||
编辑
|
||||
</el-button>
|
||||
<el-button v-if="false" icon="delete" text type="primary"
|
||||
v-auth="'admin_deviceManage_del'" @click="handleDelete([row.id])">
|
||||
删除
|
||||
</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
@@ -38,8 +38,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="systemDeviceCameraReplay">
|
||||
import { BasicTableProps, useTable } from "/@/hooks/table";
|
||||
import { useMessage, useMessageBox } from "/@/hooks/message";
|
||||
import { useMessage } from "/@/hooks/message";
|
||||
import { useDict } from '/@/hooks/dict';
|
||||
|
||||
import * as DeviceCameraReplayApi from "/@/api/third/deviceCameraReplay";
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
<template>
|
||||
<div class="layout-padding">
|
||||
<div class="layout-padding-auto layout-padding-view">
|
||||
|
||||
<draggable-org-Tree ref="draggableDepTreeRef" placeholder="请输入部门名称" :show-expand="true"
|
||||
@getSelectNode="handleGetSelectNode" switch-inactive-text="包含" switch-active-text="不包含" :defaultTreeWidth="20"
|
||||
:nodeClickable="false">
|
||||
@@ -25,7 +24,7 @@
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<el-row v-if="false">
|
||||
<div class="mb8" style="width: 100%">
|
||||
<el-button icon="folder-add" type="primary" class="ml10" @click="formDialogRef.openDialog()"
|
||||
v-auth="'admin_deviceManage_add'">
|
||||
|
||||
@@ -33,8 +33,8 @@
|
||||
<el-table-column type="selection" width="40" align="center" />
|
||||
<el-table-column type="index" label="#" width="80" />
|
||||
|
||||
<el-table-column label="类型" prop="syncConfigType">
|
||||
</el-table-column>
|
||||
<!-- <el-table-column label="类型" prop="syncConfigType">
|
||||
</el-table-column> -->
|
||||
|
||||
<el-table-column min-width="200" label="报警类型">
|
||||
<template #default="{ row }">
|
||||
|
||||
@@ -116,7 +116,6 @@ const getThemeConfig = computed(() => {
|
||||
* 其他正常
|
||||
*/
|
||||
const jumpAdminPanel = () => {
|
||||
console.log("userInfos", useUserInfoStore.userInfos);
|
||||
const isDev = import.meta.env.MODE == "development"
|
||||
const baseAdminUrl = isDev ? 'http://localhost:5173/device' : 'http://192.168.50.10:7001/device';
|
||||
const roleCodes = useUserInfoStore.userInfos?.roleCodes ?? []
|
||||
|
||||
Reference in new issue
Block a user