阶段性批量提交

This commit is contained in:
2026-01-05 14:54:06 +08:00
parent 917d76a87f
commit a697aab3e0
21 changed files with 1479 additions and 379 deletions

View File

@@ -1,87 +1,76 @@
using SHH.Contracts;
using System.IO;
using System.Windows;
using System.Windows.Media;
using System.Windows;
using System.Windows.Media.Imaging;
using SHH.CameraDashboard.Services; // 引用服务命名空间
namespace SHH.CameraDashboard
namespace SHH.CameraDashboard;
public class VideoTileViewModel : ViewModelBase
{
public class VideoTileViewModel : ViewModelBase, IDisposable
private readonly string _boundCameraId;
// --- 属性定义 ---
private string _cameraName;
public string CameraName
{
// --- 绑定属性 ---
get => _cameraName;
set { _cameraName = value; OnPropertyChanged(); }
}
private ImageSource _displayImage;
public ImageSource DisplayImage
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(() =>
{
get => _displayImage;
set => SetProperty(ref _displayImage, value);
}
VideoSource = bitmap;
private string _cameraName;
public string CameraName
{
get => _cameraName;
set => SetProperty(ref _cameraName, value);
}
// 更新状态信息 (例如显示当前时间和数据大小)
StatusInfo = $"{DateTime.Now:HH:mm:ss} | {jpgData.Length / 1024} KB";
});
}
private string _statusInfo;
public string StatusInfo
{
get => _statusInfo;
set => SetProperty(ref _statusInfo, value);
}
// --- 资源清理 ---
public void Unload()
{
// 【修正 3】从单例服务取消订阅
// 这一步至关重要,否则切换页面时会内存泄漏
StreamReceiverService.Instance.OnFrameReceived -= OnGlobalFrameReceived;
private bool _isConnected;
public bool IsConnected
{
get => _isConnected;
set => SetProperty(ref _isConnected, value);
}
// --- 构造函数 ---
public VideoTileViewModel(string ip, int port, string name)
{
CameraName = name;
StatusInfo = "连接中...";
IsConnected = true;
}
private void HandleNewFrame(VideoPayload payload)
{
// 必须回到 UI 线程更新 ImageSource
Application.Current.Dispatcher.Invoke(() =>
{
// 1. 更新图片
byte[] data = payload.TargetImageBytes ?? payload.OriginalImageBytes;
if (data != null && data.Length > 0)
{
DisplayImage = ByteToBitmap(data);
}
// 2. 更新状态文字
StatusInfo = $"{payload.CaptureTime:HH:mm:ss} | {data?.Length / 1024} KB";
});
}
// 简单的 Bytes 转 BitmapImage (生产环境建议优化为 WriteableBitmap)
private BitmapImage ByteToBitmap(byte[] bytes)
{
var bitmap = new BitmapImage();
using (var stream = new MemoryStream(bytes))
{
bitmap.BeginInit();
bitmap.CacheOption = BitmapCacheOption.OnLoad;
bitmap.StreamSource = stream;
bitmap.EndInit();
}
bitmap.Freeze(); // 必须冻结才能跨线程
return bitmap;
}
public void Dispose()
{
IsConnected = false;
}
// 清空图片引用,帮助 GC 回收内存
VideoSource = null;
}
}