diff --git a/server/src/Peripherals/HdmiInClient.cs b/server/src/Peripherals/HdmiInClient.cs index 409a181..3742f81 100644 --- a/server/src/Peripherals/HdmiInClient.cs +++ b/server/src/Peripherals/HdmiInClient.cs @@ -7,8 +7,16 @@ namespace Peripherals.HdmiInClient; static class HdmiInAddr { public const UInt32 BASE = 0xA000_0000; - public const UInt32 HdmiIn_CTRL = BASE + 0x0; //[0]: rstn, 0 is reset. - public const UInt32 HdmiIn_READFIFO = BASE + 0x1; + + public const UInt32 CAPTURE_RD_CTRL = BASE + 0x0; + + public const UInt32 START_WR_ADDR0 = BASE + 0x2; + public const UInt32 END_WR_ADDR0 = BASE + 0x3; + + public const UInt32 HDMI_NOT_READY = BASE + 0x8; + public const UInt32 HDMI_HEIGHT_WIDTH = BASE + 0x9; + + public const UInt32 ADDR_HDMI_WD_START = 0x0400_0000; } public class HdmiIn @@ -21,10 +29,9 @@ public class HdmiIn readonly string address; private IPEndPoint ep; - // 动态分辨率参数 - private UInt16 _currentWidth = 960; - private UInt16 _currentHeight = 540; - private UInt32 _currentFrameLength = 960 * 540 * 2 / 4; // RGB565格式,2字节/像素,按4字节对齐 + public int Width { get; private set; } + public int Height { get; private set; } + public int FrameLength => Width * Height * 3 / 4; /// /// 初始化HDMI输入客户端 @@ -44,9 +51,54 @@ public class HdmiIn this.timeout = timeout; } - public async ValueTask> EnableTrans(bool isEnable) + public async ValueTask> Init(bool enable = true) { - var ret = await UDPClientPool.WriteAddr(this.ep, this.taskID, HdmiInAddr.HdmiIn_CTRL, (isEnable ? 0x00000001u : 0x00000000u)); + { + var ret = await CheckHdmiIsReady(); + if (!ret.IsSuccessful) + { + logger.Error($"Failed to check HDMI ready: {ret.Error}"); + return new(ret.Error); + } + if (!ret.Value) + { + logger.Error("HDMI not ready"); + return new(false); + } + } + + int width = -1, height = -1; + { + var ret = await GetHdmiResolution(); + if (!ret.IsSuccessful) + { + logger.Error($"Failed to get HDMI resolution: {ret.Error}"); + return new(ret.Error); + } + (width, height) = ret.Value; + } + + { + var ret = await ConnectJpeg2Hdmi(width, height); + if (!ret.IsSuccessful) + { + logger.Error($"Failed to connect JPEG to HDMI: {ret.Error}"); + return new(ret.Error); + } + if (!ret.Value) + { + logger.Error("Failed to connect JPEG to HDMI"); + return false; + } + } + + if (enable) return await SetTransEnable(true); + else return true; + } + + public async ValueTask> SetTransEnable(bool isEnable) + { + var ret = await UDPClientPool.WriteAddr(this.ep, this.taskID, HdmiInAddr.CAPTURE_RD_CTRL, (isEnable ? 0x00000001u : 0x00000000u)); if (!ret.IsSuccessful) { logger.Error($"Failed to write HdmiIn_CTRL to HdmiIn at {this.address}:{this.port}, error: {ret.Error}"); @@ -75,8 +127,8 @@ public class HdmiIn var result = await UDPClientPool.ReadAddr4BytesAsync( this.ep, this.taskID, // taskID - HdmiInAddr.HdmiIn_READFIFO, - (int)_currentFrameLength, // 使用当前分辨率的动态大小 + HdmiInAddr.ADDR_HDMI_WD_START, + FrameLength, // 使用当前分辨率的动态大小 BurstType.FixedBurst, this.timeout); @@ -99,7 +151,7 @@ public class HdmiIn return result.Value; } - public async ValueTask<(byte[] header, byte[] data, byte[] footer)?> GetMJpegFrame() + public async ValueTask> GetMJpegFrame() { // 从HDMI读取RGB24数据 var readStartTime = DateTime.UtcNow; @@ -110,13 +162,13 @@ public class HdmiIn if (!frameResult.IsSuccessful || frameResult.Value == null) { logger.Warn("HDMI帧读取失败或为空"); - return null; + return Optional<(byte[] header, byte[] data, byte[] footer)>.None; } var rgb24Data = frameResult.Value; // 验证数据长度是否正确 (RGB24为每像素2字节) - var expectedLength = _currentWidth * _currentHeight * 2; + var expectedLength = Width * Height * 2; if (rgb24Data.Length != expectedLength) { logger.Warn("HDMI数据长度不匹配,期望: {Expected}, 实际: {Actual}", @@ -125,40 +177,105 @@ public class HdmiIn // 将RGB24转换为JPEG(参考Camera版本的处理) var jpegStartTime = DateTime.UtcNow; - var jpegResult = Common.Image.ConvertRGB24ToJpeg(rgb24Data, _currentWidth, _currentHeight, 80); + var jpegResult = Common.Image.ConvertRGB24ToJpeg(rgb24Data, Width, Height, 80); var jpegEndTime = DateTime.UtcNow; var jpegTime = (jpegEndTime - jpegStartTime).TotalMilliseconds; if (!jpegResult.IsSuccessful) { logger.Error("HDMI RGB24转JPEG失败: {Error}", jpegResult.Error); - return null; + return Optional<(byte[] header, byte[] data, byte[] footer)>.None; } var jpegData = jpegResult.Value; - // 发送MJPEG帧(使用Camera版本的格式) var mjpegFrameHeader = Common.Image.CreateMjpegFrameHeader(jpegData.Length); var mjpegFrameFooter = Common.Image.CreateMjpegFrameFooter(); return (mjpegFrameHeader, jpegData, mjpegFrameFooter); } - /// - /// 获取当前分辨率 - /// - /// 当前分辨率(宽度, 高度) - public (int Width, int Height) GetCurrentResolution() + public async ValueTask> CheckHdmiIsReady() { - return (_currentWidth, _currentHeight); + var ret = await UDPClientPool.ReadAddrWithWait( + this.ep, this.taskID, HdmiInAddr.HDMI_NOT_READY, 0b00, 0b01, 100, this.timeout); + if (!ret.IsSuccessful) + { + logger.Error($"Failed to check HDMI status: {ret.Error}"); + return new(ret.Error); + } + return ret.Value; } - /// - /// 获取当前帧长度 - /// - /// 当前帧长度 - public UInt32 GetCurrentFrameLength() + public async ValueTask> GetHdmiResolution() { - return _currentFrameLength; + var ret = await UDPClientPool.ReadAddrByte( + this.ep, this.taskID, HdmiInAddr.HDMI_HEIGHT_WIDTH, this.timeout); + if (!ret.IsSuccessful) + { + logger.Error($"Failed to get HDMI resolution: {ret.Error}"); + return new(ret.Error); + } + + var data = ret.Value.Options.Data; + if (data == null || data.Length != 4) + { + logger.Error($"Invalid HDMI resolution data length: {data?.Length ?? 0}"); + return new(new Exception("Invalid HDMI resolution data length")); + } + + var width = data[3] | (data[2] << 8); + var height = data[1] | (data[0] << 8); + this.Width = width; + this.Height = height; + + logger.Info($"HDMI resolution: {width}x{height}"); + + return new((width, height)); } + + public async ValueTask> ConnectJpeg2Hdmi(int width, int height) + { + if (width <= 0 || height <= 0) + { + logger.Error($"Invalid HDMI resolution: {width}x{height}"); + return new(new ArgumentException("Invalid HDMI resolution")); + } + + var frameSize = (UInt32)(width * height); + + { + var ret = await UDPClientPool.WriteAddr( + this.ep, this.taskID, HdmiInAddr.START_WR_ADDR0, HdmiInAddr.ADDR_HDMI_WD_START, this.timeout); + if (!ret.IsSuccessful) + { + logger.Error($"Failed to set HDMI output start address: {ret.Error}"); + return new(ret.Error); + } + if (!ret.Value) + { + logger.Error($"Failed to set HDMI output start address"); + return false; + } + } + + { + var ret = await UDPClientPool.WriteAddr( + this.ep, this.taskID, HdmiInAddr.END_WR_ADDR0, + HdmiInAddr.ADDR_HDMI_WD_START + frameSize - 1, this.timeout); + if (!ret.IsSuccessful) + { + logger.Error($"Failed to set HDMI output end address: {ret.Error}"); + return new(ret.Error); + } + if (!ret.Value) + { + logger.Error($"Failed to set HDMI output address"); + return false; + } + } + + return true; + } + } diff --git a/server/src/Services/HttpHdmiVideoStreamService.cs b/server/src/Services/HttpHdmiVideoStreamService.cs index 2ef4f73..a2ba04c 100644 --- a/server/src/Services/HttpHdmiVideoStreamService.cs +++ b/server/src/Services/HttpHdmiVideoStreamService.cs @@ -17,7 +17,7 @@ public class HdmiVideoStreamClient { public required HdmiIn HdmiInClient { get; set; } - public required Jpeg JpegClient { get; set; } + // public required Jpeg JpegClient { get; set; } public required CancellationTokenSource CTS { get; set; } @@ -102,7 +102,8 @@ public class HttpHdmiVideoStreamService : BackgroundService var client = _clientDict[key]; client.CTS.Cancel(); - var disableResult = await client.JpegClient.SetEnable(false); + // var disableResult = await client.JpegClient.SetEnable(false); + var disableResult = await client.HdmiInClient.SetTransEnable(false); if (disableResult) { logger.Info("Successfully disabled HDMI transmission"); @@ -111,6 +112,8 @@ public class HttpHdmiVideoStreamService : BackgroundService { logger.Error($"Failed to disable HDMI transmission"); } + + client.CTS = new CancellationTokenSource(); } catch (Exception ex) { @@ -120,53 +123,51 @@ public class HttpHdmiVideoStreamService : BackgroundService private async Task GetOrCreateClientAsync(string boardId) { - if (_clientDict.TryGetValue(boardId, out var client)) + if (!_clientDict.TryGetValue(boardId, out var client)) { - client.Width = client.JpegClient.Width; - client.Height = client.JpegClient.Height; - return client; + var userManager = new Database.UserManager(); + + var boardRet = userManager.GetBoardByID(Guid.Parse(boardId)); + if (!boardRet.IsSuccessful || !boardRet.Value.HasValue) + { + logger.Error($"Failed to get board with ID {boardId}"); + return null; + } + + var board = boardRet.Value.Value; + + client = new HdmiVideoStreamClient() + { + HdmiInClient = new HdmiIn(board.IpAddr, board.Port, 1), + // JpegClient = new Jpeg(board.IpAddr, board.Port, 1), + CTS = new CancellationTokenSource(), + Offset = 0 + }; } - var userManager = new Database.UserManager(); - - var boardRet = userManager.GetBoardByID(Guid.Parse(boardId)); - if (!boardRet.IsSuccessful || !boardRet.Value.HasValue) - { - logger.Error($"Failed to get board with ID {boardId}"); - return null; - } - - var board = boardRet.Value.Value; - - client = new HdmiVideoStreamClient() - { - HdmiInClient = new HdmiIn(board.IpAddr, board.Port, 1), - JpegClient = new Jpeg(board.IpAddr, board.Port, 1), - CTS = new CancellationTokenSource(), - Offset = 0 - }; - // 启用HDMI传输 try { - // var hdmiEnableRet = await client.JpegClient.EnableTrans(true); - // if (!hdmiEnableRet.IsSuccessful) - // { - // logger.Error($"Failed to enable HDMI transmission for board {boardId}: {hdmiEnableRet.Error}"); - // return null; - // } - // logger.Info($"Successfully enabled HDMI transmission for board {boardId}"); - - var jpegEnableRet = await client.JpegClient.Init(true); - if (!jpegEnableRet.IsSuccessful) + var hdmiEnableRet = await client.HdmiInClient.Init(true); + if (!hdmiEnableRet.IsSuccessful) { - logger.Error($"Failed to enable JPEG transmission for board {boardId}: {jpegEnableRet.Error}"); + logger.Error($"Failed to enable HDMI transmission for board {boardId}: {hdmiEnableRet.Error}"); return null; } - logger.Info($"Successfully enabled JPEG transmission for board {boardId}"); + logger.Info($"Successfully enabled HDMI transmission for board {boardId}"); - client.Width = client.JpegClient.Width; - client.Height = client.JpegClient.Height; + // var jpegEnableRet = await client.JpegClient.Init(true); + // if (!jpegEnableRet.IsSuccessful) + // { + // logger.Error($"Failed to enable JPEG transmission for board {boardId}: {jpegEnableRet.Error}"); + // return null; + // } + // logger.Info($"Successfully enabled JPEG transmission for board {boardId}"); + + client.Width = client.HdmiInClient.Width; + client.Height = client.HdmiInClient.Height; + // client.Width = client.JpegClient.Width; + // client.Height = client.JpegClient.Height; } catch (Exception ex) { @@ -195,15 +196,16 @@ public class HttpHdmiVideoStreamService : BackgroundService return; } - var hdmiInToken = _clientDict[boardId].CTS.Token; + var token = CancellationTokenSource.CreateLinkedTokenSource( + cancellationToken, client.CTS.Token).Token; if (path == "/snapshot") { - await HandleSnapshotRequestAsync(context.Response, client, hdmiInToken); + await HandleSnapshotRequestAsync(context.Response, client, token); } else if (path == "/mjpeg") { - await HandleMjpegStreamAsync(context.Response, client, hdmiInToken); + await HandleMjpegStreamAsync(context.Response, client, token); } else if (path == "/video") { @@ -223,36 +225,47 @@ public class HttpHdmiVideoStreamService : BackgroundService logger.Debug("处理HDMI快照请求"); // 从HDMI读取RGB565数据 - var frameResult = await client.JpegClient.GetMultiFrames((uint)client.Offset); - if (!frameResult.IsSuccessful || frameResult.Value == null || frameResult.Value.Count == 0) - { - logger.Error("HDMI快照获取失败"); - response.StatusCode = 500; - var errorBytes = System.Text.Encoding.UTF8.GetBytes("Failed to get HDMI snapshot"); - await response.OutputStream.WriteAsync(errorBytes, 0, errorBytes.Length, cancellationToken); - response.Close(); - return; - } + // var frameResult = await client.JpegClient.GetMultiFrames((uint)client.Offset); + // if (!frameResult.IsSuccessful || frameResult.Value == null || frameResult.Value.Count == 0) + // { + // logger.Error("HDMI快照获取失败"); + // response.StatusCode = 500; + // var errorBytes = System.Text.Encoding.UTF8.GetBytes("Failed to get HDMI snapshot"); + // await response.OutputStream.WriteAsync(errorBytes, 0, errorBytes.Length, cancellationToken); + // response.Close(); + // return; + // } - var jpegData = frameResult.Value[0]; - - var quantTableResult = await client.JpegClient.GetQuantizationTable(); - if (!quantTableResult.IsSuccessful || quantTableResult.Value == null) + // var jpegData = frameResult.Value[0]; + + // var quantTableResult = await client.JpegClient.GetQuantizationTable(); + // if (!quantTableResult.IsSuccessful || quantTableResult.Value == null) + // { + // logger.Error("获取JPEG量化表失败: {Error}", quantTableResult.Error); + // response.StatusCode = 500; + // var errorBytes = System.Text.Encoding.UTF8.GetBytes("Failed to get quantization table"); + // await response.OutputStream.WriteAsync(errorBytes, 0, errorBytes.Length, cancellationToken); + // response.Close(); + // return; + // } + + // var jpegImage = Common.Image.CompleteJpegData(jpegData, client.Width, client.Height, quantTableResult.Value); + // if (!jpegImage.IsSuccessful) + // { + // logger.Error("JPEG数据补全失败"); + // response.StatusCode = 500; + // var errorBytes = System.Text.Encoding.UTF8.GetBytes("Failed to complete JPEG data"); + // await response.OutputStream.WriteAsync(errorBytes, 0, errorBytes.Length, cancellationToken); + // response.Close(); + // return; + // } + + var jpegImage = await client.HdmiInClient.GetMJpegFrame(); + if (!jpegImage.HasValue) { - logger.Error("获取JPEG量化表失败: {Error}", quantTableResult.Error); + logger.Error("获取HDMI MJPEG帧失败"); response.StatusCode = 500; - var errorBytes = System.Text.Encoding.UTF8.GetBytes("Failed to get quantization table"); - await response.OutputStream.WriteAsync(errorBytes, 0, errorBytes.Length, cancellationToken); - response.Close(); - return; - } - - var jpegImage = Common.Image.CompleteJpegData(jpegData, client.Width, client.Height, quantTableResult.Value); - if (!jpegImage.IsSuccessful) - { - logger.Error("JPEG数据补全失败"); - response.StatusCode = 500; - var errorBytes = System.Text.Encoding.UTF8.GetBytes("Failed to complete JPEG data"); + var errorBytes = System.Text.Encoding.UTF8.GetBytes("Failed to get HDMI MJPEG frame"); await response.OutputStream.WriteAsync(errorBytes, 0, errorBytes.Length, cancellationToken); response.Close(); return; @@ -260,13 +273,13 @@ public class HttpHdmiVideoStreamService : BackgroundService // 设置响应头(参考Camera版本) response.ContentType = "image/jpeg"; - response.ContentLength64 = jpegImage.Value.Length; + response.ContentLength64 = jpegImage.Value.data.Length; response.Headers.Add("Cache-Control", "no-cache, no-store, must-revalidate"); - await response.OutputStream.WriteAsync(jpegImage.Value, 0, jpegImage.Value.Length, cancellationToken); + await response.OutputStream.WriteAsync(jpegImage.Value.data, 0, jpegImage.Value.data.Length, cancellationToken); await response.OutputStream.FlushAsync(cancellationToken); - logger.Debug("已发送HDMI快照图像,大小:{Size} 字节", jpegImage.Value.Length); + logger.Debug("已发送HDMI快照图像,大小:{Size} 字节", jpegImage.Value.data.Length); } catch (Exception ex) { @@ -275,6 +288,7 @@ public class HttpHdmiVideoStreamService : BackgroundService } finally { + response.StatusCode = 200; response.Close(); } } @@ -292,17 +306,17 @@ public class HttpHdmiVideoStreamService : BackgroundService logger.Debug("开始HDMI MJPEG流传输"); - var quantTableResult = await client.JpegClient.GetQuantizationTable(); - if (!quantTableResult.IsSuccessful || quantTableResult.Value == null) - { - logger.Error("获取JPEG量化表失败: {Error}", quantTableResult.Error); - response.StatusCode = 500; - await response.OutputStream.WriteAsync( - System.Text.Encoding.UTF8.GetBytes("Failed to get quantization table"), 0, 0, cancellationToken); - response.Close(); - return; - } - var quantTable = quantTableResult.Value; + // var quantTableResult = await client.JpegClient.GetQuantizationTable(); + // if (!quantTableResult.IsSuccessful || quantTableResult.Value == null) + // { + // logger.Error("获取JPEG量化表失败: {Error}", quantTableResult.Error); + // response.StatusCode = 500; + // await response.OutputStream.WriteAsync( + // System.Text.Encoding.UTF8.GetBytes("Failed to get quantization table"), 0, 0, cancellationToken); + // response.Close(); + // return; + // } + // var quantTable = quantTableResult.Value; int frameCounter = 0; @@ -310,51 +324,74 @@ public class HttpHdmiVideoStreamService : BackgroundService { var frameStartTime = DateTime.UtcNow; - var frameResult = - await client.JpegClient.GetMultiFrames((uint)client.Offset); - if (!frameResult.IsSuccessful || frameResult.Value == null || frameResult.Value.Count == 0) + var frameRet = await client.HdmiInClient.GetMJpegFrame(); + if (!frameRet.HasValue) { logger.Error("获取HDMI帧失败"); - await Task.Delay(100, cancellationToken); continue; } + var frame = frameRet.Value; - foreach (var framebytes in frameResult.Value) + await response.OutputStream.WriteAsync(frame.header, 0, frame.header.Length, cancellationToken); + await response.OutputStream.WriteAsync(frame.data, 0, frame.data.Length, cancellationToken); + await response.OutputStream.WriteAsync(frame.footer, 0, frame.footer.Length, cancellationToken); + await response.OutputStream.FlushAsync(cancellationToken); + + frameCounter++; + + var totalTime = (DateTime.UtcNow - frameStartTime).TotalMilliseconds; + + // 性能统计日志(每30帧记录一次) + if (frameCounter % 30 == 0) { - var jpegImage = Common.Image.CompleteJpegData(framebytes, client.Width, client.Height, quantTable); - if (!jpegImage.IsSuccessful) - { - logger.Error("JPEG数据不完整"); - await Task.Delay(100, cancellationToken); - continue; - } - - var frameRet = Common.Image.CreateMjpegFrameFromJpeg(jpegImage.Value); - if (!frameRet.IsSuccessful) - { - logger.Error("创建MJPEG帧失败"); - await Task.Delay(100, cancellationToken); - continue; - } - var frame = frameRet.Value; - - await response.OutputStream.WriteAsync(frame.header, 0, frame.header.Length, cancellationToken); - await response.OutputStream.WriteAsync(frame.data, 0, frame.data.Length, cancellationToken); - await response.OutputStream.WriteAsync(frame.footer, 0, frame.footer.Length, cancellationToken); - await response.OutputStream.FlushAsync(cancellationToken); - - frameCounter++; - - var totalTime = (DateTime.UtcNow - frameStartTime).TotalMilliseconds; - - // 性能统计日志(每30帧记录一次) - if (frameCounter % 30 == 0) - { - logger.Debug("HDMI帧 {FrameNumber} 性能统计 - 总计: {TotalTime:F1}ms, JPEG大小: {JpegSize} 字节", - frameCounter, totalTime, frame.data.Length); - } - + logger.Debug("HDMI帧 {FrameNumber} 性能统计 - 总计: {TotalTime:F1}ms, JPEG大小: {JpegSize} 字节", + frameCounter, totalTime, frame.data.Length); } + + // var frameResult = + // await client.JpegClient.GetMultiFrames((uint)client.Offset); + // if (!frameResult.IsSuccessful || frameResult.Value == null || frameResult.Value.Count == 0) + // { + // logger.Error("获取HDMI帧失败"); + // await Task.Delay(100, cancellationToken); + // continue; + // } + + // foreach (var framebytes in frameResult.Value) + // { + // var jpegImage = Common.Image.CompleteJpegData(framebytes, client.Width, client.Height, quantTable); + // if (!jpegImage.IsSuccessful) + // { + // logger.Error("JPEG数据不完整"); + // await Task.Delay(100, cancellationToken); + // continue; + // } + + // var frameRet = Common.Image.CreateMjpegFrameFromJpeg(jpegImage.Value); + // if (!frameRet.IsSuccessful) + // { + // logger.Error("创建MJPEG帧失败"); + // await Task.Delay(100, cancellationToken); + // continue; + // } + // var frame = frameRet.Value; + + // await response.OutputStream.WriteAsync(frame.header, 0, frame.header.Length, cancellationToken); // await response.OutputStream.WriteAsync(frame.data, 0, frame.data.Length, cancellationToken); + // await response.OutputStream.WriteAsync(frame.footer, 0, frame.footer.Length, cancellationToken); + // await response.OutputStream.FlushAsync(cancellationToken); + + // frameCounter++; + + // var totalTime = (DateTime.UtcNow - frameStartTime).TotalMilliseconds; + + // // 性能统计日志(每30帧记录一次) + // if (frameCounter % 30 == 0) + // { + // logger.Debug("HDMI帧 {FrameNumber} 性能统计 - 总计: {TotalTime:F1}ms, JPEG大小: {JpegSize} 字节", + // frameCounter, totalTime, frame.data.Length); + // } + + // } } } catch (Exception ex) @@ -366,7 +403,7 @@ public class HttpHdmiVideoStreamService : BackgroundService try { // 停止传输时禁用HDMI传输 - await client.HdmiInClient.EnableTrans(false); + await client.HdmiInClient.SetTransEnable(false); logger.Info("已禁用HDMI传输"); } catch (Exception ex) diff --git a/src/components/TutorialCarousel.vue b/src/components/TutorialCarousel.vue index e94d175..47ad1f2 100644 --- a/src/components/TutorialCarousel.vue +++ b/src/components/TutorialCarousel.vue @@ -81,7 +81,12 @@ import { ref, onMounted, onUnmounted } from "vue"; import { useRouter } from "vue-router"; import { AuthManager } from "@/utils/AuthManager"; -import { ExamClient, ResourceClient, type ExamInfo } from "@/APIClient"; +import { + ExamClient, + ResourceClient, + ResourcePurpose, + type ExamInfo, +} from "@/APIClient"; // 接口定义 interface Tutorial { @@ -146,7 +151,7 @@ onMounted(async () => { const resourceList = await resourceClient.getResourceList( exam.id, "cover", - "template", + ResourcePurpose.Template, ); if (resourceList && resourceList.length > 0) { // 使用第一个封面资源 diff --git a/src/views/Project/HdmiVideoStream.vue b/src/views/Project/HdmiVideoStream.vue index 0e69344..1836bea 100644 --- a/src/views/Project/HdmiVideoStream.vue +++ b/src/views/Project/HdmiVideoStream.vue @@ -430,13 +430,13 @@ function startStream() { } // 停止播放视频流 -function stopStream() { +async function stopStream() { isPlaying.value = false; currentVideoSource.value = ""; videoStatus.value = "已停止播放"; const client = AuthManager.createClient(HdmiVideoStreamClient); - client.disableHdmiTransmission(); + await client.disableHdmiTransmission(); addLog("info", "停止播放HDMI视频流"); alert?.info("已停止播放HDMI视频流"); @@ -467,8 +467,9 @@ function handleVideoClick() { } // 重试连接 -function tryReconnect() { +async function tryReconnect() { hasVideoError.value = false; + await stopStream(); if (endpoint.value) { startStream(); } diff --git a/src/views/Project/Index.vue b/src/views/Project/Index.vue index 92fab74..420fc4f 100644 --- a/src/views/Project/Index.vue +++ b/src/views/Project/Index.vue @@ -171,7 +171,12 @@ import { useProvideComponentManager } from "@/components/LabCanvas"; import { useAlertStore } from "@/components/Alert"; import { AuthManager } from "@/utils/AuthManager"; import { useEquipments } from "@/stores/equipments"; -import { DataClient, ResourceClient, type Board } from "@/APIClient"; +import { + DataClient, + ResourceClient, + ResourcePurpose, + type Board, +} from "@/APIClient"; import { useRoute } from "vue-router"; const route = useRoute(); @@ -257,7 +262,11 @@ async function loadDocumentContent() { const client = AuthManager.createClient(ResourceClient); // 获取markdown类型的模板资源列表 - const resources = await client.getResourceList(examId, "doc", "template"); + const resources = await client.getResourceList( + examId, + "doc", + ResourcePurpose.Template, + ); if (resources && resources.length > 0) { // 获取第一个markdown资源