using Microsoft.AspNetCore.Mvc; namespace SHH.CameraSdk; [ApiController] [Route("api/[controller]")] public class CamerasController : ControllerBase { private readonly CameraManager _manager; // 构造函数注入管理器 public CamerasController(CameraManager manager, DisplayWindowManager displayManager) { _manager = manager; _displayManager = displayManager; } // ========================================================================== // 区域 A: 设备全生命周期管理 (CRUD) // ========================================================================== /// /// 1. 获取所有设备清单 /// [HttpGet] public IActionResult GetAll() { var devices = _manager.GetAllDevices().Select(d => new { d.Id, d.Config.IpAddress, d.Config.Name, Status = d.Status.ToString(), d.RealFps, d.TotalFrames }); return Ok(devices); } /// /// 2. 新增设备 (写入配置并初始化) /// [HttpPost] public IActionResult Add([FromBody] CameraConfigDto dto) { if (_manager.GetDevice(dto.Id) != null) return Conflict($"设备ID {dto.Id} 已存在"); // DTO 转 Config (实际项目中建议用 AutoMapper) var config = MapToConfig(dto); _manager.AddDevice(config); // 添加到内存池 // 注意:此时 IsRunning 默认为 false,等待手动 Start 或 API 控制 return CreatedAtAction(nameof(GetAll), new { id = dto.Id }, dto); } /// /// 3. 编辑设备 (自动识别冷热更新) /// [HttpPut("{id}")] public async Task Update(long id, [FromBody] CameraConfigDto dto) { try { if (id != dto.Id) return BadRequest("ID 不匹配"); // 调用 Manager 的智能更新逻辑 (之前实现的 UpdateDeviceConfigAsync) await _manager.UpdateDeviceConfigAsync(id, MapToUpdateDto(dto)); return Ok(new { Success = true, Message = "配置已更新" }); } catch (KeyNotFoundException) { return NotFound(); } catch (System.Exception ex) { return StatusCode(500, ex.Message); } } /// /// 4. 删除设备 (销毁连接) /// [HttpDelete("{id}")] public async Task Remove(long id) { var device = _manager.GetDevice(id); if (device == null) return NotFound(); await device.StopAsync(); // 停流 _manager.RemoveDevice(id); // 从池中移除 return Ok($"设备 {id} 已移除"); } // ========================================================================== // 区域 B: 多进程流控订阅 (Subscription Strategy) // ========================================================================== // ========================================================================== // 区域 C: 句柄动态绑定 (Handle Binding) // ========================================================================== /// /// 6. 绑定显示窗口 (对应 A进程-句柄场景) /// [HttpPost("{id}/bind-handle")] public IActionResult BindHandle(long id, [FromBody] BindHandleDto dto) { var device = _manager.GetDevice(id); if (device == null) return NotFound(); // 构造动态选项,应用句柄 var options = new DynamicStreamOptions { RenderHandle = (System.IntPtr)dto.Handle }; device.ApplyOptions(options); // 触发驱动层的 OnApplyOptions device.AddAuditLog($"绑定新句柄: {dto.Handle} ({dto.Purpose})"); return Ok(new { Success = true }); } // ========================================================================== // 区域 D: 设备运行控制 // ========================================================================== /// /// 手动控制设备运行状态 (开关机) /// [HttpPost("{id}/power")] public async Task TogglePower(long id, [FromQuery] bool enabled) { var device = _manager.GetDevice(id); if (device == null) return NotFound(); // 1. 更新运行意图 device.IsRunning = enabled; // 2. 审计与执行 if (enabled) { device.AddAuditLog("用户指令:手动开启设备"); // 异步启动,Coordinator 也会在下个周期辅助检查 _ = device.StartAsync(); } else { device.AddAuditLog("用户指令:手动关闭设备"); await device.StopAsync(); } return Ok(new { DeviceId = id, IsRunning = enabled }); } /// /// 热应用动态参数 (如切换码流) /// [HttpPost("{id}/options")] public IActionResult ApplyOptions(long id, [FromBody] DynamicStreamOptions options) { var device = _manager.GetDevice(id); if (device == null) return NotFound(); // 1. 如果涉及码流切换,先同步更新配置对象 if (options.StreamType.HasValue) { var newConfig = device.Config.DeepCopy(); newConfig.StreamType = options.StreamType.Value; device.UpdateConfig(newConfig); } // 2. 应用到驱动层(触发热切换逻辑) device.ApplyOptions(options); return Ok(new { Message = "动态参数已发送", DeviceId = id }); } // ========================================================================== // 辅助方法 (Mapping) // ========================================================================== private VideoSourceConfig MapToConfig(CameraConfigDto dto) { return new VideoSourceConfig { Id = dto.Id, Name = dto.Name, Brand = (DeviceBrand)dto.Brand, IpAddress = dto.IpAddress, Port = dto.Port, Username = dto.Username, Password = dto.Password, ChannelIndex = dto.ChannelIndex, StreamType = dto.StreamType, RtspPath = dto.RtspPath }; } /// /// [辅助方法] 将全量配置 DTO 转换为更新 DTO /// private DeviceUpdateDto MapToUpdateDto(CameraConfigDto dto) { return new DeviceUpdateDto { // ========================================== // 1. 冷更新参数 (基础连接信息) // ========================================== IpAddress = dto.IpAddress, Port = dto.Port, Username = dto.Username, Password = dto.Password, ChannelIndex = dto.ChannelIndex, Brand = dto.Brand, RtspPath = dto.RtspPath, MainboardIp = dto.MainboardIp, MainboardPort = dto.MainboardPort, // ========================================== // 2. 热更新参数 (运行时属性) // ========================================== Name = dto.Name, Location = dto.Location, StreamType = dto.StreamType, // 注意:通常句柄是通过 bind-handle 接口单独绑定的, // 但如果 ConfigDto 里包含了上次保存的句柄,也可以映射 // RenderHandle = dto.RenderHandle, // ========================================== // 3. 图像处理参数 // ========================================== AllowCompress = dto.AllowCompress, AllowExpand = dto.AllowExpand, TargetResolution = dto.TargetResolution, EnhanceImage = dto.EnhanceImage, UseGrayscale = dto.UseGrayscale }; } /// /// [新增] 查询某台设备的当前流控策略 /// [HttpGet("{id}/subscriptions")] public IActionResult GetSubscriptions(long id) { var device = _manager.GetDevice(id); if (device == null) return NotFound(); // 调用刚才在 FrameController 写的方法 var subs = device.Controller.GetCurrentRequirements(); return Ok(subs); } private readonly DisplayWindowManager _displayManager; // [新增] [HttpPost("{id}/subscriptions")] public IActionResult UpdateSubscription(int id, [FromBody] SubscriptionDto dto) { var device = _manager.GetDevice(id); if (device == null) return NotFound("设备不存在"); if (device is HikVideoSource hikCam) { // 1. 更新流控策略 (FrameController) // 告诉底层:这个 AppId 需要多少帧 int totalFps = dto.DisplayFps + dto.AnalysisFps; if (totalFps > 0) { // 情况 A: 这是一个新增或更新订阅 hikCam.Controller.Register(dto.AppId, totalFps); // 如果是预览模式,启动窗口 if (dto.DisplayFps > 0) { _displayManager.StartDisplay(dto.AppId, id); } } else { // 情况 B: 这是一个停止订阅请求 (FPS 为 0) // 1. 【核心修复】从调度中心物理删除,不再出现在列表中 hikCam.Controller.Unregister(dto.AppId); // 2. 关闭可能存在的本地窗口 _displayManager.StopDisplay(dto.AppId); } return Ok(new { message = "Policy updated", currentConfig = hikCam.Controller.GetCurrentRequirements() }); } return BadRequest("Device implies no controller"); } // 1. 获取单个设备详情(用于编辑回填) [HttpGet("{id}")] public IActionResult GetDevice(int id) { var cam = _manager.GetDevice(id); if (cam == null) return NotFound(); return Ok(cam.Config); // 返回原始配置对象 } // 2. 更新设备(保存功能) [HttpPut("{id}")] public async Task UpdateDevice(int id, [FromBody] VideoSourceConfig config) { // 核心逻辑:先停止旧设备 -> 更新配置 -> 重新添加到容器 -> 如果之前在运行则重新启动 await _manager.UpdateDeviceAsync(id, config); return Ok(); } // 3. 清除特定设备的日志 [HttpDelete("{id}/logs")] public IActionResult ClearLogs(int id) { var cam = _manager.GetDevice(id); cam?.ClearAuditLogs(); return Ok(); } //// 4. 抓图诊断 //[HttpGet("{id}/capture")] //public async Task Capture(int id) //{ // var cam = _manager.GetDevice(id); // if (cam == null) return NotFound(); // var bytes = await cam.CaptureCurrentFrameAsync(); // return File(bytes, "image/jpeg"); //} }