|
@@ -52,23 +52,25 @@
|
|
|
</el-button>
|
|
</el-button>
|
|
|
</div>
|
|
</div>
|
|
|
<div style="height: 85vh; display: flex; flex-wrap: wrap">
|
|
<div style="height: 85vh; display: flex; flex-wrap: wrap">
|
|
|
- <!-- 只渲染实际可用的通道数量,不超过当前分屏数量 -->
|
|
|
|
|
|
|
+ <!-- 渲染所有分屏数量的播放框 -->
|
|
|
<div
|
|
<div
|
|
|
- v-for="i in Math.min(spilt, availableChannels.length || spilt)"
|
|
|
|
|
|
|
+ v-for="i in spilt"
|
|
|
:key="i"
|
|
:key="i"
|
|
|
class="play-box"
|
|
class="play-box"
|
|
|
:style="liveStyle"
|
|
:style="liveStyle"
|
|
|
:class="{ redborder: playerIdx == i - 1 }"
|
|
:class="{ redborder: playerIdx == i - 1 }"
|
|
|
@click="playerIdx = i - 1"
|
|
@click="playerIdx = i - 1"
|
|
|
>
|
|
>
|
|
|
|
|
+ <!-- 显示序号,如果该位置没有视频或不在可用通道范围内 -->
|
|
|
<div
|
|
<div
|
|
|
- v-if="!videoUrl[i - 1]"
|
|
|
|
|
|
|
+ v-if="!videoUrl[i - 1] || i - 1 >= availableChannels.length"
|
|
|
style="color: #ffffff; font-size: 30px; font-weight: bold"
|
|
style="color: #ffffff; font-size: 30px; font-weight: bold"
|
|
|
>
|
|
>
|
|
|
{{ i }}
|
|
{{ i }}
|
|
|
</div>
|
|
</div>
|
|
|
|
|
+ <!-- 只有当有视频URL时才渲染播放器 -->
|
|
|
<player
|
|
<player
|
|
|
- v-else
|
|
|
|
|
|
|
+ v-if="videoUrl[i - 1]"
|
|
|
:ref="(el) => setPlayerRef(el, i - 1)"
|
|
:ref="(el) => setPlayerRef(el, i - 1)"
|
|
|
:videourl="videoUrl[i - 1]"
|
|
:videourl="videoUrl[i - 1]"
|
|
|
:playerInfo="
|
|
:playerInfo="
|
|
@@ -84,30 +86,6 @@
|
|
|
@destroy="destroy"
|
|
@destroy="destroy"
|
|
|
/>
|
|
/>
|
|
|
</div>
|
|
</div>
|
|
|
-
|
|
|
|
|
- <!-- 如果通道数少于分屏数,显示空白占位 -->
|
|
|
|
|
- <div
|
|
|
|
|
- v-for="i in Math.max(0, spilt - availableChannels.length)"
|
|
|
|
|
- :key="`empty-${i}`"
|
|
|
|
|
- :class="availableChannels.length > 0 ? 'play-box' : ''"
|
|
|
|
|
- :style="availableChannels.length > 0 ? liveStyle : ''"
|
|
|
|
|
- @click="handleEmpty(i)"
|
|
|
|
|
- >
|
|
|
|
|
- <div
|
|
|
|
|
- v-if="availableChannels.length > 0"
|
|
|
|
|
- style="
|
|
|
|
|
- color: #ffffff;
|
|
|
|
|
- font-size: 30px;
|
|
|
|
|
- font-weight: bold;
|
|
|
|
|
- display: flex;
|
|
|
|
|
- align-items: center;
|
|
|
|
|
- justify-content: center;
|
|
|
|
|
- height: 100%;
|
|
|
|
|
- "
|
|
|
|
|
- >
|
|
|
|
|
- {{ availableChannels.length + i }}
|
|
|
|
|
- </div>
|
|
|
|
|
- </div>
|
|
|
|
|
</div>
|
|
</div>
|
|
|
</el-main>
|
|
</el-main>
|
|
|
</el-container>
|
|
</el-container>
|
|
@@ -323,24 +301,23 @@ let playerInfo = ref([])
|
|
|
const clickEvent = async (data) => {
|
|
const clickEvent = async (data) => {
|
|
|
// 情况1:点击的是设备节点 (type === 0)
|
|
// 情况1:点击的是设备节点 (type === 0)
|
|
|
if (data.type === 0) {
|
|
if (data.type === 0) {
|
|
|
|
|
+ // 重置选中的空闲槽位
|
|
|
|
|
+ selectedEmptySlot.value = -1
|
|
|
|
|
+
|
|
|
currentDevice.value = {
|
|
currentDevice.value = {
|
|
|
id: data.userData?.serialNumber,
|
|
id: data.userData?.serialNumber,
|
|
|
name: data.name,
|
|
name: data.name,
|
|
|
data: data
|
|
data: data
|
|
|
}
|
|
}
|
|
|
- const deviceId = data.userData?.serialNumber // 从树节点数据中提取设备ID
|
|
|
|
|
|
|
+ const deviceId = data.userData?.serialNumber
|
|
|
if (!deviceId) return
|
|
if (!deviceId) return
|
|
|
|
|
|
|
|
loading.value = true
|
|
loading.value = true
|
|
|
try {
|
|
try {
|
|
|
- // 1. 获取该设备下的所有通道
|
|
|
|
|
const channels = await getDeviceChannels(deviceId)
|
|
const channels = await getDeviceChannels(deviceId)
|
|
|
-
|
|
|
|
|
- // 只保留摄像头类型的通道
|
|
|
|
|
const cameraChannels = channels.filter((channel) => channel.basicData.model === 'Camera')
|
|
const cameraChannels = channels.filter((channel) => channel.basicData.model === 'Camera')
|
|
|
availableChannels.value = cameraChannels
|
|
availableChannels.value = cameraChannels
|
|
|
|
|
|
|
|
- // 清空之前的数据
|
|
|
|
|
playerInfo.value = []
|
|
playerInfo.value = []
|
|
|
cameraChannels.forEach((channel) => {
|
|
cameraChannels.forEach((channel) => {
|
|
|
playerInfo.value.push(channel)
|
|
playerInfo.value.push(channel)
|
|
@@ -348,13 +325,11 @@ const clickEvent = async (data) => {
|
|
|
|
|
|
|
|
if (!cameraChannels || cameraChannels.length === 0) {
|
|
if (!cameraChannels || cameraChannels.length === 0) {
|
|
|
console.warn('该设备下没有可用的摄像头通道')
|
|
console.warn('该设备下没有可用的摄像头通道')
|
|
|
- // 清空所有播放器
|
|
|
|
|
const newVideoUrls = Array(spilt.value).fill('')
|
|
const newVideoUrls = Array(spilt.value).fill('')
|
|
|
videoUrl.value = newVideoUrls
|
|
videoUrl.value = newVideoUrls
|
|
|
return
|
|
return
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- // 2. 将通道智能分配给播放器
|
|
|
|
|
await assignChannelsToPlayers(cameraChannels)
|
|
await assignChannelsToPlayers(cameraChannels)
|
|
|
} catch (error) {
|
|
} catch (error) {
|
|
|
console.error('处理设备通道时出错:', error)
|
|
console.error('处理设备通道时出错:', error)
|
|
@@ -362,10 +337,20 @@ const clickEvent = async (data) => {
|
|
|
loading.value = false
|
|
loading.value = false
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
- // 情况2:点击的是具体的通道节点,保持原有逻辑不变
|
|
|
|
|
|
|
+ // 情况2:点击的是具体的通道节点
|
|
|
else if (data.userData?.channelSipId) {
|
|
else if (data.userData?.channelSipId) {
|
|
|
- playerIdx.value = 0
|
|
|
|
|
- await sendDevicePush(data.userData)
|
|
|
|
|
|
|
+ // 检查是否有选中的空闲槽位
|
|
|
|
|
+ let targetIndex = selectedEmptySlot.value !== -1 ? selectedEmptySlot.value : playerIdx.value
|
|
|
|
|
+
|
|
|
|
|
+ // 验证目标索引是否在有效范围内
|
|
|
|
|
+ if (targetIndex >= spilt.value) {
|
|
|
|
|
+ targetIndex = playerIdx.value // 回退到当前选中的播放器
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 重置选中的空闲槽位
|
|
|
|
|
+ selectedEmptySlot.value = -1
|
|
|
|
|
+
|
|
|
|
|
+ await sendDevicePush(data.userData, targetIndex)
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -383,17 +368,15 @@ const getDeviceChannels = async (deviceId) => {
|
|
|
const assignChannelsToPlayers = async (channels) => {
|
|
const assignChannelsToPlayers = async (channels) => {
|
|
|
const currentSplit = spilt.value
|
|
const currentSplit = spilt.value
|
|
|
const actualChannels = Math.min(channels.length, currentSplit)
|
|
const actualChannels = Math.min(channels.length, currentSplit)
|
|
|
- // 清空当前所有播放器
|
|
|
|
|
- const newVideoUrls = Array(Math.max(currentSplit, channels.length)).fill('')
|
|
|
|
|
|
|
+ // 初始化所有播放器位置
|
|
|
|
|
+ const newVideoUrls = Array(currentSplit).fill('')
|
|
|
videoUrl.value = newVideoUrls
|
|
videoUrl.value = newVideoUrls
|
|
|
|
|
|
|
|
// 为每个可用通道分配播放器
|
|
// 为每个可用通道分配播放器
|
|
|
for (let i = 0; i < actualChannels; i++) {
|
|
for (let i = 0; i < actualChannels; i++) {
|
|
|
- // 延迟执行,避免同时发起大量请求
|
|
|
|
|
await new Promise((resolve) => setTimeout(resolve, 50))
|
|
await new Promise((resolve) => setTimeout(resolve, 50))
|
|
|
|
|
|
|
|
const channelData = channels[i]
|
|
const channelData = channels[i]
|
|
|
- // 构建符合 sendDevicePush 要求的参数格式
|
|
|
|
|
const playData = {
|
|
const playData = {
|
|
|
deviceSipId: channelData.deviceId,
|
|
deviceSipId: channelData.deviceId,
|
|
|
channelSipId: channelData.basicData?.channelSipId || channelData.id,
|
|
channelSipId: channelData.basicData?.channelSipId || channelData.id,
|
|
@@ -403,8 +386,6 @@ const assignChannelsToPlayers = async (channels) => {
|
|
|
|
|
|
|
|
await sendDevicePush(playData, i)
|
|
await sendDevicePush(playData, i)
|
|
|
}
|
|
}
|
|
|
-
|
|
|
|
|
- // 如果通道数少于当前分屏数,不需要做额外处理,因为已经清空了超出部分
|
|
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// 通知设备上传媒体流
|
|
// 通知设备上传媒体流
|
|
@@ -412,7 +393,10 @@ const sendDevicePush = async (itemData, targetIndex = null) => {
|
|
|
// 关键修改:允许从外部传入 targetIndex,默认使用当前激活的 playerIdx
|
|
// 关键修改:允许从外部传入 targetIndex,默认使用当前激活的 playerIdx
|
|
|
const playIndex = targetIndex !== null ? targetIndex : playerIdx.value
|
|
const playIndex = targetIndex !== null ? targetIndex : playerIdx.value
|
|
|
|
|
|
|
|
- save(itemData, playIndex) // 保存数据也要使用正确的索引
|
|
|
|
|
|
|
+ // 确保索引在有效范围内
|
|
|
|
|
+ const validIndex = Math.min(playIndex, spilt.value - 1)
|
|
|
|
|
+
|
|
|
|
|
+ save(itemData, validIndex) // 保存数据也要使用正确的索引
|
|
|
|
|
|
|
|
let deviceId = itemData.deviceSipId
|
|
let deviceId = itemData.deviceSipId
|
|
|
let channelId = itemData.channelSipId
|
|
let channelId = itemData.channelSipId
|
|
@@ -427,7 +411,7 @@ const sendDevicePush = async (itemData, targetIndex = null) => {
|
|
|
itemData.playUrl = res.playurl
|
|
itemData.playUrl = res.playurl
|
|
|
}
|
|
}
|
|
|
itemData.streamId = res.streamId
|
|
itemData.streamId = res.streamId
|
|
|
- setPlayUrl(itemData.playUrl, playIndex) // 播放URL设置到指定位置
|
|
|
|
|
|
|
+ setPlayUrl(itemData.playUrl, validIndex) // 播放URL设置到指定位置
|
|
|
})
|
|
})
|
|
|
.finally(() => {
|
|
.finally(() => {
|
|
|
loading.value = false
|
|
loading.value = false
|
|
@@ -460,11 +444,16 @@ const checkPlayByParam = () => {
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+// 存储当前选中的空闲槽位
|
|
|
|
|
+const selectedEmptySlot = ref(-1)
|
|
|
|
|
+
|
|
|
const handleEmpty = (i) => {
|
|
const handleEmpty = (i) => {
|
|
|
- // playerIdx.value = availableChannels.value.length + i - 1
|
|
|
|
|
- // if (availableChannels.value.length > 0) {
|
|
|
|
|
- // playerIdx.value = 0 // 设置为第一个播放器
|
|
|
|
|
- // }
|
|
|
|
|
|
|
+ // 计算当前点击的空闲槽位的实际索引
|
|
|
|
|
+ const slotIndex = availableChannels.value.length + i - 1
|
|
|
|
|
+ selectedEmptySlot.value = slotIndex
|
|
|
|
|
+ playerIdx.value = slotIndex
|
|
|
|
|
+
|
|
|
|
|
+ console.log('选中空闲槽位:', slotIndex)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// 截图处理
|
|
// 截图处理
|