diff --git a/CHANGELOG.md b/CHANGELOG.md index d27d4fff0..cee1f548d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ## [Unreleased] +### Changed +- Reduce initial `HLS Segmenter` delay from 5 seconds to 3 seconds ## [0.1.0-alpha] - 2021-10-08 ### Added diff --git a/ErsatzTV.Application/Streaming/Commands/StartFFmpegSessionHandler.cs b/ErsatzTV.Application/Streaming/Commands/StartFFmpegSessionHandler.cs index c31226056..121994046 100644 --- a/ErsatzTV.Application/Streaming/Commands/StartFFmpegSessionHandler.cs +++ b/ErsatzTV.Application/Streaming/Commands/StartFFmpegSessionHandler.cs @@ -5,6 +5,7 @@ using System.Threading.Channels; using System.Threading.Tasks; using ErsatzTV.Core; using ErsatzTV.Core.Errors; +using ErsatzTV.Core.FFmpeg; using ErsatzTV.Core.Interfaces.FFmpeg; using ErsatzTV.Core.Interfaces.Metadata; using LanguageExt; @@ -45,7 +46,7 @@ namespace ErsatzTV.Application.Streaming.Commands await _channel.WriteAsync(request); // TODO: find some other way to let ffmpeg get ahead - await Task.Delay(TimeSpan.FromSeconds(5)); + await Task.Delay(FFmpegSegmenterService.SegmenterDelay); return Unit.Default; } diff --git a/ErsatzTV.Application/Streaming/Queries/GetPlayoutItemProcessByChannelNumberHandler.cs b/ErsatzTV.Application/Streaming/Queries/GetPlayoutItemProcessByChannelNumberHandler.cs index 9fc5265f8..8c56ae21c 100644 --- a/ErsatzTV.Application/Streaming/Queries/GetPlayoutItemProcessByChannelNumberHandler.cs +++ b/ErsatzTV.Application/Streaming/Queries/GetPlayoutItemProcessByChannelNumberHandler.cs @@ -54,7 +54,12 @@ namespace ErsatzTV.Application.Streaming.Queries Channel channel, string ffmpegPath) { - DateTimeOffset now = DateTimeOffset.Now; + DateTimeOffset now = request.Mode switch + { + "segmenter" => DateTimeOffset.Now + FFmpegSegmenterService.SegmenterDelay, + _ => DateTimeOffset.Now + }; + Either maybePlayoutItem = await dbContext.PlayoutItems .Include(i => i.MediaItem) .ThenInclude(mi => (mi as Episode).MediaVersions) @@ -113,7 +118,8 @@ namespace ErsatzTV.Application.Streaming.Queries playoutItemWithPath.PlayoutItem.StartOffset, request.StartAtZero ? playoutItemWithPath.PlayoutItem.StartOffset : now, maybeGlobalWatermark, - maybeVaapiDriver)); + maybeVaapiDriver, + request.StartAtZero)); }, async error => { diff --git a/ErsatzTV.Core.Tests/FFmpeg/TranscodingTests.cs b/ErsatzTV.Core.Tests/FFmpeg/TranscodingTests.cs index bb630cadb..315403c77 100644 --- a/ErsatzTV.Core.Tests/FFmpeg/TranscodingTests.cs +++ b/ErsatzTV.Core.Tests/FFmpeg/TranscodingTests.cs @@ -184,7 +184,8 @@ namespace ErsatzTV.Core.Tests.FFmpeg now, now, None, - None); + None, + false); process.StartInfo.RedirectStandardError = true; diff --git a/ErsatzTV.Core/FFmpeg/FFmpegProcessBuilder.cs b/ErsatzTV.Core/FFmpeg/FFmpegProcessBuilder.cs index 581b44917..c16fc0891 100644 --- a/ErsatzTV.Core/FFmpeg/FFmpegProcessBuilder.cs +++ b/ErsatzTV.Core/FFmpeg/FFmpegProcessBuilder.cs @@ -310,8 +310,11 @@ namespace ErsatzTV.Core.FFmpeg return this; } - public FFmpegProcessBuilder WithHls(string channelNumber, MediaVersion mediaVersion) + public FFmpegProcessBuilder WithHls(string channelNumber, MediaVersion mediaVersion, bool startAtZero) { + const int INITIAL_SEGMENT_SECONDS = 1; + const int SUBSEQUENT_SEGMENT_SECONDS = 2; + if (!int.TryParse(mediaVersion.RFrameRate, out int frameRate)) { string[] split = (mediaVersion.RFrameRate ?? string.Empty).Split("/"); @@ -326,15 +329,17 @@ namespace ErsatzTV.Core.FFmpeg } } + int segmentSeconds = startAtZero ? SUBSEQUENT_SEGMENT_SECONDS : INITIAL_SEGMENT_SECONDS; + _arguments.AddRange( new[] { - "-g", $"{frameRate * 2}", - "-keyint_min", $"{frameRate * 2}", - // "-force_key_frames", - // "expr:gte(t,n_forced*2)", + "-use_wallclock_as_timestamps", "1", + "-g", $"{frameRate * segmentSeconds}", + "-keyint_min", $"{frameRate * segmentSeconds}", + "-force_key_frames", $"expr:gte(t,n_forced*{segmentSeconds})", "-f", "hls", - "-hls_time", "2", + "-hls_time", $"{segmentSeconds}", "-hls_list_size", "10", "-segment_list_flags", "+live", "-hls_flags", "delete_segments+program_date_time+append_list+discont_start+omit_endlist", diff --git a/ErsatzTV.Core/FFmpeg/FFmpegProcessService.cs b/ErsatzTV.Core/FFmpeg/FFmpegProcessService.cs index 239643532..63ec0aed6 100644 --- a/ErsatzTV.Core/FFmpeg/FFmpegProcessService.cs +++ b/ErsatzTV.Core/FFmpeg/FFmpegProcessService.cs @@ -39,7 +39,8 @@ namespace ErsatzTV.Core.FFmpeg DateTimeOffset start, DateTimeOffset now, Option globalWatermark, - Option maybeVaapiDriver) + Option maybeVaapiDriver, + bool startAtZero) { MediaStream videoStream = await _ffmpegStreamSelector.SelectVideoStream(channel, version); Option maybeAudioStream = await _ffmpegStreamSelector.SelectAudioStream(channel, version); @@ -118,7 +119,7 @@ namespace ErsatzTV.Core.FFmpeg { // HLS needs to segment and generate playlist case StreamingMode.HttpLiveStreamingSegmenter: - return builder.WithHls(channel.Number, version) + return builder.WithHls(channel.Number, version, startAtZero) .Build(); default: return builder.WithFormat("mpegts") diff --git a/ErsatzTV.Core/FFmpeg/FFmpegSegmenterService.cs b/ErsatzTV.Core/FFmpeg/FFmpegSegmenterService.cs index d20d8bb20..cfd36beb9 100644 --- a/ErsatzTV.Core/FFmpeg/FFmpegSegmenterService.cs +++ b/ErsatzTV.Core/FFmpeg/FFmpegSegmenterService.cs @@ -12,6 +12,8 @@ namespace ErsatzTV.Core.FFmpeg { public class FFmpegSegmenterService : IFFmpegSegmenterService { + public static readonly TimeSpan SegmenterDelay = TimeSpan.FromSeconds(3); + private static readonly ConcurrentDictionary Processes = new(); private readonly ILogger _logger;