public class VideoService {const string VideoFilePath = "D:\\Users\\xx\\Desktop\\";/// <summary>/// 运行中/// </summary>public bool IsRunning { get; private set; } = false;object locker = new();CancellationTokenSource cts = new();Memory<byte>? _memoryCache;/// <summary>/// 图片base64;/// </summary>public string ImageBase64 => _memoryCache == null ? "" : Convert.ToBase64String(_memoryCache.Value.Span);/// <summary>/// 启动/// </summary>public void Startup(){if (IsRunning) return;Monitor.Enter(locker);Task.Factory.StartNew(() =>{IsRunning = true;int index = 1;while (!cts.Token.IsCancellationRequested){// 长时间运行的逻辑string filePath = Path.Combine(VideoFilePath, $"{index}.jpg");if (index >= 2) index = 1; else index++;if (File.Exists(filePath)){var bytesRead = File.ReadAllBytes(filePath);_memoryCache = new Memory<byte>(bytesRead, 0, bytesRead.Length);}}}, cts.Token, TaskCreationOptions.LongRunning, TaskScheduler.Default).Unwrap().ContinueWith(t => {IsRunning = false;cts = new();Monitor.Exit(locker);});}/// <summary>/// 关闭/// </summary>public void Shutdown(){if (!IsRunning) return;cts.Cancel();} }
然后从前端轮询 ImageBase64;这样的结果是图像非常不连贯
改造后使用 SignalR 向前端推送;
依赖 Microsoft.AspNetCore.SignalR 和 Microsoft.AspNetCore.SignalR.Client ; 在 nuget 下载
1 创建一个 Hub
/// <summary> /// 主要用来映射连接端点 /// </summary> public class VideoStreamHub : Hub {// 可以在这里实现客户端连接管理 }
2 启用 SignalR 服务,并注册端点
3 改造 service
public class VideoService {const string VideoFilePath = "D:\\Users\\xx\\Desktop\\";private readonly IHubContext<VideoStreamHub> _hubContext;public VideoService(IHubContext<VideoStreamHub> hubContext){_hubContext = hubContext;}/// <summary>/// 运行中/// </summary>public bool IsRunning { get; private set; } = false;object locker = new();CancellationTokenSource cts = new();Memory<byte>? _memoryCache;/// <summary>/// 图片base64;/// </summary>public string ImageBase64 => _memoryCache == null ? "" : Convert.ToBase64String(_memoryCache.Value.Span);/// <summary>/// 启动/// </summary>public void Startup(){if (IsRunning) return;Monitor.Enter(locker);cts = new();Task.Factory.StartNew(async () =>{IsRunning = true;int index = 1;while (!cts.Token.IsCancellationRequested){// 长时间运行的逻辑string filePath = Path.Combine(VideoFilePath, $"{index}.jpg");if (index >= 2) index = 1; else index++;if (File.Exists(filePath)){var bytesRead = File.ReadAllBytes(filePath);await _hubContext.Clients.All.SendAsync("ReceiveFrame", Convert.ToBase64String(bytesRead), cts.Token); // 向前端的接收方法推送流}await Task.Delay(30, cts.Token);}cts.Token.ThrowIfCancellationRequested();}, cts.Token, TaskCreationOptions.LongRunning, TaskScheduler.Default).Unwrap().ContinueWith(t => {if (cts.Token.IsCancellationRequested){IsRunning = false;Monitor.Exit(locker);}});}/// <summary>/// 关闭/// </summary>public void Shutdown(){if (!IsRunning) return;cts.Cancel();} }
4 前端注册接收方法
@page "/" @using BlazorAppVideo.Services @using Microsoft.AspNetCore.SignalR.Client @inject VideoService _VideoService @implements IDisposable @inject NavigationManager NavManager<PageTitle>Home</PageTitle> <img src="@currentFrame" alt="Blazor logo" />@code {string currentFrame { get; set; } = string.Empty;private HubConnection? hubConnection;CancellationTokenSource source = new();protected override async Task OnAfterRenderAsync(bool firstRender){await base.OnAfterRenderAsync(firstRender);if (firstRender){// 启动视频服务 _VideoService.Startup();// 创建SignalR连接hubConnection = new HubConnectionBuilder().WithUrl(NavManager.ToAbsoluteUri("/videoStreamHub")).Build();// 注册接收帧的处理方法hubConnection.On<string>("ReceiveFrame", (frame) =>{currentFrame = $"data:image/jpeg;base64,{frame}";InvokeAsync(StateHasChanged); // 接收到新帧后更新UI });await hubConnection.StartAsync();}}public void Dispose(){hubConnection?.DisposeAsync();} }
改造后图像非常连贯,可用于视觉项目,从服务端推送采集的图像到前端