properly fix infinite playout build loop (#952)
This commit is contained in:
+2
-1
@@ -12,7 +12,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
|||||||
- Fix bug with XMLTV that caused some filler to display with primary content details
|
- Fix bug with XMLTV that caused some filler to display with primary content details
|
||||||
- Multiple fixes for content scaling with `Nvidia`, `Qsv` and `Vaapi` accelerations
|
- Multiple fixes for content scaling with `Nvidia`, `Qsv` and `Vaapi` accelerations
|
||||||
- Properly scale image-based subtitles
|
- Properly scale image-based subtitles
|
||||||
- Abort when an infinite playout building loop is detected; proper bug fix will be released soon
|
- Fix bug where a schedule containing a single item (fixed start and flood) would never finish building a playout
|
||||||
|
- Logic was also added to detect infinite playout build loops in the future and stop them
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
- Add `Preferred Audio Title` feature
|
- Add `Preferred Audio Title` feature
|
||||||
|
|||||||
@@ -515,6 +515,7 @@ public class PlayoutBuilderTests
|
|||||||
{
|
{
|
||||||
new ProgramScheduleItemFlood
|
new ProgramScheduleItemFlood
|
||||||
{
|
{
|
||||||
|
Id = 1,
|
||||||
Index = 1,
|
Index = 1,
|
||||||
Collection = floodCollection,
|
Collection = floodCollection,
|
||||||
CollectionId = floodCollection.Id,
|
CollectionId = floodCollection.Id,
|
||||||
@@ -523,6 +524,7 @@ public class PlayoutBuilderTests
|
|||||||
},
|
},
|
||||||
new ProgramScheduleItemOne
|
new ProgramScheduleItemOne
|
||||||
{
|
{
|
||||||
|
Id = 2,
|
||||||
Index = 2,
|
Index = 2,
|
||||||
Collection = fixedCollection,
|
Collection = fixedCollection,
|
||||||
CollectionId = fixedCollection.Id,
|
CollectionId = fixedCollection.Id,
|
||||||
@@ -605,6 +607,7 @@ public class PlayoutBuilderTests
|
|||||||
{
|
{
|
||||||
new ProgramScheduleItemFlood
|
new ProgramScheduleItemFlood
|
||||||
{
|
{
|
||||||
|
Id = 1,
|
||||||
Index = 1,
|
Index = 1,
|
||||||
Collection = floodCollection,
|
Collection = floodCollection,
|
||||||
CollectionId = floodCollection.Id,
|
CollectionId = floodCollection.Id,
|
||||||
@@ -613,6 +616,7 @@ public class PlayoutBuilderTests
|
|||||||
},
|
},
|
||||||
new ProgramScheduleItemOne
|
new ProgramScheduleItemOne
|
||||||
{
|
{
|
||||||
|
Id = 2,
|
||||||
Index = 2,
|
Index = 2,
|
||||||
Collection = fixedCollection,
|
Collection = fixedCollection,
|
||||||
CollectionId = fixedCollection.Id,
|
CollectionId = fixedCollection.Id,
|
||||||
@@ -742,6 +746,7 @@ public class PlayoutBuilderTests
|
|||||||
{
|
{
|
||||||
new ProgramScheduleItemFlood
|
new ProgramScheduleItemFlood
|
||||||
{
|
{
|
||||||
|
Id = 1,
|
||||||
Index = 1,
|
Index = 1,
|
||||||
Collection = floodCollection,
|
Collection = floodCollection,
|
||||||
CollectionId = floodCollection.Id,
|
CollectionId = floodCollection.Id,
|
||||||
@@ -750,6 +755,7 @@ public class PlayoutBuilderTests
|
|||||||
},
|
},
|
||||||
new ProgramScheduleItemMultiple
|
new ProgramScheduleItemMultiple
|
||||||
{
|
{
|
||||||
|
Id = 2,
|
||||||
Index = 2,
|
Index = 2,
|
||||||
Collection = fixedCollection,
|
Collection = fixedCollection,
|
||||||
CollectionId = fixedCollection.Id,
|
CollectionId = fixedCollection.Id,
|
||||||
@@ -839,6 +845,7 @@ public class PlayoutBuilderTests
|
|||||||
{
|
{
|
||||||
new ProgramScheduleItemFlood
|
new ProgramScheduleItemFlood
|
||||||
{
|
{
|
||||||
|
Id = 1,
|
||||||
Index = 1,
|
Index = 1,
|
||||||
Collection = floodCollection,
|
Collection = floodCollection,
|
||||||
CollectionId = floodCollection.Id,
|
CollectionId = floodCollection.Id,
|
||||||
@@ -847,6 +854,7 @@ public class PlayoutBuilderTests
|
|||||||
},
|
},
|
||||||
new ProgramScheduleItemOne
|
new ProgramScheduleItemOne
|
||||||
{
|
{
|
||||||
|
Id = 2,
|
||||||
Index = 2,
|
Index = 2,
|
||||||
Collection = fixedCollection,
|
Collection = fixedCollection,
|
||||||
CollectionId = fixedCollection.Id,
|
CollectionId = fixedCollection.Id,
|
||||||
@@ -934,6 +942,7 @@ public class PlayoutBuilderTests
|
|||||||
{
|
{
|
||||||
new ProgramScheduleItemFlood
|
new ProgramScheduleItemFlood
|
||||||
{
|
{
|
||||||
|
Id = 1,
|
||||||
Index = 1,
|
Index = 1,
|
||||||
Collection = floodCollection,
|
Collection = floodCollection,
|
||||||
CollectionId = floodCollection.Id,
|
CollectionId = floodCollection.Id,
|
||||||
@@ -942,6 +951,7 @@ public class PlayoutBuilderTests
|
|||||||
},
|
},
|
||||||
new ProgramScheduleItemOne
|
new ProgramScheduleItemOne
|
||||||
{
|
{
|
||||||
|
Id = 2,
|
||||||
Index = 2,
|
Index = 2,
|
||||||
Collection = fixedCollection,
|
Collection = fixedCollection,
|
||||||
CollectionId = fixedCollection.Id,
|
CollectionId = fixedCollection.Id,
|
||||||
@@ -1040,6 +1050,7 @@ public class PlayoutBuilderTests
|
|||||||
{
|
{
|
||||||
new ProgramScheduleItemFlood
|
new ProgramScheduleItemFlood
|
||||||
{
|
{
|
||||||
|
Id = 1,
|
||||||
Index = 1,
|
Index = 1,
|
||||||
Collection = floodCollection,
|
Collection = floodCollection,
|
||||||
CollectionId = floodCollection.Id,
|
CollectionId = floodCollection.Id,
|
||||||
@@ -1048,6 +1059,7 @@ public class PlayoutBuilderTests
|
|||||||
},
|
},
|
||||||
new ProgramScheduleItemDuration
|
new ProgramScheduleItemDuration
|
||||||
{
|
{
|
||||||
|
Id = 2,
|
||||||
Index = 2,
|
Index = 2,
|
||||||
Collection = fixedCollection,
|
Collection = fixedCollection,
|
||||||
CollectionId = fixedCollection.Id,
|
CollectionId = fixedCollection.Id,
|
||||||
@@ -1141,6 +1153,7 @@ public class PlayoutBuilderTests
|
|||||||
{
|
{
|
||||||
new ProgramScheduleItemMultiple
|
new ProgramScheduleItemMultiple
|
||||||
{
|
{
|
||||||
|
Id = 1,
|
||||||
Index = 1,
|
Index = 1,
|
||||||
Collection = multipleCollection,
|
Collection = multipleCollection,
|
||||||
CollectionId = multipleCollection.Id,
|
CollectionId = multipleCollection.Id,
|
||||||
@@ -1150,6 +1163,7 @@ public class PlayoutBuilderTests
|
|||||||
},
|
},
|
||||||
new ProgramScheduleItemDuration
|
new ProgramScheduleItemDuration
|
||||||
{
|
{
|
||||||
|
Id = 2,
|
||||||
Index = 2,
|
Index = 2,
|
||||||
Collection = dynamicCollection,
|
Collection = dynamicCollection,
|
||||||
CollectionId = dynamicCollection.Id,
|
CollectionId = dynamicCollection.Id,
|
||||||
@@ -2329,6 +2343,7 @@ public class PlayoutBuilderTests
|
|||||||
{
|
{
|
||||||
new ProgramScheduleItemFlood
|
new ProgramScheduleItemFlood
|
||||||
{
|
{
|
||||||
|
Id = 1,
|
||||||
Index = 1,
|
Index = 1,
|
||||||
Collection = floodCollection,
|
Collection = floodCollection,
|
||||||
CollectionId = floodCollection.Id,
|
CollectionId = floodCollection.Id,
|
||||||
@@ -2337,6 +2352,7 @@ public class PlayoutBuilderTests
|
|||||||
},
|
},
|
||||||
new ProgramScheduleItemOne
|
new ProgramScheduleItemOne
|
||||||
{
|
{
|
||||||
|
Id = 2,
|
||||||
Index = 2,
|
Index = 2,
|
||||||
Collection = fixedCollection,
|
Collection = fixedCollection,
|
||||||
CollectionId = fixedCollection.Id,
|
CollectionId = fixedCollection.Id,
|
||||||
@@ -2622,6 +2638,7 @@ public class PlayoutBuilderTests
|
|||||||
private static ProgramScheduleItem Flood(Collection mediaCollection, PlaybackOrder playbackOrder) =>
|
private static ProgramScheduleItem Flood(Collection mediaCollection, PlaybackOrder playbackOrder) =>
|
||||||
new ProgramScheduleItemFlood
|
new ProgramScheduleItemFlood
|
||||||
{
|
{
|
||||||
|
Id = 1,
|
||||||
Index = 1,
|
Index = 1,
|
||||||
Collection = mediaCollection,
|
Collection = mediaCollection,
|
||||||
CollectionId = mediaCollection.Id,
|
CollectionId = mediaCollection.Id,
|
||||||
|
|||||||
@@ -85,6 +85,98 @@ public class PlayoutModeSchedulerFloodTests : SchedulerTestBase
|
|||||||
playoutItems[2].FillerKind.Should().Be(FillerKind.None);
|
playoutItems[2].FillerKind.Should().Be(FillerKind.None);
|
||||||
playoutItems[2].CustomTitle.Should().Be("CustomTitle");
|
playoutItems[2].CustomTitle.Should().Be("CustomTitle");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void Should_Schedule_Single_Item_Fixed_Start_Flood()
|
||||||
|
{
|
||||||
|
Collection collectionOne = TwoItemCollection(1, 2, TimeSpan.FromHours(1));
|
||||||
|
|
||||||
|
var scheduleItem = new ProgramScheduleItemFlood
|
||||||
|
{
|
||||||
|
Id = 1,
|
||||||
|
Index = 1,
|
||||||
|
Collection = collectionOne,
|
||||||
|
CollectionId = collectionOne.Id,
|
||||||
|
StartTime = TimeSpan.Zero,
|
||||||
|
PlaybackOrder = PlaybackOrder.Chronological,
|
||||||
|
TailFiller = null,
|
||||||
|
FallbackFiller = null,
|
||||||
|
CustomTitle = "CustomTitle"
|
||||||
|
};
|
||||||
|
|
||||||
|
var enumerator = new ChronologicalMediaCollectionEnumerator(
|
||||||
|
collectionOne.MediaItems,
|
||||||
|
new CollectionEnumeratorState());
|
||||||
|
|
||||||
|
var sortedScheduleItems = new List<ProgramScheduleItem>
|
||||||
|
{
|
||||||
|
scheduleItem
|
||||||
|
};
|
||||||
|
|
||||||
|
var scheduleItemsEnumerator = new OrderedScheduleItemsEnumerator(
|
||||||
|
sortedScheduleItems,
|
||||||
|
new CollectionEnumeratorState());
|
||||||
|
|
||||||
|
PlayoutBuilderState startState = StartState(scheduleItemsEnumerator);
|
||||||
|
|
||||||
|
var scheduler = new PlayoutModeSchedulerFlood(new Mock<ILogger>().Object);
|
||||||
|
(PlayoutBuilderState playoutBuilderState, List<PlayoutItem> playoutItems) = scheduler.Schedule(
|
||||||
|
startState,
|
||||||
|
CollectionEnumerators(scheduleItem, enumerator),
|
||||||
|
scheduleItem,
|
||||||
|
scheduleItem,
|
||||||
|
HardStop(scheduleItemsEnumerator));
|
||||||
|
|
||||||
|
playoutBuilderState.CurrentTime.Should().Be(startState.CurrentTime.AddHours(6));
|
||||||
|
playoutItems.Last().FinishOffset.Should().Be(playoutBuilderState.CurrentTime);
|
||||||
|
|
||||||
|
playoutBuilderState.NextGuideGroup.Should().Be(2); // one guide group here because of custom title
|
||||||
|
playoutBuilderState.DurationFinish.IsNone.Should().BeTrue();
|
||||||
|
playoutBuilderState.InFlood.Should().BeTrue();
|
||||||
|
playoutBuilderState.MultipleRemaining.IsNone.Should().BeTrue();
|
||||||
|
playoutBuilderState.InDurationFiller.Should().BeFalse();
|
||||||
|
playoutBuilderState.ScheduleItemsEnumerator.State.Index.Should().Be(0);
|
||||||
|
|
||||||
|
enumerator.State.Index.Should().Be(0);
|
||||||
|
|
||||||
|
playoutItems.Count.Should().Be(6);
|
||||||
|
|
||||||
|
playoutItems[0].MediaItemId.Should().Be(1);
|
||||||
|
playoutItems[0].StartOffset.Should().Be(startState.CurrentTime);
|
||||||
|
playoutItems[0].GuideGroup.Should().Be(1);
|
||||||
|
playoutItems[0].FillerKind.Should().Be(FillerKind.None);
|
||||||
|
playoutItems[0].CustomTitle.Should().Be("CustomTitle");
|
||||||
|
|
||||||
|
playoutItems[1].MediaItemId.Should().Be(2);
|
||||||
|
playoutItems[1].StartOffset.Should().Be(startState.CurrentTime.AddHours(1));
|
||||||
|
playoutItems[1].GuideGroup.Should().Be(1);
|
||||||
|
playoutItems[1].FillerKind.Should().Be(FillerKind.None);
|
||||||
|
playoutItems[1].CustomTitle.Should().Be("CustomTitle");
|
||||||
|
|
||||||
|
playoutItems[2].MediaItemId.Should().Be(1);
|
||||||
|
playoutItems[2].StartOffset.Should().Be(startState.CurrentTime.AddHours(2));
|
||||||
|
playoutItems[2].GuideGroup.Should().Be(1);
|
||||||
|
playoutItems[2].FillerKind.Should().Be(FillerKind.None);
|
||||||
|
playoutItems[2].CustomTitle.Should().Be("CustomTitle");
|
||||||
|
|
||||||
|
playoutItems[3].MediaItemId.Should().Be(2);
|
||||||
|
playoutItems[3].StartOffset.Should().Be(startState.CurrentTime.AddHours(3));
|
||||||
|
playoutItems[3].GuideGroup.Should().Be(1);
|
||||||
|
playoutItems[3].FillerKind.Should().Be(FillerKind.None);
|
||||||
|
playoutItems[3].CustomTitle.Should().Be("CustomTitle");
|
||||||
|
|
||||||
|
playoutItems[4].MediaItemId.Should().Be(1);
|
||||||
|
playoutItems[4].StartOffset.Should().Be(startState.CurrentTime.AddHours(4));
|
||||||
|
playoutItems[4].GuideGroup.Should().Be(1);
|
||||||
|
playoutItems[4].FillerKind.Should().Be(FillerKind.None);
|
||||||
|
playoutItems[4].CustomTitle.Should().Be("CustomTitle");
|
||||||
|
|
||||||
|
playoutItems[5].MediaItemId.Should().Be(2);
|
||||||
|
playoutItems[5].StartOffset.Should().Be(startState.CurrentTime.AddHours(5));
|
||||||
|
playoutItems[5].GuideGroup.Should().Be(1);
|
||||||
|
playoutItems[5].FillerKind.Should().Be(FillerKind.None);
|
||||||
|
playoutItems[5].CustomTitle.Should().Be("CustomTitle");
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void Should_Fill_Exactly_To_Next_Schedule_Item_Flood()
|
public void Should_Fill_Exactly_To_Next_Schedule_Item_Flood()
|
||||||
|
|||||||
@@ -423,21 +423,21 @@ public class PlayoutBuilder : IPlayoutBuilder
|
|||||||
var schedulerDuration = new PlayoutModeSchedulerDuration(_logger);
|
var schedulerDuration = new PlayoutModeSchedulerDuration(_logger);
|
||||||
var schedulerFlood = new PlayoutModeSchedulerFlood(_logger);
|
var schedulerFlood = new PlayoutModeSchedulerFlood(_logger);
|
||||||
|
|
||||||
var timeHash = new Dictionary<DateTimeOffset, int>();
|
var timeCount = new Dictionary<DateTimeOffset, int>();
|
||||||
|
|
||||||
// loop until we're done filling the desired amount of time
|
// loop until we're done filling the desired amount of time
|
||||||
while (playoutBuilderState.CurrentTime < playoutFinish)
|
while (playoutBuilderState.CurrentTime < playoutFinish)
|
||||||
{
|
{
|
||||||
if (timeHash.TryGetValue(playoutBuilderState.CurrentTime, out int count))
|
if (timeCount.TryGetValue(playoutBuilderState.CurrentTime, out int count))
|
||||||
{
|
{
|
||||||
timeHash[playoutBuilderState.CurrentTime] = count + 1;
|
timeCount[playoutBuilderState.CurrentTime] = count + 1;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
timeHash[playoutBuilderState.CurrentTime] = 1;
|
timeCount[playoutBuilderState.CurrentTime] = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (timeHash[playoutBuilderState.CurrentTime] == 6)
|
if (timeCount[playoutBuilderState.CurrentTime] == 6)
|
||||||
{
|
{
|
||||||
_logger.LogWarning(
|
_logger.LogWarning(
|
||||||
"Failed to schedule beyond {Time}; aborting playout build - this is a bug",
|
"Failed to schedule beyond {Time}; aborting playout build - this is a bug",
|
||||||
|
|||||||
@@ -67,8 +67,9 @@ public class PlayoutModeSchedulerFlood : PlayoutModeSchedulerBase<ProgramSchedul
|
|||||||
SubtitleMode = scheduleItem.SubtitleMode
|
SubtitleMode = scheduleItem.SubtitleMode
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// never block scheduling when there is only one schedule item (with fixed start and flood)
|
||||||
DateTimeOffset peekScheduleItemStart =
|
DateTimeOffset peekScheduleItemStart =
|
||||||
peekScheduleItem.StartType == StartType.Fixed
|
scheduleItem.Id != peekScheduleItem.Id && peekScheduleItem.StartType == StartType.Fixed
|
||||||
? GetStartTimeAfter(nextState with { InFlood = false }, peekScheduleItem)
|
? GetStartTimeAfter(nextState with { InFlood = false }, peekScheduleItem)
|
||||||
: DateTimeOffset.MaxValue;
|
: DateTimeOffset.MaxValue;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user