Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions SentryReplay.Tests/VideoPlayerControllerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,31 @@ public async Task PlaybackSpeed_AppliesToExistingAndFuturePlayers()
back.Speed.ShouldBe(1.0);
}

[Fact]
public async Task GoToClipAsync_ShowsLoadingWhileCurrentClipStops()
{
using var firstClipFiles = TestClipFiles.Create(chunkCount: 1);
using var secondClipFiles = TestClipFiles.Create(chunkCount: 1);
var front = new FakeCameraPlayer();
using var controller = CreateController(front);

controller.LoadClips([firstClipFiles.Clip, secondClipFiles.Clip]);
controller.Playlist.MoveTo(0);
await WaitUntilAsync(() => front.PlayCount > 0);

front.StopGate = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);

var changeClipTask = controller.GoToClipAsync(secondClipFiles.Clip);

await WaitUntilAsync(() => front.StopCount > 0);

controller.IsLoading.ShouldBeTrue();

front.StopGate.SetResult(null);
await changeClipTask;
await WaitUntilAsync(() => controller.CurrentClip == secondClipFiles.Clip);
}

[Fact]
public async Task LoadClipsAsync_StopsCurrentPlaybackAndResetsSelection()
{
Expand Down Expand Up @@ -292,6 +317,7 @@ private sealed class FakeCameraPlayer : ICameraPlayer
public List<TimeSpan> SeekPositions { get; } = [];
public bool OpenResult { get; init; } = true;
public bool ThrowOnStop { get; init; }
public TaskCompletionSource<object> StopGate { get; set; }
public bool IsOpen { get; private set; }
public double Speed { get; set; } = 1.0;
public int PlayCount { get; private set; }
Expand Down Expand Up @@ -328,6 +354,11 @@ public Task PauseAsync()
public Task StopAsync()
{
StopCount++;
if (StopGate is not null)
{
return StopGate.Task;
}

return ThrowOnStop
? Task.FromException(new InvalidOperationException("stop failed"))
: Task.CompletedTask;
Expand Down
4 changes: 4 additions & 0 deletions SentryReplay/MainWindow.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Navigation;
using System.Windows.Threading;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using FlyleafLib.Controls.WPF;
Expand Down Expand Up @@ -535,13 +536,16 @@ private async Task PlaySelectedClipAsync()
return;

ClearError();
IsLoading = true;
await Dispatcher.Yield(DispatcherPriority.Background);

try
{
await _playerController.GoToClipAsync(SelectedClip);
}
catch (Exception ex)
{
IsLoading = false;
Log.Error(
ex,
"Failed to play selected clip. ClipName={ClipName}; ClipPath={ClipPath}",
Expand Down
6 changes: 2 additions & 4 deletions SentryReplay/Playback/FlyleafCameraPlayer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -99,14 +99,12 @@ public Task PauseAsync()

public Task StopAsync()
{
StopAndClose();
return Task.CompletedTask;
return Task.Run(StopAndClose);
}

public Task CloseAsync()
{
StopAndClose();
return Task.CompletedTask;
return Task.Run(StopAndClose);
}

public Task SeekAsync(TimeSpan position)
Expand Down
37 changes: 27 additions & 10 deletions SentryReplay/Playback/VideoPlayerController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,7 @@ public async Task NextAsync()
if (!Playlist.HasNext)
return;

await StopAsync();
await PrepareForClipChangeAsync();
Playlist.MoveNext();
}

Expand All @@ -223,27 +223,25 @@ public async Task PreviousAsync()
if (!Playlist.HasPrevious)
return;

await StopAsync();
await PrepareForClipChangeAsync();
Playlist.MovePrevious();
}

public async Task GoToClipAsync(CamClip clip)
{
if (clip is null || clip == Playlist.CurrentClip)
if (clip is null || clip == Playlist.CurrentClip || !Playlist.Clips.Contains(clip))
return;

BeginNewRequest();
await StopAsync();
await PrepareForClipChangeAsync();
Playlist.MoveTo(clip);
}

public async Task GoToClipAsync(int index)
{
if (index == Playlist.CurrentIndex)
if (index == Playlist.CurrentIndex || index < 0 || index >= Playlist.Clips.Count)
return;

BeginNewRequest();
await StopAsync();
await PrepareForClipChangeAsync();
Playlist.MoveTo(index);
}

Expand Down Expand Up @@ -334,6 +332,21 @@ private void CancelAndDisposePlaybackCts()
cts.Dispose();
}

private async Task PrepareForClipChangeAsync()
{
BeginNewRequest();
CancelAndDisposePlaybackCts();
ErrorMessage = null;
IsLoading = true;

await Task.Yield();

await RunSerializedPlaybackOperationAsync(async _ =>
{
await StopPlaybackInternalAsync(resetTimeline: true, clearLoading: false);
});
}

private async Task PlayInternalAsync(long requestId, CamClip clip)
{
if (clip is null)
Expand Down Expand Up @@ -399,7 +412,7 @@ await RunSerializedPlaybackOperationAsync(async ct =>
}
}

private async Task StopPlaybackInternalAsync(bool resetTimeline)
private async Task StopPlaybackInternalAsync(bool resetTimeline, bool clearLoading = true)
{
Volatile.Write(ref _currentMediaRequestId, 0);
await StopAndClosePlayersAsync();
Expand All @@ -410,7 +423,11 @@ private async Task StopPlaybackInternalAsync(bool resetTimeline)

if (resetTimeline)
{
IsLoading = false;
if (clearLoading)
{
IsLoading = false;
}

IsPlaying = false;
Position = TimeSpan.Zero;
Duration = TimeSpan.Zero;
Expand Down
Loading