let video_list = {}; //element_id -- socket let run_list = {}; //element_id -- runtag let berror_state_list = {}; //element_id -- 错误信息显示 let m_count = 0; 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){ const videoFrame = document.getElementById(`video-${key}`); const event = new Event('closeVideo'); videoFrame.dispatchEvent(event); delete video_list[key]; berror_state_list[key] = false; } }); //页面加载时执行 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 = ''; html += ''; } area_name = `${channel.area_name}`; html += `
  • ${area_name}`; html += ''; html += '
  • '; } html += ''; const treeView = document.getElementById('treeView'); treeView.innerHTML = html generateVideoNodes(4); } catch (error) { console.error('Failed to fetch data:', error); } }); document.addEventListener('click', function() { console.log("第一次页面点击,开始显示视频--已注释",m_count); // if(m_count != 0){ // count = m_count // //获取视频接口 // 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 { // console.error('Error:', error); // }); // } }, { once: true }); //视频窗口 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){ const videoFrame = document.getElementById(`video-${key}`); const event = new Event('closeVideo'); videoFrame.dispatchEvent(event); delete video_list[key]; berror_state_list[key] = false; } //切换窗口布局 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 += `
    Video Stream ${i+1}
    Video Stream
    `; } videoGrid.innerHTML = html; // if(m_count != 0){ //获取视频接口 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 { console.error('Error:', error); }); // } //m_count = count } 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 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{ //获取视频流 console.log("drop触发") connectToStream(frameId,nodeId,nodeName); //startFLVStream(frameId,nodeId,nodeName); #基于FLV的开发程度:后端直接用RTSP流转发是有画面的,但CPU占用太高,用不了。2024-8-30 } }) .catch((error) => { showModal(`Error: ${error.message}`); // 使用 Modal 显示错误信息 return; }); //console.log('retrun 只是把fetch结束,这里的代码还是会执行'); } function connectToStream(element_id,channel_id,channel_name) { console.log("开始连接视频",element_id,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); console.log("socket建立----",element_id); //全局变量需要维护好 if(element_id in video_list) { video_list[element_id].close(); }else{ berror_state_list[element_id] = false; } video_list[element_id] = socket; run_list[element_id] = true; imgElement.style.display = 'block'; // 处理连接打开事件 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, "该视频源未获取到画面,请检查后刷新重试,默认两分钟后重连"); berror_state_list[element_id] = true; //socket.close(1000, "Normal Closure"); // 停止连接 } else if(decodedData === "client_error"){ //client_error run_list[element_id] = false; displayErrorMessage(imgElement, "该通道节点数据存在问题,请重启或联系技术支持!"); socket.close(1000, "Normal Closure"); // 停止连接 berror_state_list[element_id] = true; } else { if(berror_state_list[element_id]){ removeErrorMessage(imgElement); berror_state_list[element_id] = false; //console.log("移除错误信息!"); } // 释放旧的对象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(1000, "Normal Closure"); }; }; connect(); } 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(); delete video_list[id]; videoFrame.src = ''; // 清空画面 videoFrame.style.display = 'none'; // 停止播放时隐藏 img 元素 titleElement.textContent = `Video Stream ${id+1}`; removeErrorMessage(videoFrame); berror_state_list[id] = false; } }) .catch((error) => { showModal(`Error: ${error.message}`); // 使用 Modal 显示错误信息 return; }); } function startFLVStream(element_id,channel_id,channel_name) { // 设置视频区域的标题 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 videoElement = document.getElementById(`video-${element_id}`); let reconnectAttempts = 0; const maxReconnectAttempts = 3; const flvUrl = `ws://${window.location.host}/api/ws/video_feed/${channel_id}`; function initFLVPlayer() { if (flvjs.isSupported()) { //要避免重复播放 if(element_id in video_list) { closeFLVStream(element_id) }else{ video_list[element_id] = element_id; berror_state_list[element_id] = true; } flvPlayer = flvjs.createPlayer({ type: 'flv', url: flvUrl, }); flvPlayer.attachMediaElement(videoElement); flvPlayer.load(); flvPlayer.play(); // 设定超时时间,例如10秒 timeoutId = setTimeout(() => { console.error('No video data received. Closing connection.'); flvPlayer.destroy(); // 停止视频 // 显示错误信息或提示 displayErrorMessage(videoElement, "该视频源获取画面超时,请检查后刷新重试,默认两分钟后重连"); berror_state_list[element_id] = true; }, 130000); // 130秒 // 错误处理 flvPlayer.on(flvjs.Events.ERROR, (errorType, errorDetail) => { console.error(`FLV Error: ${errorType} - ${errorDetail}`); if (reconnectAttempts < maxReconnectAttempts) { reconnectAttempts++; console.log("开始重连") setTimeout(initFLVPlayer, 30000); // 尝试重连 } else { displayErrorMessage(videoElement, "重连超时,请检查后重试,可联系技术支持!"); berror_state_list[element_id] = true; } }); // 监听播放事件,如果播放成功则清除超时计时器 flvPlayer.on(flvjs.Events.STATISTICS_INFO, () => { clearTimeout(timeoutId); timeoutId = null; removeErrorMessage(videoElement); berror_state_list[element_id] = false; }); // 关闭视频流时销毁播放器 videoElement.addEventListener('closeVideo', () => { if(flvPlayer){ flvPlayer.destroy(); videoElement.removeEventListener('closeVideo', onCloseVideo); flvPlayer.off(flvjs.Events.ERROR); flvPlayer.off(flvjs.Events.STATISTICS_INFO); delete flvPlayer } }); } else { console.error('FLV is not supported in this browser.'); } } initFLVPlayer(); } // 主动关闭视频的函数 function closeFLVStream(id) { const titleElement = document.querySelector(`[data-frame-id="${id}"] .video-title`); if (titleElement.textContent === `Video Stream ${Number(id)+1}`) { showModal('当前视频窗口未播放视频。'); return; }; //发送视频链接接口 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.getElementById(`video-${element_id}`); const event = new Event('closeVideo'); videoFrame.dispatchEvent(event); //videoFrame.style.display = 'none'; // 停止播放时隐藏 img 元素 titleElement.textContent = `Video Stream ${id+1}`; removeErrorMessage(videoFrame); berror_state_list[key] = false; delete video_list[id]; } }) .catch((error) => { showModal(`Error: ${error.message}`); // 使用 Modal 显示错误信息 return; }); } 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); } }