diff --git a/CHANGELOG.md b/CHANGELOG.md index a8a9c558c..130bb60f5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -36,6 +36,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - Optimize graphics engine to generate element frames in parallel and to eliminate redundant frame copies - Match graphics engine framerate with source content (or channel normalized) framerate - Fix loading requested number of epg entries for motion graphics elements +- Fix bug with mirror channels where seemingly random content would be played every ~40 seconds ### Changed - No longer round framerate to nearest integer when normalizing framerate diff --git a/ErsatzTV.Application/Streaming/PlayoutItemProcessModel.cs b/ErsatzTV.Application/Streaming/PlayoutItemProcessModel.cs index ad005871a..b42b2ee28 100644 --- a/ErsatzTV.Application/Streaming/PlayoutItemProcessModel.cs +++ b/ErsatzTV.Application/Streaming/PlayoutItemProcessModel.cs @@ -3,11 +3,50 @@ using ErsatzTV.Core.Interfaces.Streaming; namespace ErsatzTV.Application.Streaming; -public record PlayoutItemProcessModel( - Command Process, - Option GraphicsEngineContext, - Option MaybeDuration, - DateTimeOffset Until, - bool IsComplete, - Option SegmentKey, - Option MediaItemId); +public class PlayoutItemProcessModel +{ + public PlayoutItemProcessModel( + Command process, + Option graphicsEngineContext, + Option maybeDuration, + DateTimeOffset until, + bool isComplete, + Option segmentKey, + Option mediaItemId, + Option playoutOffset) + { + Process = process; + GraphicsEngineContext = graphicsEngineContext; + MaybeDuration = maybeDuration; + Until = until; + IsComplete = isComplete; + SegmentKey = segmentKey; + MediaItemId = mediaItemId; + + // undo the offset applied in FFmpegProcessHandler + // so we don't continually walk backward/forward in time by the offset amount + foreach (TimeSpan offset in playoutOffset) + { + foreach (long key in SegmentKey) + { + SegmentKey = key + (long)offset.TotalSeconds; + } + + Until += offset; + } + } + + public Command Process { get; init; } + + public Option GraphicsEngineContext { get; init; } + + public Option MaybeDuration { get; init; } + + public DateTimeOffset Until { get; init; } + + public bool IsComplete { get; init; } + + public Option SegmentKey { get; init; } + + public Option MediaItemId { get; init; } +} diff --git a/ErsatzTV.Application/Streaming/Queries/GetConcatProcessByChannelNumberHandler.cs b/ErsatzTV.Application/Streaming/Queries/GetConcatProcessByChannelNumberHandler.cs index 99127cfeb..fe82a3bea 100644 --- a/ErsatzTV.Application/Streaming/Queries/GetConcatProcessByChannelNumberHandler.cs +++ b/ErsatzTV.Application/Streaming/Queries/GetConcatProcessByChannelNumberHandler.cs @@ -45,6 +45,7 @@ public class GetConcatProcessByChannelNumberHandler : FFmpegProcessHandler.None, - Option.None); + Option.None, + Option.None); } } diff --git a/ErsatzTV.Application/Streaming/Queries/GetErrorProcessHandler.cs b/ErsatzTV.Application/Streaming/Queries/GetErrorProcessHandler.cs index f1d012bcb..9e280b9b7 100644 --- a/ErsatzTV.Application/Streaming/Queries/GetErrorProcessHandler.cs +++ b/ErsatzTV.Application/Streaming/Queries/GetErrorProcessHandler.cs @@ -21,12 +21,10 @@ public class GetErrorProcessHandler( string ffprobePath, CancellationToken cancellationToken) { - DateTimeOffset now = DateTimeOffset.Now; - Command process = await ffmpegProcessService.ForError( ffmpegPath, channel, - now, + request.Now, request.MaybeDuration, request.ErrorMessage, request.HlsRealtime, @@ -42,7 +40,8 @@ public class GetErrorProcessHandler( request.MaybeDuration, request.Until, true, - now.ToUnixTimeSeconds(), - Option.None); + request.Now.ToUnixTimeSeconds(), + Option.None, + Optional(channel.PlayoutOffset)); } } diff --git a/ErsatzTV.Application/Streaming/Queries/GetPlayoutItemProcessByChannelNumberHandler.cs b/ErsatzTV.Application/Streaming/Queries/GetPlayoutItemProcessByChannelNumberHandler.cs index 881aa5371..80a9a1fa2 100644 --- a/ErsatzTV.Application/Streaming/Queries/GetPlayoutItemProcessByChannelNumberHandler.cs +++ b/ErsatzTV.Application/Streaming/Queries/GetPlayoutItemProcessByChannelNumberHandler.cs @@ -332,8 +332,9 @@ public class GetPlayoutItemProcessByChannelNumberHandler : FFmpegProcessHandler< duration, finish, true, - now.ToUnixTimeSeconds(), - Option.None); + effectiveNow.ToUnixTimeSeconds(), + Option.None, + Optional(channel.PlayoutOffset)); } MediaVersion version = playoutItemWithPath.PlayoutItem.MediaItem.GetHeadVersion(); @@ -463,7 +464,8 @@ public class GetPlayoutItemProcessByChannelNumberHandler : FFmpegProcessHandler< finish, isComplete, effectiveNow.ToUnixTimeSeconds(), - playoutItemResult.MediaItemId); + playoutItemResult.MediaItemId, + Optional(channel.PlayoutOffset)); return Right(result); } @@ -519,7 +521,8 @@ public class GetPlayoutItemProcessByChannelNumberHandler : FFmpegProcessHandler< finish, true, now.ToUnixTimeSeconds(), - Option.None); + Option.None, + Optional(channel.PlayoutOffset)); case PlayoutItemDoesNotExistOnDisk: Command doesNotExistProcess = await _ffmpegProcessService.ForError( ffmpegPath, @@ -541,7 +544,8 @@ public class GetPlayoutItemProcessByChannelNumberHandler : FFmpegProcessHandler< finish, true, now.ToUnixTimeSeconds(), - Option.None); + Option.None, + Optional(channel.PlayoutOffset)); default: Command errorProcess = await _ffmpegProcessService.ForError( ffmpegPath, @@ -563,7 +567,8 @@ public class GetPlayoutItemProcessByChannelNumberHandler : FFmpegProcessHandler< finish, true, now.ToUnixTimeSeconds(), - Option.None); + Option.None, + Optional(channel.PlayoutOffset)); } } diff --git a/ErsatzTV.Application/Streaming/Queries/GetWrappedProcessByChannelNumberHandler.cs b/ErsatzTV.Application/Streaming/Queries/GetWrappedProcessByChannelNumberHandler.cs index 4866e2cd4..cd88f4197 100644 --- a/ErsatzTV.Application/Streaming/Queries/GetWrappedProcessByChannelNumberHandler.cs +++ b/ErsatzTV.Application/Streaming/Queries/GetWrappedProcessByChannelNumberHandler.cs @@ -47,6 +47,7 @@ public class GetWrappedProcessByChannelNumberHandler : FFmpegProcessHandler.None, - Option.None); + Option.None, + Option.None); } }