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