diff --git a/server/src/Controllers/VideoStreamController.cs b/server/src/Controllers/VideoStreamController.cs
index c1cc728..fe786b9 100644
--- a/server/src/Controllers/VideoStreamController.cs
+++ b/server/src/Controllers/VideoStreamController.cs
@@ -1,77 +1,22 @@
using System.ComponentModel.DataAnnotations;
-using System.Threading.Tasks;
using Microsoft.AspNetCore.Cors;
using Microsoft.AspNetCore.Mvc;
+using Microsoft.AspNetCore.Authorization;
+using System.Security.Claims;
+using Database;
+using DotNext;
///
/// 视频流控制器,支持动态配置摄像头连接
///
[ApiController]
+[Authorize]
[Route("api/[controller]")]
public class VideoStreamController : ControllerBase
{
private static NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();
private readonly server.Services.HttpVideoStreamService _videoStreamService;
- ///
- /// 视频流信息结构体
- ///
- public class StreamInfoResult
- {
- ///
- /// TODO:
- ///
- public int FrameRate { get; set; }
- ///
- /// TODO:
- ///
- public int FrameWidth { get; set; }
- ///
- /// TODO:
- ///
- public int FrameHeight { get; set; }
- ///
- /// TODO:
- ///
- public string Format { get; set; } = "MJPEG";
- ///
- /// TODO:
- ///
- public string HtmlUrl { get; set; } = "";
- ///
- /// TODO:
- ///
- public string MjpegUrl { get; set; } = "";
- ///
- /// TODO:
- ///
- public string SnapshotUrl { get; set; } = "";
- ///
- /// TODO:
- ///
- public string UsbCameraUrl { get; set; } = "";
- }
-
- ///
- /// 摄像头配置请求模型
- ///
- public class CameraConfigRequest
- {
- ///
- /// 摄像头地址
- ///
- [Required]
- [RegularExpression(@"^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$", ErrorMessage = "请输入有效的IP地址")]
- public string Address { get; set; } = "";
-
- ///
- /// 摄像头端口
- ///
- [Required]
- [Range(1, 65535, ErrorMessage = "端口必须在1-65535范围内")]
- public int Port { get; set; }
- }
-
///
/// 分辨率配置请求模型
///
@@ -92,6 +37,14 @@ public class VideoStreamController : ControllerBase
public int Height { get; set; }
}
+ public class AvailableResolutionsResponse
+ {
+ public int Width { get; set; }
+ public int Height { get; set; }
+ public string Name { get; set; } = string.Empty;
+ public string Value => $"{Width}x{Height}";
+ }
+
///
/// 初始化HTTP视频流控制器
///
@@ -102,6 +55,40 @@ public class VideoStreamController : ControllerBase
_videoStreamService = videoStreamService;
}
+ private Optional TryGetBoardId()
+ {
+ var userName = User.FindFirstValue(ClaimTypes.Name);
+ if (string.IsNullOrEmpty(userName))
+ {
+ logger.Error("User name not found in claims.");
+ return Optional.None;
+ }
+
+ var db = new AppDataConnection();
+ if (db == null)
+ {
+ logger.Error("Database connection failed.");
+ return Optional.None;
+ }
+
+ var userRet = db.GetUserByName(userName);
+ if (!userRet.IsSuccessful || !userRet.Value.HasValue)
+ {
+ logger.Error("User not found.");
+ return Optional.None;
+ }
+
+ var user = userRet.Value.Value;
+ var boardId = user.BoardID;
+ if (boardId == Guid.Empty)
+ {
+ logger.Error("No board bound to this user.");
+ return Optional.None;
+ }
+
+ return boardId.ToString();
+ }
+
///
/// 获取 HTTP 视频流服务状态
///
@@ -114,8 +101,6 @@ public class VideoStreamController : ControllerBase
{
try
{
- logger.Info("GetStatus方法被调用,控制器:{Controller},路径:api/VideoStream/Status", this.GetType().Name);
-
// 使用HttpVideoStreamService提供的状态信息
var status = _videoStreamService.GetServiceStatus();
@@ -129,101 +114,18 @@ public class VideoStreamController : ControllerBase
}
}
- ///
- /// 获取 HTTP 视频流信息
- ///
- /// 流信息
- [HttpGet("StreamInfo")]
- [EnableCors("Users")]
- [ProducesResponseType(typeof(StreamInfoResult), StatusCodes.Status200OK)]
- [ProducesResponseType(typeof(Exception), StatusCodes.Status500InternalServerError)]
- public IResult GetStreamInfo()
- {
- try
- {
- logger.Info("获取 HTTP 视频流信息");
- var result = new StreamInfoResult
- {
- FrameRate = _videoStreamService.FrameRate,
- FrameWidth = _videoStreamService.FrameWidth,
- FrameHeight = _videoStreamService.FrameHeight,
- Format = "MJPEG",
- HtmlUrl = $"http://{Global.localhost}:{_videoStreamService.ServerPort}/video-feed.html",
- MjpegUrl = $"http://{Global.localhost}:{_videoStreamService.ServerPort}/video-stream",
- SnapshotUrl = $"http://{Global.localhost}:{_videoStreamService.ServerPort}/snapshot",
- UsbCameraUrl = $"http://{Global.localhost}:{_videoStreamService.ServerPort}/usb-camera"
- };
- return TypedResults.Ok(result);
- }
- catch (Exception ex)
- {
- logger.Error(ex, "获取 HTTP 视频流信息失败");
- return TypedResults.InternalServerError(ex.Message);
- }
- }
-
- ///
- /// 配置摄像头连接参数
- ///
- /// 摄像头配置
- /// 配置结果
- [HttpPost("ConfigureCamera")]
- [EnableCors("Users")]
- [ProducesResponseType(typeof(object), StatusCodes.Status200OK)]
- [ProducesResponseType(typeof(object), StatusCodes.Status400BadRequest)]
- [ProducesResponseType(typeof(Exception), StatusCodes.Status500InternalServerError)]
- public async Task ConfigureCamera([FromBody] CameraConfigRequest config)
- {
- try
- {
- logger.Info("配置摄像头连接: {Address}:{Port}", config.Address, config.Port);
-
- var success = await _videoStreamService.ConfigureCameraAsync(config.Address, config.Port);
-
- if (success)
- {
- return TypedResults.Ok(new
- {
- success = true,
- message = "摄像头配置成功",
- cameraAddress = config.Address,
- cameraPort = config.Port
- });
- }
- else
- {
- return TypedResults.BadRequest(new
- {
- success = false,
- message = "摄像头配置失败",
- cameraAddress = config.Address,
- cameraPort = config.Port
- });
- }
- }
- catch (Exception ex)
- {
- logger.Error(ex, "配置摄像头连接失败");
- return TypedResults.InternalServerError(ex.Message);
- }
- }
-
- ///
- /// 获取当前摄像头配置
- ///
- /// 摄像头配置信息
- [HttpGet("CameraConfig")]
+ [HttpGet("MyEndpoint")]
[EnableCors("Users")]
[ProducesResponseType(typeof(object), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(Exception), StatusCodes.Status500InternalServerError)]
- public IResult GetCameraConfig()
+ public IResult MyEndpoint()
{
try
{
- logger.Info("获取摄像头配置");
- var cameraStatus = _videoStreamService.GetCameraStatus();
+ var boardId = TryGetBoardId().OrThrow(() => new Exception("Board ID not found"));
+ var endpoint = _videoStreamService.GetVideoEndpoint(boardId);
- return TypedResults.Ok(cameraStatus);
+ return TypedResults.Ok(endpoint);
}
catch (Exception ex)
{
@@ -232,22 +134,6 @@ public class VideoStreamController : ControllerBase
}
}
- ///
- /// 控制 HTTP 视频流服务开关
- ///
- /// 是否启用服务
- /// 操作结果
- [HttpPost("SetEnabled")]
- [EnableCors("Users")]
- [ProducesResponseType(typeof(object), StatusCodes.Status200OK)]
- [ProducesResponseType(typeof(Exception), StatusCodes.Status500InternalServerError)]
- public async Task SetEnabled([FromQuery] bool enabled)
- {
- logger.Info("设置视频流服务开关: {Enabled}", enabled);
- await _videoStreamService.SetEnable(enabled);
- return TypedResults.Ok();
- }
-
///
/// 测试 HTTP 视频流连接
///
@@ -260,32 +146,23 @@ public class VideoStreamController : ControllerBase
{
try
{
- logger.Info("测试 HTTP 视频流连接");
+ var boardId = TryGetBoardId().OrThrow(() => new Exception("Board ID not found"));
+ var endpoint = _videoStreamService.GetVideoEndpoint(boardId);
// 尝试通过HTTP请求检查视频流服务是否可访问
bool isConnected = false;
using (var httpClient = new HttpClient())
{
httpClient.Timeout = TimeSpan.FromSeconds(2); // 设置较短的超时时间
- var response = await httpClient.GetAsync($"http://{Global.localhost}:{_videoStreamService.ServerPort}/");
+ var response = await httpClient.GetAsync(endpoint.MjpegUrl);
// 只要能连接上就认为成功,不管返回状态
isConnected = response.IsSuccessStatusCode;
}
- logger.Info("测试摄像头连接");
+ var ret = await _videoStreamService.TestCameraConnection(boardId);
- var (isSuccess, message) = await _videoStreamService.TestCameraConnectionAsync();
-
- return TypedResults.Ok(new
- {
- isConnected = isConnected,
- success = isSuccess,
- message = message,
- cameraAddress = _videoStreamService.CameraAddress,
- cameraPort = _videoStreamService.CameraPort,
- timestamp = DateTime.Now
- });
+ return TypedResults.Ok(ret);
}
catch (Exception ex)
{
@@ -295,6 +172,23 @@ public class VideoStreamController : ControllerBase
}
}
+ [HttpPost("DisableTransmission")]
+ public async Task DisableHdmiTransmission()
+ {
+ try
+ {
+ var boardId = TryGetBoardId().OrThrow(() => new ArgumentException("Board ID is required"));
+
+ await _videoStreamService.DisableHdmiTransmissionAsync(boardId.ToString());
+ return Ok($"HDMI transmission for board {boardId} disabled.");
+ }
+ catch (Exception ex)
+ {
+ logger.Error(ex, $"Failed to disable HDMI transmission for board");
+ return StatusCode(500, $"Error disabling HDMI transmission: {ex.Message}");
+ }
+ }
+
///
/// 设置视频流分辨率
///
@@ -309,16 +203,16 @@ public class VideoStreamController : ControllerBase
{
try
{
- logger.Info($"设置视频流分辨率为 {request.Width}x{request.Height}");
+ var boardId = TryGetBoardId().OrThrow(() => new Exception("Board ID not found"));
- var (isSuccess, message) = await _videoStreamService.SetResolutionAsync(request.Width, request.Height);
+ var ret = await _videoStreamService.SetResolutionAsync(boardId, request.Width, request.Height);
- if (isSuccess)
+ if (ret.IsSuccessful && ret.Value)
{
return TypedResults.Ok(new
{
success = true,
- message = message,
+ message = $"成功设置分辨率为 {request.Width}x{request.Height}",
width = request.Width,
height = request.Height,
timestamp = DateTime.Now
@@ -329,7 +223,7 @@ public class VideoStreamController : ControllerBase
return TypedResults.BadRequest(new
{
success = false,
- message = message,
+ message = ret.Error?.ToString() ?? "未知错误",
timestamp = DateTime.Now
});
}
@@ -341,37 +235,6 @@ public class VideoStreamController : ControllerBase
}
}
- ///
- /// 获取当前分辨率
- ///
- /// 当前分辨率信息
- [HttpGet("Resolution")]
- [EnableCors("Users")]
- [ProducesResponseType(typeof(object), StatusCodes.Status200OK)]
- [ProducesResponseType(typeof(string), StatusCodes.Status500InternalServerError)]
- public IResult GetCurrentResolution()
- {
- try
- {
- logger.Info("获取当前视频流分辨率");
-
- var (width, height) = _videoStreamService.GetCurrentResolution();
-
- return TypedResults.Ok(new
- {
- width = width,
- height = height,
- resolution = $"{width}x{height}",
- timestamp = DateTime.Now
- });
- }
- catch (Exception ex)
- {
- logger.Error(ex, "获取当前分辨率失败");
- return TypedResults.InternalServerError($"获取当前分辨率失败: {ex.Message}");
- }
- }
-
///
/// 获取支持的分辨率列表
///
@@ -382,29 +245,19 @@ public class VideoStreamController : ControllerBase
[ProducesResponseType(typeof(string), StatusCodes.Status500InternalServerError)]
public IResult GetSupportedResolutions()
{
- try
+ // (640, 480, "640x480 (VGA)"),
+ // (960, 540, "960x540 (qHD)"),
+ // (1280, 720, "1280x720 (HD)"),
+ // (1280, 960, "1280x960 (SXGA)"),
+ // (1920, 1080, "1920x1080 (Full HD)")
+ return TypedResults.Ok(new AvailableResolutionsResponse[]
{
- logger.Info("获取支持的分辨率列表");
-
- var resolutions = _videoStreamService.GetSupportedResolutions();
-
- return TypedResults.Ok(new
- {
- resolutions = resolutions.Select(r => new
- {
- width = r.Width,
- height = r.Height,
- name = r.Name,
- value = $"{r.Width}x{r.Height}"
- }),
- timestamp = DateTime.Now
- });
- }
- catch (Exception ex)
- {
- logger.Error(ex, "获取支持的分辨率列表失败");
- return TypedResults.InternalServerError($"获取支持的分辨率列表失败: {ex.Message}");
- }
+ new AvailableResolutionsResponse { Width = 640, Height = 480, Name = "640x480(VGA)" },
+ new AvailableResolutionsResponse { Width = 960, Height = 480, Name = "960x480(qHD)" },
+ new AvailableResolutionsResponse { Width = 1280, Height = 720, Name = "1280x720(HD)" },
+ new AvailableResolutionsResponse { Width = 1280, Height = 960, Name = "1280x960(SXGA)" },
+ new AvailableResolutionsResponse { Width = 1920, Height = 1080, Name = "1920x1080(Full HD)" }
+ });
}
///
@@ -420,9 +273,9 @@ public class VideoStreamController : ControllerBase
{
try
{
- logger.Info("收到初始化自动对焦请求");
+ var boardId = TryGetBoardId().OrThrow(() => new Exception("Board ID not found"));
- var result = await _videoStreamService.InitAutoFocusAsync();
+ var result = await _videoStreamService.InitAutoFocusAsync(boardId);
if (result)
{
@@ -465,9 +318,9 @@ public class VideoStreamController : ControllerBase
{
try
{
- logger.Info("收到执行自动对焦请求");
+ var boardId = TryGetBoardId().OrThrow(() => new Exception("Board ID not found"));
- var result = await _videoStreamService.PerformAutoFocusAsync();
+ var result = await _videoStreamService.PerformAutoFocusAsync(boardId);
if (result)
{
@@ -496,61 +349,4 @@ public class VideoStreamController : ControllerBase
return TypedResults.InternalServerError($"执行自动对焦失败: {ex.Message}");
}
}
-
- ///
- /// 执行一次自动对焦 (GET方式)
- ///
- /// 对焦结果
- [HttpGet("Focus")]
- [EnableCors("Users")]
- [ProducesResponseType(typeof(object), StatusCodes.Status200OK)]
- [ProducesResponseType(typeof(object), StatusCodes.Status400BadRequest)]
- [ProducesResponseType(typeof(string), StatusCodes.Status500InternalServerError)]
- public async Task Focus()
- {
- try
- {
- logger.Info("收到执行一次对焦请求 (GET)");
-
- // 检查摄像头是否已配置
- if (!_videoStreamService.IsCameraConfigured())
- {
- logger.Warn("摄像头未配置,无法执行对焦");
- return TypedResults.BadRequest(new
- {
- success = false,
- message = "摄像头未配置,请先配置摄像头连接",
- timestamp = DateTime.Now
- });
- }
-
- var result = await _videoStreamService.PerformAutoFocusAsync();
-
- if (result)
- {
- logger.Info("对焦执行成功");
- return TypedResults.Ok(new
- {
- success = true,
- message = "对焦执行成功",
- timestamp = DateTime.Now
- });
- }
- else
- {
- logger.Warn("对焦执行失败");
- return TypedResults.BadRequest(new
- {
- success = false,
- message = "对焦执行失败",
- timestamp = DateTime.Now
- });
- }
- }
- catch (Exception ex)
- {
- logger.Error(ex, "执行对焦时发生异常");
- return TypedResults.InternalServerError($"执行对焦失败: {ex.Message}");
- }
- }
}
diff --git a/server/src/Peripherals/CameraClient.cs b/server/src/Peripherals/CameraClient.cs
index c201318..7771d1e 100644
--- a/server/src/Peripherals/CameraClient.cs
+++ b/server/src/Peripherals/CameraClient.cs
@@ -1,6 +1,5 @@
using System.Net;
using DotNext;
-using Peripherals.PowerClient;
using WebProtocol;
namespace Peripherals.CameraClient;
@@ -16,7 +15,7 @@ static class CameraAddr
public const UInt32 CAMERA_POWER = BASE + 0x10; //[0]: rstn, 0 is reset. [8]: power down, 1 is down.
}
-class Camera
+public class Camera
{
private static readonly NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();
@@ -276,7 +275,7 @@ class Camera
{
var currentAddress = (UInt16)(baseAddress + i - 1);
var data = (byte)cmd[i];
-
+
logger.Debug($"ConfigureRegisters: 写入地址=0x{currentAddress:X4}, 数据=0x{data:X2}");
// 准备I2C数据:16位地址 + 8位数据
@@ -322,14 +321,14 @@ class Camera
public async ValueTask> ReadRegister(UInt16 register)
{
var i2c = new Peripherals.I2cClient.I2c(this.address, this.port, this.taskID, this.timeout);
-
+
// Convert 16-bit register address to byte array
var registerBytes = new byte[] { (byte)(register >> 8), (byte)(register & 0xFF) };
-
+
var ret = await i2c.ReadData(CAM_I2C_ADDR, registerBytes, 1, CAM_PROTO);
if (!ret.IsSuccessful)
return new(ret.Error);
-
+
return new Result(ret.Value[0]);
}
@@ -412,25 +411,25 @@ class Camera
[0x3801, unchecked((byte)(hStart & 0xFF))],
[0x3802, unchecked((byte)((vStart >> 8) & 0xFF))],
[0x3803, unchecked((byte)(vStart & 0xFF))],
-
+
// H_END/V_END
[0x3804, unchecked((byte)((hEnd >> 8) & 0xFF))],
[0x3805, unchecked((byte)(hEnd & 0xFF))],
[0x3806, unchecked((byte)((vEnd >> 8) & 0xFF))],
[0x3807, unchecked((byte)(vEnd & 0xFF))],
-
+
// 输出像素个数
[0x3808, unchecked((byte)((dvpHo >> 8) & 0xFF))],
[0x3809, unchecked((byte)(dvpHo & 0xFF))],
[0x380A, unchecked((byte)((dvpVo >> 8) & 0xFF))],
[0x380B, unchecked((byte)(dvpVo & 0xFF))],
-
+
// 总像素
[0x380C, unchecked((byte)((hts >> 8) & 0xFF))],
[0x380D, unchecked((byte)(hts & 0xFF))],
[0x380E, unchecked((byte)((vts >> 8) & 0xFF))],
[0x380F, unchecked((byte)(vts & 0xFF))],
-
+
// H_OFFSET/V_OFFSET
[0x3810, unchecked((byte)((hOffset >> 8) & 0xFF))],
[0x3811, unchecked((byte)(hOffset & 0xFF))],
@@ -521,7 +520,7 @@ class Camera
hOffset: 16, vOffset: 4,
hWindow: 2624, vWindow: 1456
);
-
+
}
///
@@ -537,7 +536,7 @@ class Camera
hOffset: 16, vOffset: 4,
hWindow: 2624, vWindow: 1456
);
-
+
}
///
@@ -637,7 +636,7 @@ class Camera
[0x3008, 0x42] // 休眠命令
};
- return await ConfigureRegisters(sleepRegisters, customDelayMs: 50);
+ return await ConfigureRegisters(sleepRegisters, customDelayMs: 50);
}
///
@@ -1305,7 +1304,7 @@ class Camera
UInt16 firmwareAddr = 0x8000;
var firmwareCommand = new UInt16[1 + OV5640_AF_FIRMWARE.Length];
firmwareCommand[0] = firmwareAddr;
-
+
// 将固件数据复制到命令数组中
for (int i = 0; i < OV5640_AF_FIRMWARE.Length; i++)
{
@@ -1425,7 +1424,7 @@ class Camera
logger.Error($"自动对焦超时,状态: 0x{readResult.Value:X2}");
return new(new Exception($"自动对焦超时,状态: 0x{readResult.Value:X2}"));
}
-
+
await Task.Delay(100);
}
diff --git a/server/src/Services/HttpHdmiVideoStreamService.cs b/server/src/Services/HttpHdmiVideoStreamService.cs
index a19aacf..d6475cf 100644
--- a/server/src/Services/HttpHdmiVideoStreamService.cs
+++ b/server/src/Services/HttpHdmiVideoStreamService.cs
@@ -23,7 +23,7 @@ public class HttpHdmiVideoStreamService : BackgroundService
public override async Task StartAsync(CancellationToken cancellationToken)
{
_httpListener = new HttpListener();
- _httpListener.Prefixes.Add($"http://*:{_serverPort}/");
+ _httpListener.Prefixes.Add($"http://{Global.localhost}:{_serverPort}/");
_httpListener.Start();
logger.Info($"HDMI Video Stream Service started on port {_serverPort}");
diff --git a/server/src/Services/HttpVideoStreamService.cs b/server/src/Services/HttpVideoStreamService.cs
index 16dbd34..f06967e 100644
--- a/server/src/Services/HttpVideoStreamService.cs
+++ b/server/src/Services/HttpVideoStreamService.cs
@@ -1,6 +1,8 @@
using System.Net;
using System.Text;
-using Peripherals.CameraClient; // 添加摄像头客户端引用
+using System.Collections.Concurrent;
+using DotNext;
+using DotNext.Threading;
#if USB_CAMERA
using OpenCvSharp;
@@ -8,30 +10,47 @@ using OpenCvSharp;
namespace server.Services;
+public class VideoStreamClient
+{
+ public string? ClientId { get; set; } = string.Empty;
+ public int FrameWidth { get; set; }
+ public int FrameHeight { get; set; }
+ public int FrameRate { get; set; }
+ public Peripherals.CameraClient.Camera Camera { get; set; }
+ public CancellationTokenSource CTS { get; set; }
+ public readonly AsyncReaderWriterLock Lock = new();
+
+ public VideoStreamClient(
+ string clientId, int width, int height, Peripherals.CameraClient.Camera camera)
+ {
+ ClientId = clientId;
+ FrameWidth = width;
+ FrameHeight = height;
+ FrameRate = 0;
+ Camera = camera;
+ CTS = new CancellationTokenSource();
+ }
+}
+
///
/// 表示摄像头连接状态信息
///
-public class CameraStatus
+public class VideoEndpoint
{
- ///
- /// 摄像头的IP地址
- ///
- public string Address { get; set; } = string.Empty;
+ public string BoardId { get; set; } = "";
+ public string MjpegUrl { get; set; } = "";
+ public string VideoUrl { get; set; } = "";
+ public string SnapshotUrl { get; set; } = "";
///
- /// 摄像头的端口号
+ /// 视频流的帧率(FPS)
///
- public int Port { get; set; }
+ public int FrameRate { get; set; }
///
- /// 是否已配置摄像头
+ /// 视频分辨率(如 640x480)
///
- public bool IsConfigured { get; set; }
-
- ///
- /// 摄像头连接字符串(IP:端口)
- ///
- public string ConnectionString { get; set; } = string.Empty;
+ public string Resolution { get; set; } = string.Empty;
}
///
@@ -50,29 +69,14 @@ public class ServiceStatus
public int ServerPort { get; set; }
///
- /// 视频流的帧率(FPS)
+ /// 当前连接的客户端端点列表
///
- public int FrameRate { get; set; }
-
- ///
- /// 视频分辨率(如 640x480)
- ///
- public string Resolution { get; set; } = string.Empty;
+ public List ClientEndpoints { get; set; } = new();
///
/// 当前连接的客户端数量
///
- public int ConnectedClients { get; set; }
-
- ///
- /// 当前连接的客户端端点列表
- ///
- public List ClientEndpoints { get; set; } = new();
-
- ///
- /// 摄像头连接状态信息
- ///
- public CameraStatus CameraStatus { get; set; } = new();
+ public int ConnectedClientsNum => ClientEndpoints.Count;
}
///
@@ -82,21 +86,11 @@ public class ServiceStatus
public class HttpVideoStreamService : BackgroundService
{
private static readonly NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();
+
private HttpListener? _httpListener;
private readonly int _serverPort = 4321;
- private readonly int _frameRate = 30; // 30 FPS
- // 动态分辨率配置
- private int _frameWidth = 640; // 默认640x480
- private int _frameHeight = 480;
- private readonly object _resolutionLock = new object();
-
- // 摄像头客户端
- private Camera? _camera;
- private bool _cameraEnable = false;
- private string _cameraAddress = "192.168.1.100"; // 默认FPGA地址
- private int _cameraPort = 8888; // 默认端口
- private readonly object _cameraLock = new object();
+ private readonly ConcurrentDictionary _clientDict = new();
// USB Camera 相关
#if USB_CAMERA
@@ -105,192 +99,83 @@ public class HttpVideoStreamService : BackgroundService
private readonly object _usbCameraLock = new object();
#endif
- // 模拟 FPGA 图像数据
- private int _frameCounter = 0;
- private readonly List _activeClients = new List();
- private readonly object _clientsLock = new object();
-
- ///
- /// 获取当前连接的客户端数量
- ///
- public int ConnectedClientsCount { get { return _activeClients.Count; } }
-
- ///
- /// 获取服务端口
- ///
- public int ServerPort => _serverPort;
-
- ///
- /// 获取帧宽度
- ///
- public int FrameWidth => _frameWidth;
-
- ///
- /// 获取帧高度
- ///
- public int FrameHeight => _frameHeight;
-
- ///
- /// 获取帧率
- ///
- public int FrameRate => _frameRate;
-
- ///
- /// 获取当前摄像头地址
- ///
- public string CameraAddress { get { return _cameraAddress; } }
-
- ///
- /// 获取当前摄像头端口
- ///
- public int CameraPort { get { return _cameraPort; } }
-
///
/// 初始化 HttpVideoStreamService
///
- public HttpVideoStreamService()
+ public override async Task StartAsync(CancellationToken cancellationToken)
{
- // 延迟初始化摄像头客户端,直到配置完成
- logger.Info("HttpVideoStreamService 初始化完成,默认摄像头地址: {Address}:{Port}", _cameraAddress, _cameraPort);
+ _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);
}
///
- /// [TODO:description]
+ /// 停止 HTTP 视频流服务
///
- /// [TODO:parameter]
- /// [TODO:return]
- public async Task SetEnable(bool isEnabled)
+ public override async Task StopAsync(CancellationToken cancellationToken)
{
- if (_camera == null)
+ foreach (var clientKey in _clientDict.Keys)
{
- throw new Exception("Please config camera first");
+ var client = _clientDict[clientKey];
+ client.CTS.Cancel();
+ using (await client.Lock.AcquireWriteLockAsync(cancellationToken))
+ {
+ await client.Camera.EnableHardwareTrans(false);
+ }
}
- _cameraEnable = isEnabled;
- // if (_cameraEnable) await _camera.WakeUp();
- // else await _camera.Sleep();
- await _camera.EnableHardwareTrans(_cameraEnable);
+ _clientDict.Clear();
+ await base.StopAsync(cancellationToken);
}
- ///
- /// 配置摄像头连接参数
- ///
- /// 摄像头IP地址
- /// 摄像头端口
- /// 配置是否成功
- public async Task ConfigureCameraAsync(string address, int port)
+ private Optional TryGetClient(string boardId)
{
- if (string.IsNullOrWhiteSpace(address))
+ if (_clientDict.TryGetValue(boardId, out var client))
{
- logger.Error("摄像头地址不能为空");
- return false;
- }
-
- if (port <= 0 || port > 65535)
- {
- logger.Error("摄像头端口必须在1-65535范围内");
- return false;
- }
-
- try
- {
- lock (_cameraLock)
- {
- // 关闭现有连接
- if (_camera != null)
- {
- logger.Info("关闭现有摄像头连接");
- // Camera doesn't have Dispose method, set to null
- _camera = null;
- }
-
- // 更新配置
- _cameraAddress = address;
- _cameraPort = port;
-
- // 创建新的摄像头客户端
- _camera = new Camera(_cameraAddress, _cameraPort);
-
- logger.Info("摄像头配置已更新: {Address}:{Port}", _cameraAddress, _cameraPort);
- }
-
- // Init Camera
- {
- var ret = await _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!");
- }
- }
- return true;
- }
- catch (Exception ex)
- {
- logger.Error(ex, "配置摄像头连接时发生错误");
- return false;
+ return client;
}
+ return null;
}
- ///
- /// 测试摄像头连接
- ///
- /// 连接测试结果
- public async Task<(bool IsSuccess, string Message)> TestCameraConnectionAsync()
+ private async Task GetOrCreateClientAsync(string boardId, int initWidth, int initHeight)
{
- try
+ if (_clientDict.TryGetValue(boardId, out var client))
{
- Camera? testCamera = null;
-
- lock (_cameraLock)
- {
- if (_camera == null)
- {
- return (false, "摄像头未配置");
- }
- testCamera = _camera;
- }
-
- // 尝试读取一帧数据来测试连接
- var result = await testCamera.ReadFrame();
-
- if (result.IsSuccessful)
- {
- logger.Info("摄像头连接测试成功: {Address}:{Port}", _cameraAddress, _cameraPort);
- return (true, "连接成功");
- }
- else
- {
- logger.Warn("摄像头连接测试失败: {Error}", result.Error);
- return (false, result.Error.ToString());
- }
+ // 可在此处做分辨率/Camera等配置更新
+ return client;
}
- catch (Exception ex)
+
+ var db = new Database.AppDataConnection();
+ if (db == null)
{
- logger.Error(ex, "摄像头连接测试出错");
- return (false, ex.Message);
+ logger.Error("Failed to create HdmiIn instance");
+ return null;
}
+
+ var boardRet = db.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;
+
+ var camera = new Peripherals.CameraClient.Camera(board.IpAddr, board.Port);
+ var ret = await camera.Init();
+ if (!ret.IsSuccessful || !ret.Value)
+ {
+ logger.Error("Camera Init Failed!");
+ return null;
+ }
+
+ client = new VideoStreamClient(boardId, initWidth, initHeight, camera);
+ _clientDict[boardId] = client;
+ return client;
}
- ///
- /// 获取摄像头连接状态
- ///
- /// 连接状态信息
- public CameraStatus GetCameraStatus()
- {
- return new CameraStatus
- {
- Address = _cameraAddress,
- Port = _cameraPort,
- IsConfigured = _camera != null,
- ConnectionString = $"{_cameraAddress}:{_cameraPort}"
- };
- }
///
/// 执行 HTTP 视频流服务
@@ -299,106 +184,95 @@ public class HttpVideoStreamService : BackgroundService
/// 任务
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
- try
- {
- logger.Info("启动 HTTP 视频流服务,端口: {Port}", _serverPort);
-
- // 初始化默认摄像头连接
- await ConfigureCameraAsync(_cameraAddress, _cameraPort);
-
- // 创建 HTTP 监听器
- _httpListener = new HttpListener();
- _httpListener.Prefixes.Add($"http://{Global.localhost}:{_serverPort}/");
- _httpListener.Start();
-
- logger.Info("HTTP 视频流服务已启动,监听端口: {Port}", _serverPort);
-
- // 开始接受客户端连接
- _ = Task.Run(() => AcceptClientsAsync(stoppingToken), stoppingToken);
-
- // 开始生成视频帧
- while (!stoppingToken.IsCancellationRequested)
- {
- if (_cameraEnable)
- {
- await GenerateVideoFrames(stoppingToken);
- }
- else
- {
- await Task.Delay(500, stoppingToken);
- }
- }
- }
- catch (HttpListenerException ex)
- {
- logger.Error(ex, "HTTP 视频流服务启动失败,请确保您有管理员权限或使用netsh配置URL前缀权限");
- }
- catch (Exception ex)
- {
- logger.Error(ex, "HTTP 视频流服务启动失败");
- }
- }
-
- private async Task AcceptClientsAsync(CancellationToken cancellationToken)
- {
- while (!cancellationToken.IsCancellationRequested && _httpListener != null && _httpListener.IsListening)
+ while (!stoppingToken.IsCancellationRequested)
{
+ if (_httpListener == null) continue;
try
{
- // 等待客户端连接
- var context = await _httpListener.GetContextAsync();
- var request = context.Request;
- var response = context.Response;
-
- logger.Info("新HTTP客户端连接: {RemoteEndPoint}", request.RemoteEndPoint);
- // 处理不同的请求路径
- var requestPath = request.Url?.AbsolutePath ?? "/";
-
- if (requestPath == "/video-stream")
+ logger.Debug("Waiting for HTTP request...");
+ var contextTask = _httpListener.GetContextAsync();
+ var completedTask = await Task.WhenAny(contextTask, Task.Delay(-1, stoppingToken));
+ if (completedTask == contextTask)
{
- // MJPEG 流请求(FPGA)
- _ = Task.Run(() => HandleMjpegStreamAsync(response, cancellationToken), cancellationToken);
- }
-#if USB_CAMERA
- else if (requestPath == "/usb-camera")
- {
- // USB Camera MJPEG流请求
- _ = Task.Run(() => HandleUsbCameraStreamAsync(response, cancellationToken), cancellationToken);
- }
-#endif
- else if (requestPath == "/snapshot")
- {
- // 单帧图像请求
- await HandleSnapshotRequestAsync(response, cancellationToken);
- }
- else if (requestPath == "/video-feed.html")
- {
- // HTML页面请求
- await SendVideoHtmlPageAsync(response);
+ var context = contextTask.Result;
+ logger.Debug($"Received request: {context.Request.Url?.AbsolutePath}");
+ if (context != null)
+ _ = HandleRequestAsync(context, stoppingToken);
}
else
{
- // 默认返回简单的HTML页面,提供链接到视频页面
- await SendIndexHtmlPageAsync(response);
+ break;
}
}
- catch (HttpListenerException)
- {
- // HTTP监听器可能已停止
- break;
- }
- catch (ObjectDisposedException)
- {
- // 对象可能已被释放
- break;
- }
catch (Exception ex)
{
- logger.Error(ex, "接受HTTP客户端连接时发生错误");
+ logger.Error(ex, "Error in GetContextAsync");
+ break;
}
}
}
+ private async Task HandleRequestAsync(HttpListenerContext context, CancellationToken cancellationToken)
+ {
+ var path = context.Request.Url?.AbsolutePath ?? "/";
+ var boardId = context.Request.QueryString["board"];
+ var width = int.TryParse(context.Request.QueryString["width"], out var w) ? w : 640;
+ var height = int.TryParse(context.Request.QueryString["height"], out var h) ? h : 480;
+
+ if (string.IsNullOrEmpty(boardId))
+ {
+ await SendErrorAsync(context.Response, "Missing clientId");
+ return;
+ }
+
+ var client = await GetOrCreateClientAsync(boardId, width, height);
+ if (client == null)
+ {
+ await SendErrorAsync(context.Response, "Invalid clientId or camera not available");
+ return;
+ }
+
+ var clientToken = client.CTS.Token;
+ try
+ {
+ logger.Info("新HTTP客户端连接: {RemoteEndPoint}", context.Request.RemoteEndPoint);
+
+ if (path == "/video-stream")
+ {
+ // MJPEG 流请求(FPGA)
+ await HandleMjpegStreamAsync(context.Response, client, cancellationToken);
+ }
+#if USB_CAMERA
+ else if (requestPath == "/usb-camera")
+ {
+ // USB Camera MJPEG流请求
+ await HandleUsbCameraStreamAsync(response, cancellationToken);
+ }
+#endif
+ else if (path == "/snapshot")
+ {
+ // 单帧图像请求
+ await HandleSnapshotRequestAsync(context.Response, client, cancellationToken);
+ }
+ else
+ {
+ // 默认返回简单的HTML页面,提供链接到视频页面
+ await SendIndexHtmlPageAsync(context.Response);
+ }
+ }
+ catch (Exception ex)
+ {
+ logger.Error(ex, "接受HTTP客户端连接时发生错误");
+ }
+ }
+
+ private async Task SendErrorAsync(HttpListenerResponse response, string message)
+ {
+ response.StatusCode = 400;
+ await response.OutputStream.WriteAsync(System.Text.Encoding.UTF8.GetBytes(message));
+ response.Close();
+ }
+
// USB Camera MJPEG流处理
#if USB_CAMERA
private async Task HandleUsbCameraStreamAsync(HttpListenerResponse response, CancellationToken cancellationToken)
@@ -480,107 +354,54 @@ public class HttpVideoStreamService : BackgroundService
}
#endif
- private async Task HandleMjpegStreamAsync(HttpListenerResponse response, CancellationToken cancellationToken)
+ private async Task HandleSnapshotRequestAsync(HttpListenerResponse response, VideoStreamClient client, CancellationToken cancellationToken)
{
- try
+ // 读取 Camera 快照,返回 JPEG
+ var frameResult = await client.Camera.ReadFrame();
+ if (!frameResult.IsSuccessful || frameResult.Value == null)
{
- // 设置MJPEG流的响应头
- response.ContentType = "multipart/x-mixed-replace; boundary=--boundary";
- response.Headers.Add("Cache-Control", "no-cache, no-store, must-revalidate");
- response.Headers.Add("Pragma", "no-cache");
- response.Headers.Add("Expires", "0");
-
- // 跟踪活跃的客户端
- lock (_clientsLock)
- {
- _activeClients.Add(response);
- }
-
- logger.Debug("已启动MJPEG流,客户端: {RemoteEndPoint}", response.OutputStream?.GetHashCode() ?? 0);
-
- // 保持连接直到取消或出错
- try
- {
- while (!cancellationToken.IsCancellationRequested)
- {
- await Task.Delay(100, cancellationToken); // 简单的保活循环
- }
- }
- catch (TaskCanceledException)
- {
- // 预期的取消
- }
-
- logger.Debug("MJPEG流已结束,客户端: {ClientId}", response.OutputStream?.GetHashCode() ?? 0);
+ response.StatusCode = 500;
+ await response.OutputStream.WriteAsync(Encoding.UTF8.GetBytes("Failed to get snapshot"));
+ response.Close();
+ return;
}
- catch (Exception ex)
+ var jpegResult = Common.Image.ConvertRGB24ToJpeg(frameResult.Value, client.FrameWidth, client.FrameHeight, 80);
+ if (!jpegResult.IsSuccessful)
{
- logger.Error(ex, "处理MJPEG流时出错");
- }
- finally
- {
- lock (_clientsLock)
- {
- _activeClients.Remove(response);
- }
-
- try
- {
- response.Close();
- }
- catch
- {
- // 忽略关闭时的错误
- }
+ response.StatusCode = 500;
+ await response.OutputStream.WriteAsync(Encoding.UTF8.GetBytes("JPEG conversion failed"));
+ response.Close();
+ return;
}
+ response.ContentType = "image/jpeg";
+ response.ContentLength64 = jpegResult.Value.Length;
+ await response.OutputStream.WriteAsync(jpegResult.Value, 0, jpegResult.Value.Length, cancellationToken);
+ response.Close();
}
- private async Task HandleSnapshotRequestAsync(HttpListenerResponse response, CancellationToken cancellationToken)
+ private async Task HandleMjpegStreamAsync(HttpListenerResponse response, VideoStreamClient client, CancellationToken cancellationToken)
{
- try
+ response.ContentType = "multipart/x-mixed-replace; boundary=--boundary";
+ response.Headers.Add("Cache-Control", "no-cache, no-store, must-revalidate");
+ response.Headers.Add("Pragma", "no-cache");
+ response.Headers.Add("Expires", "0");
+
+ while (!cancellationToken.IsCancellationRequested)
{
- // 获取当前帧
- var imageData = await GetFPGAImageData();
+ var frameResult = await client.Camera.ReadFrame();
+ if (!frameResult.IsSuccessful || frameResult.Value == null) continue;
+ var jpegResult = Common.Image.ConvertRGB24ToJpeg(frameResult.Value, client.FrameWidth, client.FrameHeight, 80);
+ if (!jpegResult.IsSuccessful) continue;
- // 获取当前分辨率
- int currentWidth, currentHeight;
- lock (_resolutionLock)
- {
- currentWidth = _frameWidth;
- currentHeight = _frameHeight;
- }
-
- // 直接使用Common.Image.ConvertRGB24ToJpeg进行转换
- var jpegResult = Common.Image.ConvertRGB24ToJpeg(imageData, currentWidth, currentHeight, 80);
- if (!jpegResult.IsSuccessful)
- {
- logger.Error("RGB24转JPEG失败: {Error}", jpegResult.Error);
- response.StatusCode = 500;
- response.Close();
- return;
- }
-
- var jpegData = jpegResult.Value;
-
- // 设置响应头
- response.ContentType = "image/jpeg";
- response.ContentLength64 = jpegData.Length;
- response.Headers.Add("Cache-Control", "no-cache, no-store, must-revalidate");
-
- // 发送JPEG数据
- await response.OutputStream.WriteAsync(jpegData, 0, jpegData.Length, cancellationToken);
+ var header = Encoding.ASCII.GetBytes("--boundary\r\nContent-Type: image/jpeg\r\nContent-Length: " + jpegResult.Value.Length + "\r\n\r\n");
+ await response.OutputStream.WriteAsync(header, 0, header.Length, cancellationToken);
+ await response.OutputStream.WriteAsync(jpegResult.Value, 0, jpegResult.Value.Length, cancellationToken);
+ await response.OutputStream.WriteAsync(new byte[] { 0x0D, 0x0A }, 0, 2, cancellationToken);
await response.OutputStream.FlushAsync(cancellationToken);
- logger.Debug("已发送快照图像,大小:{Size} 字节", jpegData.Length);
- }
- catch (Exception ex)
- {
- logger.Error(ex, "处理快照请求时出错");
- }
- finally
- {
- response.Close();
+ await Task.Delay(1000 / client.FrameWidth, cancellationToken);
}
+ response.Close();
}
private async Task SendVideoHtmlPageAsync(HttpListenerResponse response)
@@ -667,147 +488,52 @@ public class HttpVideoStreamService : BackgroundService
response.Close();
}
- private async Task GenerateVideoFrames(CancellationToken cancellationToken)
- {
- var frameInterval = TimeSpan.FromMilliseconds(1000.0 / _frameRate);
- var lastFrameTime = DateTime.UtcNow;
-
- while (!cancellationToken.IsCancellationRequested && _cameraEnable)
- {
- try
- {
- var frameStartTime = DateTime.UtcNow;
-
- // 从 FPGA 获取图像数据
- var imageData = await GetFPGAImageData();
-
- var imageAcquireTime = DateTime.UtcNow;
-
- // 如果有图像数据,立即开始广播(不等待)
- if (imageData != null && imageData.Length > 0)
- {
- // 异步广播帧,不阻塞下一帧的获取
- _ = Task.Run(async () =>
- {
- try
- {
- await BroadcastFrameAsync(imageData, cancellationToken);
- }
- catch (Exception ex)
- {
- logger.Error(ex, "异步广播帧时发生错误");
- }
- }, cancellationToken);
-
- _frameCounter++;
-
- var frameEndTime = DateTime.UtcNow;
- var frameProcessingTime = (frameEndTime - frameStartTime).TotalMilliseconds;
- var imageAcquireElapsed = (imageAcquireTime - frameStartTime).TotalMilliseconds;
-
- if (_frameCounter % 30 == 0) // 每秒记录一次性能信息
- {
- logger.Debug("帧 {FrameNumber} 性能统计 - 图像获取: {AcquireTime:F1}ms, 总处理: {ProcessTime:F1}ms",
- _frameCounter, imageAcquireElapsed, frameProcessingTime);
- }
- }
-
- // 动态调整延迟 - 基于实际处理时间
- var elapsed = (DateTime.UtcNow - lastFrameTime).TotalMilliseconds;
- var targetInterval = frameInterval.TotalMilliseconds;
- var remainingDelay = Math.Max(0, targetInterval - elapsed);
-
- if (remainingDelay > 0)
- {
- await Task.Delay(TimeSpan.FromMilliseconds(remainingDelay), cancellationToken);
- }
-
- lastFrameTime = DateTime.UtcNow;
- }
- catch (OperationCanceledException)
- {
- break;
- }
- catch (Exception ex)
- {
- logger.Error(ex, "生成视频帧时发生错误");
- await Task.Delay(100, cancellationToken); // 减少错误恢复延迟
- }
- }
- }
-
///
/// 从 FPGA 获取图像数据
/// 实际从摄像头读取 RGB565 格式数据并转换为 RGB24
///
- private async Task GetFPGAImageData()
+ private async Task GetFPGAImageData(
+ VideoStreamClient client, CancellationToken cancellationToken = default)
{
- var startTime = DateTime.UtcNow;
- Camera? currentCamera = null;
-
- lock (_cameraLock)
- {
- currentCamera = _camera;
- }
-
- if (currentCamera == null)
- {
- logger.Error("摄像头客户端未初始化");
- return new byte[0];
- }
-
try
{
- // 获取当前分辨率
- int currentWidth, currentHeight;
- lock (_resolutionLock)
+ using (await client.Lock.AcquireWriteLockAsync(cancellationToken))
{
- currentWidth = _frameWidth;
- currentHeight = _frameHeight;
+ // 从摄像头读取帧数据
+ var readStartTime = DateTime.UtcNow;
+ var result = await client.Camera.ReadFrame();
+ var readEndTime = DateTime.UtcNow;
+ var readTime = (readEndTime - readStartTime).TotalMilliseconds;
+
+ if (!result.IsSuccessful)
+ {
+ logger.Error("读取摄像头帧数据失败: {Error}", result.Error);
+ return new byte[0];
+ }
+
+ var rgb565Data = result.Value;
+
+ // 验证数据长度是否正确
+ if (!Common.Image.ValidateImageDataLength(rgb565Data, client.FrameWidth, client.FrameHeight, 2))
+ {
+ logger.Warn("摄像头数据长度不匹配,期望: {Expected}, 实际: {Actual}",
+ client.FrameWidth * client.FrameHeight * 2, rgb565Data.Length);
+ }
+
+ // 将 RGB565 转换为 RGB24
+ var convertStartTime = DateTime.UtcNow;
+ var rgb24Result = Common.Image.ConvertRGB565ToRGB24(rgb565Data, client.FrameWidth, client.FrameHeight, isLittleEndian: false);
+ var convertEndTime = DateTime.UtcNow;
+ var convertTime = (convertEndTime - convertStartTime).TotalMilliseconds;
+
+ if (!rgb24Result.IsSuccessful)
+ {
+ logger.Error("RGB565转RGB24失败: {Error}", rgb24Result.Error);
+ return new byte[0];
+ }
+
+ return rgb24Result.Value;
}
-
- // 从摄像头读取帧数据
- var readStartTime = DateTime.UtcNow;
- var result = await currentCamera.ReadFrame();
- var readEndTime = DateTime.UtcNow;
- var readTime = (readEndTime - readStartTime).TotalMilliseconds;
-
- if (!result.IsSuccessful)
- {
- logger.Error("读取摄像头帧数据失败: {Error}", result.Error);
- return new byte[0];
- }
-
- var rgb565Data = result.Value;
-
- // 验证数据长度是否正确
- if (!Common.Image.ValidateImageDataLength(rgb565Data, currentWidth, currentHeight, 2))
- {
- logger.Warn("摄像头数据长度不匹配,期望: {Expected}, 实际: {Actual}",
- currentWidth * currentHeight * 2, rgb565Data.Length);
- }
-
- // 将 RGB565 转换为 RGB24
- var convertStartTime = DateTime.UtcNow;
- var rgb24Result = Common.Image.ConvertRGB565ToRGB24(rgb565Data, currentWidth, currentHeight, isLittleEndian: false);
- var convertEndTime = DateTime.UtcNow;
- var convertTime = (convertEndTime - convertStartTime).TotalMilliseconds;
-
- if (!rgb24Result.IsSuccessful)
- {
- logger.Error("RGB565转RGB24失败: {Error}", rgb24Result.Error);
- return new byte[0];
- }
-
- var totalTime = (DateTime.UtcNow - startTime).TotalMilliseconds;
-
- if (_frameCounter % 30 == 0) // 每秒更新一次日志
- {
- logger.Debug("帧 {FrameNumber} 数据获取性能 - 读取: {ReadTime:F1}ms, 转换: {ConvertTime:F1}ms, 总计: {TotalTime:F1}ms, RGB565: {RGB565Size} 字节, RGB24: {RGB24Size} 字节",
- _frameCounter, readTime, convertTime, totalTime, rgb565Data.Length, rgb24Result.Value.Length);
- }
-
- return rgb24Result.Value;
}
catch (Exception ex)
{
@@ -816,282 +542,61 @@ public class HttpVideoStreamService : BackgroundService
}
}
- ///
- /// 向所有连接的客户端广播帧数据
- ///
- private async Task BroadcastFrameAsync(byte[] frameData, CancellationToken cancellationToken)
- {
- if (frameData == null || frameData.Length == 0)
- {
- logger.Warn("尝试广播空帧数据");
- return;
- }
-
- // 获取当前分辨率
- int currentWidth, currentHeight;
- lock (_resolutionLock)
- {
- currentWidth = _frameWidth;
- currentHeight = _frameHeight;
- }
-
- // 直接使用Common.Image.ConvertRGB24ToJpeg进行转换
- var jpegResult = Common.Image.ConvertRGB24ToJpeg(frameData, currentWidth, currentHeight, 80);
- if (!jpegResult.IsSuccessful)
- {
- logger.Error("RGB24转JPEG失败: {Error}", jpegResult.Error);
- return;
- }
-
- var jpegData = jpegResult.Value;
-
- // 使用Common中的方法准备MJPEG帧数据
- var mjpegFrameHeader = Common.Image.CreateMjpegFrameHeader(jpegData.Length);
- var mjpegFrameFooter = Common.Image.CreateMjpegFrameFooter();
-
- var clientsToRemove = new List();
- var clientsToProcess = new List();
-
- // 获取当前连接的客户端列表
- lock (_clientsLock)
- {
- clientsToProcess.AddRange(_activeClients);
- }
-
- if (clientsToProcess.Count == 0)
- {
- return; // 没有活跃客户端
- }
-
- // 向每个活跃的客户端并行发送帧
- var sendTasks = clientsToProcess.Select(async client =>
- {
- try
- {
- // 发送帧头部
- await client.OutputStream.WriteAsync(mjpegFrameHeader, 0, mjpegFrameHeader.Length, cancellationToken);
-
- // 发送JPEG数据
- await client.OutputStream.WriteAsync(jpegData, 0, jpegData.Length, cancellationToken);
-
- // 发送结尾换行符
- await client.OutputStream.WriteAsync(mjpegFrameFooter, 0, mjpegFrameFooter.Length, cancellationToken);
-
- // 确保数据立即发送
- await client.OutputStream.FlushAsync(cancellationToken);
-
- return (client, success: true, error: (Exception?)null);
- }
- catch (Exception ex)
- {
- return (client, success: false, error: ex);
- }
- });
-
- // 等待所有发送任务完成
- var results = await Task.WhenAll(sendTasks);
-
- // 处理发送结果
- foreach (var (client, success, error) in results)
- {
- if (!success)
- {
- logger.Debug("发送帧数据时出错: {Error}", error?.Message ?? "未知错误");
- clientsToRemove.Add(client);
- }
- }
-
- if (_frameCounter % 30 == 0 && clientsToProcess.Count > 0) // 每秒记录一次日志
- {
- logger.Debug("已向 {ClientCount} 个客户端发送第 {FrameNumber} 帧,大小:{Size} 字节",
- clientsToProcess.Count, _frameCounter, jpegData.Length);
- }
-
- // 移除断开连接的客户端
- if (clientsToRemove.Count > 0)
- {
- lock (_clientsLock)
- {
- foreach (var client in clientsToRemove)
- {
- _activeClients.Remove(client);
- try { client.Close(); }
- catch { /* 忽略关闭错误 */ }
- }
- }
-
- logger.Info("已移除 {Count} 个断开连接的客户端,当前连接数: {ActiveCount}",
- clientsToRemove.Count, _activeClients.Count);
- }
- }
-
- ///
- /// 获取连接的客户端端点列表
- ///
- public List GetConnectedClientEndpoints()
- {
- List endpoints = new List();
-
- lock (_clientsLock)
- {
- foreach (var client in _activeClients)
- {
- endpoints.Add($"Client-{client.OutputStream?.GetHashCode() ?? 0}");
- }
- }
-
- return endpoints;
- }
-
- ///
- /// 获取服务状态信息
- ///
- public ServiceStatus GetServiceStatus()
- {
- var cameraStatus = GetCameraStatus();
-
- return new ServiceStatus
- {
- IsRunning = (_httpListener?.IsListening ?? false) && _cameraEnable,
- ServerPort = _serverPort,
- FrameRate = _frameRate,
- Resolution = $"{_frameWidth}x{_frameHeight}",
- ConnectedClients = ConnectedClientsCount,
- ClientEndpoints = GetConnectedClientEndpoints(),
- CameraStatus = cameraStatus
- };
- }
-
-
- ///
- /// 停止 HTTP 视频流服务
- ///
- public override async Task StopAsync(CancellationToken cancellationToken)
- {
- logger.Info("正在停止 HTTP 视频流服务...");
-
- _cameraEnable = false;
-
- if (_httpListener != null && _httpListener.IsListening)
- {
- _httpListener.Stop();
- _httpListener.Close();
- }
-
- // 关闭所有客户端连接
- lock (_clientsLock)
- {
- foreach (var client in _activeClients)
- {
- try { client.Close(); }
- catch { /* 忽略关闭错误 */ }
- }
- _activeClients.Clear();
- }
-
- // 关闭摄像头连接
- lock (_cameraLock)
- {
- _camera = null;
- }
-
- await base.StopAsync(cancellationToken);
-
- logger.Info("HTTP 视频流服务已停止");
- }
-
///
/// 设置视频流分辨率
///
+ /// 板卡ID
/// 宽度
/// 高度
+ /// 超时时间(毫秒)
+ /// 取消令牌
/// 设置结果
- public async Task<(bool IsSuccess, string Message)> SetResolutionAsync(int width, int height)
+ public async Task> SetResolutionAsync(
+ string boardId, int width, int height,
+ int timeout = 100, CancellationToken cancellationToken = default)
{
try
{
- logger.Info($"正在设置视频流分辨率为 {width}x{height}");
+ var client = TryGetClient(boardId).OrThrow(() => new Exception($"无法获取摄像头客户端: {boardId}"));
- Camera? currentCamera = null;
- lock (_cameraLock)
+ using (await client.Lock.AcquireWriteLockAsync(TimeSpan.FromMilliseconds(timeout), cancellationToken))
{
- currentCamera = _camera;
- }
+ var currentCamera = client.Camera;
+ if (currentCamera == null)
+ {
+ var message = $"获取摄像头失败";
+ logger.Error(message);
+ return new(new Exception(message));
+ }
- if (currentCamera == null)
- {
- var message = "摄像头未配置,无法设置分辨率";
- logger.Error(message);
- return (false, message);
- }
+ // 设置摄像头分辨率
+ var ret = await currentCamera.ChangeResolution(width, height);
+ if (!ret.IsSuccessful)
+ {
+ var message = $"设置摄像头分辨率失败: {ret.Error}";
+ logger.Error(message);
+ return new(new Exception(message));
+ }
- // 设置摄像头分辨率
- var cameraResult = await currentCamera.ChangeResolution(width, height);
- if (!cameraResult.IsSuccessful)
- {
- var message = $"设置摄像头分辨率失败: {cameraResult.Error}";
- logger.Error(message);
- return (false, message);
- }
+ if (!ret.Value)
+ {
+ logger.Warn($"设置摄像头分辨率失败");
+ return false;
+ }
- // 更新HTTP服务的分辨率配置
- lock (_resolutionLock)
- {
- _frameWidth = width;
- _frameHeight = height;
- }
+ // 更新HTTP服务的分辨率配置
+ client.FrameWidth = width;
+ client.FrameHeight = height;
- var successMessage = $"视频流分辨率已成功设置为 {width}x{height}";
- logger.Info(successMessage);
- return (true, successMessage);
+ logger.Info($"视频流分辨率已成功设置为 {width}x{height}");
+ return true;
+ }
}
catch (Exception ex)
{
var message = $"设置分辨率时发生错误: {ex.Message}";
logger.Error(ex, message);
- return (false, message);
- }
- }
-
- ///
- /// 获取当前分辨率
- ///
- /// 当前分辨率(宽度, 高度)
- public (int Width, int Height) GetCurrentResolution()
- {
- lock (_resolutionLock)
- {
- return (_frameWidth, _frameHeight);
- }
- }
-
- ///
- /// 获取支持的分辨率列表
- ///
- /// 支持的分辨率列表
- public List<(int Width, int Height, string Name)> GetSupportedResolutions()
- {
- return new List<(int, int, string)>
- {
- (640, 480, "640x480 (VGA)"),
- (960, 540, "960x540 (qHD)"),
- (1280, 720, "1280x720 (HD)"),
- (1280, 960, "1280x960 (SXGA)"),
- (1920, 1080, "1920x1080 (Full HD)")
- };
- }
-
- #region 自动对焦功能
-
- ///
- /// 检查摄像头是否已配置
- ///
- /// 是否已配置
- public bool IsCameraConfigured()
- {
- lock (_cameraLock)
- {
- return _camera != null && !string.IsNullOrEmpty(_cameraAddress);
+ return new(new Exception(message));
}
}
@@ -1099,37 +604,33 @@ public class HttpVideoStreamService : BackgroundService
/// 初始化摄像头自动对焦功能
///
/// 初始化结果
- public async Task InitAutoFocusAsync()
+ public async Task InitAutoFocusAsync(
+ string boardId, int timeout = 1000, CancellationToken cancellationToken = default)
{
try
{
- lock (_cameraLock)
+ var client = TryGetClient(boardId).OrThrow(() => new Exception($"无法获取摄像头客户端: {boardId}"));
+
+ using (await client.Lock.AcquireWriteLockAsync(
+ TimeSpan.FromMilliseconds(timeout), cancellationToken))
{
- if (_camera == null)
+ var result = await client.Camera.InitAutoFocus();
+
+ if (result.IsSuccessful && result.Value)
{
- logger.Error("摄像头未配置,无法初始化自动对焦");
+ logger.Info($"Board{boardId}摄像头自动对焦功能初始化成功");
+ return true;
+ }
+ else
+ {
+ logger.Error($"Board{boardId}摄像头自动对焦功能初始化失败: {result.Error?.Message ?? "未知错误"}");
return false;
}
}
-
- logger.Info("开始初始化摄像头自动对焦功能");
-
- var result = await _camera!.InitAutoFocus();
-
- if (result.IsSuccessful && result.Value)
- {
- logger.Info("摄像头自动对焦功能初始化成功");
- return true;
- }
- else
- {
- logger.Error($"摄像头自动对焦功能初始化失败: {result.Error?.Message ?? "未知错误"}");
- return false;
- }
}
catch (Exception ex)
{
- logger.Error(ex, "初始化摄像头自动对焦功能时发生异常");
+ logger.Error(ex, $"Board{boardId}初始化摄像头自动对焦功能时发生异常");
return false;
}
}
@@ -1138,40 +639,108 @@ public class HttpVideoStreamService : BackgroundService
/// 执行摄像头自动对焦
///
/// 对焦结果
- public async Task PerformAutoFocusAsync()
+ public async Task PerformAutoFocusAsync(
+ string boardId, int timeout = 1000, CancellationToken cancellationToken = default)
{
try
{
- lock (_cameraLock)
- {
- if (_camera == null)
- {
- logger.Error("摄像头未配置,无法执行自动对焦");
- return false;
- }
- }
+ var client = TryGetClient(boardId).OrThrow(() => new Exception($"无法获取摄像头客户端: {boardId}"));
- logger.Info("开始执行摄像头自动对焦");
+ logger.Info($"Board{boardId}开始执行摄像头自动对焦");
- var result = await _camera!.PerformAutoFocus();
+ var result = await client.Camera.PerformAutoFocus();
if (result.IsSuccessful && result.Value)
{
- logger.Info("摄像头自动对焦执行成功");
+ logger.Info($"Board{boardId}摄像头自动对焦成功");
return true;
}
else
{
- logger.Error($"摄像头自动对焦执行失败: {result.Error?.Message ?? "未知错误"}");
+ logger.Error($"Board{boardId}摄像头自动对焦执行失败: {result.Error?.Message ?? "未知错误"}");
return false;
}
}
catch (Exception ex)
{
- logger.Error(ex, "执行摄像头自动对焦时发生异常");
+ logger.Error(ex, $"Board{boardId}执行摄像头自动对焦时发生异常");
return false;
}
}
- #endregion
+ 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)
+ {
+ try
+ {
+ var client = TryGetClient(boardId).OrThrow(() => new Exception($"无法获取摄像头客户端: {boardId}"));
+
+ using (await client.Lock.AcquireWriteLockAsync())
+ {
+ var camera = client.Camera;
+ var disableResult = await camera.EnableHardwareTrans(false);
+ if (disableResult.IsSuccessful && disableResult.Value)
+ logger.Info($"Successfully disabled camera {boardId} hardware transmission");
+ else
+ logger.Error($"Failed to disable camera {boardId} hardware transmission: {disableResult.Error}");
+ }
+ }
+ catch (Exception ex)
+ {
+ logger.Error(ex, $"Exception occurred while disabling HDMI transmission for camera {boardId}");
+ }
+ }
+
+ public async ValueTask TestCameraConnection(string boardId)
+ {
+ try
+ {
+ var client = TryGetClient(boardId).OrThrow(() => new Exception($"无法获取摄像头客户端: {boardId}"));
+
+ var imageData = await GetFPGAImageData(client);
+ if (imageData == null || imageData.Length == 0)
+ return false;
+
+ return true;
+ }
+ catch (Exception ex)
+ {
+ logger.Error(ex, $"Board{boardId}执行摄像头自动对焦时发生异常");
+ return false;
+ }
+ }
}
diff --git a/server/src/Services/ProgressTrackerService.cs b/server/src/Services/ProgressTrackerService.cs
index ebf9982..4fecfca 100644
--- a/server/src/Services/ProgressTrackerService.cs
+++ b/server/src/Services/ProgressTrackerService.cs
@@ -41,7 +41,7 @@ public class ProgressReporter : ProgressInfo, IProgress
private ProgressStatus _status = ProgressStatus.Pending;
private string _errorMessage;
- public string TaskId { get; set; } = new Guid().ToString();
+ public string TaskId { get; set; } = Guid.NewGuid().ToString();
public int ProgressPercent => _progress * 100 / MaxProgress;
public ProgressStatus Status => _status;
public string ErrorMessage => _errorMessage;
diff --git a/src/APIClient.ts b/src/APIClient.ts
index 047d49c..e553bac 100644
--- a/src/APIClient.ts
+++ b/src/APIClient.ts
@@ -185,12 +185,8 @@ export class VideoStreamClient {
return Promise.resolve(null as any);
}
- /**
- * 获取 HTTP 视频流信息
- * @return 流信息
- */
- getStreamInfo( cancelToken?: CancelToken): Promise {
- let url_ = this.baseUrl + "/api/VideoStream/StreamInfo";
+ myEndpoint( cancelToken?: CancelToken): Promise {
+ let url_ = this.baseUrl + "/api/VideoStream/MyEndpoint";
url_ = url_.replace(/[?&]$/, "");
let options_: AxiosRequestConfig = {
@@ -209,208 +205,11 @@ export class VideoStreamClient {
throw _error;
}
}).then((_response: AxiosResponse) => {
- return this.processGetStreamInfo(_response);
+ return this.processMyEndpoint(_response);
});
}
- protected processGetStreamInfo(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 = StreamInfoResult.fromJS(resultData200);
- return Promise.resolve(result200);
-
- } 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);
- }
-
- /**
- * 配置摄像头连接参数
- * @param config 摄像头配置
- * @return 配置结果
- */
- configureCamera(config: CameraConfigRequest, cancelToken?: CancelToken): Promise {
- let url_ = this.baseUrl + "/api/VideoStream/ConfigureCamera";
- url_ = url_.replace(/[?&]$/, "");
-
- const content_ = JSON.stringify(config);
-
- let options_: AxiosRequestConfig = {
- data: content_,
- method: "POST",
- url: url_,
- headers: {
- "Content-Type": "application/json",
- "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);
- }
-
- /**
- * 获取当前摄像头配置
- * @return 摄像头配置信息
- */
- getCameraConfig( cancelToken?: CancelToken): Promise {
- let url_ = this.baseUrl + "/api/VideoStream/CameraConfig";
- url_ = url_.replace(/[?&]$/, "");
-
- let options_: AxiosRequestConfig = {
- method: "GET",
- 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.processGetCameraConfig(_response);
- });
- }
-
- protected processGetCameraConfig(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 === 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);
- }
-
- /**
- * 控制 HTTP 视频流服务开关
- * @param enabled (optional) 是否启用服务
- * @return 操作结果
- */
- setEnabled(enabled: boolean | undefined, cancelToken?: CancelToken): Promise {
- let url_ = this.baseUrl + "/api/VideoStream/SetEnabled?";
- if (enabled === null)
- throw new Error("The parameter 'enabled' cannot be null.");
- else if (enabled !== undefined)
- url_ += "enabled=" + encodeURIComponent("" + enabled) + "&";
- 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.processSetEnabled(_response);
- });
- }
-
- protected processSetEnabled(response: AxiosResponse): Promise {
+ protected processMyEndpoint(response: AxiosResponse): Promise {
const status = response.status;
let _headers: any = {};
if (response.headers && typeof response.headers === "object") {
@@ -502,6 +301,59 @@ export class VideoStreamClient {
return Promise.resolve(null as any);
}
+ disableHdmiTransmission( cancelToken?: CancelToken): Promise {
+ let url_ = this.baseUrl + "/api/VideoStream/DisableTransmission";
+ url_ = url_.replace(/[?&]$/, "");
+
+ let options_: AxiosRequestConfig = {
+ responseType: "blob",
+ method: "POST",
+ url: url_,
+ headers: {
+ "Accept": "application/octet-stream"
+ },
+ 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.processDisableHdmiTransmission(_response);
+ });
+ }
+
+ protected processDisableHdmiTransmission(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 || status === 206) {
+ const contentDisposition = response.headers ? response.headers["content-disposition"] : undefined;
+ let fileNameMatch = contentDisposition ? /filename\*=(?:(\\?['"])(.*?)\1|(?:[^\s]+'.*?')?([^;\n]*))/g.exec(contentDisposition) : undefined;
+ let fileName = fileNameMatch && fileNameMatch.length > 1 ? fileNameMatch[3] || fileNameMatch[2] : undefined;
+ if (fileName) {
+ fileName = decodeURIComponent(fileName);
+ } else {
+ fileNameMatch = contentDisposition ? /filename="?([^"]*?)"?(;|$)/g.exec(contentDisposition) : undefined;
+ fileName = fileNameMatch && fileNameMatch.length > 1 ? fileNameMatch[1] : undefined;
+ }
+ return Promise.resolve({ fileName: fileName, status: status, data: new Blob([response.data], { type: response.headers["content-type"] }), headers: _headers });
+ } 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);
+ }
+
/**
* 设置视频流分辨率
* @param request 分辨率配置请求
@@ -576,67 +428,6 @@ export class VideoStreamClient {
return Promise.resolve(null as any);
}
- /**
- * 获取当前分辨率
- * @return 当前分辨率信息
- */
- getCurrentResolution( cancelToken?: CancelToken): Promise {
- let url_ = this.baseUrl + "/api/VideoStream/Resolution";
- url_ = url_.replace(/[?&]$/, "");
-
- let options_: AxiosRequestConfig = {
- method: "GET",
- 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.processGetCurrentResolution(_response);
- });
- }
-
- protected processGetCurrentResolution(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 === 500) {
- const _responseText = response.data;
- let result500: any = null;
- let resultData500 = _responseText;
- result500 = resultData500 !== undefined ? resultData500 : null;
-
- 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);
- }
-
/**
* 获取支持的分辨率列表
* @return 支持的分辨率列表
@@ -835,75 +626,6 @@ export class VideoStreamClient {
}
return Promise.resolve(null as any);
}
-
- /**
- * 执行一次自动对焦 (GET方式)
- * @return 对焦结果
- */
- focus( cancelToken?: CancelToken): Promise {
- let url_ = this.baseUrl + "/api/VideoStream/Focus";
- url_ = url_.replace(/[?&]$/, "");
-
- let options_: AxiosRequestConfig = {
- method: "GET",
- 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.processFocus(_response);
- });
- }
-
- protected processFocus(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 = resultData500 !== undefined ? resultData500 : null;
-
- 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 {
@@ -7253,134 +6975,6 @@ export interface IException {
stackTrace?: string | undefined;
}
-/** 视频流信息结构体 */
-export class StreamInfoResult implements IStreamInfoResult {
- /** TODO: */
- frameRate!: number;
- /** TODO: */
- frameWidth!: number;
- /** TODO: */
- frameHeight!: number;
- /** TODO: */
- format!: string;
- /** TODO: */
- htmlUrl!: string;
- /** TODO: */
- mjpegUrl!: string;
- /** TODO: */
- snapshotUrl!: string;
- /** TODO: */
- usbCameraUrl!: string;
-
- constructor(data?: IStreamInfoResult) {
- if (data) {
- for (var property in data) {
- if (data.hasOwnProperty(property))
- (this)[property] = (data)[property];
- }
- }
- }
-
- init(_data?: any) {
- if (_data) {
- this.frameRate = _data["frameRate"];
- this.frameWidth = _data["frameWidth"];
- this.frameHeight = _data["frameHeight"];
- this.format = _data["format"];
- this.htmlUrl = _data["htmlUrl"];
- this.mjpegUrl = _data["mjpegUrl"];
- this.snapshotUrl = _data["snapshotUrl"];
- this.usbCameraUrl = _data["usbCameraUrl"];
- }
- }
-
- static fromJS(data: any): StreamInfoResult {
- data = typeof data === 'object' ? data : {};
- let result = new StreamInfoResult();
- result.init(data);
- return result;
- }
-
- toJSON(data?: any) {
- data = typeof data === 'object' ? data : {};
- data["frameRate"] = this.frameRate;
- data["frameWidth"] = this.frameWidth;
- data["frameHeight"] = this.frameHeight;
- data["format"] = this.format;
- data["htmlUrl"] = this.htmlUrl;
- data["mjpegUrl"] = this.mjpegUrl;
- data["snapshotUrl"] = this.snapshotUrl;
- data["usbCameraUrl"] = this.usbCameraUrl;
- return data;
- }
-}
-
-/** 视频流信息结构体 */
-export interface IStreamInfoResult {
- /** TODO: */
- frameRate: number;
- /** TODO: */
- frameWidth: number;
- /** TODO: */
- frameHeight: number;
- /** TODO: */
- format: string;
- /** TODO: */
- htmlUrl: string;
- /** TODO: */
- mjpegUrl: string;
- /** TODO: */
- snapshotUrl: string;
- /** TODO: */
- usbCameraUrl: string;
-}
-
-/** 摄像头配置请求模型 */
-export class CameraConfigRequest implements ICameraConfigRequest {
- /** 摄像头地址 */
- address!: string;
- /** 摄像头端口 */
- port!: number;
-
- constructor(data?: ICameraConfigRequest) {
- if (data) {
- for (var property in data) {
- if (data.hasOwnProperty(property))
- (this)[property] = (data)[property];
- }
- }
- }
-
- init(_data?: any) {
- if (_data) {
- this.address = _data["address"];
- this.port = _data["port"];
- }
- }
-
- static fromJS(data: any): CameraConfigRequest {
- data = typeof data === 'object' ? data : {};
- let result = new CameraConfigRequest();
- result.init(data);
- return result;
- }
-
- toJSON(data?: any) {
- data = typeof data === 'object' ? data : {};
- data["address"] = this.address;
- data["port"] = this.port;
- return data;
- }
-}
-
-/** 摄像头配置请求模型 */
-export interface ICameraConfigRequest {
- /** 摄像头地址 */
- address: string;
- /** 摄像头端口 */
- port: number;
-}
-
/** 分辨率配置请求模型 */
export class ResolutionConfigRequest implements IResolutionConfigRequest {
/** 宽度 */
diff --git a/src/main.ts b/src/main.ts
deleted file mode 100644
index c962b62..0000000
--- a/src/main.ts
+++ /dev/null
@@ -1,10 +0,0 @@
-import './assets/main.css'
-
-import { createApp } from 'vue'
-import { createPinia } from 'pinia'
-
-import App from '@/App.vue'
-import router from './router'
-
-const app = createApp(App).use(router).use(createPinia()).mount('#app')
-