diff --git a/.editorconfig b/.editorconfig index 825f07a7..0b67d50d 100644 --- a/.editorconfig +++ b/.editorconfig @@ -82,7 +82,7 @@ resharper_built_in_type_reference_style_highlighting=hint resharper_redundant_base_qualifier_highlighting=warning resharper_suggest_var_or_type_built_in_types_highlighting=hint resharper_suggest_var_or_type_elsewhere_highlighting=hint -resharper_suggest_var_or_type_simple_types_highlighting=hint +resharper_suggest_var_or_type_simple_types_highlighting=none resharper_web_config_module_not_resolved_highlighting=warning resharper_web_config_type_not_resolved_highlighting=warning resharper_web_config_wrong_module_highlighting=warning diff --git a/CHANGELOG.md b/CHANGELOG.md index 1e884bfb..45482e63 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - YAML playout: add `pre_roll` instruction to enable and disable a pre-roll sequence - With value of `true` and `sequence` property, will enable automatic pre-roll for all content in the playout to the sequence with the provided key - With value of `false`, will disable automatic pre-roll in the playout +- YAML playout: add `post_roll` instruction to enable and disable a post-roll sequence + - With value of `true` and `sequence` property, will enable automatic post-roll for all content in the playout to the sequence with the provided key + - With value of `false`, will disable automatic post-roll in the playout - Add YAML playout validation (using JSON Schema) - Invalid YAML playout definitions will fail to build and will log validation failures as warnings - `content` is fully validated diff --git a/ErsatzTV.Core/Scheduling/YamlScheduling/Handlers/YamlPlayoutAllHandler.cs b/ErsatzTV.Core/Scheduling/YamlScheduling/Handlers/YamlPlayoutAllHandler.cs index 1ba25015..c35a7b63 100644 --- a/ErsatzTV.Core/Scheduling/YamlScheduling/Handlers/YamlPlayoutAllHandler.cs +++ b/ErsatzTV.Core/Scheduling/YamlScheduling/Handlers/YamlPlayoutAllHandler.cs @@ -91,6 +91,13 @@ public class YamlPlayoutAllHandler(EnumeratorCache enumeratorCache) : YamlPlayou context.CurrentTime += itemDuration; enumerator.MoveNext(); } + + foreach (string postRollSequence in context.GetPostRollSequence()) + { + context.PushFillerKind(FillerKind.PostRoll); + await executeSequence(postRollSequence); + context.PopFillerKind(); + } } return true; diff --git a/ErsatzTV.Core/Scheduling/YamlScheduling/Handlers/YamlPlayoutCountHandler.cs b/ErsatzTV.Core/Scheduling/YamlScheduling/Handlers/YamlPlayoutCountHandler.cs index fb1f7138..32d4e175 100644 --- a/ErsatzTV.Core/Scheduling/YamlScheduling/Handlers/YamlPlayoutCountHandler.cs +++ b/ErsatzTV.Core/Scheduling/YamlScheduling/Handlers/YamlPlayoutCountHandler.cs @@ -116,6 +116,13 @@ public class YamlPlayoutCountHandler(EnumeratorCache enumeratorCache) : YamlPlay context.CurrentTime += itemDuration; enumerator.MoveNext(); } + + foreach (string postRollSequence in context.GetPostRollSequence()) + { + context.PushFillerKind(FillerKind.PostRoll); + await executeSequence(postRollSequence); + context.PopFillerKind(); + } } return true; diff --git a/ErsatzTV.Core/Scheduling/YamlScheduling/Handlers/YamlPlayoutDurationHandler.cs b/ErsatzTV.Core/Scheduling/YamlScheduling/Handlers/YamlPlayoutDurationHandler.cs index 149890d8..45667f66 100644 --- a/ErsatzTV.Core/Scheduling/YamlScheduling/Handlers/YamlPlayoutDurationHandler.cs +++ b/ErsatzTV.Core/Scheduling/YamlScheduling/Handlers/YamlPlayoutDurationHandler.cs @@ -229,6 +229,13 @@ public class YamlPlayoutDurationHandler(EnumeratorCache enumeratorCache) : YamlP done = true; } } + + foreach (string postRollSequence in context.GetPostRollSequence()) + { + context.PushFillerKind(FillerKind.PostRoll); + await executeSequence(postRollSequence); + context.PopFillerKind(); + } } if (!stopBeforeEnd) diff --git a/ErsatzTV.Core/Scheduling/YamlScheduling/Handlers/YamlPlayoutPostRollHandler.cs b/ErsatzTV.Core/Scheduling/YamlScheduling/Handlers/YamlPlayoutPostRollHandler.cs new file mode 100644 index 00000000..9226d5aa --- /dev/null +++ b/ErsatzTV.Core/Scheduling/YamlScheduling/Handlers/YamlPlayoutPostRollHandler.cs @@ -0,0 +1,34 @@ +using ErsatzTV.Core.Scheduling.YamlScheduling.Models; +using Microsoft.Extensions.Logging; + +namespace ErsatzTV.Core.Scheduling.YamlScheduling.Handlers; + +public class YamlPlayoutPostRollHandler : IYamlPlayoutHandler +{ + public bool Reset => false; + + public Task Handle( + YamlPlayoutContext context, + YamlPlayoutInstruction instruction, + PlayoutBuildMode mode, + Func executeSequence, + ILogger logger, + CancellationToken cancellationToken) + { + if (instruction is not YamlPostRollInstruction postRoll) + { + return Task.FromResult(false); + } + + if (postRoll.PostRoll && !string.IsNullOrWhiteSpace(postRoll.Sequence)) + { + context.SetPostRollSequence(postRoll.Sequence); + } + else + { + context.ClearPostRollSequence(); + } + + return Task.FromResult(true); + } +} diff --git a/ErsatzTV.Core/Scheduling/YamlScheduling/Models/YamlPostRollInstruction.cs b/ErsatzTV.Core/Scheduling/YamlScheduling/Models/YamlPostRollInstruction.cs new file mode 100644 index 00000000..9f98f777 --- /dev/null +++ b/ErsatzTV.Core/Scheduling/YamlScheduling/Models/YamlPostRollInstruction.cs @@ -0,0 +1,11 @@ +using YamlDotNet.Serialization; + +namespace ErsatzTV.Core.Scheduling.YamlScheduling.Models; + +public class YamlPostRollInstruction : YamlPlayoutInstruction +{ + [YamlMember(Alias = "post_roll", ApplyNamingConventions = false)] + public bool PostRoll { get; set; } + + public string Sequence { get; set; } +} diff --git a/ErsatzTV.Core/Scheduling/YamlScheduling/YamlPlayoutBuilder.cs b/ErsatzTV.Core/Scheduling/YamlScheduling/YamlPlayoutBuilder.cs index 593e4cdb..da08bd17 100644 --- a/ErsatzTV.Core/Scheduling/YamlScheduling/YamlPlayoutBuilder.cs +++ b/ErsatzTV.Core/Scheduling/YamlScheduling/YamlPlayoutBuilder.cs @@ -344,6 +344,7 @@ public class YamlPlayoutBuilder( YamlPlayoutWatermarkInstruction => new YamlPlayoutWatermarkHandler(channelRepository), YamlPlayoutShuffleSequenceInstruction => new YamlPlayoutShuffleSequenceHandler(), YamlPreRollInstruction => new YamlPlayoutPreRollHandler(), + YamlPostRollInstruction => new YamlPlayoutPostRollHandler(), YamlPlayoutSkipItemsInstruction => new YamlPlayoutSkipItemsHandler(enumeratorCache), YamlPlayoutSkipToItemInstruction => new YamlPlayoutSkipToItemHandler(enumeratorCache), @@ -403,6 +404,7 @@ public class YamlPlayoutBuilder( { "pad_to_next", typeof(YamlPlayoutPadToNextInstruction) }, { "pad_until", typeof(YamlPlayoutPadUntilInstruction) }, { "pre_roll", typeof(YamlPreRollInstruction) }, + { "post_roll", typeof(YamlPostRollInstruction) }, { "repeat", typeof(YamlPlayoutRepeatInstruction) }, { "sequence", typeof(YamlPlayoutSequenceInstruction) }, { "shuffle_sequence", typeof(YamlPlayoutShuffleSequenceInstruction) }, diff --git a/ErsatzTV.Core/Scheduling/YamlScheduling/YamlPlayoutContext.cs b/ErsatzTV.Core/Scheduling/YamlScheduling/YamlPlayoutContext.cs index b09d9d5c..61435505 100644 --- a/ErsatzTV.Core/Scheduling/YamlScheduling/YamlPlayoutContext.cs +++ b/ErsatzTV.Core/Scheduling/YamlScheduling/YamlPlayoutContext.cs @@ -13,12 +13,13 @@ public class YamlPlayoutContext(Playout playout, YamlPlayoutDefinition definitio }; private readonly System.Collections.Generic.HashSet _visitedInstructions = []; + private readonly Stack _fillerKind = new(); private int _guideGroup = guideGroup; private bool _guideGroupLocked; private int _instructionIndex; private Option _channelWatermarkId; private Option _preRollSequence; - private Stack _fillerKind = new(); + private Option _postRollSequence; public Playout Playout { get; } = playout; @@ -104,6 +105,18 @@ public class YamlPlayoutContext(Playout playout, YamlPlayoutDefinition definitio public Option GetPreRollSequence() => _preRollSequence; + public void SetPostRollSequence(string sequence) + { + _postRollSequence = sequence; + } + + public void ClearPostRollSequence() + { + _postRollSequence = Option.None; + } + + public Option GetPostRollSequence() => _postRollSequence; + public void PushFillerKind(FillerKind fillerKind) { _fillerKind.Push(fillerKind); diff --git a/ErsatzTV/Resources/yaml-playout.schema.json b/ErsatzTV/Resources/yaml-playout.schema.json index 1d38a26e..eb4e9f7e 100644 --- a/ErsatzTV/Resources/yaml-playout.schema.json +++ b/ErsatzTV/Resources/yaml-playout.schema.json @@ -40,6 +40,7 @@ { "$ref": "#/$defs/scheduling/sequenceInstruction" }, { "$ref": "#/$defs/control/epgGroupInstruction" }, { "$ref": "#/$defs/control/preRollInstruction"}, + { "$ref": "#/$defs/control/postRollInstruction"}, { "$ref": "#/$defs/control/repeatInstruction" }, { "$ref": "#/$defs/control/shuffleSequenceInstruction" }, { "$ref": "#/$defs/control/skipItemsInstruction" }, @@ -79,6 +80,7 @@ { "$ref": "#/$defs/scheduling/sequenceInstruction" }, { "$ref": "#/$defs/control/epgGroupInstruction" }, { "$ref": "#/$defs/control/preRollInstruction"}, + { "$ref": "#/$defs/control/postRollInstruction"}, { "$ref": "#/$defs/control/repeatInstruction" }, { "$ref": "#/$defs/control/shuffleSequenceInstruction" }, { "$ref": "#/$defs/control/skipItemsInstruction" }, @@ -298,6 +300,15 @@ "required": [ "pre_roll" ], "additionalProperties": false }, + "postRollInstruction": { + "type": "object", + "properties": { + "post_roll": { "type": "boolean" }, + "sequence": { "type": "string" } + }, + "required": [ "post_roll" ], + "additionalProperties": false + }, "repeatInstruction": { "type": "object", "properties": {