feat: 支持实际摄像头视频流
This commit is contained in:
		@@ -1,8 +1,9 @@
 | 
			
		||||
using Microsoft.AspNetCore.Cors;
 | 
			
		||||
using Microsoft.AspNetCore.Mvc;
 | 
			
		||||
using System.ComponentModel.DataAnnotations;
 | 
			
		||||
 | 
			
		||||
/// <summary>
 | 
			
		||||
/// [TODO:description]
 | 
			
		||||
/// 视频流控制器,支持动态配置摄像头连接
 | 
			
		||||
/// </summary>
 | 
			
		||||
[ApiController]
 | 
			
		||||
[Route("api/[controller]")]
 | 
			
		||||
@@ -11,6 +12,20 @@ public class VideoStreamController : ControllerBase
 | 
			
		||||
    private static NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();
 | 
			
		||||
    private readonly server.Services.HttpVideoStreamService _videoStreamService;
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 摄像头配置请求模型
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    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; }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 初始化HTTP视频流控制器
 | 
			
		||||
    /// </summary>
 | 
			
		||||
@@ -47,12 +62,14 @@ public class VideoStreamController : ControllerBase
 | 
			
		||||
                mjpegUrl = $"http://localhost:{_videoStreamService.ServerPort}/video-stream",
 | 
			
		||||
                snapshotUrl = $"http://localhost:{_videoStreamService.ServerPort}/snapshot",
 | 
			
		||||
                connectedClients = _videoStreamService.ConnectedClientsCount,
 | 
			
		||||
                clientEndpoints = _videoStreamService.GetConnectedClientEndpoints()
 | 
			
		||||
                clientEndpoints = _videoStreamService.GetConnectedClientEndpoints(),
 | 
			
		||||
                cameraStatus = _videoStreamService.GetCameraStatus()
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
        catch (Exception ex)
 | 
			
		||||
        {
 | 
			
		||||
            logger.Error(ex, "获取 HTTP 视频流服务状态失败"); return TypedResults.InternalServerError(ex.Message);
 | 
			
		||||
            logger.Error(ex, "获取 HTTP 视频流服务状态失败"); 
 | 
			
		||||
            return TypedResults.InternalServerError(ex.Message);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -77,12 +94,123 @@ public class VideoStreamController : ControllerBase
 | 
			
		||||
                format = "MJPEG",
 | 
			
		||||
                htmlUrl = $"http://localhost:{_videoStreamService.ServerPort}/video-feed.html",
 | 
			
		||||
                mjpegUrl = $"http://localhost:{_videoStreamService.ServerPort}/video-stream",
 | 
			
		||||
                snapshotUrl = $"http://localhost:{_videoStreamService.ServerPort}/snapshot"
 | 
			
		||||
                snapshotUrl = $"http://localhost:{_videoStreamService.ServerPort}/snapshot",
 | 
			
		||||
                cameraAddress = _videoStreamService.CameraAddress,
 | 
			
		||||
                cameraPort = _videoStreamService.CameraPort
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
        catch (Exception ex)
 | 
			
		||||
        {
 | 
			
		||||
            logger.Error(ex, "获取 HTTP 视频流信息失败"); return TypedResults.InternalServerError(ex.Message);
 | 
			
		||||
            logger.Error(ex, "获取 HTTP 视频流信息失败"); 
 | 
			
		||||
            return TypedResults.InternalServerError(ex.Message);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 配置摄像头连接参数
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    /// <param name="config">摄像头配置</param>
 | 
			
		||||
    /// <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([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);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 获取当前摄像头配置
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    /// <returns>摄像头配置信息</returns>
 | 
			
		||||
    [HttpGet("CameraConfig")]
 | 
			
		||||
    [EnableCors("Users")]
 | 
			
		||||
    [ProducesResponseType(typeof(object), StatusCodes.Status200OK)]
 | 
			
		||||
    [ProducesResponseType(typeof(Exception), StatusCodes.Status500InternalServerError)]
 | 
			
		||||
    public IResult GetCameraConfig()
 | 
			
		||||
    {
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            logger.Info("获取摄像头配置");
 | 
			
		||||
            var cameraStatus = _videoStreamService.GetCameraStatus();
 | 
			
		||||
            
 | 
			
		||||
            return TypedResults.Ok(new
 | 
			
		||||
            {
 | 
			
		||||
                address = _videoStreamService.CameraAddress,
 | 
			
		||||
                port = _videoStreamService.CameraPort,
 | 
			
		||||
                isConfigured = cameraStatus.GetType().GetProperty("IsConfigured")?.GetValue(cameraStatus),
 | 
			
		||||
                connectionString = $"{_videoStreamService.CameraAddress}:{_videoStreamService.CameraPort}"
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
        catch (Exception ex)
 | 
			
		||||
        {
 | 
			
		||||
            logger.Error(ex, "获取摄像头配置失败");
 | 
			
		||||
            return TypedResults.InternalServerError(ex.Message);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 测试摄像头连接
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    /// <returns>连接测试结果</returns>
 | 
			
		||||
    [HttpPost("TestCameraConnection")]
 | 
			
		||||
    [EnableCors("Users")]
 | 
			
		||||
    [ProducesResponseType(typeof(object), StatusCodes.Status200OK)]
 | 
			
		||||
    [ProducesResponseType(typeof(Exception), StatusCodes.Status500InternalServerError)]
 | 
			
		||||
    public async Task<IResult> TestCameraConnection()
 | 
			
		||||
    {
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            logger.Info("测试摄像头连接");
 | 
			
		||||
            
 | 
			
		||||
            var (isSuccess, message) = await _videoStreamService.TestCameraConnectionAsync();
 | 
			
		||||
            
 | 
			
		||||
            return TypedResults.Ok(new
 | 
			
		||||
            {
 | 
			
		||||
                success = isSuccess,
 | 
			
		||||
                message = message,
 | 
			
		||||
                cameraAddress = _videoStreamService.CameraAddress,
 | 
			
		||||
                cameraPort = _videoStreamService.CameraPort,
 | 
			
		||||
                timestamp = DateTime.Now
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
        catch (Exception ex)
 | 
			
		||||
        {
 | 
			
		||||
            logger.Error(ex, "测试摄像头连接失败");
 | 
			
		||||
            return TypedResults.InternalServerError(ex.Message);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,15 +1,12 @@
 | 
			
		||||
using System.Net;
 | 
			
		||||
using System.Text;
 | 
			
		||||
using SixLabors.ImageSharp;
 | 
			
		||||
using SixLabors.ImageSharp.Formats.Jpeg;
 | 
			
		||||
using SixLabors.ImageSharp.PixelFormats;
 | 
			
		||||
using Peripherals.CameraClient; // 添加摄像头客户端引用
 | 
			
		||||
 | 
			
		||||
namespace server.Services;
 | 
			
		||||
 | 
			
		||||
/// <summary>
 | 
			
		||||
/// HTTP 视频流服务,用于从 FPGA 获取图像数据并推送到前端网页
 | 
			
		||||
/// 简化版本实现,先建立基础框架
 | 
			
		||||
/// 支持动态配置摄像头地址和端口
 | 
			
		||||
/// </summary>
 | 
			
		||||
public class HttpVideoStreamService : BackgroundService
 | 
			
		||||
{
 | 
			
		||||
@@ -22,8 +19,9 @@ public class HttpVideoStreamService : BackgroundService
 | 
			
		||||
 | 
			
		||||
    // 摄像头客户端
 | 
			
		||||
    private Camera? _camera;
 | 
			
		||||
    private readonly string _cameraAddress = "192.168.1.100"; // 根据实际FPGA地址配置
 | 
			
		||||
    private readonly int _cameraPort = 8888; // 根据实际端口配置
 | 
			
		||||
    private string _cameraAddress = "192.168.1.100"; // 默认FPGA地址
 | 
			
		||||
    private int _cameraPort = 8888; // 默认端口
 | 
			
		||||
    private readonly object _cameraLock = new object();
 | 
			
		||||
 | 
			
		||||
    // 模拟 FPGA 图像数据
 | 
			
		||||
    private int _frameCounter = 0;
 | 
			
		||||
@@ -33,16 +31,7 @@ public class HttpVideoStreamService : BackgroundService
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 获取当前连接的客户端数量
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    public int ConnectedClientsCount
 | 
			
		||||
    {
 | 
			
		||||
        get
 | 
			
		||||
        {
 | 
			
		||||
            lock (_clientsLock)
 | 
			
		||||
            {
 | 
			
		||||
                return _activeClients.Count;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    public int ConnectedClientsCount { get { return _activeClients.Count; } }
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 获取服务端口
 | 
			
		||||
@@ -64,13 +53,141 @@ public class HttpVideoStreamService : BackgroundService
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    public int FrameRate => _frameRate;
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 获取当前摄像头地址
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    public string CameraAddress { get { return _cameraAddress; } }
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 获取当前摄像头端口
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    public int CameraPort { get { return _cameraPort; } }
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 初始化 HttpVideoStreamService
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    public HttpVideoStreamService()
 | 
			
		||||
    {
 | 
			
		||||
        // 初始化摄像头客户端
 | 
			
		||||
        _camera = new Camera(_cameraAddress, _cameraPort);
 | 
			
		||||
        // 延迟初始化摄像头客户端,直到配置完成
 | 
			
		||||
        logger.Info("HttpVideoStreamService 初始化完成,默认摄像头地址: {Address}:{Port}", _cameraAddress, _cameraPort);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 配置摄像头连接参数
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    /// <param name="address">摄像头IP地址</param>
 | 
			
		||||
    /// <param name="port">摄像头端口</param>
 | 
			
		||||
    /// <returns>配置是否成功</returns>
 | 
			
		||||
    public async Task<bool> ConfigureCameraAsync(string address, int port)
 | 
			
		||||
    {
 | 
			
		||||
        if (string.IsNullOrWhiteSpace(address))
 | 
			
		||||
        {
 | 
			
		||||
            logger.Error("摄像头地址不能为空");
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (port <= 0 || port > 65535)
 | 
			
		||||
        {
 | 
			
		||||
            logger.Error("摄像头端口必须在1-65535范围内");
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            await Task.Run(() =>
 | 
			
		||||
            {
 | 
			
		||||
                lock (_cameraLock)
 | 
			
		||||
                {
 | 
			
		||||
                    // 如果地址和端口没有变化,直接返回成功
 | 
			
		||||
                    if (_cameraAddress == address && _cameraPort == port && _camera != null)
 | 
			
		||||
                    {
 | 
			
		||||
                        logger.Info("摄像头配置未变化,保持当前连接");
 | 
			
		||||
                        return;
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    // 关闭现有连接
 | 
			
		||||
                    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);
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
            return true;
 | 
			
		||||
        }
 | 
			
		||||
        catch (Exception ex)
 | 
			
		||||
        {
 | 
			
		||||
            logger.Error(ex, "配置摄像头连接时发生错误");
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 测试摄像头连接
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    /// <returns>连接测试结果</returns>
 | 
			
		||||
    public async Task<(bool IsSuccess, string Message)> TestCameraConnectionAsync()
 | 
			
		||||
    {
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            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());
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        catch (Exception ex)
 | 
			
		||||
        {
 | 
			
		||||
            logger.Error(ex, "摄像头连接测试出错");
 | 
			
		||||
            return (false, ex.Message);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 获取摄像头连接状态
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    /// <returns>连接状态信息</returns>
 | 
			
		||||
    public object GetCameraStatus()
 | 
			
		||||
    {
 | 
			
		||||
        lock (_cameraLock)
 | 
			
		||||
        {
 | 
			
		||||
            return new
 | 
			
		||||
            {
 | 
			
		||||
                Address = _cameraAddress,
 | 
			
		||||
                Port = _cameraPort,
 | 
			
		||||
                IsConfigured = _camera != null,
 | 
			
		||||
                ConnectionString = $"{_cameraAddress}:{_cameraPort}"
 | 
			
		||||
            };
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
@@ -83,6 +200,10 @@ public class HttpVideoStreamService : BackgroundService
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            logger.Info("启动 HTTP 视频流服务,端口: {Port}", _serverPort);
 | 
			
		||||
 | 
			
		||||
            // 初始化默认摄像头连接
 | 
			
		||||
            await ConfigureCameraAsync(_cameraAddress, _cameraPort);
 | 
			
		||||
 | 
			
		||||
            // 创建 HTTP 监听器
 | 
			
		||||
            _httpListener = new HttpListener();
 | 
			
		||||
            _httpListener.Prefixes.Add($"http://localhost:{_serverPort}/");
 | 
			
		||||
@@ -220,7 +341,18 @@ public class HttpVideoStreamService : BackgroundService
 | 
			
		||||
        {
 | 
			
		||||
            // 获取当前帧
 | 
			
		||||
            var imageData = await GetFPGAImageData();
 | 
			
		||||
            var jpegData = ConvertToJpeg(imageData);
 | 
			
		||||
 | 
			
		||||
            // 直接使用Common.Image.ConvertRGB24ToJpeg进行转换
 | 
			
		||||
            var jpegResult = Common.Image.ConvertRGB24ToJpeg(imageData, _frameWidth, _frameHeight, 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";
 | 
			
		||||
@@ -338,11 +470,8 @@ public class HttpVideoStreamService : BackgroundService
 | 
			
		||||
                // 从 FPGA 获取图像数据(模拟)
 | 
			
		||||
                var imageData = await GetFPGAImageData();
 | 
			
		||||
 | 
			
		||||
                // 将图像数据转换为 JPEG
 | 
			
		||||
                var jpegData = ConvertToJpeg(imageData);
 | 
			
		||||
 | 
			
		||||
                // 向所有连接的客户端发送帧
 | 
			
		||||
                await BroadcastFrameAsync(jpegData, cancellationToken);
 | 
			
		||||
                await BroadcastFrameAsync(imageData, cancellationToken);
 | 
			
		||||
 | 
			
		||||
                _frameCounter++;
 | 
			
		||||
 | 
			
		||||
@@ -367,7 +496,14 @@ public class HttpVideoStreamService : BackgroundService
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    private async Task<byte[]> GetFPGAImageData()
 | 
			
		||||
    {
 | 
			
		||||
        if (_camera == null)
 | 
			
		||||
        Camera? currentCamera = null;
 | 
			
		||||
 | 
			
		||||
        lock (_cameraLock)
 | 
			
		||||
        {
 | 
			
		||||
            currentCamera = _camera;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (currentCamera == null)
 | 
			
		||||
        {
 | 
			
		||||
            logger.Error("摄像头客户端未初始化");
 | 
			
		||||
            return new byte[0];
 | 
			
		||||
@@ -376,8 +512,8 @@ public class HttpVideoStreamService : BackgroundService
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            // 从摄像头读取帧数据
 | 
			
		||||
            var result = await _camera.ReadFrame();
 | 
			
		||||
            
 | 
			
		||||
            var result = await currentCamera.ReadFrame();
 | 
			
		||||
 | 
			
		||||
            if (!result.IsSuccessful)
 | 
			
		||||
            {
 | 
			
		||||
                logger.Error("读取摄像头帧数据失败: {Error}", result.Error);
 | 
			
		||||
@@ -385,11 +521,11 @@ public class HttpVideoStreamService : BackgroundService
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            var rgb565Data = result.Value;
 | 
			
		||||
            
 | 
			
		||||
 | 
			
		||||
            // 验证数据长度是否正确
 | 
			
		||||
            if (!Common.Image.ValidateImageDataLength(rgb565Data, _frameWidth, _frameHeight, 2))
 | 
			
		||||
            {
 | 
			
		||||
                logger.Warn("摄像头数据长度不匹配,期望: {Expected}, 实际: {Actual}", 
 | 
			
		||||
                logger.Warn("摄像头数据长度不匹配,期望: {Expected}, 实际: {Actual}",
 | 
			
		||||
                    _frameWidth * _frameHeight * 2, rgb565Data.Length);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
@@ -403,7 +539,7 @@ public class HttpVideoStreamService : BackgroundService
 | 
			
		||||
 | 
			
		||||
            if (_frameCounter % 30 == 0) // 每秒更新一次日志
 | 
			
		||||
            {
 | 
			
		||||
                logger.Debug("成功获取第 {FrameNumber} 帧,RGB565大小: {RGB565Size} 字节, RGB24大小: {RGB24Size} 字节", 
 | 
			
		||||
                logger.Debug("成功获取第 {FrameNumber} 帧,RGB565大小: {RGB565Size} 字节, RGB24大小: {RGB24Size} 字节",
 | 
			
		||||
                    _frameCounter, rgb565Data.Length, rgb24Result.Value.Length);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
@@ -416,18 +552,6 @@ public class HttpVideoStreamService : BackgroundService
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private async Task<byte[]> ConvertToJpeg(byte[] rgbData)
 | 
			
		||||
    {
 | 
			
		||||
        var jpegResult = Common.Image.ConvertRGB24ToJpeg(rgbData, _frameWidth, _frameHeight, 80);
 | 
			
		||||
        if (!jpegResult.IsSuccessful)
 | 
			
		||||
        {
 | 
			
		||||
            logger.Error("RGB24转JPEG失败: {Error}", jpegResult.Error);
 | 
			
		||||
            return new byte[0];
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        return jpegResult.Value;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 向所有连接的客户端广播帧数据
 | 
			
		||||
    /// </summary>
 | 
			
		||||
@@ -439,8 +563,18 @@ public class HttpVideoStreamService : BackgroundService
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // 直接使用Common.Image.ConvertRGB24ToJpeg进行转换
 | 
			
		||||
        var jpegResult = Common.Image.ConvertRGB24ToJpeg(frameData, _frameWidth, _frameHeight, 80);
 | 
			
		||||
        if (!jpegResult.IsSuccessful)
 | 
			
		||||
        {
 | 
			
		||||
            logger.Error("RGB24转JPEG失败: {Error}", jpegResult.Error);
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        var jpegData = jpegResult.Value;
 | 
			
		||||
 | 
			
		||||
        // 使用Common中的方法准备MJPEG帧数据
 | 
			
		||||
        var mjpegFrameHeader = Common.Image.CreateMjpegFrameHeader(frameData.Length);
 | 
			
		||||
        var mjpegFrameHeader = Common.Image.CreateMjpegFrameHeader(jpegData.Length);
 | 
			
		||||
        var mjpegFrameFooter = Common.Image.CreateMjpegFrameFooter();
 | 
			
		||||
 | 
			
		||||
        var clientsToRemove = new List<HttpListenerResponse>();
 | 
			
		||||
@@ -466,7 +600,7 @@ public class HttpVideoStreamService : BackgroundService
 | 
			
		||||
                await client.OutputStream.WriteAsync(mjpegFrameHeader, 0, mjpegFrameHeader.Length, cancellationToken);
 | 
			
		||||
 | 
			
		||||
                // 发送JPEG数据
 | 
			
		||||
                await client.OutputStream.WriteAsync(frameData, 0, frameData.Length, cancellationToken);
 | 
			
		||||
                await client.OutputStream.WriteAsync(jpegData, 0, jpegData.Length, cancellationToken);
 | 
			
		||||
 | 
			
		||||
                // 发送结尾换行符
 | 
			
		||||
                await client.OutputStream.WriteAsync(mjpegFrameFooter, 0, mjpegFrameFooter.Length, cancellationToken);
 | 
			
		||||
@@ -477,7 +611,7 @@ public class HttpVideoStreamService : BackgroundService
 | 
			
		||||
                if (_frameCounter % 30 == 0) // 每秒记录一次日志
 | 
			
		||||
                {
 | 
			
		||||
                    logger.Debug("已向客户端 {ClientId} 发送第 {FrameNumber} 帧,大小:{Size} 字节",
 | 
			
		||||
                        client.OutputStream.GetHashCode(), _frameCounter, frameData.Length);
 | 
			
		||||
                        client.OutputStream.GetHashCode(), _frameCounter, jpegData.Length);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            catch (Exception ex)
 | 
			
		||||
@@ -528,6 +662,8 @@ public class HttpVideoStreamService : BackgroundService
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    public object GetServiceStatus()
 | 
			
		||||
    {
 | 
			
		||||
        var cameraStatus = GetCameraStatus();
 | 
			
		||||
 | 
			
		||||
        return new
 | 
			
		||||
        {
 | 
			
		||||
            IsRunning = _httpListener?.IsListening ?? false,
 | 
			
		||||
@@ -535,7 +671,8 @@ public class HttpVideoStreamService : BackgroundService
 | 
			
		||||
            FrameRate = _frameRate,
 | 
			
		||||
            Resolution = $"{_frameWidth}x{_frameHeight}",
 | 
			
		||||
            ConnectedClients = ConnectedClientsCount,
 | 
			
		||||
            ClientEndpoints = GetConnectedClientEndpoints()
 | 
			
		||||
            ClientEndpoints = GetConnectedClientEndpoints(),
 | 
			
		||||
            CameraStatus = cameraStatus
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -563,6 +700,12 @@ public class HttpVideoStreamService : BackgroundService
 | 
			
		||||
            _activeClients.Clear();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // 关闭摄像头连接
 | 
			
		||||
        lock (_cameraLock)
 | 
			
		||||
        {
 | 
			
		||||
            _camera = null;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        await base.StopAsync(cancellationToken);
 | 
			
		||||
 | 
			
		||||
        logger.Info("HTTP 视频流服务已停止");
 | 
			
		||||
@@ -592,6 +735,11 @@ public class HttpVideoStreamService : BackgroundService
 | 
			
		||||
            _activeClients.Clear();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        lock (_cameraLock)
 | 
			
		||||
        {
 | 
			
		||||
            _camera = null;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        base.Dispose();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										598
									
								
								src/APIClient.ts
									
									
									
									
									
								
							
							
						
						
									
										598
									
								
								src/APIClient.ts
									
									
									
									
									
								
							@@ -8,6 +8,306 @@
 | 
			
		||||
/* eslint-disable */
 | 
			
		||||
// ReSharper disable InconsistentNaming
 | 
			
		||||
 | 
			
		||||
export class VideoStreamClient {
 | 
			
		||||
    private http: { fetch(url: RequestInfo, init?: RequestInit): Promise<Response> };
 | 
			
		||||
    private baseUrl: string;
 | 
			
		||||
    protected jsonParseReviver: ((key: string, value: any) => any) | undefined = undefined;
 | 
			
		||||
 | 
			
		||||
    constructor(baseUrl?: string, http?: { fetch(url: RequestInfo, init?: RequestInit): Promise<Response> }) {
 | 
			
		||||
        this.http = http ? http : window as any;
 | 
			
		||||
        this.baseUrl = baseUrl ?? "http://localhost:5000";
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 获取 HTTP 视频流服务状态
 | 
			
		||||
     * @return 服务状态信息
 | 
			
		||||
     */
 | 
			
		||||
    getStatus(): Promise<any> {
 | 
			
		||||
        let url_ = this.baseUrl + "/api/VideoStream/Status";
 | 
			
		||||
        url_ = url_.replace(/[?&]$/, "");
 | 
			
		||||
 | 
			
		||||
        let options_: RequestInit = {
 | 
			
		||||
            method: "GET",
 | 
			
		||||
            headers: {
 | 
			
		||||
                "Accept": "application/json"
 | 
			
		||||
            }
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        return this.http.fetch(url_, options_).then((_response: Response) => {
 | 
			
		||||
            return this.processGetStatus(_response);
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    protected processGetStatus(response: Response): Promise<any> {
 | 
			
		||||
        const status = response.status;
 | 
			
		||||
        let _headers: any = {}; if (response.headers && response.headers.forEach) { response.headers.forEach((v: any, k: any) => _headers[k] = v); };
 | 
			
		||||
        if (status === 200) {
 | 
			
		||||
            return response.text().then((_responseText) => {
 | 
			
		||||
            let result200: any = null;
 | 
			
		||||
            let resultData200 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver);
 | 
			
		||||
                result200 = resultData200 !== undefined ? resultData200 : <any>null;
 | 
			
		||||
    
 | 
			
		||||
            return result200;
 | 
			
		||||
            });
 | 
			
		||||
        } else if (status === 500) {
 | 
			
		||||
            return response.text().then((_responseText) => {
 | 
			
		||||
            let result500: any = null;
 | 
			
		||||
            let resultData500 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver);
 | 
			
		||||
            result500 = Exception.fromJS(resultData500);
 | 
			
		||||
            return throwException("A server side error occurred.", status, _responseText, _headers, result500);
 | 
			
		||||
            });
 | 
			
		||||
        } else if (status !== 200 && status !== 204) {
 | 
			
		||||
            return response.text().then((_responseText) => {
 | 
			
		||||
            return throwException("An unexpected server error occurred.", status, _responseText, _headers);
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
        return Promise.resolve<any>(null as any);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 获取 HTTP 视频流信息
 | 
			
		||||
     * @return 流信息
 | 
			
		||||
     */
 | 
			
		||||
    getStreamInfo(): Promise<any> {
 | 
			
		||||
        let url_ = this.baseUrl + "/api/VideoStream/StreamInfo";
 | 
			
		||||
        url_ = url_.replace(/[?&]$/, "");
 | 
			
		||||
 | 
			
		||||
        let options_: RequestInit = {
 | 
			
		||||
            method: "GET",
 | 
			
		||||
            headers: {
 | 
			
		||||
                "Accept": "application/json"
 | 
			
		||||
            }
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        return this.http.fetch(url_, options_).then((_response: Response) => {
 | 
			
		||||
            return this.processGetStreamInfo(_response);
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    protected processGetStreamInfo(response: Response): Promise<any> {
 | 
			
		||||
        const status = response.status;
 | 
			
		||||
        let _headers: any = {}; if (response.headers && response.headers.forEach) { response.headers.forEach((v: any, k: any) => _headers[k] = v); };
 | 
			
		||||
        if (status === 200) {
 | 
			
		||||
            return response.text().then((_responseText) => {
 | 
			
		||||
            let result200: any = null;
 | 
			
		||||
            let resultData200 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver);
 | 
			
		||||
                result200 = resultData200 !== undefined ? resultData200 : <any>null;
 | 
			
		||||
    
 | 
			
		||||
            return result200;
 | 
			
		||||
            });
 | 
			
		||||
        } else if (status === 500) {
 | 
			
		||||
            return response.text().then((_responseText) => {
 | 
			
		||||
            let result500: any = null;
 | 
			
		||||
            let resultData500 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver);
 | 
			
		||||
            result500 = Exception.fromJS(resultData500);
 | 
			
		||||
            return throwException("A server side error occurred.", status, _responseText, _headers, result500);
 | 
			
		||||
            });
 | 
			
		||||
        } else if (status !== 200 && status !== 204) {
 | 
			
		||||
            return response.text().then((_responseText) => {
 | 
			
		||||
            return throwException("An unexpected server error occurred.", status, _responseText, _headers);
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
        return Promise.resolve<any>(null as any);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 配置摄像头连接参数
 | 
			
		||||
     * @param config 摄像头配置
 | 
			
		||||
     * @return 配置结果
 | 
			
		||||
     */
 | 
			
		||||
    configureCamera(config: CameraConfigRequest): Promise<any> {
 | 
			
		||||
        let url_ = this.baseUrl + "/api/VideoStream/ConfigureCamera";
 | 
			
		||||
        url_ = url_.replace(/[?&]$/, "");
 | 
			
		||||
 | 
			
		||||
        const content_ = JSON.stringify(config);
 | 
			
		||||
 | 
			
		||||
        let options_: RequestInit = {
 | 
			
		||||
            body: content_,
 | 
			
		||||
            method: "POST",
 | 
			
		||||
            headers: {
 | 
			
		||||
                "Content-Type": "application/json",
 | 
			
		||||
                "Accept": "application/json"
 | 
			
		||||
            }
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        return this.http.fetch(url_, options_).then((_response: Response) => {
 | 
			
		||||
            return this.processConfigureCamera(_response);
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    protected processConfigureCamera(response: Response): Promise<any> {
 | 
			
		||||
        const status = response.status;
 | 
			
		||||
        let _headers: any = {}; if (response.headers && response.headers.forEach) { response.headers.forEach((v: any, k: any) => _headers[k] = v); };
 | 
			
		||||
        if (status === 200) {
 | 
			
		||||
            return response.text().then((_responseText) => {
 | 
			
		||||
            let result200: any = null;
 | 
			
		||||
            let resultData200 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver);
 | 
			
		||||
                result200 = resultData200 !== undefined ? resultData200 : <any>null;
 | 
			
		||||
    
 | 
			
		||||
            return result200;
 | 
			
		||||
            });
 | 
			
		||||
        } else if (status === 400) {
 | 
			
		||||
            return response.text().then((_responseText) => {
 | 
			
		||||
            let result400: any = null;
 | 
			
		||||
            let resultData400 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver);
 | 
			
		||||
                result400 = resultData400 !== undefined ? resultData400 : <any>null;
 | 
			
		||||
    
 | 
			
		||||
            return throwException("A server side error occurred.", status, _responseText, _headers, result400);
 | 
			
		||||
            });
 | 
			
		||||
        } else if (status === 500) {
 | 
			
		||||
            return response.text().then((_responseText) => {
 | 
			
		||||
            let result500: any = null;
 | 
			
		||||
            let resultData500 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver);
 | 
			
		||||
            result500 = Exception.fromJS(resultData500);
 | 
			
		||||
            return throwException("A server side error occurred.", status, _responseText, _headers, result500);
 | 
			
		||||
            });
 | 
			
		||||
        } else if (status !== 200 && status !== 204) {
 | 
			
		||||
            return response.text().then((_responseText) => {
 | 
			
		||||
            return throwException("An unexpected server error occurred.", status, _responseText, _headers);
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
        return Promise.resolve<any>(null as any);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 获取当前摄像头配置
 | 
			
		||||
     * @return 摄像头配置信息
 | 
			
		||||
     */
 | 
			
		||||
    getCameraConfig(): Promise<any> {
 | 
			
		||||
        let url_ = this.baseUrl + "/api/VideoStream/CameraConfig";
 | 
			
		||||
        url_ = url_.replace(/[?&]$/, "");
 | 
			
		||||
 | 
			
		||||
        let options_: RequestInit = {
 | 
			
		||||
            method: "GET",
 | 
			
		||||
            headers: {
 | 
			
		||||
                "Accept": "application/json"
 | 
			
		||||
            }
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        return this.http.fetch(url_, options_).then((_response: Response) => {
 | 
			
		||||
            return this.processGetCameraConfig(_response);
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    protected processGetCameraConfig(response: Response): Promise<any> {
 | 
			
		||||
        const status = response.status;
 | 
			
		||||
        let _headers: any = {}; if (response.headers && response.headers.forEach) { response.headers.forEach((v: any, k: any) => _headers[k] = v); };
 | 
			
		||||
        if (status === 200) {
 | 
			
		||||
            return response.text().then((_responseText) => {
 | 
			
		||||
            let result200: any = null;
 | 
			
		||||
            let resultData200 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver);
 | 
			
		||||
                result200 = resultData200 !== undefined ? resultData200 : <any>null;
 | 
			
		||||
    
 | 
			
		||||
            return result200;
 | 
			
		||||
            });
 | 
			
		||||
        } else if (status === 500) {
 | 
			
		||||
            return response.text().then((_responseText) => {
 | 
			
		||||
            let result500: any = null;
 | 
			
		||||
            let resultData500 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver);
 | 
			
		||||
            result500 = Exception.fromJS(resultData500);
 | 
			
		||||
            return throwException("A server side error occurred.", status, _responseText, _headers, result500);
 | 
			
		||||
            });
 | 
			
		||||
        } else if (status !== 200 && status !== 204) {
 | 
			
		||||
            return response.text().then((_responseText) => {
 | 
			
		||||
            return throwException("An unexpected server error occurred.", status, _responseText, _headers);
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
        return Promise.resolve<any>(null as any);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 测试摄像头连接
 | 
			
		||||
     * @return 连接测试结果
 | 
			
		||||
     */
 | 
			
		||||
    testCameraConnection(): Promise<any> {
 | 
			
		||||
        let url_ = this.baseUrl + "/api/VideoStream/TestCameraConnection";
 | 
			
		||||
        url_ = url_.replace(/[?&]$/, "");
 | 
			
		||||
 | 
			
		||||
        let options_: RequestInit = {
 | 
			
		||||
            method: "POST",
 | 
			
		||||
            headers: {
 | 
			
		||||
                "Accept": "application/json"
 | 
			
		||||
            }
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        return this.http.fetch(url_, options_).then((_response: Response) => {
 | 
			
		||||
            return this.processTestCameraConnection(_response);
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    protected processTestCameraConnection(response: Response): Promise<any> {
 | 
			
		||||
        const status = response.status;
 | 
			
		||||
        let _headers: any = {}; if (response.headers && response.headers.forEach) { response.headers.forEach((v: any, k: any) => _headers[k] = v); };
 | 
			
		||||
        if (status === 200) {
 | 
			
		||||
            return response.text().then((_responseText) => {
 | 
			
		||||
            let result200: any = null;
 | 
			
		||||
            let resultData200 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver);
 | 
			
		||||
                result200 = resultData200 !== undefined ? resultData200 : <any>null;
 | 
			
		||||
    
 | 
			
		||||
            return result200;
 | 
			
		||||
            });
 | 
			
		||||
        } else if (status === 500) {
 | 
			
		||||
            return response.text().then((_responseText) => {
 | 
			
		||||
            let result500: any = null;
 | 
			
		||||
            let resultData500 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver);
 | 
			
		||||
            result500 = Exception.fromJS(resultData500);
 | 
			
		||||
            return throwException("A server side error occurred.", status, _responseText, _headers, result500);
 | 
			
		||||
            });
 | 
			
		||||
        } else if (status !== 200 && status !== 204) {
 | 
			
		||||
            return response.text().then((_responseText) => {
 | 
			
		||||
            return throwException("An unexpected server error occurred.", status, _responseText, _headers);
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
        return Promise.resolve<any>(null as any);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 测试 HTTP 视频流连接
 | 
			
		||||
     * @return 连接测试结果
 | 
			
		||||
     */
 | 
			
		||||
    testConnection(): Promise<boolean> {
 | 
			
		||||
        let url_ = this.baseUrl + "/api/VideoStream/TestConnection";
 | 
			
		||||
        url_ = url_.replace(/[?&]$/, "");
 | 
			
		||||
 | 
			
		||||
        let options_: RequestInit = {
 | 
			
		||||
            method: "POST",
 | 
			
		||||
            headers: {
 | 
			
		||||
                "Accept": "application/json"
 | 
			
		||||
            }
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        return this.http.fetch(url_, options_).then((_response: Response) => {
 | 
			
		||||
            return this.processTestConnection(_response);
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    protected processTestConnection(response: Response): Promise<boolean> {
 | 
			
		||||
        const status = response.status;
 | 
			
		||||
        let _headers: any = {}; if (response.headers && response.headers.forEach) { response.headers.forEach((v: any, k: any) => _headers[k] = v); };
 | 
			
		||||
        if (status === 200) {
 | 
			
		||||
            return response.text().then((_responseText) => {
 | 
			
		||||
            let result200: any = null;
 | 
			
		||||
            let resultData200 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver);
 | 
			
		||||
                result200 = resultData200 !== undefined ? resultData200 : <any>null;
 | 
			
		||||
    
 | 
			
		||||
            return result200;
 | 
			
		||||
            });
 | 
			
		||||
        } else if (status === 500) {
 | 
			
		||||
            return response.text().then((_responseText) => {
 | 
			
		||||
            let result500: any = null;
 | 
			
		||||
            let resultData500 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver);
 | 
			
		||||
            result500 = Exception.fromJS(resultData500);
 | 
			
		||||
            return throwException("A server side error occurred.", status, _responseText, _headers, result500);
 | 
			
		||||
            });
 | 
			
		||||
        } else if (status !== 200 && status !== 204) {
 | 
			
		||||
            return response.text().then((_responseText) => {
 | 
			
		||||
            return throwException("An unexpected server error occurred.", status, _responseText, _headers);
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
        return Promise.resolve<boolean>(null as any);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export class BsdlParserClient {
 | 
			
		||||
    private http: { fetch(url: RequestInfo, init?: RequestInit): Promise<Response> };
 | 
			
		||||
    private baseUrl: string;
 | 
			
		||||
@@ -1542,6 +1842,55 @@ export class RemoteUpdateClient {
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export class TutorialClient {
 | 
			
		||||
    private http: { fetch(url: RequestInfo, init?: RequestInit): Promise<Response> };
 | 
			
		||||
    private baseUrl: string;
 | 
			
		||||
    protected jsonParseReviver: ((key: string, value: any) => any) | undefined = undefined;
 | 
			
		||||
 | 
			
		||||
    constructor(baseUrl?: string, http?: { fetch(url: RequestInfo, init?: RequestInit): Promise<Response> }) {
 | 
			
		||||
        this.http = http ? http : window as any;
 | 
			
		||||
        this.baseUrl = baseUrl ?? "http://localhost:5000";
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 获取所有可用的教程目录
 | 
			
		||||
     * @return 教程目录列表
 | 
			
		||||
     */
 | 
			
		||||
    getTutorials(): Promise<void> {
 | 
			
		||||
        let url_ = this.baseUrl + "/api/Tutorial";
 | 
			
		||||
        url_ = url_.replace(/[?&]$/, "");
 | 
			
		||||
 | 
			
		||||
        let options_: RequestInit = {
 | 
			
		||||
            method: "GET",
 | 
			
		||||
            headers: {
 | 
			
		||||
            }
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        return this.http.fetch(url_, options_).then((_response: Response) => {
 | 
			
		||||
            return this.processGetTutorials(_response);
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    protected processGetTutorials(response: Response): Promise<void> {
 | 
			
		||||
        const status = response.status;
 | 
			
		||||
        let _headers: any = {}; if (response.headers && response.headers.forEach) { response.headers.forEach((v: any, k: any) => _headers[k] = v); };
 | 
			
		||||
        if (status === 200) {
 | 
			
		||||
            return response.text().then((_responseText) => {
 | 
			
		||||
            return;
 | 
			
		||||
            });
 | 
			
		||||
        } else if (status === 500) {
 | 
			
		||||
            return response.text().then((_responseText) => {
 | 
			
		||||
            return throwException("A server side error occurred.", status, _responseText, _headers);
 | 
			
		||||
            });
 | 
			
		||||
        } else if (status !== 200 && status !== 204) {
 | 
			
		||||
            return response.text().then((_responseText) => {
 | 
			
		||||
            return throwException("An unexpected server error occurred.", status, _responseText, _headers);
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
        return Promise.resolve<void>(null as any);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export class UDPClient {
 | 
			
		||||
    private http: { fetch(url: RequestInfo, init?: RequestInit): Promise<Response> };
 | 
			
		||||
    private baseUrl: string;
 | 
			
		||||
@@ -1800,15 +2149,20 @@ export class UDPClient {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 获取指定IP地址接受的数据列表
 | 
			
		||||
     * 获取指定IP地址接收的数据列表
 | 
			
		||||
     * @param address (optional) IP地址
 | 
			
		||||
     * @param taskID (optional) 
 | 
			
		||||
     */
 | 
			
		||||
    getRecvDataArray(address: string | undefined): Promise<UDPData[]> {
 | 
			
		||||
    getRecvDataArray(address: string | undefined, taskID: number | undefined): Promise<UDPData[]> {
 | 
			
		||||
        let url_ = this.baseUrl + "/api/UDP/GetRecvDataArray?";
 | 
			
		||||
        if (address === null)
 | 
			
		||||
            throw new Error("The parameter 'address' cannot be null.");
 | 
			
		||||
        else if (address !== undefined)
 | 
			
		||||
            url_ += "address=" + encodeURIComponent("" + address) + "&";
 | 
			
		||||
        if (taskID === null)
 | 
			
		||||
            throw new Error("The parameter 'taskID' cannot be null.");
 | 
			
		||||
        else if (taskID !== undefined)
 | 
			
		||||
            url_ += "taskID=" + encodeURIComponent("" + taskID) + "&";
 | 
			
		||||
        url_ = url_.replace(/[?&]$/, "");
 | 
			
		||||
 | 
			
		||||
        let options_: RequestInit = {
 | 
			
		||||
@@ -1853,55 +2207,6 @@ export class UDPClient {
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export class TutorialClient {
 | 
			
		||||
    private http: { fetch(url: RequestInfo, init?: RequestInit): Promise<Response> };
 | 
			
		||||
    private baseUrl: string;
 | 
			
		||||
    protected jsonParseReviver: ((key: string, value: any) => any) | undefined = undefined;
 | 
			
		||||
 | 
			
		||||
    constructor(baseUrl?: string, http?: { fetch(url: RequestInfo, init?: RequestInit): Promise<Response> }) {
 | 
			
		||||
        this.http = http ? http : window as any;
 | 
			
		||||
        this.baseUrl = baseUrl ?? "http://localhost:5000";
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 获取所有可用的教程目录
 | 
			
		||||
     * @return 教程目录列表
 | 
			
		||||
     */
 | 
			
		||||
    getTutorials(): Promise<void> {
 | 
			
		||||
        let url_ = this.baseUrl + "/api/Tutorial";
 | 
			
		||||
        url_ = url_.replace(/[?&]$/, "");
 | 
			
		||||
 | 
			
		||||
        let options_: RequestInit = {
 | 
			
		||||
            method: "GET",
 | 
			
		||||
            headers: {
 | 
			
		||||
            }
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        return this.http.fetch(url_, options_).then((_response: Response) => {
 | 
			
		||||
            return this.processGetTutorials(_response);
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    protected processGetTutorials(response: Response): Promise<void> {
 | 
			
		||||
        const status = response.status;
 | 
			
		||||
        let _headers: any = {}; if (response.headers && response.headers.forEach) { response.headers.forEach((v: any, k: any) => _headers[k] = v); };
 | 
			
		||||
        if (status === 200) {
 | 
			
		||||
            return response.text().then((_responseText) => {
 | 
			
		||||
            return;
 | 
			
		||||
            });
 | 
			
		||||
        } else if (status === 500) {
 | 
			
		||||
            return response.text().then((_responseText) => {
 | 
			
		||||
            return throwException("A server side error occurred.", status, _responseText, _headers);
 | 
			
		||||
            });
 | 
			
		||||
        } else if (status !== 200 && status !== 204) {
 | 
			
		||||
            return response.text().then((_responseText) => {
 | 
			
		||||
            return throwException("An unexpected server error occurred.", status, _responseText, _headers);
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
        return Promise.resolve<void>(null as any);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export class Exception implements IException {
 | 
			
		||||
    message?: string;
 | 
			
		||||
    innerException?: Exception | undefined;
 | 
			
		||||
@@ -1950,6 +2255,48 @@ export interface IException {
 | 
			
		||||
    stackTrace?: string | undefined;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/** 摄像头配置请求模型 */
 | 
			
		||||
export class CameraConfigRequest implements ICameraConfigRequest {
 | 
			
		||||
    address!: string;
 | 
			
		||||
    port!: number;
 | 
			
		||||
 | 
			
		||||
    constructor(data?: ICameraConfigRequest) {
 | 
			
		||||
        if (data) {
 | 
			
		||||
            for (var property in data) {
 | 
			
		||||
                if (data.hasOwnProperty(property))
 | 
			
		||||
                    (<any>this)[property] = (<any>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 SystemException extends Exception implements ISystemException {
 | 
			
		||||
 | 
			
		||||
    constructor(data?: ISystemException) {
 | 
			
		||||
@@ -2091,6 +2438,8 @@ export class UDPData implements IUDPData {
 | 
			
		||||
    address?: string;
 | 
			
		||||
    /** 发送来源的端口号 */
 | 
			
		||||
    port?: number;
 | 
			
		||||
    /** 任务ID */
 | 
			
		||||
    taskID?: number;
 | 
			
		||||
    /** 接受到的数据 */
 | 
			
		||||
    data?: string;
 | 
			
		||||
    /** 是否被读取过 */
 | 
			
		||||
@@ -2110,6 +2459,7 @@ export class UDPData implements IUDPData {
 | 
			
		||||
            this.dateTime = _data["dateTime"] ? new Date(_data["dateTime"].toString()) : <any>undefined;
 | 
			
		||||
            this.address = _data["address"];
 | 
			
		||||
            this.port = _data["port"];
 | 
			
		||||
            this.taskID = _data["taskID"];
 | 
			
		||||
            this.data = _data["data"];
 | 
			
		||||
            this.hasRead = _data["hasRead"];
 | 
			
		||||
        }
 | 
			
		||||
@@ -2127,6 +2477,7 @@ export class UDPData implements IUDPData {
 | 
			
		||||
        data["dateTime"] = this.dateTime ? this.dateTime.toISOString() : <any>undefined;
 | 
			
		||||
        data["address"] = this.address;
 | 
			
		||||
        data["port"] = this.port;
 | 
			
		||||
        data["taskID"] = this.taskID;
 | 
			
		||||
        data["data"] = this.data;
 | 
			
		||||
        data["hasRead"] = this.hasRead;
 | 
			
		||||
        return data;
 | 
			
		||||
@@ -2141,6 +2492,8 @@ export interface IUDPData {
 | 
			
		||||
    address?: string;
 | 
			
		||||
    /** 发送来源的端口号 */
 | 
			
		||||
    port?: number;
 | 
			
		||||
    /** 任务ID */
 | 
			
		||||
    taskID?: number;
 | 
			
		||||
    /** 接受到的数据 */
 | 
			
		||||
    data?: string;
 | 
			
		||||
    /** 是否被读取过 */
 | 
			
		||||
@@ -2188,145 +2541,4 @@ function throwException(message: string, status: number, response: string, heade
 | 
			
		||||
        throw result;
 | 
			
		||||
    else
 | 
			
		||||
        throw new ApiException(message, status, response, headers, null);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// VideoStreamClient - 手动添加,用于HTTP视频流控制
 | 
			
		||||
export class VideoStreamClient {
 | 
			
		||||
    private http: { fetch(url: RequestInfo, init?: RequestInit): Promise<Response> };
 | 
			
		||||
    private baseUrl: string;
 | 
			
		||||
    protected jsonParseReviver: ((key: string, value: any) => any) | undefined = undefined;
 | 
			
		||||
 | 
			
		||||
    constructor(baseUrl?: string, http?: { fetch(url: RequestInfo, init?: RequestInit): Promise<Response> }) {
 | 
			
		||||
        this.http = http ? http : window as any;
 | 
			
		||||
        this.baseUrl = baseUrl ?? "http://localhost:5000";
 | 
			
		||||
    }    /**
 | 
			
		||||
     * 获取HTTP视频流服务状态
 | 
			
		||||
     * @return HTTP视频流服务状态信息
 | 
			
		||||
     */
 | 
			
		||||
    status(): Promise<any> {
 | 
			
		||||
        let url_ = this.baseUrl + "/api/VideoStream/Status";
 | 
			
		||||
        url_ = url_.replace(/[?&]$/, "");
 | 
			
		||||
 | 
			
		||||
        let options_: RequestInit = {
 | 
			
		||||
            method: "GET",
 | 
			
		||||
            headers: {
 | 
			
		||||
                "Accept": "application/json"
 | 
			
		||||
            }
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        return this.http.fetch(url_, options_).then((_response: Response) => {
 | 
			
		||||
            return this.processStatus(_response);
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    protected processStatus(response: Response): Promise<any> {
 | 
			
		||||
        const status = response.status;
 | 
			
		||||
        let _headers: any = {};
 | 
			
		||||
        if (response.headers && response.headers.forEach) {
 | 
			
		||||
            response.headers.forEach((v: any, k: any) => _headers[k] = v);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (status === 200) {
 | 
			
		||||
            return response.text().then((_responseText) => {
 | 
			
		||||
                let result200: any = null;
 | 
			
		||||
                result200 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver);
 | 
			
		||||
                return result200;
 | 
			
		||||
            });
 | 
			
		||||
        } else if (status === 500) {
 | 
			
		||||
            return response.text().then((_responseText) => {
 | 
			
		||||
                throw new Error(_responseText);
 | 
			
		||||
            });
 | 
			
		||||
        } else if (status !== 200 && status !== 204) {
 | 
			
		||||
            return response.text().then((_responseText) => {
 | 
			
		||||
                throw new Error("未知的响应状态码: " + status + ", 响应文本: " + _responseText);
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
        return Promise.resolve<any>(null as any);
 | 
			
		||||
    }    /**
 | 
			
		||||
     * 获取HTTP视频流信息
 | 
			
		||||
     * @return HTTP视频流信息
 | 
			
		||||
     */
 | 
			
		||||
    streamInfo(): Promise<any> {
 | 
			
		||||
        let url_ = this.baseUrl + "/api/VideoStream/StreamInfo";
 | 
			
		||||
        url_ = url_.replace(/[?&]$/, "");
 | 
			
		||||
 | 
			
		||||
        let options_: RequestInit = {
 | 
			
		||||
            method: "GET",
 | 
			
		||||
            headers: {
 | 
			
		||||
                "Accept": "application/json"
 | 
			
		||||
            }
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        return this.http.fetch(url_, options_).then((_response: Response) => {
 | 
			
		||||
            return this.processStreamInfo(_response);
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    protected processStreamInfo(response: Response): Promise<any> {
 | 
			
		||||
        const status = response.status;
 | 
			
		||||
        let _headers: any = {};
 | 
			
		||||
        if (response.headers && response.headers.forEach) {
 | 
			
		||||
            response.headers.forEach((v: any, k: any) => _headers[k] = v);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (status === 200) {
 | 
			
		||||
            return response.text().then((_responseText) => {
 | 
			
		||||
                let result200: any = null;
 | 
			
		||||
                result200 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver);
 | 
			
		||||
                return result200;
 | 
			
		||||
            });
 | 
			
		||||
        } else if (status === 500) {
 | 
			
		||||
            return response.text().then((_responseText) => {
 | 
			
		||||
                throw new Error(_responseText);
 | 
			
		||||
            });
 | 
			
		||||
        } else if (status !== 200 && status !== 204) {
 | 
			
		||||
            return response.text().then((_responseText) => {
 | 
			
		||||
                throw new Error("未知的响应状态码: " + status + ", 响应文本: " + _responseText);
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
        return Promise.resolve<any>(null as any);
 | 
			
		||||
    }    /**
 | 
			
		||||
     * 测试HTTP视频流连接
 | 
			
		||||
     * @return 测试结果
 | 
			
		||||
     */
 | 
			
		||||
    testConnection(): Promise<boolean> {
 | 
			
		||||
        let url_ = this.baseUrl + "/api/VideoStream/TestConnection";
 | 
			
		||||
        url_ = url_.replace(/[?&]$/, "");
 | 
			
		||||
 | 
			
		||||
        let options_: RequestInit = {
 | 
			
		||||
            method: "POST",
 | 
			
		||||
            headers: {
 | 
			
		||||
                "Accept": "application/json"
 | 
			
		||||
            }
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        return this.http.fetch(url_, options_).then((_response: Response) => {
 | 
			
		||||
            return this.processTestConnection(_response);
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    protected processTestConnection(response: Response): Promise<boolean> {
 | 
			
		||||
        const status = response.status;
 | 
			
		||||
        let _headers: any = {};
 | 
			
		||||
        if (response.headers && response.headers.forEach) {
 | 
			
		||||
            response.headers.forEach((v: any, k: any) => _headers[k] = v);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (status === 200) {
 | 
			
		||||
            return response.text().then((_responseText) => {
 | 
			
		||||
                let result200: any = null;
 | 
			
		||||
                result200 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver);
 | 
			
		||||
                return result200;
 | 
			
		||||
            });
 | 
			
		||||
        } else if (status === 500) {
 | 
			
		||||
            return response.text().then((_responseText) => {
 | 
			
		||||
                throw new Error(_responseText);
 | 
			
		||||
            });
 | 
			
		||||
        } else if (status !== 200 && status !== 204) {
 | 
			
		||||
            return response.text().then((_responseText) => {
 | 
			
		||||
                throw new Error("未知的响应状态码: " + status + ", 响应文本: " + _responseText);
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
        return Promise.resolve<boolean>(false);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
		Reference in New Issue
	
	Block a user