Browse Source

fix remote stream durations in playouts (#2515)

pull/2517/head
Jason Dove 10 months ago committed by GitHub
parent
commit
144b3fe80b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 1
      CHANGELOG.md
  2. 19
      ErsatzTV.Core/Extensions/MediaItemExtensions.cs
  3. 13
      ErsatzTV.Core/Scheduling/BlockScheduling/BlockPlayoutBuilder.cs
  4. 15
      ErsatzTV.Core/Scheduling/BlockScheduling/BlockPlayoutFillerBuilder.cs
  5. 2
      ErsatzTV.Core/Scheduling/Engine/ISchedulingEngine.cs
  6. 15
      ErsatzTV.Core/Scheduling/Engine/SchedulingEngine.cs
  7. 25
      ErsatzTV.Core/Scheduling/PlayoutModeSchedulerBase.cs
  8. 3
      ErsatzTV.Core/Scheduling/PlayoutModeSchedulerDuration.cs
  9. 3
      ErsatzTV.Core/Scheduling/PlayoutModeSchedulerFlood.cs
  10. 3
      ErsatzTV.Core/Scheduling/PlayoutModeSchedulerMultiple.cs
  11. 3
      ErsatzTV.Core/Scheduling/PlayoutModeSchedulerOne.cs
  12. 3
      ErsatzTV.Core/Scheduling/YamlScheduling/Handlers/YamlPlayoutAllHandler.cs
  13. 11
      ErsatzTV.Core/Scheduling/YamlScheduling/Handlers/YamlPlayoutContentHandler.cs
  14. 3
      ErsatzTV.Core/Scheduling/YamlScheduling/Handlers/YamlPlayoutCountHandler.cs
  15. 3
      ErsatzTV.Core/Scheduling/YamlScheduling/Handlers/YamlPlayoutDurationHandler.cs
  16. 3
      ErsatzTV/Controllers/Api/ScriptedScheduleController.cs

1
CHANGELOG.md

@ -14,6 +14,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
### Fixed ### Fixed
- Fix NVIDIA startup errors on arm64 - Fix NVIDIA startup errors on arm64
- Fix remote stream durations in playouts created using block, sequential or scripted schedules
### Changed ### Changed
- Do not use graphics engine for single, permanent watermark - Do not use graphics engine for single, permanent watermark

19
ErsatzTV.Core/Extensions/MediaItemExtensions.cs

@ -24,6 +24,25 @@ public static class MediaItemExtensions
return maybeDuration.Any(duration => duration == TimeSpan.Zero) ? Option<TimeSpan>.None : maybeDuration; return maybeDuration.Any(duration => duration == TimeSpan.Zero) ? Option<TimeSpan>.None : maybeDuration;
} }
public static TimeSpan GetDurationForPlayout(this MediaItem mediaItem)
{
if (mediaItem is Image image)
{
return TimeSpan.FromSeconds(image.ImageMetadata.Head().DurationSeconds ?? Image.DefaultSeconds);
}
MediaVersion version = mediaItem.GetHeadVersion();
if (mediaItem is RemoteStream remoteStream)
{
return version.Duration == TimeSpan.Zero && remoteStream.Duration.HasValue
? remoteStream.Duration.Value
: version.Duration;
}
return version.Duration;
}
public static MediaVersion GetHeadVersion(this MediaItem mediaItem) => public static MediaVersion GetHeadVersion(this MediaItem mediaItem) =>
mediaItem switch mediaItem switch
{ {

13
ErsatzTV.Core/Scheduling/BlockScheduling/BlockPlayoutBuilder.cs

@ -192,7 +192,7 @@ public class BlockPlayoutBuilder(
mediaItem.Id, mediaItem.Id,
PlayoutBuilder.DisplayTitle(mediaItem)); PlayoutBuilder.DisplayTitle(mediaItem));
TimeSpan itemDuration = DurationForMediaItem(mediaItem); TimeSpan itemDuration = mediaItem.GetDurationForPlayout();
// item will never fit in block // item will never fit in block
var blockDuration = TimeSpan.FromMinutes(effectiveBlock.Block.Minutes); var blockDuration = TimeSpan.FromMinutes(effectiveBlock.Block.Minutes);
@ -439,15 +439,4 @@ public class BlockPlayoutBuilder(
return result; return result;
} }
private static TimeSpan DurationForMediaItem(MediaItem mediaItem)
{
if (mediaItem is Image image)
{
return TimeSpan.FromSeconds(image.ImageMetadata.Head().DurationSeconds ?? Image.DefaultSeconds);
}
MediaVersion version = mediaItem.GetHeadVersion();
return version.Duration;
}
} }

15
ErsatzTV.Core/Scheduling/BlockScheduling/BlockPlayoutFillerBuilder.cs

@ -203,7 +203,7 @@ public class BlockPlayoutFillerBuilder(
{ {
foreach (MediaItem mediaItem in enumerator.Current) foreach (MediaItem mediaItem in enumerator.Current)
{ {
TimeSpan itemDuration = DurationForMediaItem(mediaItem); TimeSpan itemDuration = mediaItem.GetDurationForPlayout();
var filler = new PlayoutItem var filler = new PlayoutItem
{ {
@ -348,7 +348,7 @@ public class BlockPlayoutFillerBuilder(
{ {
foreach (MediaItem mediaItem in enumerator.Current) foreach (MediaItem mediaItem in enumerator.Current)
{ {
TimeSpan itemDuration = DurationForMediaItem(mediaItem); TimeSpan itemDuration = mediaItem.GetDurationForPlayout();
// add filler from deco to unscheduled period // add filler from deco to unscheduled period
var filler = new PlayoutItem var filler = new PlayoutItem
@ -528,16 +528,5 @@ public class BlockPlayoutFillerBuilder(
} }
} }
private static TimeSpan DurationForMediaItem(MediaItem mediaItem)
{
if (mediaItem is Image image)
{
return TimeSpan.FromSeconds(image.ImageMetadata.Head().DurationSeconds ?? Image.DefaultSeconds);
}
MediaVersion version = mediaItem.GetHeadVersion();
return version.Duration;
}
private record ItemAndHistory(PlayoutItem PlayoutItem, PlayoutHistory History); private record ItemAndHistory(PlayoutItem PlayoutItem, PlayoutHistory History);
} }

2
ErsatzTV.Core/Scheduling/Engine/ISchedulingEngine.cs

@ -143,6 +143,4 @@ public interface ISchedulingEngine
PlayoutAnchor GetAnchor(); PlayoutAnchor GetAnchor();
ISchedulingEngineState GetState(); ISchedulingEngineState GetState();
TimeSpan DurationForMediaItem(MediaItem mediaItem);
} }

15
ErsatzTV.Core/Scheduling/Engine/SchedulingEngine.cs

@ -725,7 +725,7 @@ public class SchedulingEngine(
foreach (MediaItem mediaItem in enumeratorDetails.Enumerator.Current) foreach (MediaItem mediaItem in enumeratorDetails.Enumerator.Current)
{ {
TimeSpan itemDuration = DurationForMediaItem(mediaItem); TimeSpan itemDuration = mediaItem.GetDurationForPlayout();
var playoutItem = new PlayoutItem var playoutItem = new PlayoutItem
{ {
@ -906,7 +906,7 @@ public class SchedulingEngine(
foreach (MediaItem mediaItem in enumeratorDetails.Enumerator.Current) foreach (MediaItem mediaItem in enumeratorDetails.Enumerator.Current)
{ {
TimeSpan itemDuration = DurationForMediaItem(mediaItem); TimeSpan itemDuration = mediaItem.GetDurationForPlayout();
// create a playout item // create a playout item
var playoutItem = new PlayoutItem var playoutItem = new PlayoutItem
@ -1368,17 +1368,6 @@ public class SchedulingEngine(
} }
} }
public TimeSpan DurationForMediaItem(MediaItem mediaItem)
{
if (mediaItem is Image image)
{
return TimeSpan.FromSeconds(image.ImageMetadata.Head().DurationSeconds ?? Image.DefaultSeconds);
}
MediaVersion version = mediaItem.GetHeadVersion();
return version.Duration;
}
private List<PlayoutHistory> GetHistoryForItem( private List<PlayoutHistory> GetHistoryForItem(
EnumeratorDetails enumeratorDetails, EnumeratorDetails enumeratorDetails,
PlayoutItem playoutItem, PlayoutItem playoutItem,

25
ErsatzTV.Core/Scheduling/PlayoutModeSchedulerBase.cs

@ -144,7 +144,7 @@ public abstract class PlayoutModeSchedulerBase<T> : IPlayoutModeScheduler<T> whe
{ {
MediaItem mediaItem = enumerator.Current.ValueUnsafe(); MediaItem mediaItem = enumerator.Current.ValueUnsafe();
TimeSpan itemDuration = DurationForMediaItem(mediaItem); TimeSpan itemDuration = mediaItem.GetDurationForPlayout();
TimeSpan inPoint = InPointForMediaItem(mediaItem); TimeSpan inPoint = InPointForMediaItem(mediaItem);
if (nextState.CurrentTime + itemDuration > nextItemStart) if (nextState.CurrentTime + itemDuration > nextItemStart)
@ -230,25 +230,6 @@ public abstract class PlayoutModeSchedulerBase<T> : IPlayoutModeScheduler<T> whe
return Tuple(nextState, newItems); return Tuple(nextState, newItems);
} }
protected static TimeSpan DurationForMediaItem(MediaItem mediaItem)
{
if (mediaItem is Image image)
{
return TimeSpan.FromSeconds(image.ImageMetadata.Head().DurationSeconds ?? Image.DefaultSeconds);
}
MediaVersion version = mediaItem.GetHeadVersion();
if (mediaItem is RemoteStream remoteStream)
{
return version.Duration == TimeSpan.Zero && remoteStream.Duration.HasValue
? remoteStream.Duration.Value
: version.Duration;
}
return version.Duration;
}
private static TimeSpan InPointForMediaItem(MediaItem mediaItem) => private static TimeSpan InPointForMediaItem(MediaItem mediaItem) =>
mediaItem switch mediaItem switch
{ {
@ -784,7 +765,7 @@ public abstract class PlayoutModeSchedulerBase<T> : IPlayoutModeScheduler<T> whe
{ {
foreach (MediaItem mediaItem in enumerator.Current) foreach (MediaItem mediaItem in enumerator.Current)
{ {
TimeSpan itemDuration = DurationForMediaItem(mediaItem); TimeSpan itemDuration = mediaItem.GetDurationForPlayout();
TimeSpan inPoint = InPointForMediaItem(mediaItem); TimeSpan inPoint = InPointForMediaItem(mediaItem);
var playoutItem = new PlayoutItem var playoutItem = new PlayoutItem
@ -828,7 +809,7 @@ public abstract class PlayoutModeSchedulerBase<T> : IPlayoutModeScheduler<T> whe
{ {
foreach (MediaItem mediaItem in enumerator.Current) foreach (MediaItem mediaItem in enumerator.Current)
{ {
TimeSpan itemDuration = DurationForMediaItem(mediaItem); TimeSpan itemDuration = mediaItem.GetDurationForPlayout();
TimeSpan inPoint = InPointForMediaItem(mediaItem); TimeSpan inPoint = InPointForMediaItem(mediaItem);
if (remainingToFill - itemDuration >= TimeSpan.Zero) if (remainingToFill - itemDuration >= TimeSpan.Zero)

3
ErsatzTV.Core/Scheduling/PlayoutModeSchedulerDuration.cs

@ -1,5 +1,6 @@
using ErsatzTV.Core.Domain; using ErsatzTV.Core.Domain;
using ErsatzTV.Core.Domain.Filler; using ErsatzTV.Core.Domain.Filler;
using ErsatzTV.Core.Extensions;
using ErsatzTV.Core.Interfaces.Scheduling; using ErsatzTV.Core.Interfaces.Scheduling;
using LanguageExt.UnsafeValueAccess; using LanguageExt.UnsafeValueAccess;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
@ -74,7 +75,7 @@ public class PlayoutModeSchedulerDuration : PlayoutModeSchedulerBase<ProgramSche
durationUntil = nextState.DurationFinish; durationUntil = nextState.DurationFinish;
TimeSpan itemDuration = DurationForMediaItem(mediaItem); TimeSpan itemDuration = mediaItem.GetDurationForPlayout();
List<MediaChapter> itemChapters = ChaptersForMediaItem(mediaItem); List<MediaChapter> itemChapters = ChaptersForMediaItem(mediaItem);
if (itemDuration > scheduleItem.PlayoutDuration) if (itemDuration > scheduleItem.PlayoutDuration)

3
ErsatzTV.Core/Scheduling/PlayoutModeSchedulerFlood.cs

@ -1,5 +1,6 @@
using ErsatzTV.Core.Domain; using ErsatzTV.Core.Domain;
using ErsatzTV.Core.Domain.Filler; using ErsatzTV.Core.Domain.Filler;
using ErsatzTV.Core.Extensions;
using ErsatzTV.Core.Interfaces.Scheduling; using ErsatzTV.Core.Interfaces.Scheduling;
using LanguageExt.UnsafeValueAccess; using LanguageExt.UnsafeValueAccess;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
@ -46,7 +47,7 @@ public class PlayoutModeSchedulerFlood : PlayoutModeSchedulerBase<ProgramSchedul
break; break;
} }
TimeSpan itemDuration = DurationForMediaItem(mediaItem); TimeSpan itemDuration = mediaItem.GetDurationForPlayout();
// never block scheduling when there is only one schedule item (with fixed start and flood) // never block scheduling when there is only one schedule item (with fixed start and flood)
DateTimeOffset peekScheduleItemStart = DateTimeOffset peekScheduleItemStart =

3
ErsatzTV.Core/Scheduling/PlayoutModeSchedulerMultiple.cs

@ -1,5 +1,6 @@
using ErsatzTV.Core.Domain; using ErsatzTV.Core.Domain;
using ErsatzTV.Core.Domain.Filler; using ErsatzTV.Core.Domain.Filler;
using ErsatzTV.Core.Extensions;
using ErsatzTV.Core.Interfaces.Scheduling; using ErsatzTV.Core.Interfaces.Scheduling;
using LanguageExt.UnsafeValueAccess; using LanguageExt.UnsafeValueAccess;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
@ -86,7 +87,7 @@ public class PlayoutModeSchedulerMultiple : PlayoutModeSchedulerBase<ProgramSche
// find when we should start this item, based on the current time // find when we should start this item, based on the current time
DateTimeOffset itemStartTime = GetStartTimeAfter(nextState, scheduleItem, Option<ILogger>.Some(Logger)); DateTimeOffset itemStartTime = GetStartTimeAfter(nextState, scheduleItem, Option<ILogger>.Some(Logger));
TimeSpan itemDuration = DurationForMediaItem(mediaItem); TimeSpan itemDuration = mediaItem.GetDurationForPlayout();
List<MediaChapter> itemChapters = ChaptersForMediaItem(mediaItem); List<MediaChapter> itemChapters = ChaptersForMediaItem(mediaItem);
var playoutItem = new PlayoutItem var playoutItem = new PlayoutItem

3
ErsatzTV.Core/Scheduling/PlayoutModeSchedulerOne.cs

@ -1,5 +1,6 @@
using ErsatzTV.Core.Domain; using ErsatzTV.Core.Domain;
using ErsatzTV.Core.Domain.Filler; using ErsatzTV.Core.Domain.Filler;
using ErsatzTV.Core.Extensions;
using ErsatzTV.Core.Interfaces.Scheduling; using ErsatzTV.Core.Interfaces.Scheduling;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
@ -34,7 +35,7 @@ public class PlayoutModeSchedulerOne : PlayoutModeSchedulerBase<ProgramScheduleI
break; break;
} }
TimeSpan itemDuration = DurationForMediaItem(mediaItem); TimeSpan itemDuration = mediaItem.GetDurationForPlayout();
List<MediaChapter> itemChapters = ChaptersForMediaItem(mediaItem); List<MediaChapter> itemChapters = ChaptersForMediaItem(mediaItem);
var playoutItem = new PlayoutItem var playoutItem = new PlayoutItem

3
ErsatzTV.Core/Scheduling/YamlScheduling/Handlers/YamlPlayoutAllHandler.cs

@ -1,6 +1,7 @@
using ErsatzTV.Core.Domain; using ErsatzTV.Core.Domain;
using ErsatzTV.Core.Domain.Filler; using ErsatzTV.Core.Domain.Filler;
using ErsatzTV.Core.Domain.Scheduling; using ErsatzTV.Core.Domain.Scheduling;
using ErsatzTV.Core.Extensions;
using ErsatzTV.Core.Interfaces.Scheduling; using ErsatzTV.Core.Interfaces.Scheduling;
using ErsatzTV.Core.Scheduling.YamlScheduling.Models; using ErsatzTV.Core.Scheduling.YamlScheduling.Models;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
@ -41,7 +42,7 @@ public class YamlPlayoutAllHandler(EnumeratorCache enumeratorCache) : YamlPlayou
foreach (MediaItem mediaItem in enumerator.Current) foreach (MediaItem mediaItem in enumerator.Current)
{ {
TimeSpan itemDuration = DurationForMediaItem(mediaItem); TimeSpan itemDuration = mediaItem.GetDurationForPlayout();
// create a playout item // create a playout item
var playoutItem = new PlayoutItem var playoutItem = new PlayoutItem

11
ErsatzTV.Core/Scheduling/YamlScheduling/Handlers/YamlPlayoutContentHandler.cs

@ -137,17 +137,6 @@ public abstract class YamlPlayoutContentHandler(EnumeratorCache enumeratorCache)
return result; return result;
} }
protected static TimeSpan DurationForMediaItem(MediaItem mediaItem)
{
if (mediaItem is Image image)
{
return TimeSpan.FromSeconds(image.ImageMetadata.Head().DurationSeconds ?? Image.DefaultSeconds);
}
MediaVersion version = mediaItem.GetHeadVersion();
return version.Duration;
}
protected static FillerKind GetFillerKind(YamlPlayoutInstruction instruction, YamlPlayoutContext context) protected static FillerKind GetFillerKind(YamlPlayoutInstruction instruction, YamlPlayoutContext context)
{ {
if (!string.IsNullOrWhiteSpace(instruction.FillerKind) && if (!string.IsNullOrWhiteSpace(instruction.FillerKind) &&

3
ErsatzTV.Core/Scheduling/YamlScheduling/Handlers/YamlPlayoutCountHandler.cs

@ -1,6 +1,7 @@
using ErsatzTV.Core.Domain; using ErsatzTV.Core.Domain;
using ErsatzTV.Core.Domain.Filler; using ErsatzTV.Core.Domain.Filler;
using ErsatzTV.Core.Domain.Scheduling; using ErsatzTV.Core.Domain.Scheduling;
using ErsatzTV.Core.Extensions;
using ErsatzTV.Core.Interfaces.Scheduling; using ErsatzTV.Core.Interfaces.Scheduling;
using ErsatzTV.Core.Scheduling.YamlScheduling.Models; using ErsatzTV.Core.Scheduling.YamlScheduling.Models;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
@ -66,7 +67,7 @@ public class YamlPlayoutCountHandler(EnumeratorCache enumeratorCache) : YamlPlay
foreach (MediaItem mediaItem in enumerator.Current) foreach (MediaItem mediaItem in enumerator.Current)
{ {
TimeSpan itemDuration = DurationForMediaItem(mediaItem); TimeSpan itemDuration = mediaItem.GetDurationForPlayout();
// create a playout item // create a playout item
var playoutItem = new PlayoutItem var playoutItem = new PlayoutItem

3
ErsatzTV.Core/Scheduling/YamlScheduling/Handlers/YamlPlayoutDurationHandler.cs

@ -1,6 +1,7 @@
using ErsatzTV.Core.Domain; using ErsatzTV.Core.Domain;
using ErsatzTV.Core.Domain.Filler; using ErsatzTV.Core.Domain.Filler;
using ErsatzTV.Core.Domain.Scheduling; using ErsatzTV.Core.Domain.Scheduling;
using ErsatzTV.Core.Extensions;
using ErsatzTV.Core.Interfaces.Scheduling; using ErsatzTV.Core.Interfaces.Scheduling;
using ErsatzTV.Core.Scheduling.YamlScheduling.Models; using ErsatzTV.Core.Scheduling.YamlScheduling.Models;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
@ -110,7 +111,7 @@ public class YamlPlayoutDurationHandler(EnumeratorCache enumeratorCache) : YamlP
foreach (MediaItem mediaItem in enumerator.Current) foreach (MediaItem mediaItem in enumerator.Current)
{ {
TimeSpan itemDuration = DurationForMediaItem(mediaItem); TimeSpan itemDuration = mediaItem.GetDurationForPlayout();
var playoutItem = new PlayoutItem var playoutItem = new PlayoutItem
{ {

3
ErsatzTV/Controllers/Api/ScriptedScheduleController.cs

@ -1,6 +1,7 @@
using ErsatzTV.Core.Api.ScriptedPlayout; using ErsatzTV.Core.Api.ScriptedPlayout;
using ErsatzTV.Core.Domain; using ErsatzTV.Core.Domain;
using ErsatzTV.Core.Domain.Filler; using ErsatzTV.Core.Domain.Filler;
using ErsatzTV.Core.Extensions;
using ErsatzTV.Core.Interfaces.Scheduling; using ErsatzTV.Core.Interfaces.Scheduling;
using ErsatzTV.Core.Scheduling.Engine; using ErsatzTV.Core.Scheduling.Engine;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
@ -426,7 +427,7 @@ public class ScriptedScheduleController(IScriptedPlayoutBuilderService scriptedP
return new PeekItemDuration return new PeekItemDuration
{ {
Content = content, Content = content,
Milliseconds = (long)engine.DurationForMediaItem(mediaItem).TotalMilliseconds Milliseconds = (long)mediaItem.GetDurationForPlayout().TotalMilliseconds
}; };
} }

Loading…
Cancel
Save