diff --git a/ErsatzTV.Application/Streaming/HlsSessionWorker.cs b/ErsatzTV.Application/Streaming/HlsSessionWorker.cs index 8a983323a..b4b771f0c 100644 --- a/ErsatzTV.Application/Streaming/HlsSessionWorker.cs +++ b/ErsatzTV.Application/Streaming/HlsSessionWorker.cs @@ -313,9 +313,6 @@ public class HlsSessionWorker : IHlsSessionWorker var sw = Stopwatch.StartNew(); try { - DateTimeOffset start = DateTimeOffset.Now; - DateTimeOffset finish = start.AddSeconds(8); - string playlistFileName = Path.Combine(_workingDirectory, "live.m3u8"); _logger.LogDebug("Waiting for playlist to exist"); @@ -326,6 +323,10 @@ public class HlsSessionWorker : IHlsSessionWorker _logger.LogDebug("Playlist exists"); + // start the segment-wait deadline only after the playlist file appears, + // so slow pipeline setup (e.g. h264 profile probing) doesn't consume the budget + DateTimeOffset finish = DateTimeOffset.Now.AddSeconds(8); + var segmentCount = 0; int lastSegmentCount = -1; while (DateTimeOffset.Now < finish && segmentCount < initialSegmentCount) diff --git a/ErsatzTV.Infrastructure/Metadata/LocalStatisticsProvider.cs b/ErsatzTV.Infrastructure/Metadata/LocalStatisticsProvider.cs index 3ebbc2914..1d9edd2e8 100644 --- a/ErsatzTV.Infrastructure/Metadata/LocalStatisticsProvider.cs +++ b/ErsatzTV.Infrastructure/Metadata/LocalStatisticsProvider.cs @@ -359,9 +359,11 @@ public partial class LocalStatisticsProvider : ILocalStatisticsProvider string filePath, CancellationToken cancellationToken) { + // only scans first 30 seconds string[] arguments = [ "-hide_banner", + "-t", "30", "-i", filePath, "-c", "copy", "-bsf:v", "trace_headers", @@ -370,28 +372,42 @@ public partial class LocalStatisticsProvider : ILocalStatisticsProvider var uniqueProfiles = new System.Collections.Generic.HashSet(); - CommandResult traceHeaders = await Cli.Wrap(ffmpegPath) - .WithArguments(arguments) - .WithValidation(CommandResultValidation.None) - .WithStandardErrorPipe( - PipeTarget.ToDelegate(line => - { - int idx = line.IndexOf("profile_idc", StringComparison.Ordinal); - if (idx >= 0) - { - uniqueProfiles.Add(line[idx..]); - } - })) - .ExecuteAsync(cancellationToken); + // cancel as soon as we find a second distinct profile_idc + using var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); - if (traceHeaders.ExitCode != 0) + try { - _logger.LogInformation( - "FFmpeg trace headers with arguments {Arguments} exited with code {ExitCode}", - arguments, - traceHeaders.ExitCode); + CommandResult traceHeaders = await Cli.Wrap(ffmpegPath) + .WithArguments(arguments) + .WithValidation(CommandResultValidation.None) + .WithStandardErrorPipe( + PipeTarget.ToDelegate(line => + { + int idx = line.IndexOf("profile_idc", StringComparison.Ordinal); + if (idx >= 0) + { + uniqueProfiles.Add(line[idx..]); + if (uniqueProfiles.Count > 1) + { + linkedCts.Cancel(); + } + } + })) + .ExecuteAsync(linkedCts.Token); + + if (traceHeaders.ExitCode != 0) + { + _logger.LogInformation( + "FFmpeg trace headers with arguments {Arguments} exited with code {ExitCode}", + arguments, + traceHeaders.ExitCode); - return Option.None; + return Option.None; + } + } + catch (OperationCanceledException) when (!cancellationToken.IsCancellationRequested) + { + // nothing to do here - canceled because a second profile was found } return uniqueProfiles.Count;