using NAudio.Wave; using System.Collections.Concurrent; using System.Runtime.InteropServices; using ShengShengBuXi.ConsoleApp.Models; namespace ShengShengBuXi.ConsoleApp.Services; /// /// 电话亭服务实现 /// public class PhoneBoothService : IPhoneBoothService, IDisposable { // 依赖的服务 private readonly ISignalRService _signalRService; private readonly IAudioFileService _audioFileService; // 配置 private PhoneBoothConfig _config; // 音频设备相关 private readonly ConcurrentDictionary _keyToneDevices = new(); private WaveOutEvent? _waitingToneDevice; private AudioFileReader? _waitingToneReader; // 状态相关 private volatile bool _isRecording = false; private DateTime _lastKeyPressTime = DateTime.MinValue; private readonly List _pressedKeys = new(); private volatile bool _isWaitingTonePlaying = false; private CancellationTokenSource? _waitingToneCts; private readonly CancellationTokenSource _programCts = new(); private readonly HashSet _currentPressedKeys = new(); private Timer? _resetTimer; private Timer? _dialOutTimer; private readonly object _timerLock = new(); // Windows API private const int WH_KEYBOARD_LL = 13; private const int WM_KEYDOWN = 0x0100; private const int WM_KEYUP = 0x0101; private IntPtr _hookHandle = IntPtr.Zero; private HookProc? _hookProc; // 数字键盘的虚拟键码 (VK_NUMPAD0 - VK_NUMPAD9) private readonly int[] _numpadKeys = Enumerable.Range(0x60, 10).ToArray(); // 回车键的虚拟键码 private const int VK_RETURN = 0x0D; // 录音相关 private WaveInEvent? _waveIn = null; private WaveFileWriter? _waveWriter = null; private string _recordingFilePath = ""; private DateTime _lastSoundTime = DateTime.MinValue; private Timer? _silenceTimer = null; private volatile bool _isHangUpKeyPressed = false; // 背景音乐相关 private WaveOutEvent? _backgroundMusicDevice = null; private AudioFileReader? _backgroundMusicReader = null; private CancellationTokenSource? _backgroundMusicCts = null; private volatile bool _isBackgroundMusicPlaying = false; [StructLayout(LayoutKind.Sequential)] private struct KBDLLHOOKSTRUCT { public uint vkCode; public uint scanCode; public uint flags; public uint time; public IntPtr dwExtraInfo; } private delegate IntPtr HookProc(int nCode, IntPtr wParam, IntPtr lParam); [DllImport("user32.dll")] private static extern IntPtr SetWindowsHookEx(int idHook, HookProc lpfn, IntPtr hMod, uint dwThreadId); [DllImport("user32.dll")] private static extern bool UnhookWindowsHookEx(IntPtr hhk); [DllImport("user32.dll")] private static extern IntPtr CallNextHookEx(IntPtr hhk, int nCode, IntPtr wParam, IntPtr lParam); [DllImport("kernel32.dll")] private static extern IntPtr GetModuleHandle(string? lpModuleName); [DllImport("user32.dll")] private static extern short GetAsyncKeyState(int vKey); /// /// 构造函数 /// /// SignalR服务 /// 音频文件服务 public PhoneBoothService(ISignalRService signalRService, IAudioFileService audioFileService) { _signalRService = signalRService; _audioFileService = audioFileService; // 获取初始配置 _config = _signalRService.GetConfigAsync().GetAwaiter().GetResult(); // 注册事件处理 _signalRService.ConfigUpdated += SignalRService_ConfigUpdated; _signalRService.AudioFileUpdated += SignalRService_AudioFileUpdated; } /// /// 处理配置更新事件 /// private void SignalRService_ConfigUpdated(object? sender, PhoneBoothConfig e) { ReloadConfigAsync(e).ConfigureAwait(false); } /// /// 处理音频文件更新事件 /// private void SignalRService_AudioFileUpdated(object? sender, (string FileName, byte[] FileData) e) { _audioFileService.UpdateAudioFileAsync(e.FileName, e.FileData).ConfigureAwait(false); } /// /// 初始化服务 /// public async Task InitializeAsync() { // 初始化定时器 _resetTimer = new Timer(CheckResetTimeout, null, Timeout.Infinite, Timeout.Infinite); _dialOutTimer = new Timer(CheckDialOutTimeout, null, Timeout.Infinite, Timeout.Infinite); // 确保音频目录存在 _audioFileService.EnsureAudioDirectoryExists(_config.AudioFiles.AudioBasePath); // 初始化音频设备 InitializeAudioDevice(); } /// /// 启动服务 /// public async Task StartAsync() { try { Console.WriteLine("电话亭服务启动中..."); // 启动键盘监听 _ = StartKeyboardListener(); // 开始播放等待音 await StartPlayingWaitingTone(); Console.WriteLine("电话亭服务已启动"); } catch (Exception ex) { Console.WriteLine($"启动服务失败: {ex.Message}"); throw; } } /// /// 停止服务 /// public async Task StopAsync() { try { Console.WriteLine("正在停止电话亭服务..."); // 取消所有操作 _programCts.Cancel(); // 停止等待音 if (_isWaitingTonePlaying) { _waitingToneCts?.Cancel(); await Task.Delay(100); // 给一点时间让等待音停止 } // 停止背景音乐 if (_isBackgroundMusicPlaying) { StopBackgroundMusic(); } // 停止录音 if (_isRecording) { StopAudioRecording(); } // 卸载键盘钩子 if (_hookHandle != IntPtr.Zero) { UnhookWindowsHookEx(_hookHandle); _hookHandle = IntPtr.Zero; } // 释放音频设备 DisposeAudioDevices(); // 停止和释放定时器 _resetTimer?.Dispose(); _resetTimer = null; _dialOutTimer?.Dispose(); _dialOutTimer = null; _silenceTimer?.Dispose(); _silenceTimer = null; Console.WriteLine("电话亭服务已停止"); } catch (Exception ex) { Console.WriteLine($"停止服务失败: {ex.Message}"); throw; } } /// /// 重新加载配置 /// public async Task ReloadConfigAsync(PhoneBoothConfig config) { // 保存新配置 _config = config; Console.WriteLine("已重新加载配置"); } /// /// 释放资源 /// public void Dispose() { try { // 停止服务 StopAsync().GetAwaiter().GetResult(); // 取消订阅事件 if (_signalRService != null) { _signalRService.ConfigUpdated -= SignalRService_ConfigUpdated; _signalRService.AudioFileUpdated -= SignalRService_AudioFileUpdated; } // 释放取消令牌源 _programCts.Dispose(); _waitingToneCts?.Dispose(); _backgroundMusicCts?.Dispose(); GC.SuppressFinalize(this); } catch (Exception ex) { Console.WriteLine($"释放资源失败: {ex.Message}"); } } /// /// 初始化音频设备 /// private void InitializeAudioDevice() { try { _waitingToneDevice = new WaveOutEvent(); _waitingToneReader = new AudioFileReader(_config.AudioFiles.GetFullPath(_config.AudioFiles.WaitingToneFile)); _waitingToneDevice.Init(_waitingToneReader); Console.WriteLine("音频设备初始化成功"); } catch (Exception ex) { Console.WriteLine($"初始化音频设备失败: {ex.Message}"); throw; } } /// /// 释放音频设备 /// private void DisposeAudioDevices() { _waitingToneCts?.Cancel(); _waitingToneCts?.Dispose(); _waitingToneCts = null; if (_waitingToneDevice != null) { _waitingToneDevice.Stop(); _waitingToneDevice.Dispose(); _waitingToneDevice = null; } if (_waitingToneReader != null) { _waitingToneReader.Dispose(); _waitingToneReader = null; } // 释放背景音乐设备 _backgroundMusicCts?.Cancel(); _backgroundMusicCts?.Dispose(); _backgroundMusicCts = null; if (_backgroundMusicDevice != null) { _backgroundMusicDevice.Stop(); _backgroundMusicDevice.Dispose(); _backgroundMusicDevice = null; } if (_backgroundMusicReader != null) { _backgroundMusicReader.Dispose(); _backgroundMusicReader = null; } _isBackgroundMusicPlaying = false; foreach (var (device, reader) in _keyToneDevices.Values) { device.Stop(); device.Dispose(); reader.Dispose(); } _keyToneDevices.Clear(); Console.WriteLine("音频设备已释放"); } /// /// 检查重置超时 /// private void CheckResetTimeout(object? state) { try { // 如果正在录音中,不执行重置操作 if (_isRecording) { return; } if (_pressedKeys.Count > 0 && _pressedKeys.Count < _config.Dial.MinDigitsToDialOut && (DateTime.Now - _lastKeyPressTime).TotalSeconds >= _config.Dial.ResetTimeoutSeconds) { Console.WriteLine($"{_config.Dial.ResetTimeoutSeconds}秒内未完成{_config.Dial.MinDigitsToDialOut}位数字输入,重置等待..."); _pressedKeys.Clear(); _keyToneDevices.Clear(); _ = StartPlayingWaitingTone(); } } catch (Exception ex) { Console.WriteLine($"检查超时重置失败: {ex.Message}"); } } /// /// 检查拨出超时 /// private void CheckDialOutTimeout(object? state) { try { // 如果正在录音中,不执行拨出操作 if (_isRecording) { return; } // 检查是否已经过了配置的秒数且按键数大于等于配置的最小位数 int minDigits = _config.Dial.MinDigitsToDialOut; int autoDialSeconds = _config.Dial.AutoDialOutAfterSeconds; if (_pressedKeys.Count >= minDigits && (DateTime.Now - _lastKeyPressTime).TotalSeconds >= autoDialSeconds) { Console.WriteLine($"{autoDialSeconds}秒内无新按键,开始拨出..."); // 非阻塞方式启动录音 _ = StartRecording(); } } catch (Exception ex) { Console.WriteLine($"检查拨出超时失败: {ex.Message}"); } } /// /// 开始录音流程 /// private async Task StartRecording() { // 创建一个CancellationTokenSource用于取消整个录音流程 using var recordingCts = CancellationTokenSource.CreateLinkedTokenSource(_programCts.Token); // 创建一个单独的任务来监视挂断信号 var hangupMonitorTask = Task.Run(async () => { while (!recordingCts.Token.IsCancellationRequested) { // 检查是否收到挂断信号 if (_isHangUpKeyPressed) { Console.WriteLine("录音流程中检测到挂断信号,即将终止流程..."); recordingCts.Cancel(); break; } await Task.Delay(50); } }); try { // 进入录音状态,停止所有定时器 _isRecording = true; _isHangUpKeyPressed = false; lock (_timerLock) { _resetTimer?.Change(Timeout.Infinite, Timeout.Infinite); _dialOutTimer?.Change(Timeout.Infinite, Timeout.Infinite); } // 确保等待音已停止 if (_isWaitingTonePlaying) { _waitingToneCts?.Cancel(); // 等待等待音实际停止 while (_isWaitingTonePlaying && !recordingCts.Token.IsCancellationRequested) { await Task.Delay(10); } } // 如果已经收到取消信号,立即结束 if (recordingCts.Token.IsCancellationRequested) { throw new OperationCanceledException("录音流程被用户取消"); } Console.WriteLine("开始录音流程..."); // 随机播放指定秒数的等待接电话音频 int waitMin = _config.CallFlow.WaitForPickupMinSeconds; int waitMax = _config.CallFlow.WaitForPickupMaxSeconds; int waitTime = new Random().Next(waitMin, waitMax + 1); Console.WriteLine($"播放等待接电话音频,持续{waitTime}秒..."); try { await PlayAudioAndWait( _config.AudioFiles.GetFullPath(_config.AudioFiles.WaitForPickupFile), waitTime * 1000, true, recordingCts.Token); } catch (OperationCanceledException) { throw; } // 如果已经收到取消信号,立即结束 if (recordingCts.Token.IsCancellationRequested) { throw new OperationCanceledException("录音流程被用户取消"); } // 根据配置的概率播放电话接起音频 bool playPickup = new Random().NextDouble() < _config.CallFlow.PlayPickupProbability; if (playPickup) { Console.WriteLine("播放电话接起音频..."); try { await PlayAudioAndWait( _config.AudioFiles.GetFullPath(_config.AudioFiles.PhonePickupFile), null, false, recordingCts.Token); } catch (OperationCanceledException) { throw; } if (recordingCts.Token.IsCancellationRequested) { throw new OperationCanceledException("录音流程被用户取消"); } } // 播放提示用户录音的音频 Console.WriteLine("播放提示用户录音音频..."); try { await PlayAudioAndWait( _config.AudioFiles.GetFullPath(_config.AudioFiles.PromptUserRecordFile), null, false, recordingCts.Token); } catch (OperationCanceledException) { throw; } if (recordingCts.Token.IsCancellationRequested) { throw new OperationCanceledException("录音流程被用户取消"); } // 开始实际录音逻辑 Console.WriteLine("开始初始化录音设备..."); // 创建录音文件路径 string recordingsFolder = Path.Combine( AppDomain.CurrentDomain.BaseDirectory, _config.Recording.RecordingFolder); if (!Directory.Exists(recordingsFolder)) { Directory.CreateDirectory(recordingsFolder); } _recordingFilePath = Path.Combine( recordingsFolder, $"recording_{DateTime.Now:yyyyMMdd_HHmmss}.wav"); Console.WriteLine("正在初始化录音设备..."); // 初始化录音设备 await StartAudioRecording(_recordingFilePath); Console.WriteLine("正在录音中..."); if (recordingCts.Token.IsCancellationRequested) { throw new OperationCanceledException("录音流程被用户取消"); } // 创建一个等待完成的任务源 var recordingCompletionSource = new TaskCompletionSource(); // 启动静音检测计时器 _lastSoundTime = DateTime.Now; _silenceTimer = new Timer(CheckSilence, recordingCompletionSource, 1000, 1000); Console.WriteLine("播放滴提示音..."); try { await PlayAudioAndWait( _config.AudioFiles.GetFullPath(_config.AudioFiles.BeepPromptFile), null, false, recordingCts.Token); } catch (OperationCanceledException) { throw; } if (recordingCts.Token.IsCancellationRequested) { throw new OperationCanceledException("录音流程被用户取消"); } Console.WriteLine("提示音播放完成,等待用户说话结束..."); // 创建一个任务等待录音结束(通过静音检测、挂断按键或取消) var recordingTask = Task.Run(async () => { try { using var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(recordingCts.Token); // 注册取消处理程序 linkedCts.Token.Register(() => { recordingCompletionSource.TrySetResult(true); }); // 启动背景音乐播放(不等待它完成) if (_config.Recording.EnableBackgroundMusic) { _ = StartPlayingBackgroundMusic(linkedCts.Token); } // 等待录音完成 await recordingCompletionSource.Task; } catch (Exception ex) { // 捕获所有异常,确保不会中断主流程 Console.WriteLine($"录音任务异常: {ex.Message}"); } }); // 等待录音完成或取消 try { await Task.WhenAny(recordingTask, Task.Delay(Timeout.Infinite, recordingCts.Token)); } catch (OperationCanceledException) { // 预期的取消异常,可以忽略 } // 停止录音 StopAudioRecording(); // 确保背景音乐停止 StopBackgroundMusic(); Console.WriteLine("录音结束,重置状态..."); // 尝试上传录音文件到服务器 if (File.Exists(_recordingFilePath)) { _ = Task.Run(async () => { try { Console.WriteLine("正在上传录音文件到服务器..."); bool success = await _signalRService.UploadRecordingAsync(_recordingFilePath); if (success) { Console.WriteLine("录音文件上传成功"); } else { Console.WriteLine("录音文件上传失败"); } } catch (Exception ex) { Console.WriteLine($"上传录音文件时发生错误: {ex.Message}"); } }); } // 完全重置系统状态 CompletelyResetState(); Console.WriteLine("系统已重置,可以继续使用..."); } catch (OperationCanceledException ex) { Console.WriteLine($"录音流程被取消: {ex.Message}"); // 确保录音设备被释放 StopAudioRecording(); // 取消时也完全重置系统状态 CompletelyResetState(); Console.WriteLine("系统已重置,可以继续使用..."); } catch (Exception ex) { Console.WriteLine($"录音过程发生错误: {ex.Message}"); // 确保录音设备被释放 StopAudioRecording(); // 发生错误时也完全重置系统状态 CompletelyResetState(); } } /// /// 检查静音 /// private void CheckSilence(object? state) { try { var completionSource = state as TaskCompletionSource; // 如果用户按了回车键,且配置允许用户挂断,立即结束录音 if (_isHangUpKeyPressed && _config.Recording.AllowUserHangup) { Console.WriteLine("定时器检测到用户手动挂断"); completionSource?.TrySetResult(true); return; } // 检查是否超过配置秒数没有声音 int silenceTimeout = _config.Recording.SilenceTimeoutSeconds; if ((DateTime.Now - _lastSoundTime).TotalSeconds >= silenceTimeout) { Console.WriteLine($"检测到{silenceTimeout}秒无声音,自动挂断"); completionSource?.TrySetResult(true); } } catch (Exception ex) { Console.WriteLine($"静音检测错误: {ex.Message}"); } } /// /// 启动音频录制 /// private async Task StartAudioRecording(string filePath) { try { // 创建录音设备 _waveIn = new WaveInEvent { DeviceNumber = _config.Recording.RecordingDeviceNumber, WaveFormat = new WaveFormat( _config.Recording.SampleRate, _config.Recording.Channels), BufferMilliseconds = _config.Recording.BufferMilliseconds }; // 创建文件写入器 _waveWriter = new WaveFileWriter(filePath, _waveIn.WaveFormat); // 如果SignalR连接可用,通知服务器开始接收音频流 if (_signalRService.IsConnected) { await _signalRService.StartAudioStreamAsync(_waveIn.WaveFormat.SampleRate, _waveIn.WaveFormat.Channels); } // 处理录音数据 _waveIn.DataAvailable += (s, e) => { try { // 将数据写入文件 _waveWriter.Write(e.Buffer, 0, e.BytesRecorded); // 检查是否有声音(仅用于更新最后音频时间,不影响上传) if (HasSound(e.Buffer, e.BytesRecorded)) { _lastSoundTime = DateTime.Now; } // 实时上传音频数据到服务器(无论是否有声音都上传) if (_signalRService.IsConnected) { // 创建音频数据的副本以避免并发问题 byte[] audioDataCopy = new byte[e.BytesRecorded]; Buffer.BlockCopy(e.Buffer, 0, audioDataCopy, 0, e.BytesRecorded); // 非阻塞方式上传音频数据 _ = Task.Run(async () => { try { await _signalRService.UploadAudioDataRealtimeAsync(audioDataCopy); } catch (Exception ex) { Console.WriteLine($"实时音频数据上传出错: {ex.Message}"); } }); } } catch (Exception ex) { Console.WriteLine($"录音数据处理错误: {ex.Message}"); } }; // 录音完成事件 _waveIn.RecordingStopped += (s, e) => { // 在这里处理录音停止后的逻辑 Console.WriteLine("录音已停止"); // 通知服务器结束音频流 if (_signalRService.IsConnected) { _ = _signalRService.EndAudioStreamAsync(); } }; // 开始录音 _waveIn.StartRecording(); Console.WriteLine($"开始录音,保存到文件: {filePath}"); } catch (Exception ex) { Console.WriteLine($"启动录音失败: {ex.Message}"); throw; } } /// /// 停止音频录制 /// private void StopAudioRecording() { try { // 停止静音检测计时器 _silenceTimer?.Change(Timeout.Infinite, Timeout.Infinite); _silenceTimer?.Dispose(); _silenceTimer = null; // 停止录音 if (_waveIn != null) { _waveIn.StopRecording(); _waveIn.Dispose(); _waveIn = null; } // 关闭文件 if (_waveWriter != null) { _waveWriter.Dispose(); _waveWriter = null; Console.WriteLine($"录音已保存到: {_recordingFilePath}"); } // 确保结束音频流 if (_signalRService.IsConnected) { _ = _signalRService.EndAudioStreamAsync(); } } catch (Exception ex) { Console.WriteLine($"停止录音失败: {ex.Message}"); } } /// /// 检查是否有声音 /// private bool HasSound(byte[] buffer, int bytesRecorded) { // 将字节数组转换为浮点数数组以计算音量 float maxVolume = 0; // 对于16位PCM数据 for (int i = 0; i < bytesRecorded; i += 2) { if (i + 1 < bytesRecorded) { // 将两个字节转换为short(16位) short sample = (short)((buffer[i + 1] << 8) | buffer[i]); // 转换为-1.0到1.0范围内的浮点数 float normSample = sample / 32768.0f; // 取绝对值并更新最大音量 float absSample = Math.Abs(normSample); if (absSample > maxVolume) { maxVolume = absSample; } } } // 如果最大音量超过阈值,则认为有声音 return maxVolume > _config.Recording.SilenceThreshold; } /// /// 完全重置状态 /// private void CompletelyResetState() { try { // 确保录音设备被释放 StopAudioRecording(); // 确保背景音乐被停止 StopBackgroundMusic(); // 清除录音状态 _isRecording = false; _isHangUpKeyPressed = false; // 清除按键记录 _pressedKeys.Clear(); _currentPressedKeys.Clear(); // 停止所有按键音 foreach (var (digit, deviceInfo) in _keyToneDevices.ToArray()) { try { _keyToneDevices.TryRemove(digit, out _); deviceInfo.Device.Stop(); deviceInfo.Device.Dispose(); deviceInfo.Reader.Dispose(); } catch (Exception ex) { Console.WriteLine($"清理按键音设备失败: {ex.Message}"); } } // 重新开始播放等待音 _ = StartPlayingWaitingTone(); } catch (Exception ex) { Console.WriteLine($"重置状态失败: {ex.Message}"); // 最后的保障措施 _isRecording = false; _isHangUpKeyPressed = false; _pressedKeys.Clear(); _currentPressedKeys.Clear(); _keyToneDevices.Clear(); // 尝试停止背景音乐 try { StopBackgroundMusic(); } catch { // 忽略任何错误 } // 尝试重新开始播放等待音 try { _ = StartPlayingWaitingTone(); } catch { // 忽略任何错误 } } } /// /// 开始播放等待音 /// private async Task StartPlayingWaitingTone() { try { _waitingToneCts?.Cancel(); _waitingToneCts?.Dispose(); _waitingToneCts = CancellationTokenSource.CreateLinkedTokenSource(_programCts.Token); var token = _waitingToneCts.Token; _isWaitingTonePlaying = true; while (!token.IsCancellationRequested) { try { if (_waitingToneReader?.Position >= _waitingToneReader?.Length) { _waitingToneReader.Position = 0; } _waitingToneDevice?.Play(); await Task.Delay(100, token); } catch (OperationCanceledException) { break; } } } catch (Exception ex) { Console.WriteLine($"播放等待音失败: {ex.Message}"); } finally { _isWaitingTonePlaying = false; _waitingToneDevice?.Stop(); } } /// /// 启动键盘监听 /// private async Task StartKeyboardListener() { while (!_programCts.Token.IsCancellationRequested) { try { // 检查回车键 bool isEnterPressed = (GetAsyncKeyState(VK_RETURN) & 0x8000) != 0; if (isEnterPressed) { // 添加一点延迟,防止连续检测到按键 await Task.Delay(100, _programCts.Token); // 再次检查回车键状态,确保不是误触或抖动 bool stillPressed = (GetAsyncKeyState(VK_RETURN) & 0x8000) != 0; if (!stillPressed) { continue; } if (_isRecording) { // 检查是否允许用户挂断 if (_config.Recording.AllowUserHangup) { // 如果正在录音中,回车键被视为挂断信号 Console.WriteLine("检测到回车键,用户挂断..."); _isHangUpKeyPressed = true; // 等待一段时间,防止重复触发 await Task.Delay(500, _programCts.Token); } else { Console.WriteLine("当前配置不允许用户手动挂断"); } continue; } // 如果等待音正在播放,停止它 if (_isWaitingTonePlaying) { _waitingToneCts?.Cancel(); // 确保等待音停止 while (_isWaitingTonePlaying) { await Task.Delay(10); } } Console.WriteLine("检测到回车键,开始拨出..."); // 非阻塞方式启动录音,不等待其完成 _ = StartRecording(); // 延迟防止重复触发 await Task.Delay(500, _programCts.Token); continue; } foreach (int key in _numpadKeys) { int digit = key - 0x60; // 将虚拟键码转换为数字 bool isKeyDown = (GetAsyncKeyState(key) & 0x8000) != 0; if (isKeyDown) { if (_isRecording) { Console.WriteLine("正在录音中,无法点击按键..."); await Task.Delay(1000, _programCts.Token); continue; } if (!_currentPressedKeys.Contains(digit)) { Console.WriteLine($"按下数字键: {digit}"); _currentPressedKeys.Add(digit); await HandleDigitKeyPress(digit); } } else { if (_currentPressedKeys.Contains(digit)) { Console.WriteLine($"释放数字键: {digit}"); _currentPressedKeys.Remove(digit); await HandleDigitKeyRelease(digit); } } } await Task.Delay(10, _programCts.Token); // 降低CPU使用率 } catch (OperationCanceledException) { break; } catch (Exception ex) { Console.WriteLine($"键盘监听出错: {ex.Message}"); } } } /// /// 处理数字按键按下 /// private async Task HandleDigitKeyPress(int digit) { try { // 记录按键 _pressedKeys.Add(digit); _lastKeyPressTime = DateTime.Now; Console.WriteLine($"按下数字键: {digit}, 当前已按键数: {_pressedKeys.Count}"); // 如果是第一个按键,停止等待音 if (_pressedKeys.Count == 1 && _isWaitingTonePlaying) { _waitingToneCts?.Cancel(); } // 播放按键音 await PlayKeyTone(digit); // 重置定时器 lock (_timerLock) { _resetTimer?.Change(_config.Dial.ResetTimeoutSeconds * 1000, Timeout.Infinite); } // 检查是否需要拨出(达到配置的最小位数且指定秒数内无新按键) int minDigits = _config.Dial.MinDigitsToDialOut; if (_pressedKeys.Count >= minDigits) { // 重置拨出定时器,指定秒数后检查是否需要拨出 lock (_timerLock) { _dialOutTimer?.Change(_config.Dial.AutoDialOutAfterSeconds * 1000, Timeout.Infinite); } } else { // 如果位数不足最小位数,停止拨出定时器 lock (_timerLock) { _dialOutTimer?.Change(Timeout.Infinite, Timeout.Infinite); } } } catch (Exception ex) { Console.WriteLine($"处理按键 {digit} 失败: {ex.Message}"); } } /// /// 处理数字按键释放 /// private async Task HandleDigitKeyRelease(int digit) { try { // 当按键释放时,我们不做特殊处理 // 按键音的播放和结束完全由PlayKeyTone中的逻辑控制 // 这样确保了即使按键时间很短,声音也能够完整播放 // 我们只需要确认按键已经从当前按下状态列表中移除即可 // 实际的音频资源释放由PlayKeyTone中的PlaybackStopped事件处理 } catch (Exception ex) { Console.WriteLine($"处理按键 {digit} 释放失败: {ex.Message}"); } } /// /// 播放按键音 /// private async Task PlayKeyTone(int digit) { try { // 如果该按键声音正在播放中,不再重新开始播放 if (_keyToneDevices.ContainsKey(digit)) { // 如果已经在播放中,可以选择重新开始播放,或者保持当前播放 // 这里我们选择重新开始播放 if (_keyToneDevices.TryRemove(digit, out var oldDeviceInfo)) { // 安全地停止并释放旧设备 try { oldDeviceInfo.Device.Stop(); oldDeviceInfo.Device.Dispose(); oldDeviceInfo.Reader.Dispose(); } catch (Exception ex) { Console.WriteLine($"停止旧按键音 {digit} 失败: {ex.Message}"); } } } var device = new WaveOutEvent(); var reader = new AudioFileReader(_config.AudioFiles.GetDigitToneFilePath(digit)); device.Init(reader); // 记录开始播放时间 var startTime = DateTime.Now; int minPlayTime = _config.AudioFiles.MinKeyTonePlayTimeMs; if (_keyToneDevices.TryAdd(digit, (device, reader))) { device.Play(); // 确保至少播放最小时长 device.PlaybackStopped += async (s, e) => { // 计算已经播放的时间 var playedTime = (int)(DateTime.Now - startTime).TotalMilliseconds; // 如果播放时间不足最小时间,延迟处理 if (playedTime < minPlayTime) { try { // 如果播放已经停止但时间不够,先不删除设备 // 等待剩余的时间后再处理 var remainingTime = minPlayTime - playedTime; await Task.Delay(remainingTime); } catch (Exception ex) { Console.WriteLine($"按键音 {digit} 最小播放时间延迟失败: {ex.Message}"); } } // 播放完成后(包括可能的延迟),移除并释放资源 if (_keyToneDevices.TryRemove(digit, out var deviceInfo) && deviceInfo.Device == device) { deviceInfo.Device.Dispose(); deviceInfo.Reader.Dispose(); } }; } else { device.Dispose(); reader.Dispose(); } } catch (Exception ex) { Console.WriteLine($"播放按键音 {digit} 失败: {ex.Message}"); } } /// /// 播放音频并等待 /// private async Task PlayAudioAndWait(string audioPath, int? maxDuration = null, bool loop = false, CancellationToken token = default) { WaveOutEvent? device = null; AudioFileReader? reader = null; try { device = new WaveOutEvent(); reader = new AudioFileReader(audioPath); device.Init(reader); // 如果需要循环播放,创建一个循环播放的任务 if (loop && maxDuration.HasValue) { var startTime = DateTime.Now; var endTime = startTime.AddMilliseconds(maxDuration.Value); // 开始播放 device.Play(); // 循环播放直到达到指定时间 while (DateTime.Now < endTime && !token.IsCancellationRequested) { // 如果到达文件末尾,重新开始播放 if (reader.Position >= reader.Length) { reader.Position = 0; } // 如果没有在播放中,重新开始播放 if (device.PlaybackState != PlaybackState.Playing) { reader.Position = 0; device.Play(); } // 短暂等待,避免CPU占用过高 await Task.Delay(50, token); } // 时间到,停止播放 device.Stop(); } else if (maxDuration.HasValue) { // 开始播放 device.Play(); // 等待指定时间 await Task.Delay(maxDuration.Value, token); // 时间到,停止播放 device.Stop(); } else { // 创建一个TaskCompletionSource等待播放完成 var completionSource = new TaskCompletionSource(); // 一次性事件处理,播放完成后设置结果 EventHandler handler = null!; handler = (s, e) => { device.PlaybackStopped -= handler; completionSource.TrySetResult(true); }; device.PlaybackStopped += handler; device.Play(); // 注册取消操作 using var registration = token.Register(() => { device.Stop(); completionSource.TrySetCanceled(token); }); // 等待播放完成或取消 await completionSource.Task; } } catch (Exception ex) { if (token.IsCancellationRequested) { // 如果是取消引起的异常,重新抛出OperationCanceledException throw new OperationCanceledException("播放音频被用户取消", ex, token); } Console.WriteLine($"播放音频失败: {ex.Message}"); } finally { device?.Stop(); device?.Dispose(); reader?.Dispose(); } } /// /// 开始播放背景音乐 /// private async Task StartPlayingBackgroundMusic(CancellationToken token) { try { // 如果配置不启用背景音乐,则直接返回 if (!_config.Recording.EnableBackgroundMusic) { Console.WriteLine("背景音乐功能已禁用"); return; } // 获取背景音乐文件路径 string musicFilePath = _config.AudioFiles.GetFullPath(_config.Recording.BackgroundMusicFile); // 检查文件是否存在 if (!File.Exists(musicFilePath)) { Console.WriteLine($"背景音乐文件不存在: {musicFilePath}"); return; } // 取消可能正在播放的背景音乐 _backgroundMusicCts?.Cancel(); _backgroundMusicCts?.Dispose(); _backgroundMusicCts = CancellationTokenSource.CreateLinkedTokenSource(token); // 初始化背景音乐设备 _backgroundMusicDevice = new WaveOutEvent(); _backgroundMusicReader = new AudioFileReader(musicFilePath); // 设置音量(默认为10%) _backgroundMusicReader.Volume = _config.Recording.BackgroundMusicVolume; _backgroundMusicDevice.Init(_backgroundMusicReader); // 标记背景音乐开始播放 _isBackgroundMusicPlaying = true; Console.WriteLine($"开始播放背景音乐,音量: {_backgroundMusicReader.Volume * 100}%"); // 循环播放背景音乐 while (!_backgroundMusicCts.Token.IsCancellationRequested) { try { // 检查是否到达文件末尾,是则重置 if (_backgroundMusicReader.Position >= _backgroundMusicReader.Length) { _backgroundMusicReader.Position = 0; } // 如果没有在播放,则开始播放 if (_backgroundMusicDevice.PlaybackState != PlaybackState.Playing) { _backgroundMusicDevice.Play(); } // 短暂等待,避免CPU占用过高 await Task.Delay(100, _backgroundMusicCts.Token); } catch (OperationCanceledException) { // 正常取消,退出循环 break; } catch (Exception ex) { Console.WriteLine($"背景音乐播放出错: {ex.Message}"); // 短暂等待后继续尝试 await Task.Delay(1000, _backgroundMusicCts.Token); } } } catch (Exception ex) { Console.WriteLine($"启动背景音乐失败: {ex.Message}"); } finally { // 停止并释放音乐设备 StopBackgroundMusic(); } } /// /// 停止背景音乐 /// private void StopBackgroundMusic() { try { // 取消播放任务 _backgroundMusicCts?.Cancel(); // 停止并释放设备 if (_backgroundMusicDevice != null) { _backgroundMusicDevice.Stop(); _backgroundMusicDevice.Dispose(); _backgroundMusicDevice = null; } if (_backgroundMusicReader != null) { _backgroundMusicReader.Dispose(); _backgroundMusicReader = null; } // 标记背景音乐已停止 _isBackgroundMusicPlaying = false; Console.WriteLine("背景音乐已停止"); } catch (Exception ex) { Console.WriteLine($"停止背景音乐失败: {ex.Message}"); } } }