From 9e111a103e31eeb1caf3cecf99e5b941c6a44aa4 Mon Sep 17 00:00:00 2001 From: Jason Dove <1695733+jasongdove@users.noreply.github.com> Date: Wed, 17 Sep 2025 13:14:25 -0500 Subject: [PATCH] fix fallback on mirror channels (#2436) --- CHANGELOG.md | 2 + .../ErsatzTV.Application.csproj | 1 + ...layoutItemProcessByChannelNumberHandler.cs | 66 +++++++++++++++---- ErsatzTV.sln | 4 +- 4 files changed, 58 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 33619da3a..c59453364 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -48,6 +48,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - Block schedules: skip media items that will never fit in block duration - Fix HLS playlist generation for clients that actually care about discontinuities (like hls.js) - This should resolve most playback issues with built-in channel preview +- Fix deco dead air fallback selection and duration on mirror channels +- Fix fallback filler duration on mirror channels ## [25.6.0] - 2025-09-14 ### Added diff --git a/ErsatzTV.Application/ErsatzTV.Application.csproj b/ErsatzTV.Application/ErsatzTV.Application.csproj index b6e7750b8..f1666f267 100644 --- a/ErsatzTV.Application/ErsatzTV.Application.csproj +++ b/ErsatzTV.Application/ErsatzTV.Application.csproj @@ -6,6 +6,7 @@ enable latest-Recommended true + Debug;Release;Debug No Sync diff --git a/ErsatzTV.Application/Streaming/Queries/GetPlayoutItemProcessByChannelNumberHandler.cs b/ErsatzTV.Application/Streaming/Queries/GetPlayoutItemProcessByChannelNumberHandler.cs index addfe3967..06cdc330b 100644 --- a/ErsatzTV.Application/Streaming/Queries/GetPlayoutItemProcessByChannelNumberHandler.cs +++ b/ErsatzTV.Application/Streaming/Queries/GetPlayoutItemProcessByChannelNumberHandler.cs @@ -42,6 +42,7 @@ public class GetPlayoutItemProcessByChannelNumberHandler : FFmpegProcessHandler< private readonly IPlexPathReplacementService _plexPathReplacementService; private readonly ISongVideoGenerator _songVideoGenerator; private readonly ITelevisionRepository _televisionRepository; + private readonly bool _isDebugNoSync; public GetPlayoutItemProcessByChannelNumberHandler( IDbContextFactory dbContextFactory, @@ -77,6 +78,12 @@ public class GetPlayoutItemProcessByChannelNumberHandler : FFmpegProcessHandler< _graphicsElementSelector = graphicsElementSelector; _decoSelector = decoSelector; _logger = logger; + +#if DEBUG_NO_SYNC + _isDebugNoSync = true; +#else + _isDebugNoSync = false; +#endif } protected override async Task> GetProcess( @@ -233,7 +240,10 @@ public class GetPlayoutItemProcessByChannelNumberHandler : FFmpegProcessHandler< .ThenInclude(i => i.Deco) .ThenInclude(d => d.DecoWatermarks) .ThenInclude(d => d.Watermark) - .SelectOneAsync(p => p.ChannelId, p => p.ChannelId == channel.Id, cancellationToken); + .SelectOneAsync( + p => p.ChannelId, + p => p.ChannelId == (channel.MirrorSourceChannelId ?? channel.Id), + cancellationToken); foreach (Playout playout in maybePlayout) { @@ -264,6 +274,35 @@ public class GetPlayoutItemProcessByChannelNumberHandler : FFmpegProcessHandler< _logger.LogDebug(ex, "Failed to get playout item title"); } + DateTimeOffset start = playoutItemWithPath.PlayoutItem.StartOffset; + DateTimeOffset finish = playoutItemWithPath.PlayoutItem.FinishOffset; + TimeSpan inPoint = playoutItemWithPath.PlayoutItem.InPoint; + TimeSpan outPoint = playoutItemWithPath.PlayoutItem.OutPoint; + DateTimeOffset effectiveNow = request.StartAtZero ? start : now; + TimeSpan duration = finish - effectiveNow; + + if (_isDebugNoSync) + { + Command doesNotExistProcess = await _ffmpegProcessService.ForError( + ffmpegPath, + channel, + duration, + $"DEBUG_NO_SYNC:\n{Mapper.GetDisplayTitle(playoutItemWithPath.PlayoutItem.MediaItem, Option.None)}\nFrom: {start} To: {finish}", + request.HlsRealtime, + request.PtsOffset, + channel.FFmpegProfile.VaapiDisplay, + channel.FFmpegProfile.VaapiDriver, + channel.FFmpegProfile.VaapiDevice, + Optional(channel.FFmpegProfile.QsvExtraHardwareFrames)); + + return new PlayoutItemProcessModel( + doesNotExistProcess, + Option.None, + duration, + finish, + true); + } + MediaVersion version = playoutItemWithPath.PlayoutItem.MediaItem.GetHeadVersion(); string videoPath = playoutItemWithPath.Path; @@ -337,13 +376,6 @@ public class GetPlayoutItemProcessByChannelNumberHandler : FFmpegProcessHandler< .GetValue(ConfigElementKey.FFmpegSaveReports, cancellationToken) .Map(result => result.IfNone(false)); - DateTimeOffset start = playoutItemWithPath.PlayoutItem.StartOffset; - DateTimeOffset finish = playoutItemWithPath.PlayoutItem.FinishOffset; - TimeSpan inPoint = playoutItemWithPath.PlayoutItem.InPoint; - TimeSpan outPoint = playoutItemWithPath.PlayoutItem.OutPoint; - DateTimeOffset effectiveNow = request.StartAtZero ? start : now; - TimeSpan duration = finish - effectiveNow; - _logger.LogDebug( "S: {Start}, F: {Finish}, In: {InPoint}, Out: {OutPoint}, EffNow: {EffectiveNow}, Dur: {Duration}", start, @@ -402,15 +434,17 @@ public class GetPlayoutItemProcessByChannelNumberHandler : FFmpegProcessHandler< foreach (BaseError error in maybePlayoutItem.LeftToSeq()) { - Option maybeDuration = await dbContext.PlayoutItems - .Filter(pi => pi.Playout.ChannelId == channel.Id) + Option maybeNextStart = await dbContext.PlayoutItems + .Filter(pi => pi.Playout.ChannelId == (channel.MirrorSourceChannelId ?? channel.Id)) .Filter(pi => pi.Start > now.UtcDateTime) .OrderBy(pi => pi.Start) .FirstOrDefaultAsync(cancellationToken) .Map(Optional) - .MapT(pi => pi.StartOffset - now); + .MapT(pi => pi.StartOffset); - DateTimeOffset finish = maybeDuration.Match(d => now.Add(d), () => now); + Option maybeDuration = maybeNextStart.Map(s => s - now); + + DateTimeOffset finish = maybeNextStart.Match(s => s, () => now); _logger.LogWarning( "Error locating playout item {@Error}. Will display error from {Start} to {Finish}", @@ -623,7 +657,7 @@ public class GetPlayoutItemProcessByChannelNumberHandler : FFmpegProcessHandler< MediaItem item = items[new Random().Next(items.Count)]; Option maybeDuration = await dbContext.PlayoutItems - .Filter(pi => pi.Playout.ChannelId == channel.Id) + .Filter(pi => pi.Playout.ChannelId == (channel.MirrorSourceChannelId ?? channel.Id)) .Filter(pi => pi.Start > now.UtcDateTime) .OrderBy(pi => pi.Start) .FirstOrDefaultAsync(cancellationToken) @@ -681,6 +715,12 @@ public class GetPlayoutItemProcessByChannelNumberHandler : FFmpegProcessHandler< { string path = await GetPlayoutItemPath(playoutItem, cancellationToken); + if (_isDebugNoSync) + { + // pretend it exists so we get a nice error message + return new PlayoutItemWithPath(playoutItem, path); + } + // check filesystem first if (_localFileSystem.FileExists(path)) { diff --git a/ErsatzTV.sln b/ErsatzTV.sln index 59e7c0472..09896fc59 100644 --- a/ErsatzTV.sln +++ b/ErsatzTV.sln @@ -55,8 +55,8 @@ Global {CBA93B70-0241-4A73-ABC6-A6E3976A6D7C}.Debug|Any CPU.Build.0 = Debug|Any CPU {CBA93B70-0241-4A73-ABC6-A6E3976A6D7C}.Release|Any CPU.ActiveCfg = Release|Any CPU {CBA93B70-0241-4A73-ABC6-A6E3976A6D7C}.Release|Any CPU.Build.0 = Release|Any CPU - {CBA93B70-0241-4A73-ABC6-A6E3976A6D7C}.Debug No Sync|Any CPU.ActiveCfg = Debug|Any CPU - {CBA93B70-0241-4A73-ABC6-A6E3976A6D7C}.Debug No Sync|Any CPU.Build.0 = Debug|Any CPU + {CBA93B70-0241-4A73-ABC6-A6E3976A6D7C}.Debug No Sync|Any CPU.ActiveCfg = Debug No Sync|Any CPU + {CBA93B70-0241-4A73-ABC6-A6E3976A6D7C}.Debug No Sync|Any CPU.Build.0 = Debug No Sync|Any CPU {CE7F1ACD-F286-4761-A7BC-A541A1E25C86}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {CE7F1ACD-F286-4761-A7BC-A541A1E25C86}.Debug|Any CPU.Build.0 = Debug|Any CPU {CE7F1ACD-F286-4761-A7BC-A541A1E25C86}.Release|Any CPU.ActiveCfg = Release|Any CPU