|
|
@ -2,7 +2,8 @@ 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; |
|
|
|
let connection_version = {}; // 保存每个 element_id 的版本号
|
|
|
|
let channel_list = null; |
|
|
|
const fourViewButton = document.getElementById('fourView'); |
|
|
|
const nineViewButton = document.getElementById('nineView'); |
|
|
|
|
|
|
@ -127,15 +128,19 @@ document.getElementById('nineView').addEventListener('click', function() { |
|
|
|
nineViewButton.classList.add('btn-primary'); |
|
|
|
}); |
|
|
|
|
|
|
|
function generateVideoNodes(count) { //在这里显示视频-初始化
|
|
|
|
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]; |
|
|
|
//flv使用
|
|
|
|
// const videoFrame = document.getElementById(`video-${key}`);
|
|
|
|
// const event = new Event('closeVideo');
|
|
|
|
// videoFrame.dispatchEvent(event);
|
|
|
|
|
|
|
|
//通用关闭
|
|
|
|
run_list[key] = false; |
|
|
|
video_list[key].close(); |
|
|
|
berror_state_list[key] = false; |
|
|
|
delete video_list[key]; |
|
|
|
} |
|
|
|
//切换窗口布局
|
|
|
|
const videoGrid = document.getElementById('videoGrid'); |
|
|
@ -150,16 +155,15 @@ function generateVideoNodes(count) { //在这里显示视频-初始化 |
|
|
|
<div class="video-buttons"> |
|
|
|
<button onclick="toggleFullScreen(${i})">🔲</button> |
|
|
|
<button onclick="closeVideo(${i})">❌</button> |
|
|
|
<!-- <button onclick="closeFLVStream(${i})">❌</button> --> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
<div class="video-area"><img id="video-${i}" alt="Video Stream" /></div> |
|
|
|
<!-- <div class="video-area"><video id="video-${i}" controls></video></div> --> |
|
|
|
<div class="video-area"><canvas id="video-${i}"></canvas></div> |
|
|
|
</div>`; |
|
|
|
} |
|
|
|
videoGrid.innerHTML = html; |
|
|
|
|
|
|
|
//开始还原视频,获取视频接口
|
|
|
|
// if(m_count != 0){
|
|
|
|
//获取视频接口
|
|
|
|
const url = `/api/viewlist?count=${count}`; |
|
|
|
fetch(url) |
|
|
|
.then(response => response.json()) |
|
|
@ -170,7 +174,6 @@ function generateVideoNodes(count) { //在这里显示视频-初始化 |
|
|
|
nlist = data.nlist; |
|
|
|
for(let i=0;i<clist.length;i++){ |
|
|
|
if(parseInt(elist[i]) < count){ |
|
|
|
console.log("切换窗口时进行连接",clist[i]) |
|
|
|
connectToStream(elist[i],clist[i],nlist[i]) |
|
|
|
//startFLVStream(elist[i],clist[i],nlist[i]);
|
|
|
|
} |
|
|
@ -250,137 +253,160 @@ function drop(event) { |
|
|
|
//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'); |
|
|
|
}; |
|
|
|
function connect(channel_id,element_id,imgcanvas,ctx,offscreenCtx,offscreenCanvas,streamUrl) { |
|
|
|
//判断是否有重复socket,进行删除
|
|
|
|
if(element_id in video_list) { |
|
|
|
run_list[element_id] = false; |
|
|
|
video_list[element_id].close(); |
|
|
|
delete video_list[element_id]; |
|
|
|
console.log("有历史数据未删干净!!---",element_id) //要不要等待待定
|
|
|
|
} |
|
|
|
// 每次连接时增加版本号
|
|
|
|
const current_version = (connection_version[element_id] || 0) + 1; |
|
|
|
connection_version[element_id] = current_version; |
|
|
|
const socket = new WebSocket(streamUrl); |
|
|
|
socket.binaryType = 'arraybuffer'; // 设置为二进制数据接收
|
|
|
|
socket.customData = { channel_id: channel_id, element_id: element_id, |
|
|
|
imgcanvas:imgcanvas,ctx:ctx,offscreenCtx:offscreenCtx,offscreenCanvas:offscreenCanvas, |
|
|
|
version_id: current_version,streamUrl:streamUrl}; // 自定义属性 -- JS异步事件只能等到当前同步任务(代码块)完成之后才有可能被触发。
|
|
|
|
//新的连接
|
|
|
|
video_list[element_id] = socket; |
|
|
|
run_list[element_id] = true; |
|
|
|
berror_state_list[element_id] = false; |
|
|
|
imgcanvas.style.display = 'block'; |
|
|
|
|
|
|
|
// 处理连接打开事件
|
|
|
|
socket.onopen = function(){ |
|
|
|
console.log('WebSocket connection established--',socket.customData.channel_id); |
|
|
|
}; |
|
|
|
|
|
|
|
socket.onmessage = function(event) { |
|
|
|
const reader = new FileReader(); |
|
|
|
reader.readAsArrayBuffer(event.data); |
|
|
|
socket.onmessage = function(event) { |
|
|
|
let el_id = socket.customData.element_id |
|
|
|
let cl_id = socket.customData.channel_id |
|
|
|
let imgcanvas = socket.customData.imgcanvas |
|
|
|
let ctx = socket.customData.ctx |
|
|
|
let offctx = socket.customData.offscreenCtx |
|
|
|
let offscreenCanvas = socket.customData.offscreenCanvas |
|
|
|
|
|
|
|
// 转换为字符串来检查前缀
|
|
|
|
let message = new TextDecoder().decode(event.data.slice(0, 6)); // 取前6个字节
|
|
|
|
if (message.startsWith('frame:')){ |
|
|
|
//如有错误信息显示 -- 清除错误信息
|
|
|
|
if(berror_state_list[el_id]){ |
|
|
|
removeErrorMessage(imgcanvas); |
|
|
|
berror_state_list[el_id] = false; |
|
|
|
} |
|
|
|
// 接收到 JPG 图像数据,转换为 Blob
|
|
|
|
let img = new Image(); |
|
|
|
let blob = new Blob([event.data.slice(6)], { type: 'image/jpeg' }); |
|
|
|
// 将 Blob 转换为可用的图像 URL
|
|
|
|
img.src = URL.createObjectURL(blob); |
|
|
|
//定义图片加载函数
|
|
|
|
img.onload = function() { |
|
|
|
imgcanvas.width = offscreenCanvas.width = img.width; |
|
|
|
imgcanvas.height = offscreenCanvas.height = img.height; |
|
|
|
|
|
|
|
// 在 OffscreenCanvas 上绘制
|
|
|
|
offctx.clearRect(0, 0, imgcanvas.width, imgcanvas.height); |
|
|
|
offctx.drawImage(img, 0, 0, imgcanvas.width, imgcanvas.height); |
|
|
|
// 将 OffscreenCanvas 的内容复制到主 canvas
|
|
|
|
ctx.drawImage(offscreenCanvas, 0, 0); |
|
|
|
|
|
|
|
// 用完就释放
|
|
|
|
URL.revokeObjectURL(img.src); |
|
|
|
// blob = null
|
|
|
|
// img = null
|
|
|
|
// message = null
|
|
|
|
// event.data = null
|
|
|
|
// event = null
|
|
|
|
}; |
|
|
|
}else if(message.startsWith('error:')){ |
|
|
|
const errorText = new TextDecoder().decode(event.data.slice(6)); // 截掉前缀 'error:'
|
|
|
|
//目前只处理一个错误信息,暂不区分
|
|
|
|
displayErrorMessage(imgcanvas, "该视频源未获取到画面,请检查后刷新重试,默认两分钟后重连"); |
|
|
|
berror_state_list[el_id] = true; |
|
|
|
} |
|
|
|
}; |
|
|
|
|
|
|
|
reader.onload = () => { |
|
|
|
const arrayBuffer = reader.result; |
|
|
|
const decoder = new TextDecoder("utf-8"); |
|
|
|
const decodedData = decoder.decode(arrayBuffer); |
|
|
|
socket.onclose = function() { |
|
|
|
let el_id = socket.customData.element_id; |
|
|
|
let cl_id = socket.customData.channel_id; |
|
|
|
if(run_list[el_id] && socket.customData.version_id === connection_version[el_id]){ |
|
|
|
console.log(`尝试重新连接... Channel ID: ${cl_id}`); |
|
|
|
setTimeout(() => connect(cl_id, el_id, socket.customData.imgcanvas, |
|
|
|
socket.customData.ctx,socket.customData.streamUrl), 1000*10); // 尝试在10秒后重新连接
|
|
|
|
} |
|
|
|
}; |
|
|
|
|
|
|
|
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); |
|
|
|
} |
|
|
|
}; |
|
|
|
socket.onerror = function() { |
|
|
|
console.log(`WebSocket错误,Channel ID: ${socket.customData.channel_id}`); |
|
|
|
socket.close(1000, "Normal Closure"); |
|
|
|
}; |
|
|
|
} |
|
|
|
|
|
|
|
//图片显示方案二
|
|
|
|
// if (imgElement.src) {
|
|
|
|
// URL.revokeObjectURL(imgElement.src);
|
|
|
|
// }
|
|
|
|
// imgElement.src = URL.createObjectURL(event.data);
|
|
|
|
|
|
|
|
}; |
|
|
|
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 imgcanvas = document.getElementById(`video-${element_id}`); |
|
|
|
const ctx = imgcanvas.getContext('2d') |
|
|
|
// 创建 OffscreenCanvas
|
|
|
|
const offscreenCanvas = new OffscreenCanvas(imgcanvas.width, imgcanvas.height); |
|
|
|
const offscreenCtx = offscreenCanvas.getContext('2d'); |
|
|
|
|
|
|
|
socket.onclose = function() { |
|
|
|
if(run_list[element_id]){ |
|
|
|
console.log(`尝试重新连接... Channel ID: ${channel_id}`); |
|
|
|
setTimeout(connect, 1000*10); // 尝试在10秒后重新连接
|
|
|
|
} |
|
|
|
}; |
|
|
|
const streamUrl = `ws://${window.location.host}/api/ws/video_feed/${channel_id}`; |
|
|
|
|
|
|
|
socket.onerror = function() { |
|
|
|
console.log(`WebSocket错误,Channel ID: ${channel_id}`); |
|
|
|
socket.close(1000, "Normal Closure"); |
|
|
|
}; |
|
|
|
}; |
|
|
|
connect(); |
|
|
|
//创建websocket连接,并接收和显示图片
|
|
|
|
connect(channel_id,element_id,imgcanvas,ctx,offscreenCtx,offscreenCanvas,streamUrl); //执行websocket连接 -- 异步的应该会直接返回
|
|
|
|
} |
|
|
|
|
|
|
|
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 显示消息
|
|
|
|
if(id in video_list) { |
|
|
|
const imgcanvas = document.getElementById(`video-${id}`); |
|
|
|
const titleElement = document.querySelector(`[data-frame-id="${id}"] .video-title`); |
|
|
|
//断socket
|
|
|
|
run_list[id] = false; |
|
|
|
video_list[id].close(); |
|
|
|
delete video_list[id]; |
|
|
|
//清空控件状态
|
|
|
|
imgcanvas.style.display = 'none'; // 停止播放时隐藏元素
|
|
|
|
titleElement.textContent = `Video Stream ${id+1}`; |
|
|
|
removeErrorMessage(imgcanvas); |
|
|
|
berror_state_list[id] = false; |
|
|
|
//删记录
|
|
|
|
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; |
|
|
|
} |
|
|
|
}) |
|
|
|
.catch((error) => { |
|
|
|
showModal(`Error: ${error.message}`); // 使用 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 显示错误信息
|
|
|
|
}); |
|
|
|
} |
|
|
|
else{ |
|
|
|
showModal('当前视频窗口未播放视频。'); |
|
|
|
return; |
|
|
|
}); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
function startFLVStream(element_id,channel_id,channel_name) { |
|
|
|