diff --git a/src/EchoHub.Client/Services/AudioPlaybackService.cs b/src/EchoHub.Client/Services/AudioPlaybackService.cs index 7213ae7..6f819fd 100644 --- a/src/EchoHub.Client/Services/AudioPlaybackService.cs +++ b/src/EchoHub.Client/Services/AudioPlaybackService.cs @@ -6,6 +6,7 @@ namespace EchoHub.Client.Services; public class AudioPlaybackService { private readonly Player _player = new(); + private readonly SemaphoreSlim _lock = new(1, 1); public bool IsPlaying => _player.Playing; public bool IsPaused => _player.Paused; @@ -19,6 +20,7 @@ public class AudioPlaybackService public async Task PlayAsync(string filePath) { + await _lock.WaitAsync(); try { if (_player.Playing) @@ -30,10 +32,15 @@ public class AudioPlaybackService { Log.Warning(ex, "Failed to play audio file: {Path}", filePath); } + finally + { + _lock.Release(); + } } public async Task PauseAsync() { + await _lock.WaitAsync(); try { if (_player.Playing && !_player.Paused) @@ -43,10 +50,15 @@ public class AudioPlaybackService { Log.Warning(ex, "Failed to pause audio playback"); } + finally + { + _lock.Release(); + } } public async Task ResumeAsync() { + await _lock.WaitAsync(); try { if (_player.Paused) @@ -56,23 +68,33 @@ public class AudioPlaybackService { Log.Warning(ex, "Failed to resume audio playback"); } + finally + { + _lock.Release(); + } } public async Task StopAsync() { + await _lock.WaitAsync(); try { - if (_player.Playing) + if (_player.Playing || _player.Paused) await _player.Stop(); } catch (Exception ex) { Log.Warning(ex, "Failed to stop audio playback"); } + finally + { + _lock.Release(); + } } public async Task SetVolumeAsync(byte volume) { + await _lock.WaitAsync(); try { await _player.SetVolume(Math.Min(volume, (byte)100)); @@ -81,5 +103,9 @@ public class AudioPlaybackService { Log.Warning(ex, "Failed to set audio volume"); } + finally + { + _lock.Release(); + } } } diff --git a/src/EchoHub.Client/UI/Dialogs/AudioPlayerDialog.cs b/src/EchoHub.Client/UI/Dialogs/AudioPlayerDialog.cs index 7c8e81c..45db6e6 100644 --- a/src/EchoHub.Client/UI/Dialogs/AudioPlayerDialog.cs +++ b/src/EchoHub.Client/UI/Dialogs/AudioPlayerDialog.cs @@ -23,7 +23,7 @@ public sealed class AudioPlayerDialog public static void Show(IApplication app, AudioPlaybackService audioService, string filePath, string fileName) { - var dialog = new Dialog { Title = "Audio Player", Width = 52, Height = 14 }; + var dialog = new Dialog { Title = "Audio Player", Width = 52, Height = 12 }; // ── File name ── var fileLabel = new Label @@ -61,38 +61,47 @@ public sealed class AudioPlayerDialog }; byte currentVolume = 50; - var volumeBar = new ProgressBar - { - X = 14, - Y = 7, - Width = 20, - Height = 1, - Fraction = currentVolume / 100f, - ProgressBarStyle = ProgressBarStyle.Continuous - }; - - var volumePercentLabel = new Label - { - Text = $"{currentVolume}%", - X = 35, - Y = 7, - Width = 5 - }; var volDownButton = new Button { Text = "-", X = 10, Y = 7, - Width = 3 + Width = 1, + Height = 1, + NoDecorations = true, + NoPadding = true, + ShadowStyle = ShadowStyle.None + }; + + var volumeBar = new ProgressBar + { + X = 12, + Y = 7, + Width = 22, + Height = 1, + Fraction = currentVolume / 100f, + ProgressBarStyle = ProgressBarStyle.Continuous }; var volUpButton = new Button { Text = "+", - X = 41, + X = 35, Y = 7, - Width = 3 + Width = 1, + Height = 1, + NoDecorations = true, + NoPadding = true, + ShadowStyle = ShadowStyle.None + }; + + var volumePercentLabel = new Label + { + Text = $"{currentVolume}%", + X = 37, + Y = 7, + Width = 5 }; // ── Playback controls ── @@ -100,22 +109,25 @@ public sealed class AudioPlayerDialog { Text = "\u25b6 Play", X = 2, - Y = 10, - IsDefault = true + Y = 9, + IsDefault = true, + ShadowStyle = ShadowStyle.None }; var stopButton = new Button { Text = "\u25a0 Stop", - X = Pos.Right(playButton) + 2, - Y = 10 + X = Pos.Right(playButton) + 1, + Y = 9, + ShadowStyle = ShadowStyle.None }; var closeButton = new Button { Text = "Close", - X = Pos.Right(stopButton) + 2, - Y = 10 + X = Pos.Right(stopButton) + 1, + Y = 9, + ShadowStyle = ShadowStyle.None }; // ── Animation state ── @@ -128,6 +140,7 @@ public sealed class AudioPlayerDialog Timer? animationTimer = null; var isDisposed = false; + var isBusy = false; // ── Helper functions ── void UpdateWave(bool isActive) @@ -278,6 +291,8 @@ public sealed class AudioPlayerDialog playButton.Accepting += (s, e) => { e.Handled = true; + if (isBusy) return; + isBusy = true; Task.Run(async () => { if (audioService.IsPaused) @@ -287,6 +302,7 @@ public sealed class AudioPlayerDialog { UpdateStatus(); StartAnimation(); + isBusy = false; }); } else if (audioService.IsPlaying) @@ -296,6 +312,7 @@ public sealed class AudioPlayerDialog { UpdateStatus(); StopAnimation(); + isBusy = false; }); } else @@ -306,6 +323,7 @@ public sealed class AudioPlayerDialog { UpdateStatus(); StartAnimation(); + isBusy = false; }); } }); @@ -314,6 +332,8 @@ public sealed class AudioPlayerDialog stopButton.Accepting += (s, e) => { e.Handled = true; + if (isBusy) return; + isBusy = true; Task.Run(async () => { await audioService.StopAsync(); @@ -321,6 +341,7 @@ public sealed class AudioPlayerDialog { UpdateStatus(); StopAnimation(); + isBusy = false; }); }); }; @@ -337,6 +358,8 @@ public sealed class AudioPlayerDialog volDownButton.Accepting += (s, e) => { e.Handled = true; + if (isBusy) return; + isBusy = true; var newVol = (byte)Math.Max(0, currentVolume - 10); Task.Run(async () => { @@ -345,6 +368,7 @@ public sealed class AudioPlayerDialog { volumeBar.SetNeedsDraw(); volumePercentLabel.SetNeedsDraw(); + isBusy = false; }); }); }; @@ -352,6 +376,8 @@ public sealed class AudioPlayerDialog volUpButton.Accepting += (s, e) => { e.Handled = true; + if (isBusy) return; + isBusy = true; var newVol = (byte)Math.Min(100, currentVolume + 10); Task.Run(async () => { @@ -360,6 +386,7 @@ public sealed class AudioPlayerDialog { volumeBar.SetNeedsDraw(); volumePercentLabel.SetNeedsDraw(); + isBusy = false; }); }); };