diff --git a/.github/workflows/build_pipeline.yml b/.github/workflows/build_pipeline.yml index 9cc55ee8..7ad6796b 100644 --- a/.github/workflows/build_pipeline.yml +++ b/.github/workflows/build_pipeline.yml @@ -4,22 +4,19 @@ on: push: branches: - development - - master + - main workflow_dispatch: jobs: - run-tests: - uses: ./.github/workflows/run_tests.yml build: - needs: run-tests runs-on: ubuntu-latest steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v7 # version can be found here https://dotnet.microsoft.com/en-us/download/dotnet/10.0 - name: Setup .NET - uses: actions/setup-dotnet@v5 + uses: actions/setup-dotnet@v6 with: dotnet-version: 10.0.x @@ -30,7 +27,7 @@ jobs: run: dotnet publish AudioCuesheetEditor --configuration Release --output release - name: Upload Build Artifact - uses: actions/upload-artifact@v6 + uses: actions/upload-artifact@v7 with: name: AudioCuesheetEditor-Release path: ./release @@ -40,16 +37,16 @@ jobs: runs-on: ubuntu-latest environment: |- ${{ - github.ref_name == 'master' && 'Production' + github.ref_name == 'main' && 'Production' || 'Preview' }} steps: - name: Download Build Artifact - uses: actions/download-artifact@v7 + uses: actions/download-artifact@v8 with: name: AudioCuesheetEditor-Release - name: Deploy to Netlify - uses: nwtgck/actions-netlify@v3 + uses: nwtgck/actions-netlify@v4 with: publish-dir: './wwwroot' production-deploy: true diff --git a/.github/workflows/run_tests.yml b/.github/workflows/run_tests.yml index 03c1fa8f..b3b7a592 100644 --- a/.github/workflows/run_tests.yml +++ b/.github/workflows/run_tests.yml @@ -9,7 +9,7 @@ jobs: run-tests: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v7 - name: Set timestamp variable id: timestamp @@ -17,7 +17,7 @@ jobs: # version can be found here https://dotnet.microsoft.com/en-us/download/dotnet/10.0 - name: Setup .NET - uses: actions/setup-dotnet@v5 + uses: actions/setup-dotnet@v6 with: dotnet-version: 10.0.x @@ -59,7 +59,7 @@ jobs: - name: Upload traces if: always() - uses: actions/upload-artifact@v6 + uses: actions/upload-artifact@v7 with: name: playwright-traces-${{ steps.timestamp.outputs.datetime }} path: AudioCuesheetEditor.End2EndTests/bin/Debug/net10.0/playwright-traces/ diff --git a/AudioCuesheetEditor.End2EndTests/AudioCuesheetEditor.End2EndTests.csproj b/AudioCuesheetEditor.End2EndTests/AudioCuesheetEditor.End2EndTests.csproj index ce024254..a7d3d6c0 100644 --- a/AudioCuesheetEditor.End2EndTests/AudioCuesheetEditor.End2EndTests.csproj +++ b/AudioCuesheetEditor.End2EndTests/AudioCuesheetEditor.End2EndTests.csproj @@ -25,7 +25,7 @@ - + diff --git a/AudioCuesheetEditor.Tests/AudioCuesheetEditor.Tests.csproj b/AudioCuesheetEditor.Tests/AudioCuesheetEditor.Tests.csproj index 69057844..0714cf11 100644 --- a/AudioCuesheetEditor.Tests/AudioCuesheetEditor.Tests.csproj +++ b/AudioCuesheetEditor.Tests/AudioCuesheetEditor.Tests.csproj @@ -24,7 +24,7 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - + 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("revokeAudioObjectURL", It.Is(args => args != null && args.Length > 0 && (args[0] as string) == expectedUrl)), Times.Once); } @@ -116,7 +125,14 @@ public async Task SetPropertiesAsync_WithBrowserFile_ShouldSetPropertiesAsync() _jsRuntime.Setup(js => js.InvokeAsync("getAudioDurationFromFile", It.IsAny())).Returns(new ValueTask(90.0)); var audiofile = new Audiofile(); - + var audiofileChangedFired = false; + _audiofileManager.AudiofileChanged += delegate (object? sender, Audiofile e) + { + if (e == audiofile) + { + audiofileChangedFired = true; + } + }; // Act await _audiofileManager.SetPropertiesAsync(audiofile, browserFile.Object, inputId); @@ -125,7 +141,7 @@ public async Task SetPropertiesAsync_WithBrowserFile_ShouldSetPropertiesAsync() Assert.AreEqual(filename, audiofile.Name); Assert.AreEqual(objectUrl, audiofile.ObjectURL); Assert.AreEqual(TimeSpan.FromSeconds(90), audiofile.Duration); - + Assert.IsTrue(audiofileChangedFired); _fileInputManager.Verify(f => f.GetAudioCodec("audio/mpeg", filename), Times.Once); _fileInputManager.Verify(f => f.GetObjectUrlAsync(inputId), Times.Once); _jsRuntime.Verify(js => js.InvokeAsync("getAudioDurationFromFile", It.Is(o => o[0] as string == audiofile.ObjectURL)), Times.Once); @@ -161,7 +177,14 @@ public async Task SetProperty_AudiofileWithDuration_SetsLastTrackEndAlsoAsync() { Tracks = [track1, track2] }; - + var audiofileChangedFired = false; + _audiofileManager.AudiofileChanged += delegate (object? sender, Audiofile e) + { + if (e == audiofile) + { + audiofileChangedFired = true; + } + }; // Act await _audiofileManager.SetPropertiesAsync(audiofile, browserFile.Object, inputId); @@ -171,7 +194,7 @@ public async Task SetProperty_AudiofileWithDuration_SetsLastTrackEndAlsoAsync() Assert.AreEqual(objectUrl, audiofile.ObjectURL); Assert.AreEqual(TimeSpan.FromSeconds(90), audiofile.Duration); Assert.AreEqual(TimeSpan.FromSeconds(90), track2.End); - + Assert.IsTrue(audiofileChangedFired); _fileInputManager.Verify(f => f.GetAudioCodec("audio/mpeg", filename), Times.Once); _fileInputManager.Verify(f => f.GetObjectUrlAsync(inputId), Times.Once); _jsRuntime.Verify(js => js.InvokeAsync("getAudioDurationFromFile", It.Is(o => o[0] as string == audiofile.ObjectURL)), Times.Once); @@ -186,11 +209,19 @@ public async Task SetPropertiesAsync_NullBrowserFile_WithExistingObjectUrl_Shoul ObjectURL = "just a test" }; var expectedUrl = audiofile.ObjectURL; - + var audiofileChangedFired = false; + _audiofileManager.AudiofileChanged += delegate (object? sender, Audiofile e) + { + if (e == audiofile) + { + audiofileChangedFired = true; + } + }; // Act await _audiofileManager.SetPropertiesAsync(audiofile, null, string.Empty); // Assert + Assert.IsTrue(audiofileChangedFired); _jsRuntime.Verify(js =>js.InvokeAsync("revokeAudioObjectURL", It.Is(args => args != null && args.Length > 0 && (args[0] as string) == expectedUrl)),Times.Once); Assert.IsNull(audiofile.ObjectURL); } @@ -203,12 +234,20 @@ public void SetProperty_ChangedName_UpdatesPropertyAndAddsTrace() { Name = "oldname.mp3" }; - + var audiofileChangedFired = false; + _audiofileManager.AudiofileChanged += delegate (object? sender, Audiofile e) + { + if (e == audiofile) + { + audiofileChangedFired = true; + } + }; // Act _audiofileManager.SetProperty(audiofile, x => x.Name, "newname.mp3"); // Assert Assert.AreEqual("newname.mp3", audiofile.Name); + Assert.IsTrue(audiofileChangedFired); _traceChangeManager.Verify(x => x.AddChange(It.Is(y => y.TraceableObject == audiofile && y.TraceableChange.PreviousValue!.Equals("oldname.mp3") && y.TraceableChange.PropertyName == nameof(Audiofile.Name))), Times.Once); } @@ -220,11 +259,19 @@ public void SetProperty_EqualValue_DoesntChangeProperty() { Name = "oldname.mp3" }; - + var audiofileChangedFired = false; + _audiofileManager.AudiofileChanged += delegate (object? sender, Audiofile e) + { + if (e == audiofile) + { + audiofileChangedFired = true; + } + }; // Act _audiofileManager.SetProperty(audiofile, x => x.Name, "oldname.mp3"); // Assert + Assert.IsFalse(audiofileChangedFired); _traceChangeManager.Verify(x => x.AddChange(It.Is(y => y.TraceableObject == audiofile && y.TraceableChange.PreviousValue!.Equals("oldname.mp3") && y.TraceableChange.PropertyName == nameof(Audiofile.Name))), Times.Never); } @@ -248,13 +295,21 @@ public void SetProperty_DurationChanged_SetsLastTrackEndAlso() Tracks = [track1, track2], }; var duration = new TimeSpan(0, 3, 37, 12); - + var audiofileChangedFired = false; + _audiofileManager.AudiofileChanged += delegate (object? sender, Audiofile e) + { + if (e == audiofile) + { + audiofileChangedFired = true; + } + }; // Act _audiofileManager.SetProperty(audiofile, x => x.Duration, duration); // Assert Assert.AreEqual(duration, audiofile.Duration); Assert.AreEqual(duration, track2.End); + Assert.IsTrue(audiofileChangedFired); } [TestMethod] @@ -268,11 +323,20 @@ public void AddTrack_FirstTrack_AddsNewTrackWithCalulatedTrackProperties() Audiofiles = [audiofile] }; _sessionStateContainer.Setup(x => x.ActiveCuesheet).Returns(cuesheet); + var audiofileChangedFired = false; + _audiofileManager.AudiofileChanged += delegate (object? sender, Audiofile e) + { + if (e == audiofile) + { + audiofileChangedFired = true; + } + }; var track = new Track(); // Act _audiofileManager.AddTrack(audiofile, track); // Assert Assert.HasCount(1, audiofile.Tracks); + Assert.IsTrue(audiofileChangedFired); Assert.AreEqual((ushort)1, audiofile.Tracks.First().Position); Assert.AreEqual(TimeSpan.Zero, audiofile.Tracks.First().Begin); Assert.AreEqual(duration, audiofile.Tracks.First().End); @@ -307,6 +371,14 @@ public void AddTrack_AddToPreviousTracks_AddsNewTrackWithCalulatedTrackPropertie { IsLinkedToPreviousTrack = true }; + var audiofileChangedFired = false; + _audiofileManager.AudiofileChanged += delegate (object? sender, Audiofile e) + { + if (e == audiofile) + { + audiofileChangedFired = true; + } + }; // Act _audiofileManager.AddTrack(audiofile, track); // Assert @@ -316,6 +388,7 @@ public void AddTrack_AddToPreviousTracks_AddsNewTrackWithCalulatedTrackPropertie Assert.AreEqual(duration, audiofile.Tracks.Last().End); Assert.AreEqual(cuesheet, track.Cuesheet); Assert.AreEqual(audiofile, track.Audiofile); + Assert.IsTrue(audiofileChangedFired); _traceChangeManager.Verify(x => x.AddChange(It.Is(y => y.TraceableObject == audiofile && y.TraceableChange.PreviousValue == tracks && y.TraceableChange.PropertyName == nameof(Audiofile.Tracks))), Times.Once); _traceChangeManager.VerifySet(t => t.BulkEdit = true, Times.Once); _traceChangeManager.VerifySet(t => t.BulkEdit = false, Times.Once); @@ -348,9 +421,18 @@ public void AddTrack_DuringRecording_AddsNewTrackWithCalulatedTrackProperties() { IsLinkedToPreviousTrack = true }; + var audiofileChangedFired = false; + _audiofileManager.AudiofileChanged += delegate (object? sender, Audiofile e) + { + if (e == audiofile) + { + audiofileChangedFired = true; + } + }; // Act _audiofileManager.AddTrack(audiofile, track); // Assert + Assert.IsTrue(audiofileChangedFired); Assert.HasCount(2, audiofile.Tracks); Assert.AreEqual((ushort)2, audiofile.Tracks.Last().Position); Assert.IsNotNull(audiofile.Tracks.First().End); @@ -374,9 +456,18 @@ public void AddTrack_FirstTrackImporting_AddsNewTrackWithCalulatedTrackPropertie }; _sessionStateContainer.Setup(x => x.ActiveCuesheet).Returns(importCuesheet); var track = new Track(); + var audiofileChangedFired = false; + _audiofileManager.AudiofileChanged += delegate (object? sender, Audiofile e) + { + if (e == audiofile) + { + audiofileChangedFired = true; + } + }; // Act _audiofileManager.AddTrack(audiofile, track); // Assert + Assert.IsTrue(audiofileChangedFired); Assert.HasCount(1, audiofile.Tracks); Assert.AreEqual((ushort)1, audiofile.Tracks.First().Position); Assert.AreEqual(TimeSpan.Zero, audiofile.Tracks.First().Begin); @@ -442,6 +533,14 @@ public void RemoveTracks_WithAttachedTracks_RemovesTracksAndRecalculatesRemainin track4.Cuesheet = cuesheet; track5.Cuesheet = cuesheet; _sessionStateContainer.Setup(x => x.ActiveCuesheet).Returns(cuesheet); + var audiofileChangedFired = false; + _audiofileManager.AudiofileChanged += delegate (object? sender, Audiofile e) + { + if (e == audiofile) + { + audiofileChangedFired = true; + } + }; // Act _audiofileManager.RemoveTracks(audiofile, [track2, track4]); // Assert @@ -449,6 +548,7 @@ public void RemoveTracks_WithAttachedTracks_RemovesTracksAndRecalculatesRemainin Assert.Contains(track1, audiofile.Tracks); Assert.Contains(track3, audiofile.Tracks); Assert.Contains(track5, audiofile.Tracks); + Assert.IsTrue(audiofileChangedFired); Assert.AreEqual((ushort)1, track1.Position); Assert.AreEqual(TimeSpan.Zero, track1.Begin); Assert.AreEqual(track1.End, track3.Begin); @@ -520,6 +620,14 @@ public void RemoveTracks_DuringImport_RemovesTracksAndRecalculatesRemainingTrack track4.Audiofile = audiofile; track5.Audiofile = audiofile; _sessionStateContainer.Setup(x => x.ActiveCuesheet).Returns(importCuesheet); + var audiofileChangedFired = false; + _audiofileManager.AudiofileChanged += delegate (object? sender, Audiofile e) + { + if (e == audiofile) + { + audiofileChangedFired = true; + } + }; // Act _audiofileManager.RemoveTracks(audiofile, [track2, track4]); // Assert @@ -533,6 +641,7 @@ public void RemoveTracks_DuringImport_RemovesTracksAndRecalculatesRemainingTrack Assert.AreEqual((ushort)2, track3.Position); Assert.AreEqual(track5.Begin, track3.End); Assert.AreEqual((ushort)3, track5.Position); + Assert.IsTrue(audiofileChangedFired); Assert.AreEqual(duration, track5.End); _traceChangeManager.Verify(x => x.AddChange(It.Is(y => y.TraceableObject == audiofile && y.TraceableChange.PreviousValue == previousValue && y.TraceableChange.PropertyName == nameof(Audiofile.Tracks))), Times.Once); _traceChangeManager.VerifySet(t => t.BulkEdit = true, Times.Once); 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/AudioCuesheetEditor.csproj b/AudioCuesheetEditor/AudioCuesheetEditor.csproj index 90e77c73..0ee729b8 100644 --- a/AudioCuesheetEditor/AudioCuesheetEditor.csproj +++ b/AudioCuesheetEditor/AudioCuesheetEditor.csproj @@ -7,7 +7,7 @@ enable https://github.com/NeoCoderMatrix86/AudioCuesheetEditor Latest - 13.0.0 + 13.1.0 false true true diff --git a/AudioCuesheetEditor/Services/Audio/PlaybackService.cs b/AudioCuesheetEditor/Services/Audio/PlaybackService.cs index 04f785ea..2bd6d2f5 100644 --- a/AudioCuesheetEditor/Services/Audio/PlaybackService.cs +++ b/AudioCuesheetEditor/Services/Audio/PlaybackService.cs @@ -15,22 +15,24 @@ //. using AudioCuesheetEditor.Model.AudioCuesheet; using AudioCuesheetEditor.Model.IO.Audio; +using AudioCuesheetEditor.Services.AudioCuesheet; using AudioCuesheetEditor.Services.UI; using Microsoft.JSInterop; namespace AudioCuesheetEditor.Services.Audio { - public class PlaybackService(IJSRuntime jsRuntime, ISessionStateContainer sessionStateContainer) : IAsyncDisposable + public class PlaybackService(IJSRuntime jsRuntime, ISessionStateContainer sessionStateContainer, IAudiofileManager audiofileManager) : IAsyncDisposable { - private readonly ISessionStateContainer _sessionStateContainer = sessionStateContainer; private readonly IJSRuntime _jsRuntime = jsRuntime; + private readonly ISessionStateContainer _sessionStateContainer = sessionStateContainer; + private readonly IAudiofileManager _audiofileManager = audiofileManager; private Audiofile? _currentlyPlayingAudiofile; private Timer? _updateTimer; private readonly Lock _timerLock = new(); private TimeSpan? _currentPosition; private DotNetObjectReference? _dotNetObjectReference; - private TimeSpan? _audiofileDurationsBeforeCurrentlyPlayingAudiofile; + private TimeSpan _durationBeforeCurrentlyPlayingAudiofile; public event Action? CurrentPositionChanged; @@ -72,6 +74,24 @@ public async Task InitializeAsync() _dotNetObjectReference = DotNetObjectReference.Create(this); await _jsRuntime.InvokeVoidAsync("audioInterop.register", _dotNetObjectReference); } + _audiofileManager.AudiofileChanged += AudiofileManager_AudiofileChanged; + _sessionStateContainer.CuesheetChanged += SessionStateContainer_CuesheetChanged; + } + + void AudiofileManager_AudiofileChanged(object? sender, Audiofile audiofile) + { + if (audiofile == _currentlyPlayingAudiofile) + { + if (string.IsNullOrEmpty(audiofile.ObjectURL)) + { + _ = StopAsync(); + } + } + } + + void SessionStateContainer_CuesheetChanged(object? sender, EventArgs e) + { + _ = StopAsync(); } public async Task PlayOrPauseAsync() @@ -223,12 +243,14 @@ public void OnPlaybackPaused() { IsPaused = true; StopTimer(); - UpdateCurrentPosition(null); + UpdateCurrentPosition(); } public async ValueTask DisposeAsync() { GC.SuppressFinalize(this); + _audiofileManager.AudiofileChanged -= AudiofileManager_AudiofileChanged; + _sessionStateContainer.CuesheetChanged -= SessionStateContainer_CuesheetChanged; await _jsRuntime.InvokeVoidAsync("audioInterop.unregister"); _dotNetObjectReference?.Dispose(); } @@ -238,14 +260,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; } @@ -261,39 +283,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(); - 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; + } } } } diff --git a/AudioCuesheetEditor/Services/AudioCuesheet/AudiofileManager.cs b/AudioCuesheetEditor/Services/AudioCuesheet/AudiofileManager.cs index 5f101ac4..1c0bd31d 100644 --- a/AudioCuesheetEditor/Services/AudioCuesheet/AudiofileManager.cs +++ b/AudioCuesheetEditor/Services/AudioCuesheet/AudiofileManager.cs @@ -31,6 +31,9 @@ public class AudiofileManager(IFileInputManager fileInputManager, ITraceChangeMa private readonly IJSRuntime _jsRuntime = jsRuntime; private readonly ITrackManager _trackManager = trackManager; private readonly ISessionStateContainer _sessionStateContainer = sessionStateContainer; + + /// + public event EventHandler? AudiofileChanged; /// public async Task SetPropertiesAsync(Audiofile audiofile, IBrowserFile? browserFile, string fileInputId) @@ -145,6 +148,7 @@ void SetValue(Audiofile audiofile, Expression + /// Event triggered when an audiofile is changed + /// + public event EventHandler? AudiofileChanged; /// /// Set properties from a file upload /// 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 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"); diff --git a/AudioCuesheetEditor/Shared/TrackList/TrackList.razor b/AudioCuesheetEditor/Shared/TrackList/TrackList.razor index 99daa8a7..1fd92657 100644 --- a/AudioCuesheetEditor/Shared/TrackList/TrackList.razor +++ b/AudioCuesheetEditor/Shared/TrackList/TrackList.razor @@ -85,7 +85,7 @@ along with Foobar. If not, see + Validation="(string? newBegin) => _validationService.Validate(context.Item, nameof(Track.Begin))" Style="width: 55px;" /> @@ -94,7 +94,7 @@ along with Foobar. If not, see + Validation="(string? newEnd) => _validationService.Validate(context.Item, nameof(Track.End))" Style="width: 55px;" /> @@ -103,7 +103,7 @@ along with Foobar. If not, see + Validation="(string? newLength) => _validationService.Validate(context.Item, nameof(Track.Length))" Style="width: 55px;" />