视频播放操作逻辑优化

This commit is contained in:
15683799673
2025-09-02 15:11:55 +08:00
parent b91d073b8d
commit e277e5d2c3
5 changed files with 145 additions and 51 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

View File

@@ -7,17 +7,20 @@ import type { TreeNode } from '#/api/common';
defineOptions({ inheritAttrs: false });
withDefaults(defineProps<{ showSearch?: boolean }>(), { showSearch: true });
withDefaults(
defineProps<{
showSearch?: boolean;
currentSelectKey?: string;
selectKeys?: string[];
}>(),
{
showSearch: true,
currentSelectKey: '',
selectKeys: [],
},
);
const emit = defineEmits<{
checked: [];
/**
* 点击节点的事件
*/
reload: [];
select: [];
}>();
const emit = defineEmits(['selected', 'reload', 'checked']);
const searchValue = defineModel('searchValue', {
type: String,
@@ -37,6 +40,10 @@ async function loadChannelTree() {
showTreeSkeleton.value = false;
}
function onSelect(key: any, selectNode: any) {
emit('selected', key, selectNode);
}
async function handleReload() {
await loadChannelTree();
emit('reload');
@@ -46,21 +53,11 @@ onMounted(loadChannelTree);
</script>
<template>
<div class="h-[800px]" :class="$attrs.class">
<Skeleton
:loading="showTreeSkeleton"
:paragraph="{ rows: 8 }"
active
class="p-[8px]"
>
<div
class="flex h-full flex-col overflow-y-auto rounded-lg"
>
<div :class="$attrs.class">
<Skeleton :loading="showTreeSkeleton" :paragraph="{ rows: 8 }" active>
<div class="h-full">
<!-- 固定在顶部 必须加上bg-background背景色 否则会产生'穿透'效果 -->
<divx
v-if="showSearch"
class="z-100 sticky left-0 top-0 p-[8px]"
>
<div v-if="showSearch" class="z-100 sticky left-0 top-0 p-[8px]">
<InputSearch
v-model:value="searchValue"
:placeholder="$t('pages.common.search')"
@@ -72,21 +69,30 @@ onMounted(loadChannelTree);
</a-button>
</template>
</InputSearch>
</divx>
<div class="h-full overflow-x-hidden px-[8px]">
</div>
<div
class="h-[calc(100%-40px)] overflow-y-auto overflow-x-hidden px-[8px]"
>
<Tree
v-bind="$attrs"
v-if="channelTree.length > 0"
:class="$attrs.class"
:show-line="{ showLeafIcon: false }"
:tree-data="channelTree"
:virtual="false"
default-expand-all
checkable
@select="$emit('select')"
@check="$emit('checked')"
@select="onSelect"
>
<template #title="{ label }">
<template #title="{ label, level, key }">
<div class="flex">
<div v-if="level == 2" class="tree-icon">
<div
v-if="selectKeys.indexOf(key) > -1"
class="icon playing"
></div>
<div v-else class="icon unplay"></div>
</div>
<span :style="currentSelectKey == key?'color:blue':''">
<span v-if="label.indexOf(searchValue) > -1">
{{ label.substring(0, label.indexOf(searchValue)) }}
<span style="color: #f50">{{ searchValue }}</span>
@@ -96,7 +102,12 @@ onMounted(loadChannelTree);
)
}}
</span>
<span v-else>{{ label }}</span>
<span v-else>
<span>{{ label }}</span>
</span>
</span>
</div>
</template>
</Tree>
</div>
@@ -104,3 +115,25 @@ onMounted(loadChannelTree);
</Skeleton>
</div>
</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>

View File

@@ -1,9 +1,15 @@
<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="c-tree bg-background h-full pb-[5px]">
<ChannelTree class="w-[300px]" @check="onNodeChecked" />
<div class="c-tree bg-background h-full overflow-hidden pb-[5px]">
<ChannelTree
class="h-full w-[300px]"
:currentSelectKey="currentSelectKey"
:selectKeys="selectKeys"
@check="onNodeChecked"
@select="onTreeSelect"
/>
</div>
<!-- 设备分组区域 -->
@@ -79,14 +85,17 @@ const playerStyle = ref({
width: '100%',
height: '100%',
});
const currentSelectPlayerIndex = ref(-1);
const currentSelectPlayerIndex = ref(1);
function playerSelect(index: number) {
if (index === currentSelectPlayerIndex.value) {
currentSelectPlayerIndex.value = -1;
return;
}
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[] = [];
// 当前选择播放器的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 index 播放器的索引信息
* @param callback 视频播放后的回调
*/
function doPlayer(nodeData: any, index: number = 0) {
console.log('index=', index);
if (mpegts.isSupported()) {
streamProxy(nodeData, (res: AddStreamProxyResult) => {
const host = window.location.host;
const url = `ws://${host}/${res.app}/${res.streamId}.live.flv`;
// const host = window.location.host;
// 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
nodeData.url = url;
closePlayer(index);
@@ -338,6 +391,8 @@ function doPlayer(nodeData: any, index: number = 0) {
key: nodeData.id,
data: nodeData,
};
// 播放完成后, 需要处理树组件的状态
treeSelectHandle();
} else {
console.log('视频播放元素获取异常');
}
@@ -353,6 +408,7 @@ function closePlayVieo(plInfo: any) {
plInfo.pause(); // 暂停
plInfo.unload(); // 卸载
plInfo.destroy(); // 销毁
treeSelectHandle();
} catch (e) {
console.log('播放器关闭失败e=', e);
}
@@ -369,6 +425,7 @@ function closePlayer(index: number) {
player.unload(); // 卸载
player.destroy(); // 销毁
playerList[index] = null;
treeSelectHandle();
} catch (e) {
console.log('播放器关闭失败e=', e);
}
@@ -415,6 +472,10 @@ onUnmounted(() => {
}
}
.c-tree {
font-size: 12px;
}
.player.selected {
border: 2px solid deepskyblue;
}