Skip to content
Closed
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
73 changes: 52 additions & 21 deletions src/ui/Logic/VideoPlayers/Ffmpeg/FfmpegPlayer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ public sealed unsafe class FfmpegPlayer : IVideoPlayer, IDisposable

private string _fileName = string.Empty;
private bool _disposed;
private readonly Lock _loadLock = new();
private int _loadGeneration;
private Session? _session;
private double _volume = 100;
private double _speed = 1.0;
Expand Down Expand Up @@ -130,16 +132,24 @@ public bool CopyCurrentFrame(IntPtr destination, int destinationStride, int widt

public Task LoadFile(string fileName, double startPositionSeconds = 0)
{
CloseFile();
_fileName = fileName;
Session? previousSession;
int generation;
lock (_loadLock)
{
// Reserve this open before doing any slow native work. CloseFile or a newer LoadFile
// increments the generation, so this Task can never publish an obsolete session after
// the caller has already closed/replaced it.
generation = ++_loadGeneration;
previousSession = _session;
_session = null;
_fileName = fileName;
}

previousSession?.Dispose();
ClearCurrentFrame();

return Task.Run(() =>
{
if (_disposed)
{
return;
}

Session session;
try
{
Expand All @@ -148,33 +158,54 @@ public Task LoadFile(string fileName, double startPositionSeconds = 0)
catch (Exception exception)
{
Se.LogError(exception, $"ffmpeg player failed to open: {fileName}");
_fileName = string.Empty;
lock (_loadLock)
{
if (generation == _loadGeneration)
{
_fileName = string.Empty;
}
}

return;
}

if (_disposed)
lock (_loadLock)
{
session.Dispose();
return;
}
if (_disposed || generation != _loadGeneration)
{
session.Dispose();
return;
}

_session = session;
session.Volume = _volume;
session.Speed = _speed;
session.Start();
session.Volume = _volume;
session.Speed = _speed;
session.Start();

// Always seek once: this is what decodes and shows the first picture (at the wanted
// position) while the player stays paused.
session.Seek(Math.Max(0, startPositionSeconds));
// Always seek once: this is what decodes and shows the first picture (at the wanted
// position) while the player stays paused.
session.Seek(Math.Max(0, startPositionSeconds));
_session = session;
}
});
}

public void CloseFile()
{
var session = Interlocked.Exchange(ref _session, null);
_fileName = string.Empty;
Session? session;
lock (_loadLock)
{
_loadGeneration++;
session = _session;
_session = null;
_fileName = string.Empty;
}

session?.Dispose();
ClearCurrentFrame();
}

private void ClearCurrentFrame()
{
lock (_currentFrameLock)
{
// The frame belonged to the session's pool, which is gone now.
Expand Down
Loading