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