serverRoom/device-agent/Services/SignalingClientService.cs
lvfengfree ed9d1d7325 feat: 屏幕监控大规模优化 - 支持60台设备同时监控
- Agent端优化:
  * 添加质量档位定义 (Low: 320x180@3fps, High: 1280x720@15fps)
  * H.264编码器支持动态质量切换
  * 屏幕流服务支持按需推流和质量控制
  * 添加SignalR信令客户端连接服务器

- 服务器端优化:
  * 添加StreamSignalingHub处理质量控制信令
  * 支持设备注册/注销和监控状态管理
  * 支持教师端监控控制和设备选中

- 前端组件:
  * 创建H264VideoPlayer组件支持H.264和JPEG模式
  * 更新学生屏幕监控页面使用新组件

- 性能提升:
  * 带宽从120Mbps降至6-7Mbps (降低95%)
  * 监控墙模式: 60台100kbps=6Mbps
  * 单机放大模式: 1台1Mbps+59台100kbps=6.9Mbps
  * 无人观看时停止推流节省带宽
2026-01-23 15:37:37 +08:00

229 lines
7.6 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using Microsoft.AspNetCore.SignalR.Client;
using Microsoft.Extensions.Options;
using DeviceAgent.Models;
namespace DeviceAgent.Services;
/// <summary>
/// SignalR 信令客户端 - 连接到服务器接收质量控制指令
/// </summary>
public class SignalingClientService : IDisposable
{
private readonly ILogger<SignalingClientService> _logger;
private readonly AgentConfig _config;
private readonly DeviceInfoService _deviceInfoService;
private ScreenStreamService? _screenStreamService;
private HubConnection? _connection;
private bool _isConnected;
private CancellationTokenSource? _reconnectCts;
public SignalingClientService(
ILogger<SignalingClientService> logger,
IOptions<AgentConfig> config,
DeviceInfoService deviceInfoService)
{
_logger = logger;
_config = config.Value;
_deviceInfoService = deviceInfoService;
}
/// <summary>
/// 设置 ScreenStreamService 引用(避免循环依赖)
/// </summary>
public void SetScreenStreamService(ScreenStreamService screenStreamService)
{
_screenStreamService = screenStreamService;
}
public async Task StartAsync(CancellationToken cancellationToken)
{
if (!_config.ScreenStreamEnabled)
{
_logger.LogInformation("屏幕流已禁用,跳过信令连接");
return;
}
try
{
var hubUrl = $"{_config.ServerUrl}/hubs/stream-signaling";
_logger.LogInformation("连接到信令服务器: {HubUrl}", hubUrl);
_connection = new HubConnectionBuilder()
.WithUrl(hubUrl)
.WithAutomaticReconnect(new[] { TimeSpan.Zero, TimeSpan.FromSeconds(2), TimeSpan.FromSeconds(5), TimeSpan.FromSeconds(10) })
.Build();
// 注册事件处理器
RegisterHandlers();
// 连接事件
_connection.Reconnecting += error =>
{
_logger.LogWarning("信令连接断开,正在重连...");
_isConnected = false;
return Task.CompletedTask;
};
_connection.Reconnected += async connectionId =>
{
_logger.LogInformation("信令连接已恢复: {ConnectionId}", connectionId);
_isConnected = true;
await RegisterDeviceAsync();
};
_connection.Closed += async error =>
{
_logger.LogWarning("信令连接关闭: {Error}", error?.Message);
_isConnected = false;
// 自动重连
await Task.Delay(5000, cancellationToken);
if (!cancellationToken.IsCancellationRequested)
{
await StartAsync(cancellationToken);
}
};
// 启动连接
await _connection.StartAsync(cancellationToken);
_isConnected = true;
_logger.LogInformation("信令连接已建立");
// 注册设备
await RegisterDeviceAsync();
}
catch (Exception ex)
{
_logger.LogError(ex, "启动信令客户端失败");
}
}
private void RegisterHandlers()
{
if (_connection == null) return;
// 服务器通知切换质量
_connection.On<string>("SetQuality", async (quality) =>
{
_logger.LogInformation("收到质量切换指令: {Quality}", quality);
if (_screenStreamService != null)
{
var profile = quality.ToLower() == "high"
? StreamQualityProfile.High
: StreamQualityProfile.Low;
_screenStreamService.SetQuality(profile);
}
});
// 服务器通知开始推流
_connection.On<string>("StartStreaming", async (quality) =>
{
_logger.LogInformation("收到开始推流指令: {Quality}", quality);
if (_screenStreamService != null)
{
var profile = quality.ToLower() == "high"
? StreamQualityProfile.High
: StreamQualityProfile.Low;
_screenStreamService.SetQuality(profile);
}
// 注意ScreenStreamService 已经在运行,这里只是切换质量
});
// 服务器通知停止推流
_connection.On("StopStreaming", async () =>
{
_logger.LogInformation("收到停止推流指令");
// 切换到低质量,实际推流由客户端连接数控制
if (_screenStreamService != null)
{
_screenStreamService.SetQuality(StreamQualityProfile.Low);
}
});
// 批量设备质量控制
_connection.On<List<string>, string>("DevicesNeedStream", async (deviceUuids, quality) =>
{
var myUuid = _deviceInfoService.GetDeviceInfo().Uuid;
if (deviceUuids.Contains(myUuid) && _screenStreamService != null)
{
_logger.LogInformation("设备在监控列表中,质量: {Quality}", quality);
var profile = quality.ToLower() == "high"
? StreamQualityProfile.High
: StreamQualityProfile.Low;
_screenStreamService.SetQuality(profile);
}
});
_connection.On<List<string>>("DevicesStopStream", async (deviceUuids) =>
{
var myUuid = _deviceInfoService.GetDeviceInfo().Uuid;
if (deviceUuids.Contains(myUuid) && _screenStreamService != null)
{
_logger.LogInformation("设备停止监控");
_screenStreamService.SetQuality(StreamQualityProfile.Low);
}
});
_connection.On<string, string>("DeviceQualityChange", async (deviceUuid, quality) =>
{
var myUuid = _deviceInfoService.GetDeviceInfo().Uuid;
if (deviceUuid == myUuid && _screenStreamService != null)
{
_logger.LogInformation("设备质量切换: {Quality}", quality);
var profile = quality.ToLower() == "high"
? StreamQualityProfile.High
: StreamQualityProfile.Low;
_screenStreamService.SetQuality(profile);
}
});
}
private async Task RegisterDeviceAsync()
{
if (_connection == null || !_isConnected) return;
try
{
var uuid = _deviceInfoService.GetDeviceInfo().Uuid;
await _connection.InvokeAsync("RegisterDevice", uuid);
_logger.LogInformation("设备已注册到信令服务器: {Uuid}", uuid);
}
catch (Exception ex)
{
_logger.LogError(ex, "注册设备失败");
}
}
public async Task StopAsync()
{
if (_connection != null)
{
try
{
var uuid = _deviceInfoService.GetDeviceInfo().Uuid;
await _connection.InvokeAsync("UnregisterDevice", uuid);
_logger.LogInformation("设备已从信令服务器注销");
}
catch { }
await _connection.StopAsync();
await _connection.DisposeAsync();
_connection = null;
}
_isConnected = false;
_logger.LogInformation("信令客户端已停止");
}
public void Dispose()
{
_reconnectCts?.Cancel();
_reconnectCts?.Dispose();
_connection?.DisposeAsync().AsTask().Wait();
}
}