namespace SHH.CameraSdk; /// /// [管理层] 视频源总控管理器 (V3.5 持久化集成版) /// 核心职责:统一管理所有相机设备的生命周期、状态监控与资源清理,对接协调器实现自动自愈 /// public class CameraManager : IDisposable, IAsyncDisposable { #region --- 1. 核心资源与状态 (Fields & States) --- /// 全局设备实例池(线程安全),Key = 设备唯一标识 private readonly ConcurrentDictionary _cameraPool = new(); /// 后台协调器实例:负责心跳检测、断线重连、僵尸流恢复 private readonly CameraCoordinator _coordinator = new(); /// 全局取消令牌源:用于销毁时瞬间关停所有异步扫描任务 private readonly CancellationTokenSource _globalCts = new(); /// 销毁状态标记:防止重复销毁或销毁过程中执行操作 private volatile bool _isDisposed; /// /// 协调器引擎运行状态标记 /// 使用 volatile 关键字确保多线程环境下的内存可见性 /// private volatile bool _isEngineStarted = false; // [新增] 存储服务引用 private readonly IStorageService _storage; #endregion #region --- 构造函数 (Constructor) --- // [修改] 注入 IStorageService public CameraManager(IStorageService storage) { _storage = storage; } #endregion #region --- 2. 设备管理 (Device Management) --- /// /// 向管理池添加新相机设备 /// /// 相机设备配置信息 public void AddDevice(VideoSourceConfig config) { // [安全防护] 销毁过程中禁止添加新设备 if (_isDisposed) return; // 使用工厂方法创建 var device = CreateDeviceInstance(config); if (!_cameraPool.TryAdd(config.Id, device)) { // 如果添加失败(ID冲突),由于 device 还没被使用,直接释放掉 device.DisposeAsync().AsTask().Wait(); throw new InvalidOperationException($"设备 ID {config.Id} 已存在"); } // 动态激活逻辑:引擎已启动时,新设备直接标记为运行状态 if (_isEngineStarted) { device.IsRunning = true; } // [新增] 自动保存到文件 SaveChanges(); } /// /// 根据设备ID获取指定的视频源实例 /// public BaseVideoSource? GetDevice(long id) => _cameraPool.TryGetValue(id, out var source) ? source : null; /// /// 获取当前管理的所有相机设备 /// public IEnumerable GetAllDevices() { return _cameraPool.Values.ToList(); } /// /// [修改] 异步移除设备 (从 RemoveDevice 改为 RemoveDeviceAsync) /// public async Task RemoveDeviceAsync(long id) { if (_cameraPool.TryRemove(id, out var device)) { // 记录日志 Console.WriteLine($"[Manager] 正在移除设备 {id}..."); // 1. 停止物理连接 await device.StopAsync(); // 2. 释放资源 await device.DisposeAsync(); Console.WriteLine($"[Manager] 设备 {id} 已彻底移除"); // [新增] 自动保存到文件 SaveChanges(); } } // 为了兼容旧代码保留同步方法,但不推荐使用 public void RemoveDevice(long id) => RemoveDeviceAsync(id).Wait(); #endregion #region --- 3. 生命周期控制 (Engine Lifecycle) --- /// /// 启动视频管理引擎,加载配置,初始化SDK并启动协调器自愈循环 /// public async Task StartAsync() { if (_isDisposed) throw new ObjectDisposedException(nameof(CameraManager)); if (_isEngineStarted) return; // ========================================================= // 1. [新增] 从文件加载设备配置 // ========================================================= try { Console.WriteLine("[Manager] 正在检查本地配置文件..."); var savedConfigs = await _storage.LoadDevicesAsync(); int loadedCount = 0; foreach (var config in savedConfigs) { // 防止ID冲突(虽然文件里理论上不重复) if (!_cameraPool.ContainsKey(config.Id)) { var device = CreateDeviceInstance(config); // 默认设为运行状态,让协调器稍后去连接 //device.IsRunning = true; _cameraPool.TryAdd(config.Id, device); loadedCount++; } } if (loadedCount > 0) Console.WriteLine($"[Manager] 已从文件恢复 {loadedCount} 台设备配置"); } catch (Exception ex) { Console.WriteLine($"[Manager] 加载配置文件警告: {ex.Message}"); } // ========================================================= // 2. 全局驱动环境预初始化 // ========================================================= HikSdkManager.Initialize(); // 标记引擎启动状态 _isEngineStarted = true; // ========================================================= // 3. 启动协调器后台自愈循环 // ========================================================= _ = Task.Factory.StartNew( () => _coordinator.RunCoordinationLoopAsync(_globalCts.Token), _globalCts.Token, TaskCreationOptions.LongRunning, TaskScheduler.Default); // 这里传递 _cameraPool 给协调器(如果协调器是独立引用的,可能需要 Register 逻辑, // 但根据您的代码,协调器似乎是依赖外部注册或共享引用的。 // *注意*:如果 Coordinator 需要显式注册,请在这里补上: foreach (var dev in _cameraPool.Values) _coordinator.Register(dev); Console.WriteLine($"[CameraManager] 引擎启动成功,当前管理 {_cameraPool.Count} 路相机设备。"); await Task.CompletedTask; } /// /// 获取当前所有相机的全局状态简报 /// public IEnumerable<(long Id, string Ip, VideoSourceStatus Status)> GetGlobalStatus() { return _cameraPool.Values.Select(v => (v.Id, v.Config.IpAddress, v.Status)); } #endregion #region --- 4. 监控数据采集 (Telemetry Collection) --- /// /// 获取所有相机的健康度报告 /// public IEnumerable GetDetailedTelemetry() { return _cameraPool.Values.Select(cam => new CameraHealthReport { DeviceId = cam.Id, Ip = cam.Config.IpAddress, Status = cam.Status.ToString(), LastError = cam.Status == VideoSourceStatus.Faulted ? "设备故障或网络中断" : "运行正常" }); } /// /// 获取全量相机实时遥测数据快照 (MonitorController 使用) /// public IEnumerable GetTelemetrySnapshot() { return _cameraPool.Values.Select(cam => { // 健康度评分算法 int healthScore = 100; if (cam.Status == VideoSourceStatus.Faulted) healthScore = 0; else if (cam.Status == VideoSourceStatus.Reconnecting) healthScore = 60; else if (cam.RealFps < 1.0 && cam.Status == VideoSourceStatus.Playing) healthScore = 40; return new CameraTelemetryInfo { DeviceId = cam.Id, Name = cam.Config.Name, IpAddress = cam.Config.IpAddress, Status = cam.Status.ToString(), IsOnline = cam.IsPhysicalOnline, Fps = cam.RealFps, Bitrate = cam.RealBitrate, TotalFrames = cam.TotalFrames, HealthScore = healthScore, LastErrorMessage = cam.Status == VideoSourceStatus.Faulted ? "设备故障或网络中断" : null, Timestamp = DateTime.Now, Width = cam.Width, Height = cam.Height, }; }).ToList(); } #endregion #region --- 5. 配置热更新 (Config Hot Update) --- /// /// 智能更新设备配置 (含冷热分离逻辑) /// public async Task UpdateDeviceConfigAsync(long deviceId, DeviceUpdateDto dto) { if (!_cameraPool.TryGetValue(deviceId, out var device)) throw new KeyNotFoundException($"设备 {deviceId} 不存在"); // 1. 审计 device.AddAuditLog("收到配置更新请求"); // 2. 创建副本进行对比 var oldConfig = device.Config; var newConfig = oldConfig.DeepCopy(); // 3. 映射 DTO 值 if (dto.IpAddress != null) newConfig.IpAddress = dto.IpAddress; if (dto.Port != null) newConfig.Port = dto.Port.Value; if (dto.Username != null) newConfig.Username = dto.Username; if (dto.Password != null) newConfig.Password = dto.Password; if (dto.ChannelIndex != null) newConfig.ChannelIndex = dto.ChannelIndex.Value; if (dto.StreamType != null) newConfig.StreamType = dto.StreamType.Value; if (dto.Name != null) newConfig.Name = dto.Name; if (dto.Brand != null) newConfig.Brand = (DeviceBrand)dto.Brand; newConfig.RtspPath = dto.RtspPath; newConfig.MainboardIp = dto.MainboardIp; newConfig.MainboardPort = dto.MainboardPort; newConfig.RenderHandle = dto.RenderHandle; // 4. 判定冷热更新 bool needColdRestart = newConfig.IpAddress != oldConfig.IpAddress || newConfig.Port != oldConfig.Port || newConfig.Username != oldConfig.Username || newConfig.Password != oldConfig.Password || newConfig.ChannelIndex != oldConfig.ChannelIndex || newConfig.RtspPath != oldConfig.RtspPath || newConfig.RenderHandle != oldConfig.RenderHandle || newConfig.Brand != oldConfig.Brand; if (needColdRestart) { device.AddAuditLog($"检测到核心参数变更,执行冷重启 (Reboot)"); bool wasRunning = device.IsRunning; // A. 彻底停止 if (device.IsOnline) await device.StopAsync(); // B. 写入新配置 device.UpdateConfig(newConfig); // C. 自动重启 if (wasRunning) await device.StartAsync(); } else { device.AddAuditLog($"检测到运行时参数变更,执行热更新 (HotSwap)"); // A. 更新配置数据 device.UpdateConfig(newConfig); // B. 在线应用策略 if (device.IsOnline) { var options = new DynamicStreamOptions { StreamType = dto.StreamType ?? newConfig.StreamType, RenderHandle = (IntPtr)dto.RenderHandle }; device.ApplyOptions(options); } } // [新增] 保存文件 SaveChanges(); } /// /// 全量替换更新 (兼容接口) /// public async Task UpdateDeviceAsync(int id, VideoSourceConfig newConfig) { if (!_cameraPool.TryGetValue(id, out var oldDevice)) throw new KeyNotFoundException($"设备 #{id} 不存在"); bool wasRunning = oldDevice.IsRunning || oldDevice.Status == VideoSourceStatus.Playing || oldDevice.Status == VideoSourceStatus.Connecting; Console.WriteLine($"[Manager] 正在更新设备 #{id},配置变更中..."); try { await oldDevice.StopAsync(); await oldDevice.DisposeAsync(); } catch (Exception ex) { Console.WriteLine($"[Manager] 销毁旧设备时警告: {ex.Message}"); } var newDevice = CreateDeviceInstance(newConfig); _cameraPool[id] = newDevice; Console.WriteLine($"[Manager] 设备 #{id} 实例已重建。"); if (wasRunning) { await newDevice.StartAsync(); } // [新增] 保存文件 SaveChanges(); } #endregion #region --- 6. 资源清理 (Disposal) --- public void Dispose() => DisposeAsync().AsTask().GetAwaiter().GetResult(); public async ValueTask DisposeAsync() { if (_isDisposed) return; _isDisposed = true; _isEngineStarted = false; try { _globalCts.Cancel(); var devices = _cameraPool.Values.ToArray(); _cameraPool.Clear(); var disposeTasks = devices.Select(async device => { try { await device.DisposeAsync(); } catch { } }); await Task.WhenAll(disposeTasks); try { HikSdkManager.Uninitialize(); } catch { } } finally { _globalCts.Dispose(); } } #endregion #region --- 7. 内部辅助 (Helpers) --- private BaseVideoSource CreateDeviceInstance(VideoSourceConfig config) { return config.Brand switch { DeviceBrand.HikVision => new HikVideoSource(config), _ => throw new NotSupportedException($"不支持的设备品牌: {config.Brand}") }; } /// /// [新增] 触发异步保存 (Fire-and-Forget) /// 不阻塞当前 API 线程,让后台存储服务去排队写入 /// private void SaveChanges() { try { var allConfigs = _cameraPool.Values.Select(d => d.Config).ToList(); // 异步调用存储服务,不使用 await 以免阻塞 API 响应 _ = _storage.SaveDevicesAsync(allConfigs); } catch (Exception ex) { Console.WriteLine($"[Manager] 触发保存失败: {ex.Message}"); } } #endregion /// /// [新增] 获取当前管理的所有相机设备(兼容网络引擎接口) /// public IEnumerable GetAllCameras() { // 复用现有的 GetAllDevices 逻辑 return GetAllDevices(); } #region --- [新增] 状态事件总线 (SDK 对外接口) --- /// /// 当设备在线/离线状态发生变更时触发 /// 参数1: DeviceId /// 参数2: IsOnline (true=在线, false=离线) /// 参数3: Reason (变更原因) /// public event Action? OnDeviceStatusChanged; /// /// [内部方法] 供 Sentinel 调用,触发事件冒泡 /// internal void NotifyStatusChange(long deviceId, bool isOnline, string reason) { // 仅仅是触发 C# 事件,完全不知道网络发送的存在 OnDeviceStatusChanged?.Invoke(deviceId, isOnline, reason); } #endregion }