refactor: 视频流前后端适配
This commit is contained in:
3
server/.gitignore
vendored
3
server/.gitignore
vendored
@@ -1,5 +1,6 @@
|
||||
# Generate
|
||||
obj
|
||||
bin
|
||||
bitstream
|
||||
bsdl
|
||||
|
||||
data
|
||||
|
@@ -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>
|
||||
|
@@ -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()
|
||||
};
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user