362 lines
10 KiB
C#
362 lines
10 KiB
C#
using System.Text.Json;
|
||
|
||
using Microsoft.AspNetCore.SignalR.Client;
|
||
|
||
using ShengShengBuXi.ConsoleApp.Models;
|
||
|
||
namespace ShengShengBuXi.ConsoleApp.Services;
|
||
|
||
/// <summary>
|
||
/// SignalR客户端服务实现
|
||
/// </summary>
|
||
public class SignalRService : ISignalRService
|
||
{
|
||
private HubConnection? _hubConnection;
|
||
private readonly string _configBackupPath;
|
||
private PhoneBoothConfig _currentConfig;
|
||
private readonly SemaphoreSlim _semaphore = new(1, 1);
|
||
private string? _clientId;
|
||
|
||
/// <summary>
|
||
/// 连接状态改变事件
|
||
/// </summary>
|
||
public event EventHandler<bool>? ConnectionStateChanged;
|
||
|
||
/// <summary>
|
||
/// 配置更新事件
|
||
/// </summary>
|
||
public event EventHandler<PhoneBoothConfig>? ConfigUpdated;
|
||
|
||
/// <summary>
|
||
/// 音频文件更新事件
|
||
/// </summary>
|
||
public event EventHandler<(string FileName, byte[] FileData)>? AudioFileUpdated;
|
||
|
||
/// <summary>
|
||
/// 获取当前是否已连接
|
||
/// </summary>
|
||
public bool IsConnected => _hubConnection?.State == HubConnectionState.Connected;
|
||
|
||
/// <summary>
|
||
/// 构造函数
|
||
/// </summary>
|
||
/// <param name="configBackupPath">配置备份文件路径</param>
|
||
public SignalRService(string configBackupPath = "config.json")
|
||
{
|
||
_configBackupPath = configBackupPath;
|
||
|
||
// 加载本地配置
|
||
_currentConfig = PhoneBoothConfig.LoadFromFile(_configBackupPath);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 启动连接
|
||
/// </summary>
|
||
/// <param name="hubUrl">SignalR Hub URL</param>
|
||
public async Task StartConnectionAsync(string hubUrl)
|
||
{
|
||
if (_hubConnection != null)
|
||
{
|
||
return;
|
||
}
|
||
|
||
_hubConnection = new HubConnectionBuilder()
|
||
.WithUrl(hubUrl)
|
||
.WithAutomaticReconnect()
|
||
.Build();
|
||
|
||
// 注册连接关闭事件
|
||
_hubConnection.Closed += async (error) =>
|
||
{
|
||
await Task.Delay(new Random().Next(0, 5) * 1000);
|
||
ConnectionStateChanged?.Invoke(this, false);
|
||
await _hubConnection.StartAsync();
|
||
};
|
||
|
||
// 注册连接恢复事件
|
||
_hubConnection.Reconnected += connectionId =>
|
||
{
|
||
ConnectionStateChanged?.Invoke(this, true);
|
||
return Task.CompletedTask;
|
||
};
|
||
|
||
// 注册接收配置更新事件
|
||
_hubConnection.On<string>("ReceiveConfigUpdate", async (configJson) =>
|
||
{
|
||
try
|
||
{
|
||
var config = JsonSerializer.Deserialize<PhoneBoothConfig>(configJson);
|
||
if (config != null)
|
||
{
|
||
await UpdateConfigAsync(config);
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Console.WriteLine($"处理配置更新失败: {ex.Message}");
|
||
}
|
||
});
|
||
|
||
// 注册接收音频文件更新事件
|
||
_hubConnection.On<string, byte[]>("ReceiveAudioFile", (fileName, fileData) =>
|
||
{
|
||
try
|
||
{
|
||
AudioFileUpdated?.Invoke(this, (fileName, fileData));
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Console.WriteLine($"处理音频文件更新失败: {ex.Message}");
|
||
}
|
||
});
|
||
|
||
// 注册客户端ID确认事件
|
||
_hubConnection.On<string>("RegistrationConfirmed", (clientId) =>
|
||
{
|
||
_clientId = clientId;
|
||
Console.WriteLine($"客户端注册成功,ID: {clientId}");
|
||
});
|
||
|
||
try
|
||
{
|
||
await _hubConnection.StartAsync();
|
||
ConnectionStateChanged?.Invoke(this, true);
|
||
|
||
// 连接成功后,注册为控制器客户端
|
||
await RegisterAsControllerAsync();
|
||
|
||
// 连接成功后,尝试获取最新配置
|
||
await RequestLatestConfigAsync();
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Console.WriteLine($"SignalR连接失败: {ex.Message}");
|
||
ConnectionStateChanged?.Invoke(this, false);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 注册为控制器客户端
|
||
/// </summary>
|
||
private async Task RegisterAsControllerAsync()
|
||
{
|
||
if (!IsConnected || _hubConnection == null)
|
||
{
|
||
return;
|
||
}
|
||
|
||
try
|
||
{
|
||
await _hubConnection.InvokeAsync("RegisterClient", ClientType.Controller, "电话亭控制器");
|
||
Console.WriteLine("已向服务器注册为控制器客户端");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Console.WriteLine($"注册客户端失败: {ex.Message}");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 停止连接
|
||
/// </summary>
|
||
public async Task StopConnectionAsync()
|
||
{
|
||
if (_hubConnection != null)
|
||
{
|
||
try
|
||
{
|
||
// 如果有活动的音频流,先结束它
|
||
await EndAudioStreamAsync();
|
||
}
|
||
catch { /* 忽略错误 */ }
|
||
|
||
await _hubConnection.StopAsync();
|
||
await _hubConnection.DisposeAsync();
|
||
_hubConnection = null;
|
||
_clientId = null;
|
||
ConnectionStateChanged?.Invoke(this, false);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取最新配置
|
||
/// </summary>
|
||
public async Task<PhoneBoothConfig> GetConfigAsync()
|
||
{
|
||
await _semaphore.WaitAsync();
|
||
try
|
||
{
|
||
// 如果连接正常,尝试从服务器获取最新配置
|
||
if (IsConnected)
|
||
{
|
||
await RequestLatestConfigAsync();
|
||
}
|
||
|
||
return _currentConfig;
|
||
}
|
||
finally
|
||
{
|
||
_semaphore.Release();
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 上传录音文件
|
||
/// </summary>
|
||
/// <param name="filePath">文件路径</param>
|
||
public async Task<bool> UploadRecordingAsync(string filePath)
|
||
{
|
||
// 检查配置是否允许上传录音文件
|
||
if (!_currentConfig.Recording.UploadRecordingToServer)
|
||
{
|
||
Console.WriteLine("根据配置设置,不上传录音文件");
|
||
return false;
|
||
}
|
||
|
||
if (!IsConnected || _hubConnection == null)
|
||
{
|
||
Console.WriteLine("无法上传录音文件:未连接到服务器");
|
||
return false;
|
||
}
|
||
|
||
try
|
||
{
|
||
if (!File.Exists(filePath))
|
||
{
|
||
Console.WriteLine($"无法上传录音文件:文件不存在 {filePath}");
|
||
return false;
|
||
}
|
||
|
||
string fileName = Path.GetFileName(filePath);
|
||
byte[] fileData = await File.ReadAllBytesAsync(filePath);
|
||
|
||
Console.WriteLine($"开始上传录音文件: {fileName}, 大小: {fileData.Length/1024} KB");
|
||
await _hubConnection.InvokeAsync("UploadRecording", fileName, fileData);
|
||
Console.WriteLine($"录音文件上传成功: {fileName}");
|
||
return true;
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Console.WriteLine($"上传录音文件失败: {ex.Message}");
|
||
return false;
|
||
}
|
||
|
||
}
|
||
|
||
/// <summary>
|
||
/// 开始音频流
|
||
/// </summary>
|
||
/// <param name="sampleRate">采样率</param>
|
||
/// <param name="channels">通道数</param>
|
||
public async Task StartAudioStreamAsync(int sampleRate, int channels)
|
||
{
|
||
if (!IsConnected || _hubConnection == null)
|
||
{
|
||
Console.WriteLine("无法开始音频流:未连接到服务器");
|
||
return;
|
||
}
|
||
|
||
try
|
||
{
|
||
await _hubConnection.InvokeAsync("StartAudioStream", sampleRate, channels);
|
||
Console.WriteLine($"已通知服务器开始接收音频流,采样率: {sampleRate}, 通道数: {channels}");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Console.WriteLine($"开始音频流失败: {ex.Message}");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 结束音频流
|
||
/// </summary>
|
||
public async Task EndAudioStreamAsync()
|
||
{
|
||
if (!IsConnected || _hubConnection == null)
|
||
{
|
||
return;
|
||
}
|
||
|
||
try
|
||
{
|
||
await _hubConnection.InvokeAsync("EndAudioStream");
|
||
Console.WriteLine("已通知服务器结束音频流");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Console.WriteLine($"结束音频流失败: {ex.Message}");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 实时上传音频数据
|
||
/// </summary>
|
||
/// <param name="audioData">音频数据字节数组</param>
|
||
public async Task<bool> UploadAudioDataRealtimeAsync(byte[] audioData)
|
||
{
|
||
if (!IsConnected || _hubConnection == null)
|
||
{
|
||
return false;
|
||
}
|
||
|
||
try
|
||
{
|
||
// 直接将音频数据发送到服务器
|
||
await _hubConnection.InvokeAsync("ReceiveAudioData", audioData);
|
||
return true;
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Console.WriteLine($"实时上传音频数据失败: {ex.Message}");
|
||
return false;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 请求最新配置
|
||
/// </summary>
|
||
private async Task RequestLatestConfigAsync()
|
||
{
|
||
if (!IsConnected || _hubConnection == null)
|
||
{
|
||
return;
|
||
}
|
||
|
||
try
|
||
{
|
||
string configJson = await _hubConnection.InvokeAsync<string>("GetLatestConfig");
|
||
var config = JsonSerializer.Deserialize<PhoneBoothConfig>(configJson);
|
||
if (config != null)
|
||
{
|
||
await UpdateConfigAsync(config);
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Console.WriteLine($"获取最新配置失败: {ex.Message}");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 更新配置
|
||
/// </summary>
|
||
/// <param name="newConfig">新配置</param>
|
||
private async Task UpdateConfigAsync(PhoneBoothConfig newConfig)
|
||
{
|
||
await _semaphore.WaitAsync();
|
||
try
|
||
{
|
||
_currentConfig = newConfig;
|
||
|
||
// 保存到本地备份
|
||
_currentConfig.SaveToFile(_configBackupPath);
|
||
|
||
// 触发配置更新事件
|
||
ConfigUpdated?.Invoke(this, _currentConfig);
|
||
}
|
||
finally
|
||
{
|
||
_semaphore.Release();
|
||
}
|
||
}
|
||
} |