Exemple #1
0
        public async Task FloodContent_Should_FloodAroundFixedContent_Multiple()
        {
            var floodCollection = new Collection
            {
                Id         = 1,
                Name       = "Flood Items",
                MediaItems = new List <MediaItem>
                {
                    TestMovie(1, TimeSpan.FromHours(1), new DateTime(2020, 1, 1)),
                    TestMovie(2, TimeSpan.FromHours(1), new DateTime(2020, 2, 1))
                }
            };

            var fixedCollection = new Collection
            {
                Id         = 2,
                Name       = "Fixed Items",
                MediaItems = new List <MediaItem>
                {
                    TestMovie(3, TimeSpan.FromHours(2), new DateTime(2020, 1, 1)),
                    TestMovie(4, TimeSpan.FromHours(1), new DateTime(2020, 1, 2))
                }
            };

            var fakeRepository = new FakeMediaCollectionRepository(
                Map(
                    (floodCollection.Id, floodCollection.MediaItems.ToList()),
                    (fixedCollection.Id, fixedCollection.MediaItems.ToList())));

            var items = new List <ProgramScheduleItem>
            {
                new ProgramScheduleItemFlood
                {
                    Index        = 1,
                    Collection   = floodCollection,
                    CollectionId = floodCollection.Id,
                    StartTime    = null
                },
                new ProgramScheduleItemMultiple
                {
                    Index        = 2,
                    Collection   = fixedCollection,
                    CollectionId = fixedCollection.Id,
                    StartTime    = TimeSpan.FromHours(3),
                    Count        = 2
                }
            };

            var playout = new Playout
            {
                ProgramSchedule = new ProgramSchedule
                {
                    Items = items,
                    MediaCollectionPlaybackOrder = PlaybackOrder.Chronological
                },
                Channel = new Channel(Guid.Empty)
                {
                    Id = 1, Name = "Test Channel"
                }
            };

            var televisionRepo = new FakeTelevisionRepository();
            var builder        = new PlayoutBuilder(fakeRepository, televisionRepo, _logger);

            DateTimeOffset start  = HoursAfterMidnight(0);
            DateTimeOffset finish = start + TimeSpan.FromHours(7);

            Playout result = await builder.BuildPlayoutItems(playout, start, finish);

            result.Items.Count.Should().Be(6);

            result.Items[0].StartOffset.TimeOfDay.Should().Be(TimeSpan.Zero);
            result.Items[0].MediaItemId.Should().Be(1);
            result.Items[1].StartOffset.TimeOfDay.Should().Be(TimeSpan.FromHours(1));
            result.Items[1].MediaItemId.Should().Be(2);
            result.Items[2].StartOffset.TimeOfDay.Should().Be(TimeSpan.FromHours(2));
            result.Items[2].MediaItemId.Should().Be(1);

            result.Items[3].StartOffset.TimeOfDay.Should().Be(TimeSpan.FromHours(3));
            result.Items[3].MediaItemId.Should().Be(3);
            result.Items[4].StartOffset.TimeOfDay.Should().Be(TimeSpan.FromHours(5));
            result.Items[4].MediaItemId.Should().Be(4);

            result.Items[5].StartOffset.TimeOfDay.Should().Be(TimeSpan.FromHours(6));
            result.Items[5].MediaItemId.Should().Be(2);
        }
Exemple #2
0
        public async Task MultipleContent_Should_WrapAroundDynamicContent_DurationWithoutOfflineTail()
        {
            var multipleCollection = new Collection
            {
                Id         = 1,
                Name       = "Multiple Items",
                MediaItems = new List <MediaItem>
                {
                    TestMovie(1, TimeSpan.FromHours(1), new DateTime(2020, 1, 1)),
                    TestMovie(2, TimeSpan.FromHours(1), new DateTime(2020, 2, 1))
                }
            };

            var dynamicCollection = new Collection
            {
                Id         = 2,
                Name       = "Dynamic Items",
                MediaItems = new List <MediaItem>
                {
                    TestMovie(3, TimeSpan.FromHours(0.75), new DateTime(2020, 1, 1)),
                    TestMovie(4, TimeSpan.FromHours(1.5), new DateTime(2020, 1, 2))
                }
            };

            var fakeRepository = new FakeMediaCollectionRepository(
                Map(
                    (multipleCollection.Id, multipleCollection.MediaItems.ToList()),
                    (dynamicCollection.Id, dynamicCollection.MediaItems.ToList())));

            var items = new List <ProgramScheduleItem>
            {
                new ProgramScheduleItemMultiple
                {
                    Index        = 1,
                    Collection   = multipleCollection,
                    CollectionId = multipleCollection.Id,
                    StartTime    = null,
                    Count        = 2
                },
                new ProgramScheduleItemDuration
                {
                    Index           = 2,
                    Collection      = dynamicCollection,
                    CollectionId    = dynamicCollection.Id,
                    StartTime       = null,
                    PlayoutDuration = TimeSpan.FromHours(2),
                    OfflineTail     = false // immediately continue
                }
            };

            var playout = new Playout
            {
                ProgramSchedule = new ProgramSchedule
                {
                    Items = items,
                    MediaCollectionPlaybackOrder = PlaybackOrder.Chronological
                },
                Channel = new Channel(Guid.Empty)
                {
                    Id = 1, Name = "Test Channel"
                }
            };

            var televisionRepo = new FakeTelevisionRepository();
            var builder        = new PlayoutBuilder(fakeRepository, televisionRepo, _logger);

            DateTimeOffset start  = HoursAfterMidnight(0);
            DateTimeOffset finish = start + TimeSpan.FromHours(6);

            Playout result = await builder.BuildPlayoutItems(playout, start, finish);

            result.Items.Count.Should().Be(6);

            result.Items[0].StartOffset.TimeOfDay.Should().Be(TimeSpan.Zero);
            result.Items[0].MediaItemId.Should().Be(1);
            result.Items[1].StartOffset.TimeOfDay.Should().Be(TimeSpan.FromHours(1));
            result.Items[1].MediaItemId.Should().Be(2);

            result.Items[2].StartOffset.TimeOfDay.Should().Be(TimeSpan.FromHours(2));
            result.Items[2].MediaItemId.Should().Be(3);

            result.Items[3].StartOffset.TimeOfDay.Should().Be(TimeSpan.FromHours(2.75));
            result.Items[3].MediaItemId.Should().Be(1);
            result.Items[4].StartOffset.TimeOfDay.Should().Be(TimeSpan.FromHours(3.75));
            result.Items[4].MediaItemId.Should().Be(2);

            result.Items[5].StartOffset.TimeOfDay.Should().Be(TimeSpan.FromHours(4.75));
            result.Items[5].MediaItemId.Should().Be(4);
        }
Exemple #3
0
    public override Tuple <PlayoutBuilderState, List <PlayoutItem> > Schedule(
        PlayoutBuilderState playoutBuilderState,
        Dictionary <CollectionKey, IMediaCollectionEnumerator> collectionEnumerators,
        ProgramScheduleItemDuration scheduleItem,
        ProgramScheduleItem nextScheduleItem,
        DateTimeOffset hardStop)
    {
        var playoutItems = new List <PlayoutItem>();

        PlayoutBuilderState nextState = playoutBuilderState;

        var willFinishInTime = true;
        Option <DateTimeOffset> durationUntil = None;

        IMediaCollectionEnumerator contentEnumerator =
            collectionEnumerators[CollectionKey.ForScheduleItem(scheduleItem)];

        while (contentEnumerator.Current.IsSome && nextState.CurrentTime < hardStop && willFinishInTime)
        {
            MediaItem mediaItem = contentEnumerator.Current.ValueUnsafe();

            // find when we should start this item, based on the current time
            DateTimeOffset itemStartTime = GetStartTimeAfter(nextState, scheduleItem);

            // remember when we need to finish this duration item
            if (nextState.DurationFinish.IsNone)
            {
                nextState = nextState with
                {
                    DurationFinish = itemStartTime + scheduleItem.PlayoutDuration
                };

                durationUntil = nextState.DurationFinish;
            }

            TimeSpan            itemDuration = DurationForMediaItem(mediaItem);
            List <MediaChapter> itemChapters = ChaptersForMediaItem(mediaItem);

            if (itemDuration > scheduleItem.PlayoutDuration)
            {
                _logger.LogWarning(
                    "Skipping playout item {Title} with duration {Duration} that is longer than schedule item duration {PlayoutDuration}",
                    PlayoutBuilder.DisplayTitle(mediaItem),
                    itemDuration,
                    scheduleItem.PlayoutDuration);

                contentEnumerator.MoveNext();
                continue;
            }

            var playoutItem = new PlayoutItem
            {
                MediaItemId = mediaItem.Id,
                Start       = itemStartTime.UtcDateTime,
                Finish      = itemStartTime.UtcDateTime + itemDuration,
                InPoint     = TimeSpan.Zero,
                OutPoint    = itemDuration,
                GuideGroup  = nextState.NextGuideGroup,
                FillerKind  = scheduleItem.GuideMode == GuideMode.Filler
                    ? FillerKind.Tail
                    : FillerKind.None,
                CustomTitle = scheduleItem.CustomTitle,
                WatermarkId = scheduleItem.WatermarkId,
                PreferredAudioLanguageCode    = scheduleItem.PreferredAudioLanguageCode,
                PreferredSubtitleLanguageCode = scheduleItem.PreferredSubtitleLanguageCode,
                SubtitleMode = scheduleItem.SubtitleMode
            };

            durationUntil.Do(du => playoutItem.GuideFinish = du.UtcDateTime);

            DateTimeOffset durationFinish        = nextState.DurationFinish.IfNone(SystemTime.MaxValueUtc);
            DateTimeOffset itemEndTimeWithFiller = CalculateEndTimeWithFiller(
                collectionEnumerators,
                scheduleItem,
                itemStartTime,
                itemDuration,
                itemChapters);
            willFinishInTime = itemStartTime > durationFinish ||
                               itemEndTimeWithFiller <= durationFinish;
            if (willFinishInTime)
            {
                // LogScheduledItem(scheduleItem, mediaItem, itemStartTime);
                playoutItems.AddRange(
                    AddFiller(nextState, collectionEnumerators, scheduleItem, playoutItem, itemChapters));

                nextState = nextState with
                {
                    CurrentTime = itemEndTimeWithFiller,

                    // only bump guide group if we don't have a custom title
                    NextGuideGroup = string.IsNullOrWhiteSpace(scheduleItem.CustomTitle)
                        ? nextState.IncrementGuideGroup
                        : nextState.NextGuideGroup
                };

                contentEnumerator.MoveNext();
            }
            else
            {
                TimeSpan durationBlock = itemEndTimeWithFiller - itemStartTime;
                if (itemEndTimeWithFiller - itemStartTime > scheduleItem.PlayoutDuration)
                {
                    _logger.LogWarning(
                        "Unable to schedule duration block of {DurationBlock} which is longer than the configured playout duration {PlayoutDuration}",
                        durationBlock,
                        scheduleItem.PlayoutDuration);
                }

                nextState = nextState with
                {
                    DurationFinish = None
                };

                nextState.ScheduleItemsEnumerator.MoveNext();
            }
        }

        // this is needed when the duration finish exactly matches the hard stop
        if (nextState.DurationFinish.IsSome && nextState.CurrentTime == nextState.DurationFinish)
        {
            nextState = nextState with
            {
                DurationFinish = None
            };

            nextState.ScheduleItemsEnumerator.MoveNext();
        }

        if (playoutItems.Select(pi => pi.GuideGroup).Distinct().Count() != 1)
        {
            nextState = nextState with {
                NextGuideGroup = nextState.DecrementGuideGroup
            };
        }

        foreach (DateTimeOffset nextItemStart in durationUntil)
        {
            switch (scheduleItem.TailMode)
            {
            case TailMode.Filler:
                if (scheduleItem.TailFiller != null)
                {
                    (nextState, playoutItems) = AddTailFiller(
                        nextState,
                        collectionEnumerators,
                        scheduleItem,
                        playoutItems,
                        nextItemStart);
                }

                if (scheduleItem.FallbackFiller != null)
                {
                    (nextState, playoutItems) = AddFallbackFiller(
                        nextState,
                        collectionEnumerators,
                        scheduleItem,
                        playoutItems,
                        nextItemStart);
                }

                nextState = nextState with {
                    CurrentTime = nextItemStart
                };
                break;

            case TailMode.Offline:
                if (scheduleItem.FallbackFiller != null)
                {
                    (nextState, playoutItems) = AddFallbackFiller(
                        nextState,
                        collectionEnumerators,
                        scheduleItem,
                        playoutItems,
                        nextItemStart);
                }

                nextState = nextState with {
                    CurrentTime = nextItemStart
                };
                break;
            }
        }

        // clear guide finish on all but the last item
        var         all  = playoutItems.Filter(pi => pi.FillerKind == FillerKind.None).ToList();
        PlayoutItem last = all.OrderBy(pi => pi.FinishOffset).LastOrDefault();

        foreach (PlayoutItem item in all.Filter(pi => pi != last))
        {
            item.GuideFinish = null;
        }

        nextState = nextState with {
            NextGuideGroup = nextState.IncrementGuideGroup
        };

        return(Tuple(nextState, playoutItems));
    }
}