|
|
@@ -88,6 +88,7 @@ import { FullScreen, Menu, Grid } from '@element-plus/icons-vue'
|
|
|
import player from './components/player/jessibuca.vue'
|
|
|
import DeviceTree from './components/player/DeviceTree.vue'
|
|
|
import { startPlay } from '@/api/pms/video/channel'
|
|
|
+import { listSipDeviceChannel } from '@/api/pms/video/sipdevice';
|
|
|
|
|
|
const { t } = useI18n()
|
|
|
|
|
|
@@ -106,6 +107,8 @@ const count = ref(15)
|
|
|
const total = ref(0)
|
|
|
const loading = ref(false)
|
|
|
|
|
|
+
|
|
|
+
|
|
|
// 设置播放器引用
|
|
|
const setPlayerRef = (el, index) => {
|
|
|
if (el) {
|
|
|
@@ -138,19 +141,49 @@ const liveStyle = computed(() => {
|
|
|
})
|
|
|
|
|
|
// 监听分屏变化
|
|
|
-watch(spilt, (newValue) => {
|
|
|
- for (let i = 1; i <= newValue; i++) {
|
|
|
- const player = playerRefs.value[i - 1]
|
|
|
- if (player) {
|
|
|
- nextTick(() => {
|
|
|
- if (typeof player.resize === 'function') {
|
|
|
- player.resize()
|
|
|
+watch(
|
|
|
+ () => spilt.value,
|
|
|
+ async (newSplit, oldSplit) => {
|
|
|
+ // 只有当分屏数增加(例如从1到4,或从4到9)且有当前选中设备时才执行
|
|
|
+ if (newSplit > oldSplit && currentDevice.value) {
|
|
|
+ console.log(`分屏从 ${oldSplit} 变为 ${newSplit},自动填充设备 ${currentDevice.value.name} 的通道`)
|
|
|
+
|
|
|
+ // 延迟执行,确保DOM更新完成
|
|
|
+ await nextTick()
|
|
|
+
|
|
|
+ loading.value = true
|
|
|
+ try {
|
|
|
+ // 1. 重新获取当前设备的通道
|
|
|
+ const channels = await getDeviceChannels(currentDevice.value.id)
|
|
|
+
|
|
|
+ if (!channels || channels.length === 0) {
|
|
|
+ console.warn('该设备下没有可用通道')
|
|
|
+ return
|
|
|
}
|
|
|
- })
|
|
|
+
|
|
|
+ // 2. 重新分配通道到所有播放器
|
|
|
+ await assignChannelsToPlayers(channels)
|
|
|
+
|
|
|
+ } catch (error) {
|
|
|
+ console.error('分屏变化时填充通道出错:', error)
|
|
|
+ ElMessage.error('自动填充通道失败')
|
|
|
+ } finally {
|
|
|
+ loading.value = false
|
|
|
+ }
|
|
|
}
|
|
|
- }
|
|
|
- window.localStorage.setItem('split', newValue.toString())
|
|
|
-})
|
|
|
+ // 可选:当分屏数减少时,可以清空多余的播放器
|
|
|
+ else if (newSplit < oldSplit) {
|
|
|
+ // 清空超出当前分屏数的播放器
|
|
|
+ for (let i = newSplit; i < oldSplit; i++) {
|
|
|
+ const newVideoUrls = [...videoUrl.value]
|
|
|
+ newVideoUrls[i] = ''
|
|
|
+ videoUrl.value = newVideoUrls
|
|
|
+ clear(i + 1) // 清理存储的数据
|
|
|
+ }
|
|
|
+ }
|
|
|
+ },
|
|
|
+ { immediate: false } // 不需要立即执行
|
|
|
+)
|
|
|
|
|
|
// 监听路由变化
|
|
|
watch(
|
|
|
@@ -174,40 +207,126 @@ const destroy = (idx) => {
|
|
|
console.log(idx)
|
|
|
clear(idx.substring(idx.length - 1))
|
|
|
}
|
|
|
-
|
|
|
+const currentDevice = ref(null) // 存储当前选中的设备信息
|
|
|
// 点击事件
|
|
|
-const clickEvent = (data) => {
|
|
|
- if (data.channelSipId) {
|
|
|
- sendDevicePush(data)
|
|
|
+const clickEvent = async (data) => {
|
|
|
+ // 情况1:点击的是设备节点 (type === 0)
|
|
|
+ if (data.type === 0) {
|
|
|
+ currentDevice.value = {
|
|
|
+ id: data.userData?.serialNumber,
|
|
|
+ name: data.name,
|
|
|
+ data: data
|
|
|
+ }
|
|
|
+ const deviceId = data.userData?.serialNumber; // 从树节点数据中提取设备ID
|
|
|
+ if (!deviceId) return;
|
|
|
+
|
|
|
+ loading.value = true;
|
|
|
+ try {
|
|
|
+ // 1. 获取该设备下的所有通道
|
|
|
+ const channels = await getDeviceChannels(deviceId);
|
|
|
+ if (!channels || channels.length === 0) {
|
|
|
+ console.warn('该设备下没有可用通道');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 2. 将通道智能分配给播放器
|
|
|
+ await assignChannelsToPlayers(channels);
|
|
|
+
|
|
|
+ } catch (error) {
|
|
|
+ console.error('处理设备通道时出错:', error);
|
|
|
+ } finally {
|
|
|
+ loading.value = false;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ // 情况2:点击的是具体的通道节点,保持原有逻辑不变
|
|
|
+ else if (data.userData?.channelSipId) {
|
|
|
+ sendDevicePush(data.userData);
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+const getDeviceChannels = async (deviceId) => {
|
|
|
+ try {
|
|
|
+ const response = await listSipDeviceChannel(deviceId);
|
|
|
+ console.log('获取设备通道列表成功:', response);
|
|
|
+ // 假设接口返回的是通道数组,且每个通道对象结构符合 { basicData: { channelSipId, ... }, ... }
|
|
|
+ return response || [];
|
|
|
+ } catch (error) {
|
|
|
+ console.error('获取设备通道列表失败:', error);
|
|
|
+ return [];
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+const assignChannelsToPlayers = async (channels) => {
|
|
|
+ const currentSplit = spilt.value
|
|
|
+ console.log(`分配 ${channels.length} 个通道到 ${currentSplit} 个播放窗口`)
|
|
|
+
|
|
|
+ // 决定从哪个播放器窗口开始分配:始终从第一个窗口(0)开始
|
|
|
+ // 这样保证每次分屏变化时都能从头开始重新分配
|
|
|
+ const startPlayerIndex = 0
|
|
|
+
|
|
|
+ // 决定从哪个通道开始取:总是从第一个通道开始
|
|
|
+ const startChannelIndex = 0
|
|
|
+
|
|
|
+ // 循环,为当前分屏布局中的每个位置分配通道
|
|
|
+ for (let screenPosition = 0; screenPosition < currentSplit; screenPosition++) {
|
|
|
+ const channelIndex = startChannelIndex + screenPosition
|
|
|
+ const targetPlayerIndex = startPlayerIndex + screenPosition
|
|
|
+
|
|
|
+ // 延迟执行,避免同时发起大量请求
|
|
|
+ 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 sendDevicePush = (itemData) => {
|
|
|
- save(itemData)
|
|
|
- let deviceId = itemData.deviceSipId
|
|
|
- let channelId = itemData.channelSipId
|
|
|
+const sendDevicePush = (itemData, targetIndex = null) => {
|
|
|
+ // 关键修改:允许从外部传入 targetIndex,默认使用当前激活的 playerIdx
|
|
|
+ const playIndex = targetIndex !== null ? targetIndex : playerIdx.value;
|
|
|
|
|
|
- let idxTmp = playerIdx.value
|
|
|
- loading.value = true
|
|
|
+ save(itemData, playIndex); // 保存数据也要使用正确的索引
|
|
|
|
|
|
- startPlay(deviceId, channelId)
|
|
|
- .then((response) => {
|
|
|
- let res = response
|
|
|
+ let deviceId = itemData.deviceSipId;
|
|
|
+ let channelId = itemData.channelSipId;
|
|
|
+ loading.value = true;
|
|
|
|
|
|
+ return startPlay(deviceId, channelId)
|
|
|
+ .then((response) => {
|
|
|
+ let res = response;
|
|
|
if (window.location.protocol === 'http:') {
|
|
|
- itemData.playUrl = res.ws_flv
|
|
|
-
|
|
|
+ itemData.playUrl = res.ws_flv;
|
|
|
} else {
|
|
|
- itemData.playUrl = res.wss_flv
|
|
|
+ itemData.playUrl = res.wss_flv;
|
|
|
}
|
|
|
- itemData.streamId = res.streamId
|
|
|
- setPlayUrl(itemData.playUrl, idxTmp)
|
|
|
+ itemData.streamId = res.streamId;
|
|
|
+ setPlayUrl(itemData.playUrl, playIndex); // 播放URL设置到指定位置
|
|
|
})
|
|
|
.finally(() => {
|
|
|
- loading.value = false
|
|
|
- })
|
|
|
-}
|
|
|
+ loading.value = false;
|
|
|
+ });
|
|
|
+};
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
|
|
|
// 设置播放URL
|
|
|
const setPlayUrl = (url, idx) => {
|
|
|
@@ -254,12 +373,12 @@ const shot = (e) => {
|
|
|
}
|
|
|
|
|
|
// 保存数据
|
|
|
-const save = (item) => {
|
|
|
- let dataStr = window.localStorage.getItem('playData') || '[]'
|
|
|
- let data = JSON.parse(dataStr)
|
|
|
- data[playerIdx.value] = item
|
|
|
- window.localStorage.setItem('playData', JSON.stringify(data))
|
|
|
-}
|
|
|
+const save = (item, index) => {
|
|
|
+ let dataStr = window.localStorage.getItem('playData') || '[]';
|
|
|
+ let data = JSON.parse(dataStr);
|
|
|
+ data[index] = item;
|
|
|
+ window.localStorage.setItem('playData', JSON.stringify(data));
|
|
|
+};
|
|
|
|
|
|
// 清除数据
|
|
|
const clear = (idx) => {
|