mirror of
https://github.com/RedWizardsLab/EchoHub.git
synced 2026-09-04 00:26:07 +02:00
refactor: enhance audio playback service with semaphore locking for thread safety
This commit is contained in:
@@ -6,6 +6,7 @@ namespace EchoHub.Client.Services;
|
|||||||
public class AudioPlaybackService
|
public class AudioPlaybackService
|
||||||
{
|
{
|
||||||
private readonly Player _player = new();
|
private readonly Player _player = new();
|
||||||
|
private readonly SemaphoreSlim _lock = new(1, 1);
|
||||||
|
|
||||||
public bool IsPlaying => _player.Playing;
|
public bool IsPlaying => _player.Playing;
|
||||||
public bool IsPaused => _player.Paused;
|
public bool IsPaused => _player.Paused;
|
||||||
@@ -19,6 +20,7 @@ public class AudioPlaybackService
|
|||||||
|
|
||||||
public async Task PlayAsync(string filePath)
|
public async Task PlayAsync(string filePath)
|
||||||
{
|
{
|
||||||
|
await _lock.WaitAsync();
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (_player.Playing)
|
if (_player.Playing)
|
||||||
@@ -30,10 +32,15 @@ public class AudioPlaybackService
|
|||||||
{
|
{
|
||||||
Log.Warning(ex, "Failed to play audio file: {Path}", filePath);
|
Log.Warning(ex, "Failed to play audio file: {Path}", filePath);
|
||||||
}
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_lock.Release();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task PauseAsync()
|
public async Task PauseAsync()
|
||||||
{
|
{
|
||||||
|
await _lock.WaitAsync();
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (_player.Playing && !_player.Paused)
|
if (_player.Playing && !_player.Paused)
|
||||||
@@ -43,10 +50,15 @@ public class AudioPlaybackService
|
|||||||
{
|
{
|
||||||
Log.Warning(ex, "Failed to pause audio playback");
|
Log.Warning(ex, "Failed to pause audio playback");
|
||||||
}
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_lock.Release();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task ResumeAsync()
|
public async Task ResumeAsync()
|
||||||
{
|
{
|
||||||
|
await _lock.WaitAsync();
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (_player.Paused)
|
if (_player.Paused)
|
||||||
@@ -56,23 +68,33 @@ public class AudioPlaybackService
|
|||||||
{
|
{
|
||||||
Log.Warning(ex, "Failed to resume audio playback");
|
Log.Warning(ex, "Failed to resume audio playback");
|
||||||
}
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_lock.Release();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task StopAsync()
|
public async Task StopAsync()
|
||||||
{
|
{
|
||||||
|
await _lock.WaitAsync();
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (_player.Playing)
|
if (_player.Playing || _player.Paused)
|
||||||
await _player.Stop();
|
await _player.Stop();
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Log.Warning(ex, "Failed to stop audio playback");
|
Log.Warning(ex, "Failed to stop audio playback");
|
||||||
}
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_lock.Release();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task SetVolumeAsync(byte volume)
|
public async Task SetVolumeAsync(byte volume)
|
||||||
{
|
{
|
||||||
|
await _lock.WaitAsync();
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await _player.SetVolume(Math.Min(volume, (byte)100));
|
await _player.SetVolume(Math.Min(volume, (byte)100));
|
||||||
@@ -81,5 +103,9 @@ public class AudioPlaybackService
|
|||||||
{
|
{
|
||||||
Log.Warning(ex, "Failed to set audio volume");
|
Log.Warning(ex, "Failed to set audio volume");
|
||||||
}
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_lock.Release();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ public sealed class AudioPlayerDialog
|
|||||||
|
|
||||||
public static void Show(IApplication app, AudioPlaybackService audioService, string filePath, string fileName)
|
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 ──
|
// ── File name ──
|
||||||
var fileLabel = new Label
|
var fileLabel = new Label
|
||||||
@@ -61,38 +61,47 @@ public sealed class AudioPlayerDialog
|
|||||||
};
|
};
|
||||||
|
|
||||||
byte currentVolume = 50;
|
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
|
var volDownButton = new Button
|
||||||
{
|
{
|
||||||
Text = "-",
|
Text = "-",
|
||||||
X = 10,
|
X = 10,
|
||||||
Y = 7,
|
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
|
var volUpButton = new Button
|
||||||
{
|
{
|
||||||
Text = "+",
|
Text = "+",
|
||||||
X = 41,
|
X = 35,
|
||||||
Y = 7,
|
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 ──
|
// ── Playback controls ──
|
||||||
@@ -100,22 +109,25 @@ public sealed class AudioPlayerDialog
|
|||||||
{
|
{
|
||||||
Text = "\u25b6 Play",
|
Text = "\u25b6 Play",
|
||||||
X = 2,
|
X = 2,
|
||||||
Y = 10,
|
Y = 9,
|
||||||
IsDefault = true
|
IsDefault = true,
|
||||||
|
ShadowStyle = ShadowStyle.None
|
||||||
};
|
};
|
||||||
|
|
||||||
var stopButton = new Button
|
var stopButton = new Button
|
||||||
{
|
{
|
||||||
Text = "\u25a0 Stop",
|
Text = "\u25a0 Stop",
|
||||||
X = Pos.Right(playButton) + 2,
|
X = Pos.Right(playButton) + 1,
|
||||||
Y = 10
|
Y = 9,
|
||||||
|
ShadowStyle = ShadowStyle.None
|
||||||
};
|
};
|
||||||
|
|
||||||
var closeButton = new Button
|
var closeButton = new Button
|
||||||
{
|
{
|
||||||
Text = "Close",
|
Text = "Close",
|
||||||
X = Pos.Right(stopButton) + 2,
|
X = Pos.Right(stopButton) + 1,
|
||||||
Y = 10
|
Y = 9,
|
||||||
|
ShadowStyle = ShadowStyle.None
|
||||||
};
|
};
|
||||||
|
|
||||||
// ── Animation state ──
|
// ── Animation state ──
|
||||||
@@ -128,6 +140,7 @@ public sealed class AudioPlayerDialog
|
|||||||
|
|
||||||
Timer? animationTimer = null;
|
Timer? animationTimer = null;
|
||||||
var isDisposed = false;
|
var isDisposed = false;
|
||||||
|
var isBusy = false;
|
||||||
|
|
||||||
// ── Helper functions ──
|
// ── Helper functions ──
|
||||||
void UpdateWave(bool isActive)
|
void UpdateWave(bool isActive)
|
||||||
@@ -278,6 +291,8 @@ public sealed class AudioPlayerDialog
|
|||||||
playButton.Accepting += (s, e) =>
|
playButton.Accepting += (s, e) =>
|
||||||
{
|
{
|
||||||
e.Handled = true;
|
e.Handled = true;
|
||||||
|
if (isBusy) return;
|
||||||
|
isBusy = true;
|
||||||
Task.Run(async () =>
|
Task.Run(async () =>
|
||||||
{
|
{
|
||||||
if (audioService.IsPaused)
|
if (audioService.IsPaused)
|
||||||
@@ -287,6 +302,7 @@ public sealed class AudioPlayerDialog
|
|||||||
{
|
{
|
||||||
UpdateStatus();
|
UpdateStatus();
|
||||||
StartAnimation();
|
StartAnimation();
|
||||||
|
isBusy = false;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
else if (audioService.IsPlaying)
|
else if (audioService.IsPlaying)
|
||||||
@@ -296,6 +312,7 @@ public sealed class AudioPlayerDialog
|
|||||||
{
|
{
|
||||||
UpdateStatus();
|
UpdateStatus();
|
||||||
StopAnimation();
|
StopAnimation();
|
||||||
|
isBusy = false;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -306,6 +323,7 @@ public sealed class AudioPlayerDialog
|
|||||||
{
|
{
|
||||||
UpdateStatus();
|
UpdateStatus();
|
||||||
StartAnimation();
|
StartAnimation();
|
||||||
|
isBusy = false;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -314,6 +332,8 @@ public sealed class AudioPlayerDialog
|
|||||||
stopButton.Accepting += (s, e) =>
|
stopButton.Accepting += (s, e) =>
|
||||||
{
|
{
|
||||||
e.Handled = true;
|
e.Handled = true;
|
||||||
|
if (isBusy) return;
|
||||||
|
isBusy = true;
|
||||||
Task.Run(async () =>
|
Task.Run(async () =>
|
||||||
{
|
{
|
||||||
await audioService.StopAsync();
|
await audioService.StopAsync();
|
||||||
@@ -321,6 +341,7 @@ public sealed class AudioPlayerDialog
|
|||||||
{
|
{
|
||||||
UpdateStatus();
|
UpdateStatus();
|
||||||
StopAnimation();
|
StopAnimation();
|
||||||
|
isBusy = false;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@@ -337,6 +358,8 @@ public sealed class AudioPlayerDialog
|
|||||||
volDownButton.Accepting += (s, e) =>
|
volDownButton.Accepting += (s, e) =>
|
||||||
{
|
{
|
||||||
e.Handled = true;
|
e.Handled = true;
|
||||||
|
if (isBusy) return;
|
||||||
|
isBusy = true;
|
||||||
var newVol = (byte)Math.Max(0, currentVolume - 10);
|
var newVol = (byte)Math.Max(0, currentVolume - 10);
|
||||||
Task.Run(async () =>
|
Task.Run(async () =>
|
||||||
{
|
{
|
||||||
@@ -345,6 +368,7 @@ public sealed class AudioPlayerDialog
|
|||||||
{
|
{
|
||||||
volumeBar.SetNeedsDraw();
|
volumeBar.SetNeedsDraw();
|
||||||
volumePercentLabel.SetNeedsDraw();
|
volumePercentLabel.SetNeedsDraw();
|
||||||
|
isBusy = false;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@@ -352,6 +376,8 @@ public sealed class AudioPlayerDialog
|
|||||||
volUpButton.Accepting += (s, e) =>
|
volUpButton.Accepting += (s, e) =>
|
||||||
{
|
{
|
||||||
e.Handled = true;
|
e.Handled = true;
|
||||||
|
if (isBusy) return;
|
||||||
|
isBusy = true;
|
||||||
var newVol = (byte)Math.Min(100, currentVolume + 10);
|
var newVol = (byte)Math.Min(100, currentVolume + 10);
|
||||||
Task.Run(async () =>
|
Task.Run(async () =>
|
||||||
{
|
{
|
||||||
@@ -360,6 +386,7 @@ public sealed class AudioPlayerDialog
|
|||||||
{
|
{
|
||||||
volumeBar.SetNeedsDraw();
|
volumeBar.SetNeedsDraw();
|
||||||
volumePercentLabel.SetNeedsDraw();
|
volumePercentLabel.SetNeedsDraw();
|
||||||
|
isBusy = false;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user