|
|
@@ -56,8 +56,9 @@
|
|
|
</el-button>
|
|
|
</div>
|
|
|
<div style="height: 85vh; display: flex; flex-wrap: wrap">
|
|
|
+ <!-- 只渲染实际可用的通道数量,不超过当前分屏数量 -->
|
|
|
<div
|
|
|
- v-for="i in spilt"
|
|
|
+ v-for="i in Math.min(spilt, availableChannels.length || spilt)"
|
|
|
:key="i"
|
|
|
class="play-box"
|
|
|
:style="liveStyle"
|
|
|
@@ -67,8 +68,9 @@
|
|
|
<div
|
|
|
v-if="!videoUrl[i - 1]"
|
|
|
style="color: #ffffff; font-size: 30px; font-weight: bold"
|
|
|
- >{{ i }}</div
|
|
|
>
|
|
|
+ {{ i }}
|
|
|
+ </div>
|
|
|
<player
|
|
|
v-else
|
|
|
:ref="(el) => setPlayerRef(el, i - 1)"
|
|
|
@@ -86,6 +88,28 @@
|
|
|
@destroy="destroy"
|
|
|
/>
|
|
|
</div>
|
|
|
+
|
|
|
+ <!-- 如果通道数少于分屏数,显示空白占位 -->
|
|
|
+ <div
|
|
|
+ v-for="i in Math.max(0, spilt - availableChannels.length)"
|
|
|
+ :key="`empty-${i}`"
|
|
|
+ class="play-box"
|
|
|
+ :style="liveStyle"
|
|
|
+ >
|
|
|
+ <div
|
|
|
+ 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>
|
|
|
</el-main>
|
|
|
</el-container>
|
|
|
@@ -120,6 +144,9 @@ const count = ref(15)
|
|
|
const total = ref(0)
|
|
|
const loading = ref(false)
|
|
|
|
|
|
+// 新增:存储可用通道
|
|
|
+const availableChannels = ref([])
|
|
|
+
|
|
|
// 设置播放器引用
|
|
|
const setPlayerRef = (el, index) => {
|
|
|
if (el) {
|
|
|
@@ -140,7 +167,7 @@ const liveStyle = computed(() => {
|
|
|
}
|
|
|
|
|
|
nextTick(() => {
|
|
|
- for (let i = 0; i < spilt.value; i++) {
|
|
|
+ for (let i = 0; i < Math.min(spilt.value, availableChannels.value.length || spilt.value); i++) {
|
|
|
const player = playerRefs.value[i]
|
|
|
if (player && typeof player.updatePlayerDomSize === 'function') {
|
|
|
player.updatePlayerDomSize()
|
|
|
@@ -166,16 +193,8 @@ watch(
|
|
|
|
|
|
loading.value = true
|
|
|
try {
|
|
|
- // 1. 重新获取当前设备的通道
|
|
|
- const channels = await getDeviceChannels(currentDevice.value.id)
|
|
|
-
|
|
|
- if (!channels || channels.length === 0) {
|
|
|
- console.warn('该设备下没有可用通道')
|
|
|
- return
|
|
|
- }
|
|
|
-
|
|
|
- // 2. 重新分配通道到所有播放器
|
|
|
- await assignChannelsToPlayers(channels)
|
|
|
+ // 重新分配通道到所有播放器
|
|
|
+ await assignChannelsToPlayers(availableChannels.value)
|
|
|
} catch (error) {
|
|
|
console.error('分屏变化时填充通道出错:', error)
|
|
|
ElMessage.error('自动填充通道失败')
|
|
|
@@ -183,18 +202,21 @@ watch(
|
|
|
loading.value = false
|
|
|
}
|
|
|
}
|
|
|
- // 可选:当分屏数减少时,可以清空多余的播放器
|
|
|
+ // 当分屏数减少时,清空超出当前通道数的播放器
|
|
|
else if (newSplit < oldSplit) {
|
|
|
- // 清空超出当前分屏数的播放器
|
|
|
- for (let i = newSplit; i < oldSplit; i++) {
|
|
|
+ // 清空超出当前分屏数或超出通道数的播放器
|
|
|
+ const maxVisible = Math.min(newSplit, availableChannels.value.length)
|
|
|
+ for (let i = maxVisible; i < Math.max(oldSplit, availableChannels.value.length); i++) {
|
|
|
const newVideoUrls = [...videoUrl.value]
|
|
|
- newVideoUrls[i] = ''
|
|
|
- videoUrl.value = newVideoUrls
|
|
|
- clear(i + 1) // 清理存储的数据
|
|
|
+ if (newVideoUrls[i]) {
|
|
|
+ newVideoUrls[i] = ''
|
|
|
+ videoUrl.value = newVideoUrls
|
|
|
+ clear(i + 1) // 清理存储的数据
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
},
|
|
|
- { immediate: false } // 不需要立即执行
|
|
|
+ { immediate: false }
|
|
|
)
|
|
|
|
|
|
// 监听路由变化
|
|
|
@@ -237,22 +259,31 @@ const clickEvent = async (data) => {
|
|
|
try {
|
|
|
// 1. 获取该设备下的所有通道
|
|
|
const channels = await getDeviceChannels(deviceId)
|
|
|
- channels.forEach((channel) => {
|
|
|
- if (channel.basicData.model === 'Camera') {
|
|
|
- playerInfo.value.push(channel)
|
|
|
- }
|
|
|
+
|
|
|
+ // 只保留摄像头类型的通道
|
|
|
+ const cameraChannels = channels.filter((channel) => channel.basicData.model === 'Camera')
|
|
|
+ availableChannels.value = cameraChannels
|
|
|
+
|
|
|
+ // 清空之前的数据
|
|
|
+ playerInfo.value = []
|
|
|
+ cameraChannels.forEach((channel) => {
|
|
|
+ playerInfo.value.push(channel)
|
|
|
})
|
|
|
|
|
|
console.log('playerInfo>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>', playerInfo.value)
|
|
|
+ console.log('availableChannels>>>>>>>>>>>>>>>>>>>>>>>>>>>>>', availableChannels.value)
|
|
|
console.log('videourl**************************', videoUrl.value)
|
|
|
|
|
|
- if (!channels || channels.length === 0) {
|
|
|
- console.warn('该设备下没有可用通道')
|
|
|
+ if (!cameraChannels || cameraChannels.length === 0) {
|
|
|
+ console.warn('该设备下没有可用的摄像头通道')
|
|
|
+ // 清空所有播放器
|
|
|
+ const newVideoUrls = Array(spilt.value).fill('')
|
|
|
+ videoUrl.value = newVideoUrls
|
|
|
return
|
|
|
}
|
|
|
|
|
|
// 2. 将通道智能分配给播放器
|
|
|
- await assignChannelsToPlayers(channels)
|
|
|
+ await assignChannelsToPlayers(cameraChannels)
|
|
|
} catch (error) {
|
|
|
console.error('处理设备通道时出错:', error)
|
|
|
} finally {
|
|
|
@@ -269,7 +300,6 @@ const getDeviceChannels = async (deviceId) => {
|
|
|
try {
|
|
|
const response = await listSipDeviceChannel(deviceId)
|
|
|
console.log('获取设备通道列表成功:', response)
|
|
|
- // 假设接口返回的是通道数组,且每个通道对象结构符合 { basicData: { channelSipId, ... }, ... }
|
|
|
return response || []
|
|
|
} catch (error) {
|
|
|
console.error('获取设备通道列表失败:', error)
|
|
|
@@ -279,46 +309,34 @@ const getDeviceChannels = async (deviceId) => {
|
|
|
|
|
|
const assignChannelsToPlayers = async (channels) => {
|
|
|
const currentSplit = spilt.value
|
|
|
- console.log(`分配 ${channels.length} 个通道到 ${currentSplit} 个播放窗口`)
|
|
|
+ const actualChannels = Math.min(channels.length, currentSplit)
|
|
|
|
|
|
- // 决定从哪个播放器窗口开始分配:始终从第一个窗口(0)开始
|
|
|
- // 这样保证每次分屏变化时都能从头开始重新分配
|
|
|
- const startPlayerIndex = 0
|
|
|
+ console.log(`分配 ${actualChannels} 个通道到最多 ${currentSplit} 个播放窗口`)
|
|
|
|
|
|
- // 决定从哪个通道开始取:总是从第一个通道开始
|
|
|
- const startChannelIndex = 0
|
|
|
-
|
|
|
- // 循环,为当前分屏布局中的每个位置分配通道
|
|
|
- for (let screenPosition = 0; screenPosition < currentSplit; screenPosition++) {
|
|
|
- const channelIndex = startChannelIndex + screenPosition
|
|
|
- const targetPlayerIndex = startPlayerIndex + screenPosition
|
|
|
+ // 清空当前所有播放器
|
|
|
+ const newVideoUrls = Array(Math.max(currentSplit, channels.length)).fill('')
|
|
|
+ videoUrl.value = newVideoUrls
|
|
|
|
|
|
+ // 为每个可用通道分配播放器
|
|
|
+ for (let i = 0; i < actualChannels; i++) {
|
|
|
// 延迟执行,避免同时发起大量请求
|
|
|
await new Promise((resolve) => setTimeout(resolve, 50))
|
|
|
|
|
|
- if (channelIndex < channels.length) {
|
|
|
- // 有对应通道,开始播放
|
|
|
- const channelData = channels[channelIndex]
|
|
|
- // 构建符合 sendDevicePush 要求的参数格式
|
|
|
- const playData = {
|
|
|
- deviceSipId: channelData.deviceId,
|
|
|
- channelSipId: channelData.basicData?.channelSipId || channelData.id,
|
|
|
- name: channelData.name || `通道${channelIndex + 1}`,
|
|
|
- ...channelData.basicData
|
|
|
- }
|
|
|
- console.log(`将通道 ${playData.name} 分配给播放器 ${targetPlayerIndex}`)
|
|
|
- await sendDevicePush(playData, targetPlayerIndex)
|
|
|
- } else {
|
|
|
- // 没有更多通道,清空当前位置
|
|
|
- const newVideoUrls = [...videoUrl.value]
|
|
|
- newVideoUrls[targetPlayerIndex] = ''
|
|
|
- videoUrl.value = newVideoUrls
|
|
|
- // 清理存储的数据
|
|
|
- clear(targetPlayerIndex + 1)
|
|
|
- console.log(`播放器 ${targetPlayerIndex} 没有可用通道,已清空`)
|
|
|
+ const channelData = channels[i]
|
|
|
+ // 构建符合 sendDevicePush 要求的参数格式
|
|
|
+ const playData = {
|
|
|
+ deviceSipId: channelData.deviceId,
|
|
|
+ channelSipId: channelData.basicData?.channelSipId || channelData.id,
|
|
|
+ name: channelData.name || `通道${i + 1}`,
|
|
|
+ ...channelData.basicData
|
|
|
}
|
|
|
+ console.log(`将通道 ${playData.name} 分配给播放器 ${i}`)
|
|
|
+ await sendDevicePush(playData, i)
|
|
|
}
|
|
|
+
|
|
|
+ // 如果通道数少于当前分屏数,不需要做额外处理,因为已经清空了超出部分
|
|
|
}
|
|
|
+
|
|
|
// 通知设备上传媒体流
|
|
|
const sendDevicePush = (itemData, targetIndex = null) => {
|
|
|
// 关键修改:允许从外部传入 targetIndex,默认使用当前激活的 playerIdx
|
|
|
@@ -349,25 +367,15 @@ const sendDevicePush = (itemData, targetIndex = null) => {
|
|
|
let playInfoRes = ref('')
|
|
|
// 设置播放URL
|
|
|
const setPlayUrl = (url, idx) => {
|
|
|
+ // 确保数组长度足够
|
|
|
+ while (videoUrl.value.length <= idx) {
|
|
|
+ videoUrl.value.push('')
|
|
|
+ }
|
|
|
+
|
|
|
const newVideoUrls = [...videoUrl.value]
|
|
|
newVideoUrls[idx] = url
|
|
|
videoUrl.value = newVideoUrls
|
|
|
|
|
|
- // playerInfo.value.forEach((item) => {
|
|
|
- // if (videoUrl.value[0].includes(item.id) && videoUrl.value[0].includes(item.deviceId)) {
|
|
|
- // playInfoRes.value = item
|
|
|
- // }
|
|
|
- // })
|
|
|
-
|
|
|
- // console.log('hhhhhhhhhhhhhhhhhhhhhhh', videoUrl.value)
|
|
|
-
|
|
|
- // console.log(
|
|
|
- // 'xxxxxxxxxxxxxxxxxxxxx',
|
|
|
- // playerInfo.filter(
|
|
|
- // (item) => videoUrl[0].includes(item.id) && videoUrl[0].includes(item.deviceId)
|
|
|
- // )
|
|
|
- // )
|
|
|
-
|
|
|
setTimeout(() => {
|
|
|
window.localStorage.setItem('videoUrl', JSON.stringify(videoUrl.value))
|
|
|
}, 100)
|
|
|
@@ -410,6 +418,10 @@ const shot = (e) => {
|
|
|
const save = (item, index) => {
|
|
|
let dataStr = window.localStorage.getItem('playData') || '[]'
|
|
|
let data = JSON.parse(dataStr)
|
|
|
+ // 确保数组长度足够
|
|
|
+ while (data.length <= index) {
|
|
|
+ data.push(null)
|
|
|
+ }
|
|
|
data[index] = item
|
|
|
window.localStorage.setItem('playData', JSON.stringify(data))
|
|
|
}
|
|
|
@@ -418,7 +430,9 @@ const save = (item, index) => {
|
|
|
const clear = (idx) => {
|
|
|
let dataStr = window.localStorage.getItem('playData') || '[]'
|
|
|
let data = JSON.parse(dataStr)
|
|
|
- data[idx - 1] = null
|
|
|
+ if (data.length > idx - 1) {
|
|
|
+ data[idx - 1] = null
|
|
|
+ }
|
|
|
console.log(data)
|
|
|
window.localStorage.setItem('playData', JSON.stringify(data))
|
|
|
}
|
|
|
@@ -452,6 +466,12 @@ const clear = (idx) => {
|
|
|
border-radius: 5px;
|
|
|
}
|
|
|
|
|
|
+.empty-box {
|
|
|
+ background-color: #333;
|
|
|
+ opacity: 0.5;
|
|
|
+ cursor: default;
|
|
|
+}
|
|
|
+
|
|
|
.player-wrap {
|
|
|
position: absolute;
|
|
|
top: 0px;
|