refactor: 视频流前后端适配

This commit is contained in:
2025-08-11 13:09:30 +08:00
parent b95a61c532
commit c1d641c20c
7 changed files with 752 additions and 229 deletions

3
server/.gitignore vendored
View File

@@ -1,5 +1,6 @@
# Generate
obj
bin
bitstream
bsdl
data

View File

@@ -4,6 +4,7 @@ using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Authorization;
using System.Security.Claims;
using DotNext;
using server.Services;
/// <summary>
/// 视频流控制器,支持动态配置摄像头连接
@@ -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;
/// <summary>
@@ -24,7 +25,7 @@ public class VideoStreamController : ControllerBase
/// <param name="videoStreamService">HTTP视频流服务</param>
/// <param name="userManager">用户管理服务</param>
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 视频流服务状态
/// </summary>
/// <returns>服务状态信息</returns>
[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<IActionResult> DisableHdmiTransmission()
[HttpPost("SetVideoStreamEnable")]
public async Task<IActionResult> 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
/// <returns>支持的分辨率列表</returns>
[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
}
}
/// <summary>
/// 配置摄像头连接参数
/// </summary>
/// <returns>配置结果</returns>
[HttpPost("ConfigureCamera")]
[EnableCors("Users")]
[ProducesResponseType(typeof(object), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(object), StatusCodes.Status400BadRequest)]
[ProducesResponseType(typeof(Exception), StatusCodes.Status500InternalServerError)]
public async Task<IResult> 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);
}
}
/// <summary>
/// 分辨率配置请求模型
/// </summary>

View File

@@ -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
/// <summary>
/// 表示摄像头连接状态信息
/// </summary>
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; }
/// <summary>
/// 视频流的帧率FPS
/// </summary>
public int FrameRate { get; set; }
public required int FrameRate { get; set; }
public int FrameWidth { get; set; }
public int FrameHeight { get; set; }
/// <summary>
/// 视频分辨率(如 640x480
/// </summary>
public string Resolution { get; set; } = string.Empty;
public string Resolution => $"{FrameWidth}x{FrameHeight}";
}
/// <summary>
/// 表示视频流服务的运行状态
/// </summary>
public class ServiceStatus
public class VideoStreamServiceStatus
{
/// <summary>
/// 服务是否正在运行
@@ -71,7 +79,7 @@ public class ServiceStatus
/// <summary>
/// 当前连接的客户端端点列表
/// </summary>
public List<VideoEndpoint> ClientEndpoints { get; set; } = new();
public List<VideoStreamEndpoint> ClientEndpoints { get; set; } = new();
/// <summary>
/// 当前连接的客户端数量
@@ -106,36 +114,6 @@ public class HttpVideoStreamService : BackgroundService
_userManager = userManager;
}
/// <summary>
/// 初始化 HttpVideoStreamService
/// </summary>
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);
}
/// <summary>
/// 停止 HTTP 视频流服务
/// </summary>
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<VideoStreamClient> TryGetClient(string boardId)
{
@@ -176,6 +154,36 @@ public class HttpVideoStreamService : BackgroundService
return client;
}
/// <summary>
/// 初始化 HttpVideoStreamService
/// </summary>
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);
}
/// <summary>
/// 停止 HTTP 视频流服务
/// </summary>
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);
}
/// <summary>
/// 执行 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<VideoEndpoint> GetAllVideoEndpoints()
{
var endpoints = new List<VideoEndpoint>();
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)
/// <summary>
/// 配置摄像头连接参数
/// </summary>
/// <param name="boardId">板卡ID</param>
/// <returns>配置是否成功</returns>
public async Task<bool> 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<VideoStreamEndpoint> GetAllVideoEndpoints()
{
var endpoints = new List<VideoStreamEndpoint>();
foreach (var boardId in _clientDict.Keys)
endpoints.Add(GetVideoEndpoint(boardId));
return endpoints;
}
public VideoStreamServiceStatus GetServiceStatus()
{
return new VideoStreamServiceStatus
{
IsRunning = true,
ServerPort = _serverPort,
ClientEndpoints = GetAllVideoEndpoints()
};
}
}