|
|
|
let video_list = {}; //element_id -- socket
|
|
|
|
let run_list = {}; //element_id -- runtag(替换berror)
|
|
|
|
var channel_list = null;
|
|
|
|
const fourViewButton = document.getElementById('fourView');
|
|
|
|
const nineViewButton = document.getElementById('nineView');
|
|
|
|
|
|
|
|
//页面即将卸载时执行
|
|
|
|
window.addEventListener('beforeunload', function (event) {
|
|
|
|
// 关闭所有 WebSocket 连接或执行其他清理操作
|
|
|
|
for(let key in video_list){
|
|
|
|
delete run_list[key];
|
|
|
|
video_list[key].close();
|
|
|
|
delete video_list[key];
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
//页面加载时执行
|
|
|
|
document.addEventListener('DOMContentLoaded', async function() {
|
|
|
|
console.log('DOM fully loaded and parsed');
|
|
|
|
// 发送请求获取额外数据 --- 这个接口功能有点大了---暂时只是更新通道树2024-7-29
|
|
|
|
try {
|
|
|
|
let response = await fetch('/api/channel/list');
|
|
|
|
if (!response.ok) {
|
|
|
|
throw new Error('Network response was not ok');
|
|
|
|
}
|
|
|
|
channel_list = await response.json();
|
|
|
|
// 遍历输出每个元素的信息
|
|
|
|
let area_name = ""
|
|
|
|
let html = '<ul class="list-group">';
|
|
|
|
channel_list.forEach(channel => {
|
|
|
|
// console.log(`Area Name: ${channel.area_name}`);
|
|
|
|
// console.log(`ID: ${channel.ID}`);
|
|
|
|
// console.log(`Channel Name: ${channel.channel_name}`);
|
|
|
|
// console.log(`URL: ${channel.url}`);
|
|
|
|
// console.log(`Type: ${channel.type}`);
|
|
|
|
// console.log(`Status: ${channel.status}`);
|
|
|
|
// console.log(`Element ID: ${channel.element_id}`);
|
|
|
|
if(area_name !== `${channel.area_name}`){
|
|
|
|
if(area_name !== ""){
|
|
|
|
html += '</ul>';
|
|
|
|
html += '</li>';
|
|
|
|
}
|
|
|
|
area_name = `${channel.area_name}`;
|
|
|
|
html += `<li class="list-group-item"><strong>${area_name}</strong>`;
|
|
|
|
html += '<ul class="list-group">';
|
|
|
|
}
|
|
|
|
//html += `<li class="list-group-item">${channel.channel_name}</li>`;
|
|
|
|
html += `<li class="list-group-item" draggable="true" ondragstart="drag(event)"
|
|
|
|
data-node-id="${channel.ID}" data-node-name="${area_name}--${channel.channel_name}">
|
|
|
|
<svg class="bi" width="16" height="16"><use xlink:href="#view"/></svg>
|
|
|
|
${channel.channel_name}
|
|
|
|
</li>`;
|
|
|
|
});
|
|
|
|
if(area_name !== ""){
|
|
|
|
html += '</ul>';
|
|
|
|
html += '</li>';
|
|
|
|
}
|
|
|
|
html += '</ul>';
|
|
|
|
const treeView = document.getElementById('treeView');
|
|
|
|
treeView.innerHTML = html
|
|
|
|
generateVideoNodes(4);
|
|
|
|
} catch (error) {
|
|
|
|
console.error('Failed to fetch data:', error);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//视频窗口
|
|
|
|
document.getElementById('fourView').addEventListener('click', function() {
|
|
|
|
if (fourViewButton.classList.contains('btn-primary')) {
|
|
|
|
return; // 如果按钮已经是选中状态,直接返回
|
|
|
|
}
|
|
|
|
const videoGrid = document.getElementById('videoGrid');
|
|
|
|
videoGrid.classList.remove('nine');
|
|
|
|
videoGrid.classList.add('four');
|
|
|
|
generateVideoNodes(4);
|
|
|
|
//更新按钮点击状态
|
|
|
|
fourViewButton.classList.remove('btn-secondary');
|
|
|
|
fourViewButton.classList.add('btn-primary');
|
|
|
|
nineViewButton.classList.remove('btn-primary');
|
|
|
|
nineViewButton.classList.add('btn-secondary');
|
|
|
|
});
|
|
|
|
|
|
|
|
document.getElementById('nineView').addEventListener('click', function() {
|
|
|
|
if (nineViewButton.classList.contains('btn-primary')) {
|
|
|
|
return; // 如果按钮已经是选中状态,直接返回
|
|
|
|
}
|
|
|
|
const videoGrid = document.getElementById('videoGrid');
|
|
|
|
videoGrid.classList.remove('four');
|
|
|
|
videoGrid.classList.add('nine');
|
|
|
|
generateVideoNodes(9);
|
|
|
|
//更新按钮点击状态
|
|
|
|
fourViewButton.classList.remove('btn-primary');
|
|
|
|
fourViewButton.classList.add('btn-secondary');
|
|
|
|
nineViewButton.classList.remove('btn-secondary');
|
|
|
|
nineViewButton.classList.add('btn-primary');
|
|
|
|
});
|
|
|
|
|
|
|
|
function generateVideoNodes(count) { //在这里显示视频-初始化
|
|
|
|
//结束在播放的socket
|
|
|
|
for(let key in video_list){
|
|
|
|
delete run_list[key];
|
|
|
|
video_list[key].close();
|
|
|
|
delete video_list[key];
|
|
|
|
}
|
|
|
|
//切换窗口布局
|
|
|
|
const videoGrid = document.getElementById('videoGrid');
|
|
|
|
let html = '';
|
|
|
|
for (let i = 0; i < count; i++) {
|
|
|
|
let frameWidth = count === 4 ? 'calc(50% - 10px)' : 'calc(33.33% - 10px)';
|
|
|
|
html += `
|
|
|
|
<div class="video-frame" data-frame-id="${i}" style="width: ${frameWidth};"
|
|
|
|
ondrop="drop(event)" ondragover="allowDrop(event)">
|
|
|
|
<div class="video-header">
|
|
|
|
<div class="video-title">Video Stream ${i+1}</div>
|
|
|
|
<div class="video-buttons">
|
|
|
|
<button onclick="toggleFullScreen(${i})">🔲</button>
|
|
|
|
<button onclick="closeVideo(${i})">❌</button>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<div class="video-area"><img id="video-${i}" alt="Video Stream" /></div>
|
|
|
|
</div>`;
|
|
|
|
}
|
|
|
|
videoGrid.innerHTML = html;
|
|
|
|
//获取视频接口
|
|
|
|
const url = `/api/viewlist?count=${count}`;
|
|
|
|
fetch(url)
|
|
|
|
.then(response => response.json())
|
|
|
|
.then(data => {
|
|
|
|
console.log('Success:', data);
|
|
|
|
clist = data.clist;
|
|
|
|
elist = data.elist;
|
|
|
|
nlist = data.nlist;
|
|
|
|
for(let i=0;i<clist.length;i++){
|
|
|
|
if(parseInt(elist[i]) < count){
|
|
|
|
connectToStream(elist[i],clist[i],nlist[i])
|
|
|
|
}
|
|
|
|
}
|
|
|
|
})
|
|
|
|
.catch(error => {
|
|
|
|
console.error('Error:', error);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
function toggleFullScreen(id) {
|
|
|
|
console.log('toggleFullScreen');
|
|
|
|
const videoFrame = document.querySelector(`[data-frame-id="${id}"]`);
|
|
|
|
if (!document.fullscreenElement) {
|
|
|
|
videoFrame.requestFullscreen().catch(err => {
|
|
|
|
alert(`Error attempting to enable full-screen mode: ${err.message} (${err.name})`);
|
|
|
|
});
|
|
|
|
} else {
|
|
|
|
document.exitFullscreen();
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
function closeVideo(id) {
|
|
|
|
const titleElement = document.querySelector(`[data-frame-id="${id}"] .video-title`);
|
|
|
|
if (titleElement.textContent === `Video Stream ${Number(id)+1}`) {
|
|
|
|
showModal('当前视频窗口未播放视频。');
|
|
|
|
return;
|
|
|
|
};
|
|
|
|
console.log('closeVideo');
|
|
|
|
//发送视频链接接口
|
|
|
|
const url = '/api/close_stream';
|
|
|
|
const data = {"element_id":id};
|
|
|
|
// 发送 POST 请求
|
|
|
|
fetch(url, {
|
|
|
|
method: 'POST', // 指定请求方法为 POST
|
|
|
|
headers: {
|
|
|
|
'Content-Type': 'application/json' // 设置请求头,告诉服务器请求体的数据类型为 JSON
|
|
|
|
},
|
|
|
|
body: JSON.stringify(data) // 将 JavaScript 对象转换为 JSON 字符串
|
|
|
|
})
|
|
|
|
.then(response => response.json()) // 将响应解析为 JSON
|
|
|
|
.then(data => {
|
|
|
|
console.log('Success:', data);
|
|
|
|
const istatus = data.status;
|
|
|
|
if(istatus === 0){
|
|
|
|
showModal(data.msg); // 使用 Modal 显示消息
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
else{
|
|
|
|
const videoFrame = document.querySelector(`[data-frame-id="${id}"] .video-area img`);
|
|
|
|
const titleElement = document.querySelector(`[data-frame-id="${id}"] .video-title`);
|
|
|
|
run_list[id] = false;
|
|
|
|
video_list[id].close();
|
|
|
|
|
|
|
|
videoFrame.src = ''; // 清空画面
|
|
|
|
videoFrame.style.display = 'none'; // 停止播放时隐藏 img 元素
|
|
|
|
|
|
|
|
titleElement.textContent = `Video Stream ${id+1}`;
|
|
|
|
removeErrorMessage(videoFrame)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
.catch((error) => {
|
|
|
|
showModal(`Error: ${error.message}`); // 使用 Modal 显示错误信息
|
|
|
|
return;
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
function allowDrop(event) {
|
|
|
|
event.preventDefault();
|
|
|
|
}
|
|
|
|
|
|
|
|
function drag(event) {
|
|
|
|
event.dataTransfer.setData("text", event.target.dataset.nodeId);
|
|
|
|
event.dataTransfer.setData("name", event.target.dataset.nodeName);
|
|
|
|
}
|
|
|
|
|
|
|
|
function drop(event) {
|
|
|
|
event.preventDefault();
|
|
|
|
const nodeId = event.dataTransfer.getData("text");
|
|
|
|
const nodeName = event.dataTransfer.getData("name");
|
|
|
|
const frameId = event.currentTarget.dataset.frameId;
|
|
|
|
|
|
|
|
//需要判断下当前窗口是否已经在播放视频
|
|
|
|
const imgElement = document.getElementById(`video-${frameId}`);
|
|
|
|
const titleElement = document.querySelector(`[data-frame-id="${frameId}"] .video-title`);
|
|
|
|
|
|
|
|
if (titleElement.textContent !== `Video Stream ${Number(frameId)+1}`) {
|
|
|
|
showModal('请先关闭当前窗口视频,然后再播放新的视频。');
|
|
|
|
return;
|
|
|
|
};
|
|
|
|
//发送视频链接接口
|
|
|
|
const url = '/api/start_stream';
|
|
|
|
const data = {"channel_id":nodeId,"element_id":frameId};
|
|
|
|
// 发送 POST 请求
|
|
|
|
fetch(url, {
|
|
|
|
method: 'POST', // 指定请求方法为 POST
|
|
|
|
headers: {
|
|
|
|
'Content-Type': 'application/json' // 设置请求头,告诉服务器请求体的数据类型为 JSON
|
|
|
|
},
|
|
|
|
body: JSON.stringify(data) // 将 JavaScript 对象转换为 JSON 字符串
|
|
|
|
})
|
|
|
|
.then(response => response.json()) // 将响应解析为 JSON
|
|
|
|
.then(data => {
|
|
|
|
const istatus = data.status;
|
|
|
|
if(istatus === 0){
|
|
|
|
showModal(data.msg); // 使用 Modal 显示消息
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
else{
|
|
|
|
//获取视频流
|
|
|
|
connectToStream(frameId,nodeId,nodeName);
|
|
|
|
}
|
|
|
|
})
|
|
|
|
.catch((error) => {
|
|
|
|
showModal(`Error: ${error.message}`); // 使用 Modal 显示错误信息
|
|
|
|
return;
|
|
|
|
});
|
|
|
|
//console.log('retrun 只是把fetch结束,这里的代码还是会执行');
|
|
|
|
}
|
|
|
|
|
|
|
|
function connectToStream(element_id,channel_id,channel_name) {
|
|
|
|
console.log("开始连接视频",channel_id);
|
|
|
|
// 设置视频区域的标题
|
|
|
|
const titleElement = document.querySelector(`[data-frame-id="${element_id}"] .video-title`);
|
|
|
|
titleElement.textContent = channel_name;
|
|
|
|
//获取视频
|
|
|
|
const imgElement = document.getElementById(`video-${element_id}`);
|
|
|
|
imgElement.alt = `Stream ${channel_name}`;
|
|
|
|
const streamUrl = `ws://${window.location.host}/api/ws/video_feed/${channel_id}`;
|
|
|
|
let berror = false;
|
|
|
|
|
|
|
|
function connect() {
|
|
|
|
const socket = new WebSocket(streamUrl);
|
|
|
|
video_list[element_id] = socket;
|
|
|
|
run_list[element_id] = true;
|
|
|
|
imgElement.style.display = 'block';
|
|
|
|
berror_state = false;
|
|
|
|
// 处理连接打开事件
|
|
|
|
socket.onopen = () => {
|
|
|
|
console.log('WebSocket connection established');
|
|
|
|
};
|
|
|
|
|
|
|
|
socket.onmessage = function(event) {
|
|
|
|
const reader = new FileReader();
|
|
|
|
reader.readAsArrayBuffer(event.data);
|
|
|
|
|
|
|
|
reader.onload = () => {
|
|
|
|
const arrayBuffer = reader.result;
|
|
|
|
const decoder = new TextDecoder("utf-8");
|
|
|
|
const decodedData = decoder.decode(arrayBuffer);
|
|
|
|
|
|
|
|
if (decodedData === "video_error") { //video_error
|
|
|
|
displayErrorMessage(imgElement, "该视频源未获取到画面,请检查,默认每隔2分钟将重新链接视频源。");
|
|
|
|
berror_state = true;
|
|
|
|
} else if(decodedData === "client_error"){ //client_error
|
|
|
|
run_list[element_id] = false;
|
|
|
|
displayErrorMessage(imgElement, "该通道节点数据存在问题,请重启或联系技术支持!");
|
|
|
|
socket.close(); // 停止连接
|
|
|
|
berror_state = true;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
if(berror_state){
|
|
|
|
removeErrorMessage(imgElement);
|
|
|
|
berror_state = false;
|
|
|
|
}
|
|
|
|
// 释放旧的对象URL
|
|
|
|
if (imgElement.src) {
|
|
|
|
URL.revokeObjectURL(imgElement.src);
|
|
|
|
}
|
|
|
|
//blob = new Blob([arrayBuffer], { type: 'image/jpeg' });
|
|
|
|
imgElement.src = URL.createObjectURL(event.data);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
//图片显示方案二
|
|
|
|
// if (imgElement.src) {
|
|
|
|
// URL.revokeObjectURL(imgElement.src);
|
|
|
|
// }
|
|
|
|
// imgElement.src = URL.createObjectURL(event.data);
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
socket.onclose = function() {
|
|
|
|
if(run_list[element_id]){
|
|
|
|
console.log(`尝试重新连接... Channel ID: ${channel_id}`);
|
|
|
|
setTimeout(connect, 1000*10); // 尝试在10秒后重新连接
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
socket.onerror = function() {
|
|
|
|
console.log(`WebSocket错误,Channel ID: ${channel_id}`);
|
|
|
|
socket.close();
|
|
|
|
};
|
|
|
|
};
|
|
|
|
connect();
|
|
|
|
}
|
|
|
|
|
|
|
|
function displayErrorMessage(imgElement, message) {
|
|
|
|
removeErrorMessage(imgElement)
|
|
|
|
imgElement.style.display = 'none'; // 隐藏图片
|
|
|
|
const errorElement = document.createElement('div');
|
|
|
|
errorElement.textContent = message;
|
|
|
|
errorElement.classList.add('error-message');
|
|
|
|
imgElement.parentNode.appendChild(errorElement);
|
|
|
|
}
|
|
|
|
|
|
|
|
function removeErrorMessage(imgElement) {
|
|
|
|
const errorElement = imgElement.parentNode.querySelector('.error-message');
|
|
|
|
if (errorElement) {
|
|
|
|
imgElement.parentNode.removeChild(errorElement);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|