diff --git a/server/.gitignore b/server/.gitignore index 05e698d..b9f6ea7 100644 --- a/server/.gitignore +++ b/server/.gitignore @@ -1,5 +1,6 @@ +# Generate obj bin bitstream bsdl - +data diff --git a/server/src/Controllers/VideoStreamController.cs b/server/src/Controllers/VideoStreamController.cs index b5a09bd..0c23365 100644 --- a/server/src/Controllers/VideoStreamController.cs +++ b/server/src/Controllers/VideoStreamController.cs @@ -4,6 +4,7 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Authorization; using System.Security.Claims; using DotNext; +using server.Services; /// /// 视频流控制器,支持动态配置摄像头连接 @@ -15,7 +16,7 @@ public class VideoStreamController : ControllerBase { private static NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger(); - private readonly server.Services.HttpVideoStreamService _videoStreamService; + private readonly HttpVideoStreamService _videoStreamService; private readonly Database.UserManager _userManager; /// @@ -24,7 +25,7 @@ public class VideoStreamController : ControllerBase /// HTTP视频流服务 /// 用户管理服务 public VideoStreamController( - server.Services.HttpVideoStreamService videoStreamService, Database.UserManager userManager) + HttpVideoStreamService videoStreamService, Database.UserManager userManager) { logger.Info("创建VideoStreamController,命名空间:{Namespace}", this.GetType().Namespace); _videoStreamService = videoStreamService; @@ -62,11 +63,11 @@ public class VideoStreamController : ControllerBase /// 获取 HTTP 视频流服务状态 /// /// 服务状态信息 - [HttpGet("Status")] + [HttpGet("ServiceStatus")] [EnableCors("Users")] - [ProducesResponseType(typeof(object), StatusCodes.Status200OK)] + [ProducesResponseType(typeof(VideoStreamServiceStatus), StatusCodes.Status200OK)] [ProducesResponseType(typeof(Exception), StatusCodes.Status500InternalServerError)] - public IResult GetStatus() + public IResult GetServiceStatus() { try { @@ -85,7 +86,7 @@ public class VideoStreamController : ControllerBase [HttpGet("MyEndpoint")] [EnableCors("Users")] - [ProducesResponseType(typeof(object), StatusCodes.Status200OK)] + [ProducesResponseType(typeof(VideoStreamEndpoint), StatusCodes.Status200OK)] [ProducesResponseType(typeof(Exception), StatusCodes.Status500InternalServerError)] public IResult MyEndpoint() { @@ -141,14 +142,14 @@ public class VideoStreamController : ControllerBase } } - [HttpPost("DisableTransmission")] - public async Task DisableHdmiTransmission() + [HttpPost("SetVideoStreamEnable")] + public async Task SetVideoStreamEnable(bool enable) { try { var boardId = TryGetBoardId().OrThrow(() => new ArgumentException("Board ID is required")); - await _videoStreamService.DisableHdmiTransmissionAsync(boardId.ToString()); + await _videoStreamService.SetVideoStreamEnableAsync(boardId.ToString(), enable); return Ok($"HDMI transmission for board {boardId} disabled."); } catch (Exception ex) @@ -210,7 +211,7 @@ public class VideoStreamController : ControllerBase /// 支持的分辨率列表 [HttpGet("SupportedResolutions")] [EnableCors("Users")] - [ProducesResponseType(typeof(object), StatusCodes.Status200OK)] + [ProducesResponseType(typeof(AvailableResolutionsResponse[]), StatusCodes.Status200OK)] [ProducesResponseType(typeof(string), StatusCodes.Status500InternalServerError)] public IResult GetSupportedResolutions() { @@ -319,6 +320,39 @@ public class VideoStreamController : ControllerBase } } + /// + /// 配置摄像头连接参数 + /// + /// 配置结果 + [HttpPost("ConfigureCamera")] + [EnableCors("Users")] + [ProducesResponseType(typeof(object), StatusCodes.Status200OK)] + [ProducesResponseType(typeof(object), StatusCodes.Status400BadRequest)] + [ProducesResponseType(typeof(Exception), StatusCodes.Status500InternalServerError)] + public async Task ConfigureCamera() + { + try + { + var boardId = TryGetBoardId().OrThrow(() => new Exception("Board ID not found")); + + var ret = await _videoStreamService.ConfigureCameraAsync(boardId); + + if (ret) + { + return TypedResults.Ok(new { Message = "配置成功" }); + } + else + { + return TypedResults.BadRequest(new { Message = "配置失败" }); + } + } + catch (Exception ex) + { + logger.Error(ex, "配置摄像头连接失败"); + return TypedResults.InternalServerError(ex.Message); + } + } + /// /// 分辨率配置请求模型 /// diff --git a/server/src/Services/HttpVideoStreamService.cs b/server/src/Services/HttpVideoStreamService.cs index 8bcbcce..a237bf5 100644 --- a/server/src/Services/HttpVideoStreamService.cs +++ b/server/src/Services/HttpVideoStreamService.cs @@ -13,6 +13,7 @@ namespace server.Services; public class VideoStreamClient { public string? ClientId { get; set; } = string.Empty; + public bool IsEnabled { get; set; } = true; public int FrameWidth { get; set; } public int FrameHeight { get; set; } public int FrameRate { get; set; } @@ -35,28 +36,35 @@ public class VideoStreamClient /// /// 表示摄像头连接状态信息 /// -public class VideoEndpoint +public class VideoStreamEndpoint { - public string BoardId { get; set; } = ""; - public string MjpegUrl { get; set; } = ""; - public string VideoUrl { get; set; } = ""; - public string SnapshotUrl { get; set; } = ""; + public required string BoardId { get; set; } = ""; + public required string MjpegUrl { get; set; } = ""; + public required string VideoUrl { get; set; } = ""; + public required string SnapshotUrl { get; set; } = ""; + public required string HtmlUrl { get; set; } = ""; + public required string UsbCameraUrl { get; set; } = ""; + + public required bool IsEnabled { get; set; } /// /// 视频流的帧率(FPS) /// - public int FrameRate { get; set; } + public required int FrameRate { get; set; } + + public int FrameWidth { get; set; } + public int FrameHeight { get; set; } /// /// 视频分辨率(如 640x480) /// - public string Resolution { get; set; } = string.Empty; + public string Resolution => $"{FrameWidth}x{FrameHeight}"; } /// /// 表示视频流服务的运行状态 /// -public class ServiceStatus +public class VideoStreamServiceStatus { /// /// 服务是否正在运行 @@ -71,7 +79,7 @@ public class ServiceStatus /// /// 当前连接的客户端端点列表 /// - public List ClientEndpoints { get; set; } = new(); + public List ClientEndpoints { get; set; } = new(); /// /// 当前连接的客户端数量 @@ -106,36 +114,6 @@ public class HttpVideoStreamService : BackgroundService _userManager = userManager; } - /// - /// 初始化 HttpVideoStreamService - /// - public override async Task StartAsync(CancellationToken cancellationToken) - { - _httpListener = new HttpListener(); - _httpListener.Prefixes.Add($"http://{Global.LocalHost}:{_serverPort}/"); - _httpListener.Start(); - logger.Info($"Video Stream Service started on port {_serverPort}"); - - await base.StartAsync(cancellationToken); - } - - /// - /// 停止 HTTP 视频流服务 - /// - public override async Task StopAsync(CancellationToken cancellationToken) - { - foreach (var clientKey in _clientDict.Keys) - { - var client = _clientDict[clientKey]; - client.CTS.Cancel(); - using (await client.Lock.AcquireWriteLockAsync(cancellationToken)) - { - await client.Camera.EnableHardwareTrans(false); - } - } - _clientDict.Clear(); - await base.StopAsync(cancellationToken); - } private Optional TryGetClient(string boardId) { @@ -176,6 +154,36 @@ public class HttpVideoStreamService : BackgroundService return client; } + /// + /// 初始化 HttpVideoStreamService + /// + public override async Task StartAsync(CancellationToken cancellationToken) + { + _httpListener = new HttpListener(); + _httpListener.Prefixes.Add($"http://{Global.LocalHost}:{_serverPort}/"); + _httpListener.Start(); + logger.Info($"Video Stream Service started on port {_serverPort}"); + + await base.StartAsync(cancellationToken); + } + + /// + /// 停止 HTTP 视频流服务 + /// + public override async Task StopAsync(CancellationToken cancellationToken) + { + foreach (var clientKey in _clientDict.Keys) + { + var client = _clientDict[clientKey]; + client.CTS.Cancel(); + using (await client.Lock.AcquireWriteLockAsync(cancellationToken)) + { + await client.Camera.EnableHardwareTrans(false); + } + } + _clientDict.Clear(); + await base.StopAsync(cancellationToken); + } /// /// 执行 HTTP 视频流服务 @@ -254,6 +262,11 @@ public class HttpVideoStreamService : BackgroundService // 单帧图像请求 await HandleSnapshotRequestAsync(context.Response, client, cancellationToken); } + else if (path == "/html") + { + // HTML页面请求 + await SendIndexHtmlPageAsync(context.Response); + } else { // 默认返回简单的HTML页面,提供链接到视频页面 @@ -668,42 +681,12 @@ public class HttpVideoStreamService : BackgroundService } } - public VideoEndpoint GetVideoEndpoint(string boardId) - { - var client = TryGetClient(boardId).OrThrow(() => new Exception($"无法获取摄像头客户端: {boardId}")); - - return new VideoEndpoint - { - BoardId = boardId, - MjpegUrl = $"http://{Global.LocalHost}:{_serverPort}/mjpeg?boardId={boardId}", - VideoUrl = $"http://{Global.LocalHost}:{_serverPort}/video?boardId={boardId}", - SnapshotUrl = $"http://{Global.LocalHost}:{_serverPort}/snapshot?boardId={boardId}", - Resolution = $"{client.FrameWidth}x{client.FrameHeight}", - FrameRate = client.FrameRate - }; - } - - public List GetAllVideoEndpoints() - { - var endpoints = new List(); - - foreach (var boardId in _clientDict.Keys) - endpoints.Add(GetVideoEndpoint(boardId)); - - return endpoints; - } - - public ServiceStatus GetServiceStatus() - { - return new ServiceStatus - { - IsRunning = true, - ServerPort = _serverPort, - ClientEndpoints = GetAllVideoEndpoints() - }; - } - - public async Task DisableHdmiTransmissionAsync(string boardId) + /// + /// 配置摄像头连接参数 + /// + /// 板卡ID + /// 配置是否成功 + public async Task ConfigureCameraAsync(string boardId) { try { @@ -711,8 +694,67 @@ public class HttpVideoStreamService : BackgroundService using (await client.Lock.AcquireWriteLockAsync()) { + var ret = await client.Camera.Init(); + if (!ret.IsSuccessful) + { + logger.Error(ret.Error); + throw ret.Error; + } + + if (!ret.Value) + { + logger.Error($"Camera Init Failed!"); + throw new Exception($"Camera Init Failed!"); + } + } + + using (await client.Lock.AcquireWriteLockAsync()) + { + var ret = await client.Camera.ChangeResolution(client.FrameWidth, client.FrameHeight); + if (!ret.IsSuccessful) + { + logger.Error(ret.Error); + throw ret.Error; + } + + if (!ret.Value) + { + logger.Error($"Camera Resolution Change Failed!"); + throw new Exception($"Camera Resolution Change Failed!"); + } + } + + return true; + } + catch (Exception ex) + { + logger.Error(ex, "配置摄像头连接时发生错误"); + return false; + } + } + + public async Task SetVideoStreamEnableAsync(string boardId, bool enable) + { + try + { + var client = TryGetClient(boardId).OrThrow(() => new Exception($"无法获取摄像头客户端: {boardId}")); + + if (client.IsEnabled == enable) + return; + + using (await client.Lock.AcquireWriteLockAsync()) + { + if (enable) + { + client.CTS = new CancellationTokenSource(); + } + else + { + client.CTS.Cancel(); + } + var camera = client.Camera; - var disableResult = await camera.EnableHardwareTrans(false); + var disableResult = await camera.EnableHardwareTrans(enable); if (disableResult.IsSuccessful && disableResult.Value) logger.Info($"Successfully disabled camera {boardId} hardware transmission"); else @@ -743,4 +785,41 @@ public class HttpVideoStreamService : BackgroundService return false; } } + + public VideoStreamEndpoint GetVideoEndpoint(string boardId) + { + var client = TryGetClient(boardId).OrThrow(() => new Exception($"无法获取摄像头客户端: {boardId}")); + + return new VideoStreamEndpoint + { + BoardId = boardId, + MjpegUrl = $"http://{Global.LocalHost}:{_serverPort}/mjpeg?boardId={boardId}", + VideoUrl = $"http://{Global.LocalHost}:{_serverPort}/video?boardId={boardId}", + SnapshotUrl = $"http://{Global.LocalHost}:{_serverPort}/snapshot?boardId={boardId}", + UsbCameraUrl = $"http://{Global.LocalHost}:{_serverPort}/usbCamera?boardId={boardId}", + HtmlUrl = $"http://{Global.LocalHost}:{_serverPort}/html?boardId={boardId}", + IsEnabled = client.IsEnabled, + FrameRate = client.FrameRate + }; + } + + public List GetAllVideoEndpoints() + { + var endpoints = new List(); + + foreach (var boardId in _clientDict.Keys) + endpoints.Add(GetVideoEndpoint(boardId)); + + return endpoints; + } + + public VideoStreamServiceStatus GetServiceStatus() + { + return new VideoStreamServiceStatus + { + IsRunning = true, + ServerPort = _serverPort, + ClientEndpoints = GetAllVideoEndpoints() + }; + } } diff --git a/src/APIClient.ts b/src/APIClient.ts index e553bac..1c3764b 100644 --- a/src/APIClient.ts +++ b/src/APIClient.ts @@ -129,8 +129,8 @@ export class VideoStreamClient { * 获取 HTTP 视频流服务状态 * @return 服务状态信息 */ - getStatus( cancelToken?: CancelToken): Promise { - let url_ = this.baseUrl + "/api/VideoStream/Status"; + getServiceStatus( cancelToken?: CancelToken): Promise { + let url_ = this.baseUrl + "/api/VideoStream/ServiceStatus"; url_ = url_.replace(/[?&]$/, ""); let options_: AxiosRequestConfig = { @@ -149,11 +149,11 @@ export class VideoStreamClient { throw _error; } }).then((_response: AxiosResponse) => { - return this.processGetStatus(_response); + return this.processGetServiceStatus(_response); }); } - protected processGetStatus(response: AxiosResponse): Promise { + protected processGetServiceStatus(response: AxiosResponse): Promise { const status = response.status; let _headers: any = {}; if (response.headers && typeof response.headers === "object") { @@ -167,9 +167,8 @@ export class VideoStreamClient { const _responseText = response.data; let result200: any = null; let resultData200 = _responseText; - result200 = resultData200 !== undefined ? resultData200 : null; - - return Promise.resolve(result200); + result200 = VideoStreamServiceStatus.fromJS(resultData200); + return Promise.resolve(result200); } else if (status === 500) { const _responseText = response.data; @@ -182,10 +181,10 @@ export class VideoStreamClient { const _responseText = response.data; return throwException("An unexpected server error occurred.", status, _responseText, _headers); } - return Promise.resolve(null as any); + return Promise.resolve(null as any); } - myEndpoint( cancelToken?: CancelToken): Promise { + myEndpoint( cancelToken?: CancelToken): Promise { let url_ = this.baseUrl + "/api/VideoStream/MyEndpoint"; url_ = url_.replace(/[?&]$/, ""); @@ -209,7 +208,7 @@ export class VideoStreamClient { }); } - protected processMyEndpoint(response: AxiosResponse): Promise { + protected processMyEndpoint(response: AxiosResponse): Promise { const status = response.status; let _headers: any = {}; if (response.headers && typeof response.headers === "object") { @@ -223,9 +222,8 @@ export class VideoStreamClient { const _responseText = response.data; let result200: any = null; let resultData200 = _responseText; - result200 = resultData200 !== undefined ? resultData200 : null; - - return Promise.resolve(result200); + result200 = VideoStreamEndpoint.fromJS(resultData200); + return Promise.resolve(result200); } else if (status === 500) { const _responseText = response.data; @@ -238,7 +236,7 @@ export class VideoStreamClient { const _responseText = response.data; return throwException("An unexpected server error occurred.", status, _responseText, _headers); } - return Promise.resolve(null as any); + return Promise.resolve(null as any); } /** @@ -301,8 +299,12 @@ export class VideoStreamClient { return Promise.resolve(null as any); } - disableHdmiTransmission( cancelToken?: CancelToken): Promise { - let url_ = this.baseUrl + "/api/VideoStream/DisableTransmission"; + setVideoStreamEnable(enable: boolean | undefined, cancelToken?: CancelToken): Promise { + let url_ = this.baseUrl + "/api/VideoStream/SetVideoStreamEnable?"; + if (enable === null) + throw new Error("The parameter 'enable' cannot be null."); + else if (enable !== undefined) + url_ += "enable=" + encodeURIComponent("" + enable) + "&"; url_ = url_.replace(/[?&]$/, ""); let options_: AxiosRequestConfig = { @@ -322,11 +324,11 @@ export class VideoStreamClient { throw _error; } }).then((_response: AxiosResponse) => { - return this.processDisableHdmiTransmission(_response); + return this.processSetVideoStreamEnable(_response); }); } - protected processDisableHdmiTransmission(response: AxiosResponse): Promise { + protected processSetVideoStreamEnable(response: AxiosResponse): Promise { const status = response.status; let _headers: any = {}; if (response.headers && typeof response.headers === "object") { @@ -432,7 +434,7 @@ export class VideoStreamClient { * 获取支持的分辨率列表 * @return 支持的分辨率列表 */ - getSupportedResolutions( cancelToken?: CancelToken): Promise { + getSupportedResolutions( cancelToken?: CancelToken): Promise { let url_ = this.baseUrl + "/api/VideoStream/SupportedResolutions"; url_ = url_.replace(/[?&]$/, ""); @@ -456,7 +458,7 @@ export class VideoStreamClient { }); } - protected processGetSupportedResolutions(response: AxiosResponse): Promise { + protected processGetSupportedResolutions(response: AxiosResponse): Promise { const status = response.status; let _headers: any = {}; if (response.headers && typeof response.headers === "object") { @@ -470,9 +472,15 @@ export class VideoStreamClient { const _responseText = response.data; let result200: any = null; let resultData200 = _responseText; - result200 = resultData200 !== undefined ? resultData200 : null; - - return Promise.resolve(result200); + if (Array.isArray(resultData200)) { + result200 = [] as any; + for (let item of resultData200) + result200!.push(AvailableResolutionsResponse.fromJS(item)); + } + else { + result200 = null; + } + return Promise.resolve(result200); } else if (status === 500) { const _responseText = response.data; @@ -486,7 +494,7 @@ export class VideoStreamClient { const _responseText = response.data; return throwException("An unexpected server error occurred.", status, _responseText, _headers); } - return Promise.resolve(null as any); + return Promise.resolve(null as any); } /** @@ -626,6 +634,74 @@ export class VideoStreamClient { } return Promise.resolve(null as any); } + + /** + * 配置摄像头连接参数 + * @return 配置结果 + */ + configureCamera( cancelToken?: CancelToken): Promise { + let url_ = this.baseUrl + "/api/VideoStream/ConfigureCamera"; + url_ = url_.replace(/[?&]$/, ""); + + let options_: AxiosRequestConfig = { + method: "POST", + url: url_, + headers: { + "Accept": "application/json" + }, + cancelToken + }; + + return this.instance.request(options_).catch((_error: any) => { + if (isAxiosError(_error) && _error.response) { + return _error.response; + } else { + throw _error; + } + }).then((_response: AxiosResponse) => { + return this.processConfigureCamera(_response); + }); + } + + protected processConfigureCamera(response: AxiosResponse): Promise { + const status = response.status; + let _headers: any = {}; + if (response.headers && typeof response.headers === "object") { + for (const k in response.headers) { + if (response.headers.hasOwnProperty(k)) { + _headers[k] = response.headers[k]; + } + } + } + if (status === 200) { + const _responseText = response.data; + let result200: any = null; + let resultData200 = _responseText; + result200 = resultData200 !== undefined ? resultData200 : null; + + return Promise.resolve(result200); + + } else if (status === 400) { + const _responseText = response.data; + let result400: any = null; + let resultData400 = _responseText; + result400 = resultData400 !== undefined ? resultData400 : null; + + return throwException("A server side error occurred.", status, _responseText, _headers, result400); + + } else if (status === 500) { + const _responseText = response.data; + let result500: any = null; + let resultData500 = _responseText; + result500 = Exception.fromJS(resultData500); + return throwException("A server side error occurred.", status, _responseText, _headers, result500); + + } else if (status !== 200 && status !== 204) { + const _responseText = response.data; + return throwException("An unexpected server error occurred.", status, _responseText, _headers); + } + return Promise.resolve(null as any); + } } export class BsdlParserClient { @@ -6927,6 +7003,157 @@ export class UDPClient { } } +/** 表示视频流服务的运行状态 */ +export class VideoStreamServiceStatus implements IVideoStreamServiceStatus { + /** 服务是否正在运行 */ + isRunning!: boolean; + /** 服务监听的端口号 */ + serverPort!: number; + /** 当前连接的客户端端点列表 */ + clientEndpoints!: VideoStreamEndpoint[]; + /** 当前连接的客户端数量 */ + connectedClientsNum!: number; + + constructor(data?: IVideoStreamServiceStatus) { + if (data) { + for (var property in data) { + if (data.hasOwnProperty(property)) + (this)[property] = (data)[property]; + } + } + if (!data) { + this.clientEndpoints = []; + } + } + + init(_data?: any) { + if (_data) { + this.isRunning = _data["isRunning"]; + this.serverPort = _data["serverPort"]; + if (Array.isArray(_data["clientEndpoints"])) { + this.clientEndpoints = [] as any; + for (let item of _data["clientEndpoints"]) + this.clientEndpoints!.push(VideoStreamEndpoint.fromJS(item)); + } + this.connectedClientsNum = _data["connectedClientsNum"]; + } + } + + static fromJS(data: any): VideoStreamServiceStatus { + data = typeof data === 'object' ? data : {}; + let result = new VideoStreamServiceStatus(); + result.init(data); + return result; + } + + toJSON(data?: any) { + data = typeof data === 'object' ? data : {}; + data["isRunning"] = this.isRunning; + data["serverPort"] = this.serverPort; + if (Array.isArray(this.clientEndpoints)) { + data["clientEndpoints"] = []; + for (let item of this.clientEndpoints) + data["clientEndpoints"].push(item ? item.toJSON() : undefined); + } + data["connectedClientsNum"] = this.connectedClientsNum; + return data; + } +} + +/** 表示视频流服务的运行状态 */ +export interface IVideoStreamServiceStatus { + /** 服务是否正在运行 */ + isRunning: boolean; + /** 服务监听的端口号 */ + serverPort: number; + /** 当前连接的客户端端点列表 */ + clientEndpoints: VideoStreamEndpoint[]; + /** 当前连接的客户端数量 */ + connectedClientsNum: number; +} + +/** 表示摄像头连接状态信息 */ +export class VideoStreamEndpoint implements IVideoStreamEndpoint { + boardId!: string; + mjpegUrl!: string; + videoUrl!: string; + snapshotUrl!: string; + htmlUrl!: string; + usbCameraUrl!: string; + isEnabled!: boolean; + /** 视频流的帧率(FPS) */ + frameRate!: number; + frameWidth!: number; + frameHeight!: number; + /** 视频分辨率(如 640x480) */ + resolution!: string; + + constructor(data?: IVideoStreamEndpoint) { + if (data) { + for (var property in data) { + if (data.hasOwnProperty(property)) + (this)[property] = (data)[property]; + } + } + } + + init(_data?: any) { + if (_data) { + this.boardId = _data["boardId"]; + this.mjpegUrl = _data["mjpegUrl"]; + this.videoUrl = _data["videoUrl"]; + this.snapshotUrl = _data["snapshotUrl"]; + this.htmlUrl = _data["htmlUrl"]; + this.usbCameraUrl = _data["usbCameraUrl"]; + this.isEnabled = _data["isEnabled"]; + this.frameRate = _data["frameRate"]; + this.frameWidth = _data["frameWidth"]; + this.frameHeight = _data["frameHeight"]; + this.resolution = _data["resolution"]; + } + } + + static fromJS(data: any): VideoStreamEndpoint { + data = typeof data === 'object' ? data : {}; + let result = new VideoStreamEndpoint(); + result.init(data); + return result; + } + + toJSON(data?: any) { + data = typeof data === 'object' ? data : {}; + data["boardId"] = this.boardId; + data["mjpegUrl"] = this.mjpegUrl; + data["videoUrl"] = this.videoUrl; + data["snapshotUrl"] = this.snapshotUrl; + data["htmlUrl"] = this.htmlUrl; + data["usbCameraUrl"] = this.usbCameraUrl; + data["isEnabled"] = this.isEnabled; + data["frameRate"] = this.frameRate; + data["frameWidth"] = this.frameWidth; + data["frameHeight"] = this.frameHeight; + data["resolution"] = this.resolution; + return data; + } +} + +/** 表示摄像头连接状态信息 */ +export interface IVideoStreamEndpoint { + boardId: string; + mjpegUrl: string; + videoUrl: string; + snapshotUrl: string; + htmlUrl: string; + usbCameraUrl: string; + isEnabled: boolean; + /** 视频流的帧率(FPS) */ + frameRate: number; + frameWidth: number; + frameHeight: number; + /** 视频分辨率(如 640x480) */ + resolution: string; +} + export class Exception implements IException { declare message: string; innerException?: Exception | undefined; @@ -7021,6 +7248,54 @@ export interface IResolutionConfigRequest { height: number; } +export class AvailableResolutionsResponse implements IAvailableResolutionsResponse { + width!: number; + height!: number; + name!: string; + value!: string; + + constructor(data?: IAvailableResolutionsResponse) { + if (data) { + for (var property in data) { + if (data.hasOwnProperty(property)) + (this)[property] = (data)[property]; + } + } + } + + init(_data?: any) { + if (_data) { + this.width = _data["width"]; + this.height = _data["height"]; + this.name = _data["name"]; + this.value = _data["value"]; + } + } + + static fromJS(data: any): AvailableResolutionsResponse { + data = typeof data === 'object' ? data : {}; + let result = new AvailableResolutionsResponse(); + result.init(data); + return result; + } + + toJSON(data?: any) { + data = typeof data === 'object' ? data : {}; + data["width"] = this.width; + data["height"] = this.height; + data["name"] = this.name; + data["value"] = this.value; + return data; + } +} + +export interface IAvailableResolutionsResponse { + width: number; + height: number; + name: string; + value: string; +} + export class ProblemDetails implements IProblemDetails { type?: string | undefined; title?: string | undefined; diff --git a/src/components/UploadCard.vue b/src/components/UploadCard.vue index a2207b9..7853981 100644 --- a/src/components/UploadCard.vue +++ b/src/components/UploadCard.vue @@ -87,9 +87,12 @@ import type { HubConnection } from "@microsoft/signalr"; import type { IProgressHub, IProgressReceiver, -} from "@/TypedSignalR.Client/server.Hubs"; -import { getHubProxyFactory, getReceiverRegister } from "@/TypedSignalR.Client"; -import { ProgressStatus } from "@/server.Hubs"; +} from "@/utils/signalR/TypedSignalR.Client/server.Hubs"; +import { + getHubProxyFactory, + getReceiverRegister, +} from "@/utils/signalR/TypedSignalR.Client"; +import { ProgressStatus } from "@/utils/signalR/server.Hubs"; import { useRequiredInjection } from "@/utils/Common"; import { useAlertStore } from "./Alert"; diff --git a/src/stores/equipments.ts b/src/stores/equipments.ts index 225d17c..6cb79c5 100644 --- a/src/stores/equipments.ts +++ b/src/stores/equipments.ts @@ -10,9 +10,12 @@ import { useDialogStore } from "./dialog"; import { toFileParameterOrUndefined } from "@/utils/Common"; import { AuthManager } from "@/utils/AuthManager"; import { HubConnection, HubConnectionBuilder } from "@microsoft/signalr"; -import { getHubProxyFactory, getReceiverRegister } from "@/TypedSignalR.Client"; +import { + getHubProxyFactory, + getReceiverRegister, +} from "@/utils/signalR/TypedSignalR.Client"; import type { ResourceInfo } from "@/APIClient"; -import type { IJtagHub } from "@/TypedSignalR.Client/server.Hubs"; +import type { IJtagHub } from "@/utils/signalR/TypedSignalR.Client/server.Hubs"; export const useEquipments = defineStore("equipments", () => { // Global Stores diff --git a/src/views/Project/VideoStream.vue b/src/views/Project/VideoStream.vue index a444871..007ae24 100644 --- a/src/views/Project/VideoStream.vue +++ b/src/views/Project/VideoStream.vue @@ -8,20 +8,31 @@ 控制面板 -
+
-
- {{ statusInfo.isRunning ? "运行中" : "已停止" }} +
+ {{ videoStreamInfo.isRunning ? "运行中" : "已停止" }}
服务状态
HTTP
-
端口: {{ statusInfo.serverPort }}
+
+ 端口: {{ videoStreamInfo.serverPort }} +
@@ -33,9 +44,11 @@
视频规格
- {{ streamInfo.frameWidth }}×{{ streamInfo.frameHeight }} + {{ videoStreamInfo.frameWidth }}×{{ + videoStreamInfo.frameHeight + }}
-
{{ streamInfo.frameRate }} FPS
+
{{ videoStreamInfo.frameRate }} FPS
@@ -47,17 +60,31 @@
分辨率设置
- +
-
@@ -72,22 +99,34 @@
连接数
- {{ statusInfo.connectedClients }} + {{ videoStreamInfo.connectedClients }}