#nullable enable
using System.Collections.Concurrent;
using System.Threading.Tasks;
using T3.Core.Resource;
using T3.Core.Resource.Assets;
using T3.Core.Video;
namespace T3.Editor.Gui.Windows.TimeLine.TimeClips;
///
/// Caches the full source-file duration (seconds) per video asset, probed once via the video assembly's
/// demux-only metadata reader () on a background
/// thread. Lets the timeline outline a clip's available footage (head/tail handles, over-length looping) on
/// hover without waiting for the clip to play. Mirrors .
///
internal static class VideoClipDurationCache
{
///
/// Returns true with the cached duration once known. The first call per asset kicks off an async probe and
/// returns false; later calls return the result. A failed probe (or no video backend loaded) caches 0 so it
/// isn't retried every frame.
///
public static bool TryGetDurationSecs(string assetPath, IResourceConsumer owner, out double durationSecs)
{
if (_durationByAssetPath.TryGetValue(assetPath, out durationSecs))
return durationSecs > 0;
// First request for this asset: resolve the absolute path (cheap, on this thread) and probe the
// duration on a worker so the draw thread never blocks on an FFmpeg file open.
if (_probing.TryAdd(assetPath, true))
{
var factory = VideoExport.Factory;
if (factory != null && AssetRegistry.TryResolveAddress(assetPath, owner, out var absolutePath, out _))
{
var path = absolutePath;
var key = assetPath;
Task.Run(() =>
{
_durationByAssetPath[key] = factory.TryProbeDurationSeconds(path, out var secs) ? secs : 0;
_probing.TryRemove(key, out _);
});
}
else
{
_durationByAssetPath[assetPath] = 0;
_probing.TryRemove(assetPath, out _);
}
}
durationSecs = 0;
return false;
}
private static readonly ConcurrentDictionary _durationByAssetPath = new();
private static readonly ConcurrentDictionary _probing = new();
}