using System.Windows; using System.Windows.Media.Imaging; using SHH.CameraDashboard.Services; // 引用服务命名空间 namespace SHH.CameraDashboard; public class VideoTileViewModel : ViewModelBase { private readonly string _boundCameraId; // --- 属性定义 --- private string _cameraName; public string CameraName { get => _cameraName; set { _cameraName = value; OnPropertyChanged(); } } private string _statusInfo; public string StatusInfo { get => _statusInfo; set { _statusInfo = value; OnPropertyChanged(); } } private BitmapImage _videoSource; public BitmapImage VideoSource { get => _videoSource; set { _videoSource = value; OnPropertyChanged(); } } // --- 构造函数 --- public VideoTileViewModel(string cameraId, string name) { _boundCameraId = cameraId; CameraName = name; StatusInfo = "等待信号..."; // 【修正 1】直接订阅单例服务 // 不需要判断 null,因为 Instance 是静态初始化的,永远存在 StreamReceiverService.Instance.OnFrameReceived += OnGlobalFrameReceived; } // --- 事件回调 (后台线程) --- private void OnGlobalFrameReceived(string cameraId, byte[] jpgData) { // 1. 过滤:不是我的画面,直接忽略 if (cameraId != _boundCameraId) return; // 2. 解码:耗时操作在后台完成 var bitmap = BitmapHelper.ToBitmapImage(jpgData); if (bitmap == null) return; // 3. 【修正 2】恢复 UI 更新逻辑 // 必须使用 Dispatcher,因为 VideoSource 绑定在界面上,只能在主线程修改 Application.Current.Dispatcher.InvokeAsync(() => { VideoSource = bitmap; // 更新状态信息 (例如显示当前时间和数据大小) StatusInfo = $"{DateTime.Now:HH:mm:ss} | {jpgData.Length / 1024} KB"; }); } // --- 资源清理 --- public void Unload() { // 【修正 3】从单例服务取消订阅 // 这一步至关重要,否则切换页面时会内存泄漏 StreamReceiverService.Instance.OnFrameReceived -= OnGlobalFrameReceived; // 清空图片引用,帮助 GC 回收内存 VideoSource = null; } }