视频播放操作逻辑优化
This commit is contained in:
BIN
apps/web-antd/src/assets/tree/player-err.png
Normal file
BIN
apps/web-antd/src/assets/tree/player-err.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 14 KiB |
BIN
apps/web-antd/src/assets/tree/playering.png
Normal file
BIN
apps/web-antd/src/assets/tree/playering.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 14 KiB |
BIN
apps/web-antd/src/assets/tree/unplayer.png
Normal file
BIN
apps/web-antd/src/assets/tree/unplayer.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 14 KiB |
@@ -7,17 +7,20 @@ import type { TreeNode } from '#/api/common';
|
|||||||
|
|
||||||
defineOptions({ inheritAttrs: false });
|
defineOptions({ inheritAttrs: false });
|
||||||
|
|
||||||
withDefaults(defineProps<{ showSearch?: boolean }>(), { showSearch: true });
|
withDefaults(
|
||||||
|
defineProps<{
|
||||||
|
showSearch?: boolean;
|
||||||
|
currentSelectKey?: string;
|
||||||
|
selectKeys?: string[];
|
||||||
|
}>(),
|
||||||
|
{
|
||||||
|
showSearch: true,
|
||||||
|
currentSelectKey: '',
|
||||||
|
selectKeys: [],
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits(['selected', 'reload', 'checked']);
|
||||||
|
|
||||||
checked: [];
|
|
||||||
/**
|
|
||||||
* 点击节点的事件
|
|
||||||
*/
|
|
||||||
reload: [];
|
|
||||||
select: [];
|
|
||||||
}>();
|
|
||||||
|
|
||||||
const searchValue = defineModel('searchValue', {
|
const searchValue = defineModel('searchValue', {
|
||||||
type: String,
|
type: String,
|
||||||
@@ -37,6 +40,10 @@ async function loadChannelTree() {
|
|||||||
showTreeSkeleton.value = false;
|
showTreeSkeleton.value = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function onSelect(key: any, selectNode: any) {
|
||||||
|
emit('selected', key, selectNode);
|
||||||
|
}
|
||||||
|
|
||||||
async function handleReload() {
|
async function handleReload() {
|
||||||
await loadChannelTree();
|
await loadChannelTree();
|
||||||
emit('reload');
|
emit('reload');
|
||||||
@@ -46,21 +53,11 @@ onMounted(loadChannelTree);
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="h-[800px]" :class="$attrs.class">
|
<div :class="$attrs.class">
|
||||||
<Skeleton
|
<Skeleton :loading="showTreeSkeleton" :paragraph="{ rows: 8 }" active>
|
||||||
:loading="showTreeSkeleton"
|
<div class="h-full">
|
||||||
:paragraph="{ rows: 8 }"
|
|
||||||
active
|
|
||||||
class="p-[8px]"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="flex h-full flex-col overflow-y-auto rounded-lg"
|
|
||||||
>
|
|
||||||
<!-- 固定在顶部 必须加上bg-background背景色 否则会产生'穿透'效果 -->
|
<!-- 固定在顶部 必须加上bg-background背景色 否则会产生'穿透'效果 -->
|
||||||
<divx
|
<div v-if="showSearch" class="z-100 sticky left-0 top-0 p-[8px]">
|
||||||
v-if="showSearch"
|
|
||||||
class="z-100 sticky left-0 top-0 p-[8px]"
|
|
||||||
>
|
|
||||||
<InputSearch
|
<InputSearch
|
||||||
v-model:value="searchValue"
|
v-model:value="searchValue"
|
||||||
:placeholder="$t('pages.common.search')"
|
:placeholder="$t('pages.common.search')"
|
||||||
@@ -72,31 +69,45 @@ onMounted(loadChannelTree);
|
|||||||
</a-button>
|
</a-button>
|
||||||
</template>
|
</template>
|
||||||
</InputSearch>
|
</InputSearch>
|
||||||
</divx>
|
</div>
|
||||||
<div class="h-full overflow-x-hidden px-[8px]">
|
|
||||||
|
<div
|
||||||
|
class="h-[calc(100%-40px)] overflow-y-auto overflow-x-hidden px-[8px]"
|
||||||
|
>
|
||||||
<Tree
|
<Tree
|
||||||
v-bind="$attrs"
|
v-bind="$attrs"
|
||||||
v-if="channelTree.length > 0"
|
v-if="channelTree.length > 0"
|
||||||
:class="$attrs.class"
|
|
||||||
:show-line="{ showLeafIcon: false }"
|
|
||||||
:tree-data="channelTree"
|
:tree-data="channelTree"
|
||||||
:virtual="false"
|
:virtual="false"
|
||||||
default-expand-all
|
default-expand-all
|
||||||
checkable
|
|
||||||
@select="$emit('select')"
|
|
||||||
@check="$emit('checked')"
|
@check="$emit('checked')"
|
||||||
|
@select="onSelect"
|
||||||
>
|
>
|
||||||
<template #title="{ label }">
|
<template #title="{ label, level, key }">
|
||||||
<span v-if="label.indexOf(searchValue) > -1">
|
<div class="flex">
|
||||||
{{ label.substring(0, label.indexOf(searchValue)) }}
|
<div v-if="level == 2" class="tree-icon">
|
||||||
<span style="color: #f50">{{ searchValue }}</span>
|
<div
|
||||||
{{
|
v-if="selectKeys.indexOf(key) > -1"
|
||||||
label.substring(
|
class="icon playing"
|
||||||
label.indexOf(searchValue) + searchValue.length,
|
></div>
|
||||||
)
|
<div v-else class="icon unplay"></div>
|
||||||
}}
|
</div>
|
||||||
</span>
|
<span :style="currentSelectKey == key?'color:blue':''">
|
||||||
<span v-else>{{ label }}</span>
|
<span v-if="label.indexOf(searchValue) > -1">
|
||||||
|
{{ label.substring(0, label.indexOf(searchValue)) }}
|
||||||
|
<span style="color: #f50">{{ searchValue }}</span>
|
||||||
|
{{
|
||||||
|
label.substring(
|
||||||
|
label.indexOf(searchValue) + searchValue.length,
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<span v-else>
|
||||||
|
<span>{{ label }}</span>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</Tree>
|
</Tree>
|
||||||
</div>
|
</div>
|
||||||
@@ -104,3 +115,25 @@ onMounted(loadChannelTree);
|
|||||||
</Skeleton>
|
</Skeleton>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.tree-icon {
|
||||||
|
display: inline-block;
|
||||||
|
height: 20px;
|
||||||
|
width: 20px;
|
||||||
|
|
||||||
|
.unplay {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background: url('/src/assets/tree/unplayer.png') no-repeat;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.playing {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background: url('/src/assets/tree/playering.png') no-repeat;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
@@ -1,9 +1,15 @@
|
|||||||
<template>
|
<template>
|
||||||
<Page class="h-full w-full">
|
<Page style="height: calc(100vh - 88px)" class="video-page h-full w-full">
|
||||||
<!-- 设备分组区域 -->
|
<!-- 设备分组区域 -->
|
||||||
<div class="flex h-full gap-[8px]">
|
<div class="flex h-full gap-[8px]">
|
||||||
<div class="c-tree bg-background h-full pb-[5px]">
|
<div class="c-tree bg-background h-full overflow-hidden pb-[5px]">
|
||||||
<ChannelTree class="w-[300px]" @check="onNodeChecked" />
|
<ChannelTree
|
||||||
|
class="h-full w-[300px]"
|
||||||
|
:currentSelectKey="currentSelectKey"
|
||||||
|
:selectKeys="selectKeys"
|
||||||
|
@check="onNodeChecked"
|
||||||
|
@select="onTreeSelect"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 设备分组区域 -->
|
<!-- 设备分组区域 -->
|
||||||
@@ -79,14 +85,17 @@ const playerStyle = ref({
|
|||||||
width: '100%',
|
width: '100%',
|
||||||
height: '100%',
|
height: '100%',
|
||||||
});
|
});
|
||||||
const currentSelectPlayerIndex = ref(-1);
|
|
||||||
|
const currentSelectPlayerIndex = ref(1);
|
||||||
|
|
||||||
function playerSelect(index: number) {
|
function playerSelect(index: number) {
|
||||||
if (index === currentSelectPlayerIndex.value) {
|
|
||||||
currentSelectPlayerIndex.value = -1;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
currentSelectPlayerIndex.value = index;
|
currentSelectPlayerIndex.value = index;
|
||||||
|
const player = playerList[index - 1];
|
||||||
|
if (player) {
|
||||||
|
currentSelectKey.value = player.key;
|
||||||
|
} else {
|
||||||
|
currentSelectKey.value = '';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -170,6 +179,48 @@ function handleParentNoe(node: any, newNode: any[] = []) {
|
|||||||
|
|
||||||
// 播放器数据, 每一个位置代表页面上行的一个矩形
|
// 播放器数据, 每一个位置代表页面上行的一个矩形
|
||||||
const playerList: any[] = [];
|
const playerList: any[] = [];
|
||||||
|
// 当前选择播放器的key
|
||||||
|
const currentSelectKey = ref('');
|
||||||
|
// 当前所有的播放设备key
|
||||||
|
const selectKeys = ref<string[]>([]);
|
||||||
|
|
||||||
|
function onTreeSelect(_key: any, selectNode: any) {
|
||||||
|
const {
|
||||||
|
selected,
|
||||||
|
node: { level, data },
|
||||||
|
} = selectNode;
|
||||||
|
// 代表点击的是摄像头
|
||||||
|
if (level === 2) {
|
||||||
|
// 播放
|
||||||
|
if (selected) {
|
||||||
|
doPlayer(data, currentSelectPlayerIndex.value - 1);
|
||||||
|
}
|
||||||
|
// 取消播放
|
||||||
|
else {
|
||||||
|
for (let i = 0; i < playerNum.value; i++) {
|
||||||
|
const player = playerList[i];
|
||||||
|
if (player && player.data.id === data.id) {
|
||||||
|
closePlayer(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
message.error('请选择摄像头');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function treeSelectHandle() {
|
||||||
|
// 此处player可能已经释放所以不可能在取到只
|
||||||
|
const player = playerList[currentSelectPlayerIndex.value - 1];
|
||||||
|
currentSelectKey.value = player ? player.key : '';
|
||||||
|
const arr: string[] = [];
|
||||||
|
playerList.forEach((item: any) => {
|
||||||
|
if (item && item.key) {
|
||||||
|
arr.push(item.key);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
selectKeys.value = arr;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 节点选中时间处理
|
* 节点选中时间处理
|
||||||
@@ -302,13 +353,15 @@ function streamProxy(nodeData: any, cb: Function) {
|
|||||||
* 开始播放视频流
|
* 开始播放视频流
|
||||||
* @param nodeData 播放的节点数据
|
* @param nodeData 播放的节点数据
|
||||||
* @param index 播放器的索引信息
|
* @param index 播放器的索引信息
|
||||||
|
* @param callback 视频播放后的回调
|
||||||
*/
|
*/
|
||||||
function doPlayer(nodeData: any, index: number = 0) {
|
function doPlayer(nodeData: any, index: number = 0) {
|
||||||
console.log('index=', index);
|
console.log('index=', index);
|
||||||
if (mpegts.isSupported()) {
|
if (mpegts.isSupported()) {
|
||||||
streamProxy(nodeData, (res: AddStreamProxyResult) => {
|
streamProxy(nodeData, (res: AddStreamProxyResult) => {
|
||||||
const host = window.location.host;
|
// const host = window.location.host;
|
||||||
const url = `ws://${host}/${res.app}/${res.streamId}.live.flv`;
|
// const url = `ws://${host}/${res.app}/${res.streamId}.live.flv`;
|
||||||
|
const url = `ws://183.230.235.66:11010/${res.app}/${res.streamId}.live.flv`;
|
||||||
// 将url 绑定到 nodeData
|
// 将url 绑定到 nodeData
|
||||||
nodeData.url = url;
|
nodeData.url = url;
|
||||||
closePlayer(index);
|
closePlayer(index);
|
||||||
@@ -338,6 +391,8 @@ function doPlayer(nodeData: any, index: number = 0) {
|
|||||||
key: nodeData.id,
|
key: nodeData.id,
|
||||||
data: nodeData,
|
data: nodeData,
|
||||||
};
|
};
|
||||||
|
// 播放完成后, 需要处理树组件的状态
|
||||||
|
treeSelectHandle();
|
||||||
} else {
|
} else {
|
||||||
console.log('视频播放元素获取异常');
|
console.log('视频播放元素获取异常');
|
||||||
}
|
}
|
||||||
@@ -353,6 +408,7 @@ function closePlayVieo(plInfo: any) {
|
|||||||
plInfo.pause(); // 暂停
|
plInfo.pause(); // 暂停
|
||||||
plInfo.unload(); // 卸载
|
plInfo.unload(); // 卸载
|
||||||
plInfo.destroy(); // 销毁
|
plInfo.destroy(); // 销毁
|
||||||
|
treeSelectHandle();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log('播放器关闭失败,e=', e);
|
console.log('播放器关闭失败,e=', e);
|
||||||
}
|
}
|
||||||
@@ -369,6 +425,7 @@ function closePlayer(index: number) {
|
|||||||
player.unload(); // 卸载
|
player.unload(); // 卸载
|
||||||
player.destroy(); // 销毁
|
player.destroy(); // 销毁
|
||||||
playerList[index] = null;
|
playerList[index] = null;
|
||||||
|
treeSelectHandle();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log('播放器关闭失败,e=', e);
|
console.log('播放器关闭失败,e=', e);
|
||||||
}
|
}
|
||||||
@@ -415,6 +472,10 @@ onUnmounted(() => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.c-tree {
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
.player.selected {
|
.player.selected {
|
||||||
border: 2px solid deepskyblue;
|
border: 2px solid deepskyblue;
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user