From e936cdaedaa9b60f5ccad30d24c4fa9a275235da Mon Sep 17 00:00:00 2001
From: NeoCodeMatrix86 <40752681+NeoCoderMatrix86@users.noreply.github.com>
Date: Mon, 21 Sep 2026 14:59:12 +0200
Subject: [PATCH 1/5] Update AudioPlayer.razor
---
.../Shared/Audio/AudioPlayer.razor | 16 ++++++++--------
1 file changed, 8 insertions(+), 8 deletions(-)
diff --git a/AudioCuesheetEditor/Shared/Audio/AudioPlayer.razor b/AudioCuesheetEditor/Shared/Audio/AudioPlayer.razor
index c3fe120a..49baffdb 100644
--- a/AudioCuesheetEditor/Shared/Audio/AudioPlayer.razor
+++ b/AudioCuesheetEditor/Shared/Audio/AudioPlayer.razor
@@ -31,7 +31,7 @@ along with Foobar. If not, see
{
@String.Format("--{0}--{1}--", CultureInfo.CurrentCulture.DateTimeFormat.TimeSeparator, CultureInfo.CurrentCulture.DateTimeFormat.TimeSeparator)
}
-
+
@GetSliderTimeValue()
@@ -63,21 +63,21 @@ along with Foobar. If not, see
@code {
- double sliderValue;
- HotKeysContext? hotKeysContext;
+ double _sliderValue;
+ HotKeysContext? _hotKeysContext;
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
_playbackService.CurrentPositionChanged -= PlaybackService_CurrentPositionChanged;
- hotKeysContext?.DisposeAsync();
+ _hotKeysContext?.DisposeAsync();
}
protected override async Task OnInitializedAsync()
{
await base.OnInitializedAsync();
_playbackService.CurrentPositionChanged += PlaybackService_CurrentPositionChanged;
- hotKeysContext = _hotKeys.CreateContext()
+ _hotKeysContext = _hotKeys.CreateContext()
.Add(ModKey.Ctrl, Key.p, OnPlayOrPauseClicked)
.Add(ModKey.Ctrl, Key.ArrowRight, PlayNextTrackAsync)
.Add(ModKey.Ctrl, Key.ArrowLeft, PlayPreviousTrackAsync)
@@ -121,18 +121,18 @@ along with Foobar. If not, see
{
if ((_playbackService.CurrentPosition.HasValue) && (_playbackService.TotalTime.HasValue))
{
- sliderValue = (_playbackService.CurrentPosition.Value / _playbackService.TotalTime.Value) * 100.0;
+ _sliderValue = (_playbackService.CurrentPosition.Value / _playbackService.TotalTime.Value) * 100.0;
}
else
{
- sliderValue = 0.0;
+ _sliderValue = 0.0;
}
InvokeAsync(StateHasChanged);
}
string GetSliderTimeValue()
{
- var time = sliderValue * _playbackService.TotalTime / 100.0;
+ var time = _sliderValue * _playbackService.TotalTime / 100.0;
if (time.HasValue)
{
return time.Value.ToString(@"hh\:mm\:ss");
From a9332badbceafca5534ddb4b067c4e411229f855 Mon Sep 17 00:00:00 2001
From: NeoCodeMatrix86 <40752681+NeoCoderMatrix86@users.noreply.github.com>
Date: Mon, 21 Sep 2026 15:58:44 +0200
Subject: [PATCH 2/5] Update PlaybackService.cs
---
AudioCuesheetEditor/Services/Audio/PlaybackService.cs | 3 +++
1 file changed, 3 insertions(+)
diff --git a/AudioCuesheetEditor/Services/Audio/PlaybackService.cs b/AudioCuesheetEditor/Services/Audio/PlaybackService.cs
index 04f785ea..db96491c 100644
--- a/AudioCuesheetEditor/Services/Audio/PlaybackService.cs
+++ b/AudioCuesheetEditor/Services/Audio/PlaybackService.cs
@@ -47,6 +47,7 @@ private set
}
}
public Track? CurrentlyPlayingTrack => _sessionStateContainer.Cuesheet.Audiofiles.SelectMany(x => x.Tracks).SingleOrDefault(x => x.Begin.HasValue == true && x.End.HasValue == true && x.Begin <= CurrentPosition && x.End > CurrentPosition);
+ //TODO: Doesn't get reset when cuesheet or application is reset
public TimeSpan? TotalTime
{
get
@@ -63,6 +64,7 @@ public TimeSpan? TotalTime
public Boolean IsPlaybackPossible => _sessionStateContainer.Cuesheet.Audiofiles.Any(x => string.IsNullOrEmpty(x.ObjectURL) == false);
public Boolean IsPreviousPossible => (CurrentlyPlayingTrack != null) && _sessionStateContainer.Cuesheet.Audiofiles.SelectMany(x => x.Tracks).FirstOrDefault(x => x.End <= CurrentlyPlayingTrack.Begin) != null;
public Boolean IsNextPossible => (CurrentlyPlayingTrack != null) && _sessionStateContainer.Cuesheet.Audiofiles.SelectMany(x => x.Tracks).FirstOrDefault(x => x.Begin >= CurrentlyPlayingTrack.End) != null;
+ //TODO: Doesn't stop playback when resetting cuesheet or application
public Boolean IsPlaying => _currentlyPlayingAudiofile != null;
public async Task InitializeAsync()
@@ -271,6 +273,7 @@ async void UpdateCurrentPosition(object? state)
}
}
CalculateDurationsBeforeCurrentlyPlayingAudiofile();
+ //TODO: When audio is stopped the current position seems to be 00:00:00 and not --:--:--
var currentSecondsInCurrentlyPlayingAudiofile = await _jsRuntime.InvokeAsync("audioInterop.getAudioCurrentTime");
if (_audiofileDurationsBeforeCurrentlyPlayingAudiofile.HasValue)
{
From 1b83d633c685b3424fa707e39778cf5c3ffabb91 Mon Sep 17 00:00:00 2001
From: NeoCodeMatrix86 <40752681+NeoCoderMatrix86@users.noreply.github.com>
Date: Mon, 21 Sep 2026 16:41:54 +0200
Subject: [PATCH 3/5] reset playback when application gets reset
---
.../Services/UI/SessionStateContainerTests.cs | 13 +++++++++----
.../Services/Audio/PlaybackService.cs | 9 +++++++--
.../Services/UI/ISessionStateContainer.cs | 1 +
.../Services/UI/SessionStateContainer.cs | 2 ++
4 files changed, 19 insertions(+), 6 deletions(-)
diff --git a/AudioCuesheetEditor.Tests/Services/UI/SessionStateContainerTests.cs b/AudioCuesheetEditor.Tests/Services/UI/SessionStateContainerTests.cs
index 0ba87be4..5188a1de 100644
--- a/AudioCuesheetEditor.Tests/Services/UI/SessionStateContainerTests.cs
+++ b/AudioCuesheetEditor.Tests/Services/UI/SessionStateContainerTests.cs
@@ -45,14 +45,17 @@ public async Task Cuesheet_SetNewValueInDetailView_ShouldTriggerActiveCuesheetCh
var viewOptions = new ViewOptions();
_localStorageOptionsProvider.Setup(x => x.GetOptionsAsync()).ReturnsAsync(viewOptions);
await _sessionStateContainer.InitializeAsync();
- bool eventTriggered = false;
- _sessionStateContainer.ActiveCuesheetChanged += (sender, args) => eventTriggered = true;
+ bool activeCuesheetChangedEvent = false;
+ bool cuesheetChangedEvent = false;
+ _sessionStateContainer.ActiveCuesheetChanged += (sender, args) => activeCuesheetChangedEvent = true;
+ _sessionStateContainer.CuesheetChanged += (sender, args) => cuesheetChangedEvent = true;
// Act
_sessionStateContainer.Cuesheet = newCuesheet;
// Assert
- Assert.IsTrue(eventTriggered);
+ Assert.IsTrue(activeCuesheetChangedEvent);
+ Assert.IsTrue(cuesheetChangedEvent);
}
[TestMethod]
@@ -67,13 +70,15 @@ public async Task Cuesheet_SetNewValueInImportView_ShouldNotTriggerActiveCueshee
_localStorageOptionsProvider.Setup(x => x.GetOptionsAsync()).ReturnsAsync(viewOptions);
await _sessionStateContainer.InitializeAsync();
bool eventTriggered = false;
+ bool cuesheetChangedEvent = false;
_sessionStateContainer.ActiveCuesheetChanged += (sender, args) => eventTriggered = true;
-
+ _sessionStateContainer.CuesheetChanged += (sender, args) => cuesheetChangedEvent = true;
// Act
_sessionStateContainer.Cuesheet = newCuesheet;
// Assert
Assert.IsFalse(eventTriggered);
+ Assert.IsTrue(cuesheetChangedEvent);
}
[TestMethod]
diff --git a/AudioCuesheetEditor/Services/Audio/PlaybackService.cs b/AudioCuesheetEditor/Services/Audio/PlaybackService.cs
index db96491c..3b6bc1ba 100644
--- a/AudioCuesheetEditor/Services/Audio/PlaybackService.cs
+++ b/AudioCuesheetEditor/Services/Audio/PlaybackService.cs
@@ -47,7 +47,6 @@ private set
}
}
public Track? CurrentlyPlayingTrack => _sessionStateContainer.Cuesheet.Audiofiles.SelectMany(x => x.Tracks).SingleOrDefault(x => x.Begin.HasValue == true && x.End.HasValue == true && x.Begin <= CurrentPosition && x.End > CurrentPosition);
- //TODO: Doesn't get reset when cuesheet or application is reset
public TimeSpan? TotalTime
{
get
@@ -64,7 +63,6 @@ public TimeSpan? TotalTime
public Boolean IsPlaybackPossible => _sessionStateContainer.Cuesheet.Audiofiles.Any(x => string.IsNullOrEmpty(x.ObjectURL) == false);
public Boolean IsPreviousPossible => (CurrentlyPlayingTrack != null) && _sessionStateContainer.Cuesheet.Audiofiles.SelectMany(x => x.Tracks).FirstOrDefault(x => x.End <= CurrentlyPlayingTrack.Begin) != null;
public Boolean IsNextPossible => (CurrentlyPlayingTrack != null) && _sessionStateContainer.Cuesheet.Audiofiles.SelectMany(x => x.Tracks).FirstOrDefault(x => x.Begin >= CurrentlyPlayingTrack.End) != null;
- //TODO: Doesn't stop playback when resetting cuesheet or application
public Boolean IsPlaying => _currentlyPlayingAudiofile != null;
public async Task InitializeAsync()
@@ -74,6 +72,12 @@ public async Task InitializeAsync()
_dotNetObjectReference = DotNetObjectReference.Create(this);
await _jsRuntime.InvokeVoidAsync("audioInterop.register", _dotNetObjectReference);
}
+ _sessionStateContainer.CuesheetChanged += SessionStateContainer_CuesheetChanged;
+ }
+
+ void SessionStateContainer_CuesheetChanged(object? sender, EventArgs e)
+ {
+ _ = StopAsync();
}
public async Task PlayOrPauseAsync()
@@ -231,6 +235,7 @@ public void OnPlaybackPaused()
public async ValueTask DisposeAsync()
{
GC.SuppressFinalize(this);
+ _sessionStateContainer.CuesheetChanged -= SessionStateContainer_CuesheetChanged;
await _jsRuntime.InvokeVoidAsync("audioInterop.unregister");
_dotNetObjectReference?.Dispose();
}
diff --git a/AudioCuesheetEditor/Services/UI/ISessionStateContainer.cs b/AudioCuesheetEditor/Services/UI/ISessionStateContainer.cs
index a4ea5331..0e68b86b 100644
--- a/AudioCuesheetEditor/Services/UI/ISessionStateContainer.cs
+++ b/AudioCuesheetEditor/Services/UI/ISessionStateContainer.cs
@@ -21,6 +21,7 @@ namespace AudioCuesheetEditor.Services.UI
{
public interface ISessionStateContainer
{
+ public event EventHandler? CuesheetChanged;
public event EventHandler? ActiveCuesheetChanged;
public Cuesheet Cuesheet { get; set; }
public Cuesheet? ImportCuesheet { get; set; }
diff --git a/AudioCuesheetEditor/Services/UI/SessionStateContainer.cs b/AudioCuesheetEditor/Services/UI/SessionStateContainer.cs
index d177360c..4e5d6cb7 100644
--- a/AudioCuesheetEditor/Services/UI/SessionStateContainer.cs
+++ b/AudioCuesheetEditor/Services/UI/SessionStateContainer.cs
@@ -31,6 +31,7 @@ public class SessionStateContainer : ISessionStateContainer, IDisposable
private ViewOptions? _viewOptions;
private bool disposedValue;
+ public event EventHandler? CuesheetChanged;
public event EventHandler? ActiveCuesheetChanged;
public SessionStateContainer(ILocalStorageOptionsProvider localStorageOptionsProvider)
@@ -52,6 +53,7 @@ public Cuesheet Cuesheet
{
_cuesheet = value;
SetActiveCuesheet();
+ CuesheetChanged?.Invoke(this, EventArgs.Empty);
}
}
public Cuesheet? ImportCuesheet
From f9616b595bd37fd153df524df657c2334aec76aa Mon Sep 17 00:00:00 2001
From: NeoCodeMatrix86 <40752681+NeoCoderMatrix86@users.noreply.github.com>
Date: Wed, 23 Sep 2026 10:18:49 +0200
Subject: [PATCH 4/5] Update PlaybackService.cs
---
.../Services/Audio/PlaybackService.cs | 43 ++++++++++---------
1 file changed, 23 insertions(+), 20 deletions(-)
diff --git a/AudioCuesheetEditor/Services/Audio/PlaybackService.cs b/AudioCuesheetEditor/Services/Audio/PlaybackService.cs
index 3b6bc1ba..be29f6a7 100644
--- a/AudioCuesheetEditor/Services/Audio/PlaybackService.cs
+++ b/AudioCuesheetEditor/Services/Audio/PlaybackService.cs
@@ -30,7 +30,7 @@ public class PlaybackService(IJSRuntime jsRuntime, ISessionStateContainer sessio
private readonly Lock _timerLock = new();
private TimeSpan? _currentPosition;
private DotNetObjectReference? _dotNetObjectReference;
- private TimeSpan? _audiofileDurationsBeforeCurrentlyPlayingAudiofile;
+ private TimeSpan _durationBeforeCurrentlyPlayingAudiofile;
public event Action? CurrentPositionChanged;
@@ -72,6 +72,7 @@ public async Task InitializeAsync()
_dotNetObjectReference = DotNetObjectReference.Create(this);
await _jsRuntime.InvokeVoidAsync("audioInterop.register", _dotNetObjectReference);
}
+ //TODO: When audiofile gets cleared playback doesnt stop but the audioplayer doesn't offer stop anymore
_sessionStateContainer.CuesheetChanged += SessionStateContainer_CuesheetChanged;
}
@@ -229,7 +230,7 @@ public void OnPlaybackPaused()
{
IsPaused = true;
StopTimer();
- UpdateCurrentPosition(null);
+ UpdateCurrentPosition();
}
public async ValueTask DisposeAsync()
@@ -245,14 +246,14 @@ async Task PlayAsync(Audiofile audiofileToPlay)
await _jsRuntime.InvokeVoidAsync("audioInterop.setAudioSource", audiofileToPlay.ObjectURL);
await _jsRuntime.InvokeVoidAsync("audioInterop.playAudio");
_currentlyPlayingAudiofile = audiofileToPlay;
- _audiofileDurationsBeforeCurrentlyPlayingAudiofile = null;
+ _durationBeforeCurrentlyPlayingAudiofile = TimeSpan.Zero;
}
void Reset()
{
StopTimer();
_currentlyPlayingAudiofile = null;
- _audiofileDurationsBeforeCurrentlyPlayingAudiofile = null;
+ _durationBeforeCurrentlyPlayingAudiofile = TimeSpan.Zero;
CurrentPosition = null;
IsPaused = false;
}
@@ -268,40 +269,42 @@ void StopTimer()
_updateTimer = null;
}
- async void UpdateCurrentPosition(object? state)
+ async void UpdateCurrentPosition(object? state = null)
{
lock (_timerLock)
{
- if (_currentlyPlayingAudiofile == null)
+ if (IsPlaying == false)
{
- StopTimer();
+ Reset();
+ return;
+ }
+ else
+ {
+ SetDurationBeforeCurrentlyPlayingAudiofile();
}
}
- CalculateDurationsBeforeCurrentlyPlayingAudiofile();
- //TODO: When audio is stopped the current position seems to be 00:00:00 and not --:--:--
- var currentSecondsInCurrentlyPlayingAudiofile = await _jsRuntime.InvokeAsync("audioInterop.getAudioCurrentTime");
- if (_audiofileDurationsBeforeCurrentlyPlayingAudiofile.HasValue)
- {
- CurrentPosition = _audiofileDurationsBeforeCurrentlyPlayingAudiofile + TimeSpan.FromSeconds(currentSecondsInCurrentlyPlayingAudiofile);
- }
- else
+ if (IsPlaying)
{
- CurrentPosition = TimeSpan.FromSeconds(currentSecondsInCurrentlyPlayingAudiofile);
+ var currentSecondsInCurrentlyPlayingAudiofile = await _jsRuntime.InvokeAsync("audioInterop.getAudioCurrentTime");
+ CurrentPosition = _durationBeforeCurrentlyPlayingAudiofile + TimeSpan.FromSeconds(currentSecondsInCurrentlyPlayingAudiofile);
}
}
- void CalculateDurationsBeforeCurrentlyPlayingAudiofile()
+ void SetDurationBeforeCurrentlyPlayingAudiofile()
{
- if ((_audiofileDurationsBeforeCurrentlyPlayingAudiofile != null) || (_currentlyPlayingAudiofile == null))
+ if (_currentlyPlayingAudiofile == null)
{
return;
}
- _audiofileDurationsBeforeCurrentlyPlayingAudiofile = TimeSpan.Zero;
+ _durationBeforeCurrentlyPlayingAudiofile = TimeSpan.Zero;
var index = _sessionStateContainer.Cuesheet.Audiofiles.IndexOf(_currentlyPlayingAudiofile);
for (int i = 0; i < index; i++)
{
var audiofile = _sessionStateContainer.Cuesheet.Audiofiles[i];
- _audiofileDurationsBeforeCurrentlyPlayingAudiofile += audiofile.Duration;
+ if (audiofile.Duration.HasValue)
+ {
+ _durationBeforeCurrentlyPlayingAudiofile += audiofile.Duration.Value;
+ }
}
}
}
From 9624198d9e8c96aceac6e55fcb29be035ee42a6a Mon Sep 17 00:00:00 2001
From: NeoCodeMatrix86 <40752681+NeoCoderMatrix86@users.noreply.github.com>
Date: Wed, 23 Sep 2026 11:13:13 +0200
Subject: [PATCH 5/5] fix audiofile reset bug
---
.../AudioCuesheet/AudiofileManagerTests.cs | 125 ++++++++++++++++--
.../Services/Audio/PlaybackService.cs | 20 ++-
.../AudioCuesheet/AudiofileManager.cs | 4 +
.../AudioCuesheet/IAudiofileManager.cs | 4 +
4 files changed, 142 insertions(+), 11 deletions(-)
diff --git a/AudioCuesheetEditor.Tests/Services/AudioCuesheet/AudiofileManagerTests.cs b/AudioCuesheetEditor.Tests/Services/AudioCuesheet/AudiofileManagerTests.cs
index 6f30d296..73565f58 100644
--- a/AudioCuesheetEditor.Tests/Services/AudioCuesheet/AudiofileManagerTests.cs
+++ b/AudioCuesheetEditor.Tests/Services/AudioCuesheet/AudiofileManagerTests.cs
@@ -88,6 +88,14 @@ public async Task SetPropertiesAsync_EmptyBrowserFile_ShouldClearPropertiesAsync
ObjectURL = "Just a test",
Duration = TimeSpan.FromSeconds(120)
};
+ var audiofileChangedFired = false;
+ _audiofileManager.AudiofileChanged += delegate(object? sender, Audiofile e)
+ {
+ if (e == audiofile)
+ {
+ audiofileChangedFired = true;
+ }
+ };
var expectedUrl = audiofile.ObjectURL;
// Act
await _audiofileManager.SetPropertiesAsync(audiofile, null, string.Empty);
@@ -96,6 +104,7 @@ public async Task SetPropertiesAsync_EmptyBrowserFile_ShouldClearPropertiesAsync
Assert.IsNull(audiofile.Name);
Assert.IsNull(audiofile.ObjectURL);
Assert.IsNull(audiofile.Duration);
+ Assert.IsTrue(audiofileChangedFired);
_jsRuntime.Verify(js => js.InvokeAsync