mirror of https://github.com/ErsatzTV/ErsatzTV.git
Browse Source
* add chapter statistics and new filler options * refactor playout builder * more refactor prep for filler * rewrite schedulers * refactor collectionkey * add tail filler kind * migrate tail filler to filler preset * optionally show filler * fix playout detail row count * remove duration tail filler options * implement tail and fallback in flood scheduler * implement tail and fallback in one scheduler * implement tail and fallback in multiple scheduler * implement looping fallback filler * more duration tests * start to add post-roll filler to flood * rework playoutitem filler tagging * rework scheduler logging * calculate whether configured filler will fit * implement pre-roll and post-roll duration and count filler * improve duration filler calculation * add minutes to search index * update channel guide to work with new filler * add mid-roll filler * don't clone enumerators for filler calculations * support pre-roll and post-roll pad filler * implement mid-roll pad filler * allow clearing filler selections in schedule editor * fix tests * filler config validation * use consistent time zone for testspull/450/head
133 changed files with 54638 additions and 955 deletions
@ -0,0 +1,24 @@
@@ -0,0 +1,24 @@
|
||||
using System; |
||||
using ErsatzTV.Core; |
||||
using ErsatzTV.Core.Domain; |
||||
using ErsatzTV.Core.Domain.Filler; |
||||
using LanguageExt; |
||||
using MediatR; |
||||
using Unit = LanguageExt.Unit; |
||||
|
||||
namespace ErsatzTV.Application.Filler.Commands |
||||
{ |
||||
public record CreateFillerPreset( |
||||
string Name, |
||||
FillerKind FillerKind, |
||||
FillerMode FillerMode, |
||||
TimeSpan? Duration, |
||||
int? Count, |
||||
int? PadToNearestMinute, |
||||
ProgramScheduleItemCollectionType CollectionType, |
||||
int? CollectionId, |
||||
int? MediaItemId, |
||||
int? MultiCollectionId, |
||||
int? SmartCollectionId |
||||
) : IRequest<Either<BaseError, Unit>>; |
||||
} |
@ -0,0 +1,55 @@
@@ -0,0 +1,55 @@
|
||||
using System; |
||||
using System.Threading; |
||||
using System.Threading.Tasks; |
||||
using ErsatzTV.Core; |
||||
using ErsatzTV.Core.Domain.Filler; |
||||
using ErsatzTV.Infrastructure.Data; |
||||
using LanguageExt; |
||||
using MediatR; |
||||
using Microsoft.EntityFrameworkCore; |
||||
using Unit = LanguageExt.Unit; |
||||
|
||||
namespace ErsatzTV.Application.Filler.Commands |
||||
{ |
||||
public class CreateFillerPresetHandler : IRequestHandler<CreateFillerPreset, Either<BaseError, Unit>> |
||||
{ |
||||
private readonly IDbContextFactory<TvContext> _dbContextFactory; |
||||
|
||||
public CreateFillerPresetHandler(IDbContextFactory<TvContext> dbContextFactory) |
||||
{ |
||||
_dbContextFactory = dbContextFactory; |
||||
} |
||||
|
||||
public async Task<Either<BaseError, Unit>> Handle(CreateFillerPreset request, CancellationToken cancellationToken) |
||||
{ |
||||
try |
||||
{ |
||||
await using TvContext dbContext = _dbContextFactory.CreateDbContext(); |
||||
|
||||
var fillerPreset = new FillerPreset |
||||
{ |
||||
Name = request.Name, |
||||
FillerKind = request.FillerKind, |
||||
FillerMode = request.FillerMode, |
||||
Duration = request.Duration, |
||||
Count = request.Count, |
||||
PadToNearestMinute = request.PadToNearestMinute, |
||||
CollectionType = request.CollectionType, |
||||
CollectionId = request.CollectionId, |
||||
MediaItemId = request.MediaItemId, |
||||
MultiCollectionId = request.MultiCollectionId, |
||||
SmartCollectionId = request.SmartCollectionId |
||||
}; |
||||
|
||||
await dbContext.FillerPresets.AddAsync(fillerPreset, cancellationToken); |
||||
await dbContext.SaveChangesAsync(cancellationToken); |
||||
|
||||
return Unit.Default; |
||||
} |
||||
catch (Exception ex) |
||||
{ |
||||
return BaseError.New(ex.Message); |
||||
} |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,8 @@
@@ -0,0 +1,8 @@
|
||||
using ErsatzTV.Core; |
||||
using LanguageExt; |
||||
using MediatR; |
||||
|
||||
namespace ErsatzTV.Application.Filler.Commands |
||||
{ |
||||
public record DeleteFillerPreset(int FillerPresetId) : IRequest<Either<BaseError, LanguageExt.Unit>>; |
||||
} |
@ -0,0 +1,43 @@
@@ -0,0 +1,43 @@
|
||||
using System.Threading; |
||||
using System.Threading.Tasks; |
||||
using ErsatzTV.Core; |
||||
using ErsatzTV.Core.Domain.Filler; |
||||
using ErsatzTV.Infrastructure.Data; |
||||
using ErsatzTV.Infrastructure.Extensions; |
||||
using LanguageExt; |
||||
using MediatR; |
||||
using Microsoft.EntityFrameworkCore; |
||||
using Unit = LanguageExt.Unit; |
||||
|
||||
namespace ErsatzTV.Application.Filler.Commands |
||||
{ |
||||
public class DeleteFillerPresetHandler : IRequestHandler<DeleteFillerPreset, Either<BaseError, Unit>> |
||||
{ |
||||
private readonly IDbContextFactory<TvContext> _dbContextFactory; |
||||
|
||||
public DeleteFillerPresetHandler(IDbContextFactory<TvContext> dbContextFactory) => |
||||
_dbContextFactory = dbContextFactory; |
||||
|
||||
public async Task<Either<BaseError, Unit>> Handle( |
||||
DeleteFillerPreset request, |
||||
CancellationToken cancellationToken) |
||||
{ |
||||
await using TvContext dbContext = _dbContextFactory.CreateDbContext(); |
||||
Validation<BaseError, FillerPreset> validation = await FillerPresetMustExist(dbContext, request); |
||||
return await validation.Apply(ps => DoDeletion(dbContext, ps)); |
||||
} |
||||
|
||||
private static Task<Unit> DoDeletion(TvContext dbContext, FillerPreset fillerPreset) |
||||
{ |
||||
dbContext.FillerPresets.Remove(fillerPreset); |
||||
return dbContext.SaveChangesAsync().ToUnit(); |
||||
} |
||||
|
||||
private Task<Validation<BaseError, FillerPreset>> FillerPresetMustExist( |
||||
TvContext dbContext, |
||||
DeleteFillerPreset request) => |
||||
dbContext.FillerPresets |
||||
.SelectOneAsync(fp => fp.Id, ps => ps.Id == request.FillerPresetId) |
||||
.Map(o => o.ToValidation<BaseError>($"FillerPreset {request.FillerPresetId} does not exist.")); |
||||
} |
||||
} |
@ -0,0 +1,25 @@
@@ -0,0 +1,25 @@
|
||||
using System; |
||||
using ErsatzTV.Core; |
||||
using ErsatzTV.Core.Domain; |
||||
using ErsatzTV.Core.Domain.Filler; |
||||
using LanguageExt; |
||||
using MediatR; |
||||
using Unit = LanguageExt.Unit; |
||||
|
||||
namespace ErsatzTV.Application.Filler.Commands |
||||
{ |
||||
public record UpdateFillerPreset( |
||||
int Id, |
||||
string Name, |
||||
FillerKind FillerKind, |
||||
FillerMode FillerMode, |
||||
TimeSpan? Duration, |
||||
int? Count, |
||||
int? PadToNearestMinute, |
||||
ProgramScheduleItemCollectionType CollectionType, |
||||
int? CollectionId, |
||||
int? MediaItemId, |
||||
int? MultiCollectionId, |
||||
int? SmartCollectionId |
||||
) : IRequest<Either<BaseError, Unit>>; |
||||
} |
@ -0,0 +1,60 @@
@@ -0,0 +1,60 @@
|
||||
using System.Threading; |
||||
using System.Threading.Tasks; |
||||
using ErsatzTV.Core; |
||||
using ErsatzTV.Core.Domain.Filler; |
||||
using ErsatzTV.Infrastructure.Data; |
||||
using ErsatzTV.Infrastructure.Extensions; |
||||
using LanguageExt; |
||||
using MediatR; |
||||
using Microsoft.EntityFrameworkCore; |
||||
using Unit = LanguageExt.Unit; |
||||
|
||||
namespace ErsatzTV.Application.Filler.Commands |
||||
{ |
||||
public class UpdateFillerPresetHandler : IRequestHandler<UpdateFillerPreset, Either<BaseError, Unit>> |
||||
{ |
||||
private readonly IDbContextFactory<TvContext> _dbContextFactory; |
||||
|
||||
public UpdateFillerPresetHandler(IDbContextFactory<TvContext> dbContextFactory) |
||||
{ |
||||
_dbContextFactory = dbContextFactory; |
||||
} |
||||
|
||||
public async Task<Either<BaseError, Unit>> Handle(UpdateFillerPreset request, CancellationToken cancellationToken) |
||||
{ |
||||
await using TvContext dbContext = _dbContextFactory.CreateDbContext(); |
||||
|
||||
Validation<BaseError, FillerPreset> validation = await FillerPresetMustExist(dbContext, request); |
||||
return await validation.Apply(ps => ApplyUpdateRequest(dbContext, ps, request)); |
||||
} |
||||
|
||||
private async Task<Unit> ApplyUpdateRequest( |
||||
TvContext dbContext, |
||||
FillerPreset existing, |
||||
UpdateFillerPreset request) |
||||
{ |
||||
existing.Name = request.Name; |
||||
existing.FillerKind = request.FillerKind; |
||||
existing.FillerMode = request.FillerMode; |
||||
existing.Duration = request.Duration; |
||||
existing.Count = request.Count; |
||||
existing.PadToNearestMinute = request.PadToNearestMinute; |
||||
existing.CollectionType = request.CollectionType; |
||||
existing.CollectionId = request.CollectionId; |
||||
existing.MediaItemId = request.MediaItemId; |
||||
existing.MultiCollectionId = request.MultiCollectionId; |
||||
existing.SmartCollectionId = request.SmartCollectionId; |
||||
|
||||
await dbContext.SaveChangesAsync(); |
||||
|
||||
return Unit.Default; |
||||
} |
||||
|
||||
private static Task<Validation<BaseError, FillerPreset>> FillerPresetMustExist( |
||||
TvContext dbContext, |
||||
UpdateFillerPreset request) => |
||||
dbContext.FillerPresets |
||||
.SelectOneAsync(ps => ps.Id, ps => ps.Id == request.Id) |
||||
.Map(o => o.ToValidation<BaseError>("FillerPreset does not exist")); |
||||
} |
||||
} |
@ -0,0 +1,20 @@
@@ -0,0 +1,20 @@
|
||||
using System; |
||||
using ErsatzTV.Core.Domain; |
||||
using ErsatzTV.Core.Domain.Filler; |
||||
|
||||
namespace ErsatzTV.Application.Filler |
||||
{ |
||||
public record FillerPresetViewModel( |
||||
int Id, |
||||
string Name, |
||||
FillerKind FillerKind, |
||||
FillerMode FillerMode, |
||||
TimeSpan? Duration, |
||||
int? Count, |
||||
int? PadToNearestMinute, |
||||
ProgramScheduleItemCollectionType CollectionType, |
||||
int? CollectionId, |
||||
int? MediaItemId, |
||||
int? MultiCollectionId, |
||||
int? SmartCollectionId); |
||||
} |
@ -0,0 +1,22 @@
@@ -0,0 +1,22 @@
|
||||
using ErsatzTV.Core.Domain.Filler; |
||||
|
||||
namespace ErsatzTV.Application.Filler |
||||
{ |
||||
internal static class Mapper |
||||
{ |
||||
internal static FillerPresetViewModel ProjectToViewModel(FillerPreset fillerPreset) => |
||||
new( |
||||
fillerPreset.Id, |
||||
fillerPreset.Name, |
||||
fillerPreset.FillerKind, |
||||
fillerPreset.FillerMode, |
||||
fillerPreset.Duration, |
||||
fillerPreset.Count, |
||||
fillerPreset.PadToNearestMinute, |
||||
fillerPreset.CollectionType, |
||||
fillerPreset.CollectionId, |
||||
fillerPreset.MediaItemId, |
||||
fillerPreset.MultiCollectionId, |
||||
fillerPreset.SmartCollectionId); |
||||
} |
||||
} |
@ -0,0 +1,6 @@
@@ -0,0 +1,6 @@
|
||||
using System.Collections.Generic; |
||||
|
||||
namespace ErsatzTV.Application.Filler |
||||
{ |
||||
public record PagedFillerPresetsViewModel(int TotalCount, List<FillerPresetViewModel> Page); |
||||
} |
@ -0,0 +1,7 @@
@@ -0,0 +1,7 @@
|
||||
using System.Collections.Generic; |
||||
using MediatR; |
||||
|
||||
namespace ErsatzTV.Application.Filler.Queries |
||||
{ |
||||
public record GetAllFillerPresets : IRequest<List<FillerPresetViewModel>>; |
||||
} |
@ -0,0 +1,29 @@
@@ -0,0 +1,29 @@
|
||||
using System.Collections.Generic; |
||||
using System.Linq; |
||||
using System.Threading; |
||||
using System.Threading.Tasks; |
||||
using ErsatzTV.Infrastructure.Data; |
||||
using MediatR; |
||||
using Microsoft.EntityFrameworkCore; |
||||
using LanguageExt; |
||||
using static ErsatzTV.Application.Filler.Mapper; |
||||
|
||||
namespace ErsatzTV.Application.Filler.Queries |
||||
{ |
||||
public class GetAllFillerPresetsHandler : IRequestHandler<GetAllFillerPresets, List<FillerPresetViewModel>> |
||||
{ |
||||
private readonly IDbContextFactory<TvContext> _dbContextFactory; |
||||
|
||||
public GetAllFillerPresetsHandler(IDbContextFactory<TvContext> dbContextFactory) => |
||||
_dbContextFactory = dbContextFactory; |
||||
|
||||
public async Task<List<FillerPresetViewModel>> Handle( |
||||
GetAllFillerPresets request, |
||||
CancellationToken cancellationToken) |
||||
{ |
||||
await using TvContext dbContext = _dbContextFactory.CreateDbContext(); |
||||
return await dbContext.FillerPresets.ToListAsync(cancellationToken) |
||||
.Map(presets => presets.Map(ProjectToViewModel).ToList()); |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,7 @@
@@ -0,0 +1,7 @@
|
||||
using LanguageExt; |
||||
using MediatR; |
||||
|
||||
namespace ErsatzTV.Application.Filler.Queries |
||||
{ |
||||
public record GetFillerPresetById(int Id) : IRequest<Option<FillerPresetViewModel>>; |
||||
} |
@ -0,0 +1,29 @@
@@ -0,0 +1,29 @@
|
||||
using System.Threading; |
||||
using System.Threading.Tasks; |
||||
using ErsatzTV.Infrastructure.Data; |
||||
using ErsatzTV.Infrastructure.Extensions; |
||||
using LanguageExt; |
||||
using MediatR; |
||||
using Microsoft.EntityFrameworkCore; |
||||
using static ErsatzTV.Application.Filler.Mapper; |
||||
|
||||
namespace ErsatzTV.Application.Filler.Queries |
||||
{ |
||||
public class GetFillerPresetByIdHandler : IRequestHandler<GetFillerPresetById, Option<FillerPresetViewModel>> |
||||
{ |
||||
private readonly IDbContextFactory<TvContext> _dbContextFactory; |
||||
|
||||
public GetFillerPresetByIdHandler(IDbContextFactory<TvContext> dbContextFactory) => |
||||
_dbContextFactory = dbContextFactory; |
||||
|
||||
public async Task<Option<FillerPresetViewModel>> Handle( |
||||
GetFillerPresetById request, |
||||
CancellationToken cancellationToken) |
||||
{ |
||||
await using TvContext dbContext = _dbContextFactory.CreateDbContext(); |
||||
return await dbContext.FillerPresets |
||||
.SelectOneAsync(c => c.Id, c => c.Id == request.Id) |
||||
.MapT(ProjectToViewModel); |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,6 @@
@@ -0,0 +1,6 @@
|
||||
using MediatR; |
||||
|
||||
namespace ErsatzTV.Application.Filler.Queries |
||||
{ |
||||
public record GetPagedFillerPresets(int PageNum, int PageSize) : IRequest<PagedFillerPresetsViewModel>; |
||||
} |
@ -0,0 +1,46 @@
@@ -0,0 +1,46 @@
|
||||
using System.Collections.Generic; |
||||
using System.Data; |
||||
using System.Linq; |
||||
using System.Threading; |
||||
using System.Threading.Tasks; |
||||
using Dapper; |
||||
using ErsatzTV.Infrastructure.Data; |
||||
using MediatR; |
||||
using Microsoft.EntityFrameworkCore; |
||||
using LanguageExt; |
||||
using static ErsatzTV.Application.Filler.Mapper; |
||||
|
||||
namespace ErsatzTV.Application.Filler.Queries |
||||
{ |
||||
public class GetPagedFillerPresetsHandler : IRequestHandler<GetPagedFillerPresets, PagedFillerPresetsViewModel> |
||||
{ |
||||
private readonly IDbConnection _dbConnection; |
||||
private readonly IDbContextFactory<TvContext> _dbContextFactory; |
||||
|
||||
public GetPagedFillerPresetsHandler(IDbContextFactory<TvContext> dbContextFactory, IDbConnection dbConnection) |
||||
{ |
||||
_dbContextFactory = dbContextFactory; |
||||
_dbConnection = dbConnection; |
||||
} |
||||
|
||||
public async Task<PagedFillerPresetsViewModel> Handle( |
||||
GetPagedFillerPresets request, |
||||
CancellationToken cancellationToken) |
||||
{ |
||||
int count = await _dbConnection.QuerySingleAsync<int>(@"SELECT COUNT (*) FROM FillerPreset"); |
||||
|
||||
await using TvContext dbContext = _dbContextFactory.CreateDbContext(); |
||||
List<FillerPresetViewModel> page = await dbContext.FillerPresets.FromSqlRaw( |
||||
@"SELECT * FROM FillerPreset
|
||||
ORDER BY Name |
||||
COLLATE NOCASE |
||||
LIMIT {0} OFFSET {1}",
|
||||
request.PageSize, |
||||
request.PageNum * request.PageSize) |
||||
.ToListAsync(cancellationToken) |
||||
.Map(list => list.Map(ProjectToViewModel).ToList()); |
||||
|
||||
return new PagedFillerPresetsViewModel(count, page); |
||||
} |
||||
} |
||||
} |
@ -1,7 +1,6 @@
@@ -1,7 +1,6 @@
|
||||
using System.Collections.Generic; |
||||
using MediatR; |
||||
using MediatR; |
||||
|
||||
namespace ErsatzTV.Application.Playouts.Queries |
||||
{ |
||||
public record GetFuturePlayoutItemsById(int PlayoutId, int PageNum, int PageSize) : IRequest<PagedPlayoutItemsViewModel>; |
||||
public record GetFuturePlayoutItemsById(int PlayoutId, bool ShowFiller, int PageNum, int PageSize) : IRequest<PagedPlayoutItemsViewModel>; |
||||
} |
||||
|
@ -0,0 +1,205 @@
@@ -0,0 +1,205 @@
|
||||
using System; |
||||
using System.Collections.Generic; |
||||
using ErsatzTV.Core.Domain; |
||||
using ErsatzTV.Core.Domain.Filler; |
||||
using ErsatzTV.Core.Interfaces.Scheduling; |
||||
using ErsatzTV.Core.Scheduling; |
||||
using FluentAssertions; |
||||
using NUnit.Framework; |
||||
|
||||
namespace ErsatzTV.Core.Tests.Scheduling |
||||
{ |
||||
[TestFixture] |
||||
public class PlayoutModeSchedulerBaseTests |
||||
{ |
||||
[Test] |
||||
public void CalculateEndTimeWithFiller_Should_Not_Touch_Enumerator() |
||||
{ |
||||
var collection = new Collection |
||||
{ |
||||
Id = 1, |
||||
Name = "Filler Items", |
||||
MediaItems = new List<MediaItem>() |
||||
}; |
||||
|
||||
for (var i = 0; i < 5; i++) |
||||
{ |
||||
collection.MediaItems.Add(TestMovie(i + 1, TimeSpan.FromHours(i + 1), new DateTime(2020, 2, i + 1))); |
||||
} |
||||
|
||||
var fillerPreset = new FillerPreset |
||||
{ |
||||
FillerKind = FillerKind.PreRoll, |
||||
FillerMode = FillerMode.Count, |
||||
Count = 3, |
||||
Collection = collection, |
||||
CollectionId = collection.Id |
||||
}; |
||||
|
||||
var enumerator = new ChronologicalMediaCollectionEnumerator( |
||||
collection.MediaItems, |
||||
new CollectionEnumeratorState { Index = 0, Seed = 1 }); |
||||
|
||||
DateTimeOffset result = PlayoutModeSchedulerBase<ProgramScheduleItem> |
||||
.CalculateEndTimeWithFiller( |
||||
new Dictionary<CollectionKey, IMediaCollectionEnumerator> |
||||
{ |
||||
{ CollectionKey.ForFillerPreset(fillerPreset), enumerator } |
||||
}, |
||||
new ProgramScheduleItemOne |
||||
{ |
||||
PreRollFiller = fillerPreset |
||||
}, |
||||
new DateTimeOffset(2020, 2, 1, 12, 0, 0, TimeSpan.FromHours(-5)), |
||||
new TimeSpan(0, 12, 30), |
||||
new List<MediaChapter>()); |
||||
|
||||
result.Should().Be(new DateTimeOffset(2020, 2, 1, 18, 12, 30, TimeSpan.FromHours(-5))); |
||||
enumerator.State.Index.Should().Be(0); |
||||
enumerator.State.Seed.Should().Be(1); |
||||
} |
||||
|
||||
[Test] |
||||
public void CalculateEndTimeWithFiller_Should_Pad_To_15_Minutes_15() |
||||
{ |
||||
DateTimeOffset result = PlayoutModeSchedulerBase<ProgramScheduleItem> |
||||
.CalculateEndTimeWithFiller( |
||||
new Dictionary<CollectionKey, IMediaCollectionEnumerator>(), |
||||
new ProgramScheduleItemOne |
||||
{ |
||||
MidRollFiller = new FillerPreset |
||||
{ |
||||
FillerKind = FillerKind.MidRoll, |
||||
FillerMode = FillerMode.Pad, |
||||
PadToNearestMinute = 15 |
||||
} |
||||
}, |
||||
new DateTimeOffset(2020, 2, 1, 12, 0, 0, TimeSpan.FromHours(-5)), |
||||
new TimeSpan(0, 12, 30), |
||||
new List<MediaChapter>()); |
||||
|
||||
result.Should().Be(new DateTimeOffset(2020, 2, 1, 12, 15, 0, TimeSpan.FromHours(-5))); |
||||
} |
||||
|
||||
[Test] |
||||
public void CalculateEndTimeWithFiller_Should_Pad_To_15_Minutes_30() |
||||
{ |
||||
DateTimeOffset result = PlayoutModeSchedulerBase<ProgramScheduleItem> |
||||
.CalculateEndTimeWithFiller( |
||||
new Dictionary<CollectionKey, IMediaCollectionEnumerator>(), |
||||
new ProgramScheduleItemOne |
||||
{ |
||||
MidRollFiller = new FillerPreset |
||||
{ |
||||
FillerKind = FillerKind.MidRoll, |
||||
FillerMode = FillerMode.Pad, |
||||
PadToNearestMinute = 15 |
||||
} |
||||
}, |
||||
new DateTimeOffset(2020, 2, 1, 12, 16, 0, TimeSpan.FromHours(-5)), |
||||
new TimeSpan(0, 12, 30), |
||||
new List<MediaChapter>()); |
||||
|
||||
result.Should().Be(new DateTimeOffset(2020, 2, 1, 12, 30, 0, TimeSpan.FromHours(-5))); |
||||
} |
||||
|
||||
[Test] |
||||
public void CalculateEndTimeWithFiller_Should_Pad_To_15_Minutes_45() |
||||
{ |
||||
DateTimeOffset result = PlayoutModeSchedulerBase<ProgramScheduleItem> |
||||
.CalculateEndTimeWithFiller( |
||||
new Dictionary<CollectionKey, IMediaCollectionEnumerator>(), |
||||
new ProgramScheduleItemOne |
||||
{ |
||||
MidRollFiller = new FillerPreset |
||||
{ |
||||
FillerKind = FillerKind.MidRoll, |
||||
FillerMode = FillerMode.Pad, |
||||
PadToNearestMinute = 15 |
||||
} |
||||
}, |
||||
new DateTimeOffset(2020, 2, 1, 12, 30, 0, TimeSpan.FromHours(-5)), |
||||
new TimeSpan(0, 12, 30), |
||||
new List<MediaChapter>()); |
||||
|
||||
result.Should().Be(new DateTimeOffset(2020, 2, 1, 12, 45, 0, TimeSpan.FromHours(-5))); |
||||
} |
||||
|
||||
[Test] |
||||
public void CalculateEndTimeWithFiller_Should_Pad_To_15_Minutes_00() |
||||
{ |
||||
DateTimeOffset result = PlayoutModeSchedulerBase<ProgramScheduleItem> |
||||
.CalculateEndTimeWithFiller( |
||||
new Dictionary<CollectionKey, IMediaCollectionEnumerator>(), |
||||
new ProgramScheduleItemOne |
||||
{ |
||||
MidRollFiller = new FillerPreset |
||||
{ |
||||
FillerKind = FillerKind.MidRoll, |
||||
FillerMode = FillerMode.Pad, |
||||
PadToNearestMinute = 15 |
||||
} |
||||
}, |
||||
new DateTimeOffset(2020, 2, 1, 12, 46, 0, TimeSpan.FromHours(-5)), |
||||
new TimeSpan(0, 12, 30), |
||||
new List<MediaChapter>()); |
||||
|
||||
result.Should().Be(new DateTimeOffset(2020, 2, 1, 13, 0, 0, TimeSpan.FromHours(-5))); |
||||
} |
||||
|
||||
[Test] |
||||
public void CalculateEndTimeWithFiller_Should_Pad_To_30_Minutes_30() |
||||
{ |
||||
DateTimeOffset result = PlayoutModeSchedulerBase<ProgramScheduleItem> |
||||
.CalculateEndTimeWithFiller( |
||||
new Dictionary<CollectionKey, IMediaCollectionEnumerator>(), |
||||
new ProgramScheduleItemOne |
||||
{ |
||||
MidRollFiller = new FillerPreset |
||||
{ |
||||
FillerKind = FillerKind.MidRoll, |
||||
FillerMode = FillerMode.Pad, |
||||
PadToNearestMinute = 30 |
||||
} |
||||
}, |
||||
new DateTimeOffset(2020, 2, 1, 12, 0, 0, TimeSpan.FromHours(-5)), |
||||
new TimeSpan(0, 12, 30), |
||||
new List<MediaChapter>()); |
||||
|
||||
result.Should().Be(new DateTimeOffset(2020, 2, 1, 12, 30, 0, TimeSpan.FromHours(-5))); |
||||
} |
||||
|
||||
[Test] |
||||
public void CalculateEndTimeWithFiller_Should_Pad_To_30_Minutes_00() |
||||
{ |
||||
DateTimeOffset result = PlayoutModeSchedulerBase<ProgramScheduleItem> |
||||
.CalculateEndTimeWithFiller( |
||||
new Dictionary<CollectionKey, IMediaCollectionEnumerator>(), |
||||
new ProgramScheduleItemOne |
||||
{ |
||||
MidRollFiller = new FillerPreset |
||||
{ |
||||
FillerKind = FillerKind.MidRoll, |
||||
FillerMode = FillerMode.Pad, |
||||
PadToNearestMinute = 30 |
||||
} |
||||
}, |
||||
new DateTimeOffset(2020, 2, 1, 12, 20, 0, TimeSpan.FromHours(-5)), |
||||
new TimeSpan(0, 12, 30), |
||||
new List<MediaChapter>()); |
||||
|
||||
result.Should().Be(new DateTimeOffset(2020, 2, 1, 13, 0, 0, TimeSpan.FromHours(-5))); |
||||
} |
||||
|
||||
private static Movie TestMovie(int id, TimeSpan duration, DateTime aired) => |
||||
new() |
||||
{ |
||||
Id = id, |
||||
MovieMetadata = new List<MovieMetadata> { new() { ReleaseDate = aired } }, |
||||
MediaVersions = new List<MediaVersion> |
||||
{ |
||||
new() { Duration = duration } |
||||
} |
||||
}; |
||||
} |
||||
} |
@ -0,0 +1,552 @@
@@ -0,0 +1,552 @@
|
||||
using System; |
||||
using System.Collections.Generic; |
||||
using System.Linq; |
||||
using ErsatzTV.Core.Domain; |
||||
using ErsatzTV.Core.Domain.Filler; |
||||
using ErsatzTV.Core.Scheduling; |
||||
using FluentAssertions; |
||||
using Microsoft.Extensions.Logging; |
||||
using Moq; |
||||
using NUnit.Framework; |
||||
|
||||
namespace ErsatzTV.Core.Tests.Scheduling |
||||
{ |
||||
[TestFixture] |
||||
public class PlayoutModeSchedulerDurationTests : SchedulerTestBase |
||||
{ |
||||
[Test] |
||||
public void Should_Fill_Exact_Duration() |
||||
{ |
||||
Collection collectionOne = TwoItemCollection(1, 2, TimeSpan.FromHours(1)); |
||||
|
||||
var scheduleItem = new ProgramScheduleItemDuration |
||||
{ |
||||
Id = 1, |
||||
Index = 1, |
||||
Collection = collectionOne, |
||||
CollectionId = collectionOne.Id, |
||||
StartTime = null, |
||||
PlayoutDuration = TimeSpan.FromHours(3), |
||||
TailMode = TailMode.None, |
||||
PlaybackOrder = PlaybackOrder.Chronological |
||||
}; |
||||
|
||||
var enumerator = new ChronologicalMediaCollectionEnumerator( |
||||
collectionOne.MediaItems, |
||||
new CollectionEnumeratorState()); |
||||
|
||||
var scheduler = new PlayoutModeSchedulerDuration(new Mock<ILogger>().Object); |
||||
(PlayoutBuilderState playoutBuilderState, List<PlayoutItem> playoutItems) = scheduler.Schedule( |
||||
StartState, |
||||
CollectionEnumerators(scheduleItem, enumerator), |
||||
scheduleItem, |
||||
NextScheduleItem, |
||||
HardStop); |
||||
|
||||
playoutBuilderState.CurrentTime.Should().Be(StartState.CurrentTime.AddHours(3)); |
||||
playoutItems.Last().FinishOffset.Should().Be(playoutBuilderState.CurrentTime); |
||||
|
||||
playoutBuilderState.NextGuideGroup.Should().Be(2); |
||||
playoutBuilderState.DurationFinish.IsNone.Should().BeTrue(); |
||||
playoutBuilderState.InFlood.Should().BeFalse(); |
||||
playoutBuilderState.MultipleRemaining.IsNone.Should().BeTrue(); |
||||
playoutBuilderState.InDurationFiller.Should().BeFalse(); |
||||
playoutBuilderState.ScheduleItemIndex.Should().Be(1); |
||||
|
||||
enumerator.State.Index.Should().Be(1); |
||||
|
||||
playoutItems.Count.Should().Be(3); |
||||
|
||||
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[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[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); |
||||
} |
||||
|
||||
[Test] |
||||
public void Should_Not_Have_Gap_Duration_Tail_Mode_None() |
||||
{ |
||||
Collection collectionOne = TwoItemCollection(1, 2, TimeSpan.FromMinutes(55)); |
||||
|
||||
var scheduleItem = new ProgramScheduleItemDuration |
||||
{ |
||||
Id = 1, |
||||
Index = 1, |
||||
Collection = collectionOne, |
||||
CollectionId = collectionOne.Id, |
||||
StartTime = null, |
||||
PlayoutDuration = TimeSpan.FromHours(3), |
||||
TailMode = TailMode.None, |
||||
PlaybackOrder = PlaybackOrder.Chronological |
||||
}; |
||||
|
||||
var enumerator = new ChronologicalMediaCollectionEnumerator( |
||||
collectionOne.MediaItems, |
||||
new CollectionEnumeratorState()); |
||||
|
||||
var scheduler = new PlayoutModeSchedulerDuration(new Mock<ILogger>().Object); |
||||
(PlayoutBuilderState playoutBuilderState, List<PlayoutItem> playoutItems) = scheduler.Schedule( |
||||
StartState, |
||||
CollectionEnumerators(scheduleItem, enumerator), |
||||
scheduleItem, |
||||
NextScheduleItem, |
||||
HardStop); |
||||
|
||||
playoutBuilderState.CurrentTime.Should().Be(StartState.CurrentTime.Add(new TimeSpan(2, 45, 0))); |
||||
playoutItems.Last().FinishOffset.Should().Be(playoutBuilderState.CurrentTime); |
||||
|
||||
playoutBuilderState.NextGuideGroup.Should().Be(2); |
||||
playoutBuilderState.DurationFinish.IsNone.Should().BeTrue(); |
||||
playoutBuilderState.InFlood.Should().BeFalse(); |
||||
playoutBuilderState.MultipleRemaining.IsNone.Should().BeTrue(); |
||||
playoutBuilderState.InDurationFiller.Should().BeFalse(); |
||||
playoutBuilderState.ScheduleItemIndex.Should().Be(1); |
||||
|
||||
enumerator.State.Index.Should().Be(1); |
||||
|
||||
playoutItems.Count.Should().Be(3); |
||||
|
||||
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[1].MediaItemId.Should().Be(2); |
||||
playoutItems[1].StartOffset.Should().Be(StartState.CurrentTime.AddMinutes(55)); |
||||
playoutItems[1].GuideGroup.Should().Be(1); |
||||
playoutItems[1].FillerKind.Should().Be(FillerKind.None); |
||||
|
||||
playoutItems[2].MediaItemId.Should().Be(1); |
||||
playoutItems[2].StartOffset.Should().Be(StartState.CurrentTime.Add(new TimeSpan(1, 50, 0))); |
||||
playoutItems[2].GuideGroup.Should().Be(1); |
||||
playoutItems[2].FillerKind.Should().Be(FillerKind.None); |
||||
} |
||||
|
||||
[Test] |
||||
public void Should_Have_Gap_Duration_Tail_Mode_Offline_No_Fallback() |
||||
{ |
||||
Collection collectionOne = TwoItemCollection(1, 2, TimeSpan.FromMinutes(55)); |
||||
|
||||
var scheduleItem = new ProgramScheduleItemDuration |
||||
{ |
||||
Id = 1, |
||||
Index = 1, |
||||
Collection = collectionOne, |
||||
CollectionId = collectionOne.Id, |
||||
StartTime = null, |
||||
PlayoutDuration = TimeSpan.FromHours(3), |
||||
TailMode = TailMode.Offline, |
||||
PlaybackOrder = PlaybackOrder.Chronological |
||||
}; |
||||
|
||||
var enumerator = new ChronologicalMediaCollectionEnumerator( |
||||
collectionOne.MediaItems, |
||||
new CollectionEnumeratorState()); |
||||
|
||||
var scheduler = new PlayoutModeSchedulerDuration(new Mock<ILogger>().Object); |
||||
(PlayoutBuilderState playoutBuilderState, List<PlayoutItem> playoutItems) = scheduler.Schedule( |
||||
StartState, |
||||
CollectionEnumerators(scheduleItem, enumerator), |
||||
scheduleItem, |
||||
NextScheduleItem, |
||||
HardStop); |
||||
|
||||
// duration block should end after exact duration, with gap
|
||||
playoutBuilderState.CurrentTime.Should().Be(StartState.CurrentTime.AddHours(3)); |
||||
playoutItems.Last().FinishOffset.Should().Be(StartState.CurrentTime.Add(new TimeSpan(2, 45, 0))); |
||||
|
||||
playoutBuilderState.NextGuideGroup.Should().Be(2); |
||||
playoutBuilderState.DurationFinish.IsNone.Should().BeTrue(); |
||||
playoutBuilderState.InFlood.Should().BeFalse(); |
||||
playoutBuilderState.MultipleRemaining.IsNone.Should().BeTrue(); |
||||
playoutBuilderState.InDurationFiller.Should().BeFalse(); |
||||
playoutBuilderState.ScheduleItemIndex.Should().Be(1); |
||||
|
||||
enumerator.State.Index.Should().Be(1); |
||||
|
||||
playoutItems.Count.Should().Be(3); |
||||
|
||||
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[1].MediaItemId.Should().Be(2); |
||||
playoutItems[1].StartOffset.Should().Be(StartState.CurrentTime.AddMinutes(55)); |
||||
playoutItems[1].GuideGroup.Should().Be(1); |
||||
playoutItems[1].FillerKind.Should().Be(FillerKind.None); |
||||
|
||||
playoutItems[2].MediaItemId.Should().Be(1); |
||||
playoutItems[2].StartOffset.Should().Be(StartState.CurrentTime.Add(new TimeSpan(1, 50, 0))); |
||||
playoutItems[2].GuideGroup.Should().Be(1); |
||||
playoutItems[2].FillerKind.Should().Be(FillerKind.None); |
||||
} |
||||
|
||||
[Test] |
||||
public void Should_Not_Have_Gap_Duration_Tail_Mode_Offline_With_Fallback() |
||||
{ |
||||
Collection collectionOne = TwoItemCollection(1, 2, TimeSpan.FromMinutes(55)); |
||||
Collection collectionTwo = TwoItemCollection(3, 4, TimeSpan.FromMinutes(1)); |
||||
|
||||
var scheduleItem = new ProgramScheduleItemDuration |
||||
{ |
||||
Id = 1, |
||||
Index = 1, |
||||
Collection = collectionOne, |
||||
CollectionId = collectionOne.Id, |
||||
StartTime = null, |
||||
PlayoutDuration = TimeSpan.FromHours(3), |
||||
TailMode = TailMode.Offline, |
||||
PlaybackOrder = PlaybackOrder.Chronological, |
||||
FallbackFiller = new FillerPreset |
||||
{ |
||||
FillerKind = FillerKind.Fallback, |
||||
Collection = collectionTwo, |
||||
CollectionId = collectionTwo.Id |
||||
} |
||||
}; |
||||
|
||||
var enumerator1 = new ChronologicalMediaCollectionEnumerator( |
||||
collectionOne.MediaItems, |
||||
new CollectionEnumeratorState()); |
||||
|
||||
var enumerator2 = new ChronologicalMediaCollectionEnumerator( |
||||
collectionTwo.MediaItems, |
||||
new CollectionEnumeratorState()); |
||||
|
||||
var scheduler = new PlayoutModeSchedulerDuration(new Mock<ILogger>().Object); |
||||
(PlayoutBuilderState playoutBuilderState, List<PlayoutItem> playoutItems) = scheduler.Schedule( |
||||
StartState, |
||||
CollectionEnumerators(scheduleItem, enumerator1, scheduleItem.FallbackFiller, enumerator2), |
||||
scheduleItem, |
||||
NextScheduleItem, |
||||
HardStop); |
||||
|
||||
playoutBuilderState.CurrentTime.Should().Be(StartState.CurrentTime.AddHours(3)); |
||||
playoutItems.Last().FinishOffset.Should().Be(playoutBuilderState.CurrentTime); |
||||
|
||||
playoutBuilderState.NextGuideGroup.Should().Be(2); |
||||
playoutBuilderState.DurationFinish.IsNone.Should().BeTrue(); |
||||
playoutBuilderState.InFlood.Should().BeFalse(); |
||||
playoutBuilderState.MultipleRemaining.IsNone.Should().BeTrue(); |
||||
playoutBuilderState.InDurationFiller.Should().BeFalse(); |
||||
playoutBuilderState.ScheduleItemIndex.Should().Be(1); |
||||
|
||||
enumerator1.State.Index.Should().Be(1); |
||||
enumerator2.State.Index.Should().Be(1); |
||||
|
||||
playoutItems.Count.Should().Be(4); |
||||
|
||||
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[1].MediaItemId.Should().Be(2); |
||||
playoutItems[1].StartOffset.Should().Be(StartState.CurrentTime.AddMinutes(55)); |
||||
playoutItems[1].GuideGroup.Should().Be(1); |
||||
playoutItems[1].FillerKind.Should().Be(FillerKind.None); |
||||
|
||||
playoutItems[2].MediaItemId.Should().Be(1); |
||||
playoutItems[2].StartOffset.Should().Be(StartState.CurrentTime.Add(new TimeSpan(1, 50, 0))); |
||||
playoutItems[2].GuideGroup.Should().Be(1); |
||||
playoutItems[2].FillerKind.Should().Be(FillerKind.None); |
||||
|
||||
playoutItems[3].MediaItemId.Should().Be(3); |
||||
playoutItems[3].StartOffset.Should().Be(StartState.CurrentTime.Add(new TimeSpan(2, 45, 0))); |
||||
playoutItems[3].GuideGroup.Should().Be(1); |
||||
playoutItems[3].FillerKind.Should().Be(FillerKind.Fallback); |
||||
} |
||||
|
||||
[Test] |
||||
public void Should_Not_Have_Gap_Duration_Tail_Mode_Filler_Exact_Duration() |
||||
{ |
||||
Collection collectionOne = TwoItemCollection(1, 2, TimeSpan.FromMinutes(55)); |
||||
Collection collectionTwo = TwoItemCollection(3, 4, TimeSpan.FromMinutes(5)); |
||||
|
||||
var scheduleItem = new ProgramScheduleItemDuration |
||||
{ |
||||
Id = 1, |
||||
Index = 1, |
||||
Collection = collectionOne, |
||||
CollectionId = collectionOne.Id, |
||||
StartTime = null, |
||||
PlayoutDuration = TimeSpan.FromHours(3), |
||||
TailMode = TailMode.Filler, |
||||
PlaybackOrder = PlaybackOrder.Chronological, |
||||
TailFiller = new FillerPreset |
||||
{ |
||||
FillerKind = FillerKind.Tail, |
||||
Collection = collectionTwo, |
||||
CollectionId = collectionTwo.Id |
||||
} |
||||
}; |
||||
|
||||
var enumerator1 = new ChronologicalMediaCollectionEnumerator( |
||||
collectionOne.MediaItems, |
||||
new CollectionEnumeratorState()); |
||||
|
||||
var enumerator2 = new ChronologicalMediaCollectionEnumerator( |
||||
collectionTwo.MediaItems, |
||||
new CollectionEnumeratorState()); |
||||
|
||||
var scheduler = new PlayoutModeSchedulerDuration(new Mock<ILogger>().Object); |
||||
(PlayoutBuilderState playoutBuilderState, List<PlayoutItem> playoutItems) = scheduler.Schedule( |
||||
StartState, |
||||
CollectionEnumerators(scheduleItem, enumerator1, scheduleItem.TailFiller, enumerator2), |
||||
scheduleItem, |
||||
NextScheduleItem, |
||||
HardStop); |
||||
|
||||
playoutBuilderState.CurrentTime.Should().Be(StartState.CurrentTime.AddHours(3)); |
||||
playoutItems.Last().FinishOffset.Should().Be(playoutBuilderState.CurrentTime); |
||||
|
||||
playoutBuilderState.NextGuideGroup.Should().Be(2); |
||||
playoutBuilderState.DurationFinish.IsNone.Should().BeTrue(); |
||||
playoutBuilderState.InFlood.Should().BeFalse(); |
||||
playoutBuilderState.MultipleRemaining.IsNone.Should().BeTrue(); |
||||
playoutBuilderState.InDurationFiller.Should().BeFalse(); |
||||
playoutBuilderState.ScheduleItemIndex.Should().Be(1); |
||||
|
||||
enumerator1.State.Index.Should().Be(1); |
||||
enumerator2.State.Index.Should().Be(1); |
||||
|
||||
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[1].MediaItemId.Should().Be(2); |
||||
playoutItems[1].StartOffset.Should().Be(StartState.CurrentTime.AddMinutes(55)); |
||||
playoutItems[1].GuideGroup.Should().Be(1); |
||||
playoutItems[1].FillerKind.Should().Be(FillerKind.None); |
||||
|
||||
playoutItems[2].MediaItemId.Should().Be(1); |
||||
playoutItems[2].StartOffset.Should().Be(StartState.CurrentTime.Add(new TimeSpan(1, 50, 0))); |
||||
playoutItems[2].GuideGroup.Should().Be(1); |
||||
playoutItems[2].FillerKind.Should().Be(FillerKind.None); |
||||
|
||||
playoutItems[3].MediaItemId.Should().Be(3); |
||||
playoutItems[3].StartOffset.Should().Be(StartState.CurrentTime.Add(new TimeSpan(2, 45, 0))); |
||||
playoutItems[3].GuideGroup.Should().Be(1); |
||||
playoutItems[3].FillerKind.Should().Be(FillerKind.Tail); |
||||
|
||||
playoutItems[4].MediaItemId.Should().Be(4); |
||||
playoutItems[4].StartOffset.Should().Be(StartState.CurrentTime.Add(new TimeSpan(2, 50, 0))); |
||||
playoutItems[4].GuideGroup.Should().Be(1); |
||||
playoutItems[4].FillerKind.Should().Be(FillerKind.Tail); |
||||
|
||||
playoutItems[5].MediaItemId.Should().Be(3); |
||||
playoutItems[5].StartOffset.Should().Be(StartState.CurrentTime.Add(new TimeSpan(2, 55, 0))); |
||||
playoutItems[5].GuideGroup.Should().Be(1); |
||||
playoutItems[5].FillerKind.Should().Be(FillerKind.Tail); |
||||
} |
||||
|
||||
[Test] |
||||
public void Should_Have_Gap_Duration_Tail_Mode_Filler_No_Fallback() |
||||
{ |
||||
Collection collectionOne = TwoItemCollection(1, 2, TimeSpan.FromMinutes(55)); |
||||
Collection collectionTwo = TwoItemCollection(3, 4, TimeSpan.FromMinutes(4)); |
||||
|
||||
var scheduleItem = new ProgramScheduleItemDuration |
||||
{ |
||||
Id = 1, |
||||
Index = 1, |
||||
Collection = collectionOne, |
||||
CollectionId = collectionOne.Id, |
||||
StartTime = null, |
||||
PlayoutDuration = TimeSpan.FromHours(3), |
||||
TailMode = TailMode.Filler, |
||||
PlaybackOrder = PlaybackOrder.Chronological, |
||||
TailFiller = new FillerPreset |
||||
{ |
||||
FillerKind = FillerKind.Tail, |
||||
Collection = collectionTwo, |
||||
CollectionId = collectionTwo.Id |
||||
} |
||||
}; |
||||
|
||||
var enumerator1 = new ChronologicalMediaCollectionEnumerator( |
||||
collectionOne.MediaItems, |
||||
new CollectionEnumeratorState()); |
||||
|
||||
var enumerator2 = new ChronologicalMediaCollectionEnumerator( |
||||
collectionTwo.MediaItems, |
||||
new CollectionEnumeratorState()); |
||||
|
||||
var scheduler = new PlayoutModeSchedulerDuration(new Mock<ILogger>().Object); |
||||
(PlayoutBuilderState playoutBuilderState, List<PlayoutItem> playoutItems) = scheduler.Schedule( |
||||
StartState, |
||||
CollectionEnumerators(scheduleItem, enumerator1, scheduleItem.TailFiller, enumerator2), |
||||
scheduleItem, |
||||
NextScheduleItem, |
||||
HardStop); |
||||
|
||||
playoutBuilderState.CurrentTime.Should().Be(StartState.CurrentTime.AddHours(3)); |
||||
playoutItems.Last().FinishOffset.Should().Be(StartState.CurrentTime.Add(new TimeSpan(2, 57, 0))); |
||||
|
||||
playoutBuilderState.NextGuideGroup.Should().Be(2); |
||||
playoutBuilderState.DurationFinish.IsNone.Should().BeTrue(); |
||||
playoutBuilderState.InFlood.Should().BeFalse(); |
||||
playoutBuilderState.MultipleRemaining.IsNone.Should().BeTrue(); |
||||
playoutBuilderState.InDurationFiller.Should().BeFalse(); |
||||
playoutBuilderState.ScheduleItemIndex.Should().Be(1); |
||||
|
||||
enumerator1.State.Index.Should().Be(1); |
||||
enumerator2.State.Index.Should().Be(1); |
||||
|
||||
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[1].MediaItemId.Should().Be(2); |
||||
playoutItems[1].StartOffset.Should().Be(StartState.CurrentTime.AddMinutes(55)); |
||||
playoutItems[1].GuideGroup.Should().Be(1); |
||||
playoutItems[1].FillerKind.Should().Be(FillerKind.None); |
||||
|
||||
playoutItems[2].MediaItemId.Should().Be(1); |
||||
playoutItems[2].StartOffset.Should().Be(StartState.CurrentTime.Add(new TimeSpan(1, 50, 0))); |
||||
playoutItems[2].GuideGroup.Should().Be(1); |
||||
playoutItems[2].FillerKind.Should().Be(FillerKind.None); |
||||
|
||||
playoutItems[3].MediaItemId.Should().Be(3); |
||||
playoutItems[3].StartOffset.Should().Be(StartState.CurrentTime.Add(new TimeSpan(2, 45, 0))); |
||||
playoutItems[3].GuideGroup.Should().Be(1); |
||||
playoutItems[3].FillerKind.Should().Be(FillerKind.Tail); |
||||
|
||||
playoutItems[4].MediaItemId.Should().Be(4); |
||||
playoutItems[4].StartOffset.Should().Be(StartState.CurrentTime.Add(new TimeSpan(2, 49, 0))); |
||||
playoutItems[4].GuideGroup.Should().Be(1); |
||||
playoutItems[4].FillerKind.Should().Be(FillerKind.Tail); |
||||
|
||||
playoutItems[5].MediaItemId.Should().Be(3); |
||||
playoutItems[5].StartOffset.Should().Be(StartState.CurrentTime.Add(new TimeSpan(2, 53, 0))); |
||||
playoutItems[5].GuideGroup.Should().Be(1); |
||||
playoutItems[5].FillerKind.Should().Be(FillerKind.Tail); |
||||
} |
||||
|
||||
[Test] |
||||
public void Should_Not_Have_Gap_Duration_Tail_Mode_Filler_With_Fallback() |
||||
{ |
||||
Collection collectionOne = TwoItemCollection(1, 2, TimeSpan.FromMinutes(55)); |
||||
Collection collectionTwo = TwoItemCollection(3, 4, TimeSpan.FromMinutes(4)); |
||||
Collection collectionThree = TwoItemCollection(5, 6, TimeSpan.FromMinutes(1)); |
||||
|
||||
var scheduleItem = new ProgramScheduleItemDuration |
||||
{ |
||||
Id = 1, |
||||
Index = 1, |
||||
Collection = collectionOne, |
||||
CollectionId = collectionOne.Id, |
||||
StartTime = null, |
||||
PlayoutDuration = TimeSpan.FromHours(3), |
||||
TailMode = TailMode.Filler, |
||||
PlaybackOrder = PlaybackOrder.Chronological, |
||||
TailFiller = new FillerPreset |
||||
{ |
||||
FillerKind = FillerKind.Tail, |
||||
Collection = collectionTwo, |
||||
CollectionId = collectionTwo.Id |
||||
}, |
||||
FallbackFiller = new FillerPreset |
||||
{ |
||||
FillerKind = FillerKind.Fallback, |
||||
Collection = collectionThree, |
||||
CollectionId = collectionThree.Id |
||||
} |
||||
}; |
||||
|
||||
var enumerator1 = new ChronologicalMediaCollectionEnumerator( |
||||
collectionOne.MediaItems, |
||||
new CollectionEnumeratorState()); |
||||
|
||||
var enumerator2 = new ChronologicalMediaCollectionEnumerator( |
||||
collectionTwo.MediaItems, |
||||
new CollectionEnumeratorState()); |
||||
|
||||
var enumerator3 = new ChronologicalMediaCollectionEnumerator( |
||||
collectionThree.MediaItems, |
||||
new CollectionEnumeratorState()); |
||||
|
||||
var scheduler = new PlayoutModeSchedulerDuration(new Mock<ILogger>().Object); |
||||
(PlayoutBuilderState playoutBuilderState, List<PlayoutItem> playoutItems) = scheduler.Schedule( |
||||
StartState, |
||||
CollectionEnumerators( |
||||
scheduleItem, |
||||
enumerator1, |
||||
scheduleItem.TailFiller, |
||||
enumerator2, |
||||
scheduleItem.FallbackFiller, |
||||
enumerator3), |
||||
scheduleItem, |
||||
NextScheduleItem, |
||||
HardStop); |
||||
|
||||
playoutBuilderState.CurrentTime.Should().Be(StartState.CurrentTime.AddHours(3)); |
||||
playoutItems.Last().FinishOffset.Should().Be(playoutBuilderState.CurrentTime); |
||||
|
||||
playoutBuilderState.NextGuideGroup.Should().Be(2); |
||||
playoutBuilderState.DurationFinish.IsNone.Should().BeTrue(); |
||||
playoutBuilderState.InFlood.Should().BeFalse(); |
||||
playoutBuilderState.MultipleRemaining.IsNone.Should().BeTrue(); |
||||
playoutBuilderState.InDurationFiller.Should().BeFalse(); |
||||
playoutBuilderState.ScheduleItemIndex.Should().Be(1); |
||||
|
||||
enumerator1.State.Index.Should().Be(1); |
||||
enumerator2.State.Index.Should().Be(1); |
||||
enumerator3.State.Index.Should().Be(1); |
||||
|
||||
playoutItems.Count.Should().Be(7); |
||||
|
||||
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[1].MediaItemId.Should().Be(2); |
||||
playoutItems[1].StartOffset.Should().Be(StartState.CurrentTime.AddMinutes(55)); |
||||
playoutItems[1].GuideGroup.Should().Be(1); |
||||
playoutItems[1].FillerKind.Should().Be(FillerKind.None); |
||||
|
||||
playoutItems[2].MediaItemId.Should().Be(1); |
||||
playoutItems[2].StartOffset.Should().Be(StartState.CurrentTime.Add(new TimeSpan(1, 50, 0))); |
||||
playoutItems[2].GuideGroup.Should().Be(1); |
||||
playoutItems[2].FillerKind.Should().Be(FillerKind.None); |
||||
|
||||
playoutItems[3].MediaItemId.Should().Be(3); |
||||
playoutItems[3].StartOffset.Should().Be(StartState.CurrentTime.Add(new TimeSpan(2, 45, 0))); |
||||
playoutItems[3].GuideGroup.Should().Be(1); |
||||
playoutItems[3].FillerKind.Should().Be(FillerKind.Tail); |
||||
|
||||
playoutItems[4].MediaItemId.Should().Be(4); |
||||
playoutItems[4].StartOffset.Should().Be(StartState.CurrentTime.Add(new TimeSpan(2, 49, 0))); |
||||
playoutItems[4].GuideGroup.Should().Be(1); |
||||
playoutItems[4].FillerKind.Should().Be(FillerKind.Tail); |
||||
|
||||
playoutItems[5].MediaItemId.Should().Be(3); |
||||
playoutItems[5].StartOffset.Should().Be(StartState.CurrentTime.Add(new TimeSpan(2, 53, 0))); |
||||
playoutItems[5].GuideGroup.Should().Be(1); |
||||
playoutItems[5].FillerKind.Should().Be(FillerKind.Tail); |
||||
|
||||
playoutItems[6].MediaItemId.Should().Be(5); |
||||
playoutItems[6].StartOffset.Should().Be(StartState.CurrentTime.Add(new TimeSpan(2, 57, 0))); |
||||
playoutItems[6].GuideGroup.Should().Be(1); |
||||
playoutItems[6].FillerKind.Should().Be(FillerKind.Fallback); |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,715 @@
@@ -0,0 +1,715 @@
|
||||
using System; |
||||
using System.Collections.Generic; |
||||
using System.Linq; |
||||
using ErsatzTV.Core.Domain; |
||||
using ErsatzTV.Core.Domain.Filler; |
||||
using ErsatzTV.Core.Scheduling; |
||||
using FluentAssertions; |
||||
using Microsoft.Extensions.Logging; |
||||
using Moq; |
||||
using NUnit.Framework; |
||||
|
||||
namespace ErsatzTV.Core.Tests.Scheduling |
||||
{ |
||||
[TestFixture] |
||||
public class PlayoutModeSchedulerFloodTests : SchedulerTestBase |
||||
{ |
||||
[Test] |
||||
public void Should_Fill_Exactly_To_Next_Schedule_Item() |
||||
{ |
||||
Collection collectionOne = TwoItemCollection(1, 2, TimeSpan.FromHours(1)); |
||||
|
||||
var scheduleItem = new ProgramScheduleItemFlood |
||||
{ |
||||
Id = 1, |
||||
Index = 1, |
||||
Collection = collectionOne, |
||||
CollectionId = collectionOne.Id, |
||||
StartTime = null, |
||||
PlaybackOrder = PlaybackOrder.Chronological, |
||||
TailFiller = null, |
||||
FallbackFiller = null |
||||
}; |
||||
|
||||
var enumerator = new ChronologicalMediaCollectionEnumerator( |
||||
collectionOne.MediaItems, |
||||
new CollectionEnumeratorState()); |
||||
|
||||
var sortedScheduleItems = new List<ProgramScheduleItem> |
||||
{ |
||||
scheduleItem, |
||||
NextScheduleItem |
||||
}; |
||||
|
||||
var scheduler = new PlayoutModeSchedulerFlood(sortedScheduleItems, new Mock<ILogger>().Object); |
||||
(PlayoutBuilderState playoutBuilderState, List<PlayoutItem> playoutItems) = scheduler.Schedule( |
||||
StartState, |
||||
CollectionEnumerators(scheduleItem, enumerator), |
||||
scheduleItem, |
||||
NextScheduleItem, |
||||
HardStop); |
||||
|
||||
playoutBuilderState.CurrentTime.Should().Be(StartState.CurrentTime.AddHours(3)); |
||||
playoutItems.Last().FinishOffset.Should().Be(playoutBuilderState.CurrentTime); |
||||
|
||||
playoutBuilderState.NextGuideGroup.Should().Be(4); |
||||
playoutBuilderState.DurationFinish.IsNone.Should().BeTrue(); |
||||
playoutBuilderState.InFlood.Should().BeFalse(); |
||||
playoutBuilderState.MultipleRemaining.IsNone.Should().BeTrue(); |
||||
playoutBuilderState.InDurationFiller.Should().BeFalse(); |
||||
playoutBuilderState.ScheduleItemIndex.Should().Be(1); |
||||
|
||||
enumerator.State.Index.Should().Be(1); |
||||
|
||||
playoutItems.Count.Should().Be(3); |
||||
|
||||
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[1].MediaItemId.Should().Be(2); |
||||
playoutItems[1].StartOffset.Should().Be(StartState.CurrentTime.AddHours(1)); |
||||
playoutItems[1].GuideGroup.Should().Be(2); |
||||
playoutItems[1].FillerKind.Should().Be(FillerKind.None); |
||||
|
||||
playoutItems[2].MediaItemId.Should().Be(1); |
||||
playoutItems[2].StartOffset.Should().Be(StartState.CurrentTime.AddHours(2)); |
||||
playoutItems[2].GuideGroup.Should().Be(3); |
||||
playoutItems[2].FillerKind.Should().Be(FillerKind.None); |
||||
} |
||||
|
||||
[Test] |
||||
public void Should_Fill_Exactly_To_Next_Schedule_Item_With_Post_Roll_Multiple_One() |
||||
{ |
||||
Collection collectionOne = TwoItemCollection(1, 2, TimeSpan.FromMinutes(55)); |
||||
Collection collectionTwo = TwoItemCollection(3, 4, TimeSpan.FromMinutes(5)); |
||||
|
||||
var scheduleItem = new ProgramScheduleItemFlood |
||||
{ |
||||
Id = 1, |
||||
Index = 1, |
||||
Collection = collectionOne, |
||||
CollectionId = collectionOne.Id, |
||||
StartTime = null, |
||||
PlaybackOrder = PlaybackOrder.Chronological, |
||||
PostRollFiller = new FillerPreset |
||||
{ |
||||
FillerKind = FillerKind.PostRoll, |
||||
FillerMode = FillerMode.Count, |
||||
Count = 1, |
||||
Collection = collectionTwo, |
||||
CollectionId = collectionTwo.Id |
||||
}, |
||||
TailFiller = null, |
||||
FallbackFiller = null |
||||
}; |
||||
|
||||
var enumerator1 = new ChronologicalMediaCollectionEnumerator( |
||||
collectionOne.MediaItems, |
||||
new CollectionEnumeratorState()); |
||||
|
||||
var enumerator2 = new ChronologicalMediaCollectionEnumerator( |
||||
collectionTwo.MediaItems, |
||||
new CollectionEnumeratorState()); |
||||
|
||||
var sortedScheduleItems = new List<ProgramScheduleItem> |
||||
{ |
||||
scheduleItem, |
||||
NextScheduleItem |
||||
}; |
||||
|
||||
var scheduler = new PlayoutModeSchedulerFlood(sortedScheduleItems, new Mock<ILogger>().Object); |
||||
(PlayoutBuilderState playoutBuilderState, List<PlayoutItem> playoutItems) = scheduler.Schedule( |
||||
StartState, |
||||
CollectionEnumerators(scheduleItem, enumerator1, scheduleItem.PostRollFiller, enumerator2), |
||||
scheduleItem, |
||||
NextScheduleItem, |
||||
HardStop); |
||||
|
||||
playoutBuilderState.CurrentTime.Should().Be(StartState.CurrentTime.AddHours(3)); |
||||
playoutItems.Last().FinishOffset.Should().Be(playoutBuilderState.CurrentTime); |
||||
|
||||
playoutBuilderState.NextGuideGroup.Should().Be(4); |
||||
playoutBuilderState.DurationFinish.IsNone.Should().BeTrue(); |
||||
playoutBuilderState.InFlood.Should().BeFalse(); |
||||
playoutBuilderState.MultipleRemaining.IsNone.Should().BeTrue(); |
||||
playoutBuilderState.InDurationFiller.Should().BeFalse(); |
||||
playoutBuilderState.ScheduleItemIndex.Should().Be(1); |
||||
|
||||
enumerator1.State.Index.Should().Be(1); |
||||
enumerator2.State.Index.Should().Be(1); |
||||
|
||||
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[1].MediaItemId.Should().Be(3); |
||||
playoutItems[1].StartOffset.Should().Be(StartState.CurrentTime.AddMinutes(55)); |
||||
playoutItems[1].GuideGroup.Should().Be(1); |
||||
playoutItems[1].FillerKind.Should().Be(FillerKind.PostRoll); |
||||
|
||||
playoutItems[2].MediaItemId.Should().Be(2); |
||||
playoutItems[2].StartOffset.Should().Be(StartState.CurrentTime.AddHours(1)); |
||||
playoutItems[2].GuideGroup.Should().Be(2); |
||||
playoutItems[2].FillerKind.Should().Be(FillerKind.None); |
||||
|
||||
playoutItems[3].MediaItemId.Should().Be(4); |
||||
playoutItems[3].StartOffset.Should().Be(StartState.CurrentTime.Add(new TimeSpan(1, 55, 0))); |
||||
playoutItems[3].GuideGroup.Should().Be(2); |
||||
playoutItems[3].FillerKind.Should().Be(FillerKind.PostRoll); |
||||
|
||||
playoutItems[4].MediaItemId.Should().Be(1); |
||||
playoutItems[4].StartOffset.Should().Be(StartState.CurrentTime.AddHours(2)); |
||||
playoutItems[4].GuideGroup.Should().Be(3); |
||||
playoutItems[4].FillerKind.Should().Be(FillerKind.None); |
||||
|
||||
playoutItems[5].MediaItemId.Should().Be(3); |
||||
playoutItems[5].StartOffset.Should().Be(StartState.CurrentTime.Add(new TimeSpan(2, 55, 0))); |
||||
playoutItems[5].GuideGroup.Should().Be(3); |
||||
playoutItems[5].FillerKind.Should().Be(FillerKind.PostRoll); |
||||
} |
||||
|
||||
[Test] |
||||
public void Should_Have_Gap_With_No_Tail_No_Fallback() |
||||
{ |
||||
Collection collectionOne = TwoItemCollection(1, 2, TimeSpan.FromMinutes(55)); |
||||
|
||||
var scheduleItem = new ProgramScheduleItemFlood |
||||
{ |
||||
Id = 1, |
||||
Index = 1, |
||||
Collection = collectionOne, |
||||
CollectionId = collectionOne.Id, |
||||
StartTime = null, |
||||
PlaybackOrder = PlaybackOrder.Chronological, |
||||
TailFiller = null, |
||||
FallbackFiller = null |
||||
}; |
||||
|
||||
var enumerator = new ChronologicalMediaCollectionEnumerator( |
||||
collectionOne.MediaItems, |
||||
new CollectionEnumeratorState()); |
||||
|
||||
var sortedScheduleItems = new List<ProgramScheduleItem> |
||||
{ |
||||
scheduleItem, |
||||
NextScheduleItem |
||||
}; |
||||
|
||||
var scheduler = new PlayoutModeSchedulerFlood(sortedScheduleItems, new Mock<ILogger>().Object); |
||||
(PlayoutBuilderState playoutBuilderState, List<PlayoutItem> playoutItems) = scheduler.Schedule( |
||||
StartState, |
||||
CollectionEnumerators(scheduleItem, enumerator), |
||||
scheduleItem, |
||||
NextScheduleItem, |
||||
HardStop); |
||||
|
||||
playoutBuilderState.CurrentTime.Should().Be(StartState.CurrentTime.Add(new TimeSpan(2, 45, 0))); |
||||
playoutItems.Last().FinishOffset.Should().Be(playoutBuilderState.CurrentTime); |
||||
|
||||
playoutBuilderState.NextGuideGroup.Should().Be(4); |
||||
playoutBuilderState.DurationFinish.IsNone.Should().BeTrue(); |
||||
playoutBuilderState.InFlood.Should().BeFalse(); |
||||
playoutBuilderState.MultipleRemaining.IsNone.Should().BeTrue(); |
||||
playoutBuilderState.InDurationFiller.Should().BeFalse(); |
||||
playoutBuilderState.ScheduleItemIndex.Should().Be(1); |
||||
|
||||
enumerator.State.Index.Should().Be(1); |
||||
|
||||
playoutItems.Count.Should().Be(3); |
||||
|
||||
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[1].MediaItemId.Should().Be(2); |
||||
playoutItems[1].StartOffset.Should().Be(StartState.CurrentTime.AddMinutes(55)); |
||||
playoutItems[1].GuideGroup.Should().Be(2); |
||||
playoutItems[1].FillerKind.Should().Be(FillerKind.None); |
||||
|
||||
playoutItems[2].MediaItemId.Should().Be(1); |
||||
playoutItems[2].StartOffset.Should().Be(StartState.CurrentTime.Add(new TimeSpan(1, 50, 0))); |
||||
playoutItems[2].GuideGroup.Should().Be(3); |
||||
playoutItems[2].FillerKind.Should().Be(FillerKind.None); |
||||
} |
||||
|
||||
[Test] |
||||
public void Should_Not_Have_Gap_With_Exact_Tail() |
||||
{ |
||||
Collection collectionOne = TwoItemCollection(1, 2, TimeSpan.FromMinutes(55)); |
||||
Collection collectionTwo = TwoItemCollection(3, 4, TimeSpan.FromMinutes(5)); |
||||
|
||||
var scheduleItem = new ProgramScheduleItemFlood |
||||
{ |
||||
Id = 1, |
||||
Index = 1, |
||||
Collection = collectionOne, |
||||
CollectionId = collectionOne.Id, |
||||
StartTime = null, |
||||
PlaybackOrder = PlaybackOrder.Chronological, |
||||
TailFiller = new FillerPreset |
||||
{ |
||||
FillerKind = FillerKind.Tail, |
||||
Collection = collectionTwo, |
||||
CollectionId = collectionTwo.Id |
||||
}, |
||||
FallbackFiller = null |
||||
}; |
||||
|
||||
var enumerator1 = new ChronologicalMediaCollectionEnumerator( |
||||
collectionOne.MediaItems, |
||||
new CollectionEnumeratorState()); |
||||
|
||||
var enumerator2 = new ChronologicalMediaCollectionEnumerator( |
||||
collectionTwo.MediaItems, |
||||
new CollectionEnumeratorState()); |
||||
|
||||
var sortedScheduleItems = new List<ProgramScheduleItem> |
||||
{ |
||||
scheduleItem, |
||||
NextScheduleItem |
||||
}; |
||||
|
||||
var scheduler = new PlayoutModeSchedulerFlood(sortedScheduleItems, new Mock<ILogger>().Object); |
||||
(PlayoutBuilderState playoutBuilderState, List<PlayoutItem> playoutItems) = scheduler.Schedule( |
||||
StartState, |
||||
CollectionEnumerators(scheduleItem, enumerator1, scheduleItem.TailFiller, enumerator2), |
||||
scheduleItem, |
||||
NextScheduleItem, |
||||
HardStop); |
||||
|
||||
playoutBuilderState.CurrentTime.Should().Be(StartState.CurrentTime.AddHours(3)); |
||||
playoutItems.Last().FinishOffset.Should().Be(playoutBuilderState.CurrentTime); |
||||
|
||||
playoutBuilderState.NextGuideGroup.Should().Be(4); |
||||
playoutBuilderState.DurationFinish.IsNone.Should().BeTrue(); |
||||
playoutBuilderState.InFlood.Should().BeFalse(); |
||||
playoutBuilderState.MultipleRemaining.IsNone.Should().BeTrue(); |
||||
playoutBuilderState.InDurationFiller.Should().BeFalse(); |
||||
playoutBuilderState.ScheduleItemIndex.Should().Be(1); |
||||
|
||||
enumerator1.State.Index.Should().Be(1); |
||||
enumerator2.State.Index.Should().Be(1); |
||||
|
||||
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[1].MediaItemId.Should().Be(2); |
||||
playoutItems[1].StartOffset.Should().Be(StartState.CurrentTime.AddMinutes(55)); |
||||
playoutItems[1].GuideGroup.Should().Be(2); |
||||
playoutItems[1].FillerKind.Should().Be(FillerKind.None); |
||||
|
||||
playoutItems[2].MediaItemId.Should().Be(1); |
||||
playoutItems[2].StartOffset.Should().Be(StartState.CurrentTime.Add(new TimeSpan(1, 50, 0))); |
||||
playoutItems[2].GuideGroup.Should().Be(3); |
||||
playoutItems[2].FillerKind.Should().Be(FillerKind.None); |
||||
|
||||
playoutItems[3].MediaItemId.Should().Be(3); |
||||
playoutItems[3].StartOffset.Should().Be(StartState.CurrentTime.Add(new TimeSpan(2, 45, 0))); |
||||
playoutItems[3].GuideGroup.Should().Be(3); |
||||
playoutItems[3].FillerKind.Should().Be(FillerKind.Tail); |
||||
|
||||
playoutItems[4].MediaItemId.Should().Be(4); |
||||
playoutItems[4].StartOffset.Should().Be(StartState.CurrentTime.Add(new TimeSpan(2, 50, 0))); |
||||
playoutItems[4].GuideGroup.Should().Be(3); |
||||
playoutItems[4].FillerKind.Should().Be(FillerKind.Tail); |
||||
|
||||
playoutItems[5].MediaItemId.Should().Be(3); |
||||
playoutItems[5].StartOffset.Should().Be(StartState.CurrentTime.Add(new TimeSpan(2, 55, 0))); |
||||
playoutItems[5].GuideGroup.Should().Be(3); |
||||
playoutItems[5].FillerKind.Should().Be(FillerKind.Tail); |
||||
} |
||||
|
||||
[Test] |
||||
public void Should_Not_Have_Gap_With_Fallback() |
||||
{ |
||||
Collection collectionOne = TwoItemCollection(1, 2, TimeSpan.FromMinutes(55)); |
||||
Collection collectionTwo = TwoItemCollection(3, 4, TimeSpan.FromMinutes(5)); |
||||
|
||||
var scheduleItem = new ProgramScheduleItemFlood |
||||
{ |
||||
Id = 1, |
||||
Index = 1, |
||||
Collection = collectionOne, |
||||
CollectionId = collectionOne.Id, |
||||
StartTime = null, |
||||
PlaybackOrder = PlaybackOrder.Chronological, |
||||
TailFiller = null, |
||||
FallbackFiller = new FillerPreset |
||||
{ |
||||
FillerKind = FillerKind.Tail, |
||||
Collection = collectionTwo, |
||||
CollectionId = collectionTwo.Id |
||||
} |
||||
}; |
||||
|
||||
var enumerator1 = new ChronologicalMediaCollectionEnumerator( |
||||
collectionOne.MediaItems, |
||||
new CollectionEnumeratorState()); |
||||
|
||||
var enumerator2 = new ChronologicalMediaCollectionEnumerator( |
||||
collectionTwo.MediaItems, |
||||
new CollectionEnumeratorState()); |
||||
|
||||
var sortedScheduleItems = new List<ProgramScheduleItem> |
||||
{ |
||||
scheduleItem, |
||||
NextScheduleItem |
||||
}; |
||||
|
||||
var scheduler = new PlayoutModeSchedulerFlood(sortedScheduleItems, new Mock<ILogger>().Object); |
||||
(PlayoutBuilderState playoutBuilderState, List<PlayoutItem> playoutItems) = scheduler.Schedule( |
||||
StartState, |
||||
CollectionEnumerators(scheduleItem, enumerator1, scheduleItem.FallbackFiller, enumerator2), |
||||
scheduleItem, |
||||
NextScheduleItem, |
||||
HardStop); |
||||
|
||||
playoutBuilderState.CurrentTime.Should().Be(StartState.CurrentTime.AddHours(3)); |
||||
playoutItems.Last().FinishOffset.Should().Be(playoutBuilderState.CurrentTime); |
||||
|
||||
playoutBuilderState.NextGuideGroup.Should().Be(4); |
||||
playoutBuilderState.DurationFinish.IsNone.Should().BeTrue(); |
||||
playoutBuilderState.InFlood.Should().BeFalse(); |
||||
playoutBuilderState.MultipleRemaining.IsNone.Should().BeTrue(); |
||||
playoutBuilderState.InDurationFiller.Should().BeFalse(); |
||||
playoutBuilderState.ScheduleItemIndex.Should().Be(1); |
||||
|
||||
enumerator1.State.Index.Should().Be(1); |
||||
enumerator2.State.Index.Should().Be(1); |
||||
|
||||
playoutItems.Count.Should().Be(4); |
||||
|
||||
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[1].MediaItemId.Should().Be(2); |
||||
playoutItems[1].StartOffset.Should().Be(StartState.CurrentTime.AddMinutes(55)); |
||||
playoutItems[1].GuideGroup.Should().Be(2); |
||||
playoutItems[1].FillerKind.Should().Be(FillerKind.None); |
||||
|
||||
playoutItems[2].MediaItemId.Should().Be(1); |
||||
playoutItems[2].StartOffset.Should().Be(StartState.CurrentTime.Add(new TimeSpan(1, 50, 0))); |
||||
playoutItems[2].GuideGroup.Should().Be(3); |
||||
playoutItems[2].FillerKind.Should().Be(FillerKind.None); |
||||
|
||||
playoutItems[3].MediaItemId.Should().Be(3); |
||||
playoutItems[3].StartOffset.Should().Be(StartState.CurrentTime.Add(new TimeSpan(2, 45, 0))); |
||||
playoutItems[3].GuideGroup.Should().Be(3); |
||||
playoutItems[3].FillerKind.Should().Be(FillerKind.Fallback); |
||||
} |
||||
|
||||
[Test] |
||||
public void Should_Have_Gap_With_Tail_No_Fallback() |
||||
{ |
||||
Collection collectionOne = TwoItemCollection(1, 2, TimeSpan.FromMinutes(55)); |
||||
Collection collectionTwo = TwoItemCollection(3, 4, TimeSpan.FromMinutes(4)); |
||||
|
||||
var scheduleItem = new ProgramScheduleItemFlood |
||||
{ |
||||
Id = 1, |
||||
Index = 1, |
||||
Collection = collectionOne, |
||||
CollectionId = collectionOne.Id, |
||||
StartTime = null, |
||||
PlaybackOrder = PlaybackOrder.Chronological, |
||||
TailFiller = new FillerPreset |
||||
{ |
||||
FillerKind = FillerKind.Tail, |
||||
Collection = collectionTwo, |
||||
CollectionId = collectionTwo.Id |
||||
}, |
||||
FallbackFiller = null |
||||
}; |
||||
|
||||
var enumerator1 = new ChronologicalMediaCollectionEnumerator( |
||||
collectionOne.MediaItems, |
||||
new CollectionEnumeratorState()); |
||||
|
||||
var enumerator2 = new ChronologicalMediaCollectionEnumerator( |
||||
collectionTwo.MediaItems, |
||||
new CollectionEnumeratorState()); |
||||
|
||||
var sortedScheduleItems = new List<ProgramScheduleItem> |
||||
{ |
||||
scheduleItem, |
||||
NextScheduleItem |
||||
}; |
||||
|
||||
var scheduler = new PlayoutModeSchedulerFlood(sortedScheduleItems, new Mock<ILogger>().Object); |
||||
(PlayoutBuilderState playoutBuilderState, List<PlayoutItem> playoutItems) = scheduler.Schedule( |
||||
StartState, |
||||
CollectionEnumerators(scheduleItem, enumerator1, scheduleItem.TailFiller, enumerator2), |
||||
scheduleItem, |
||||
NextScheduleItem, |
||||
HardStop); |
||||
|
||||
playoutBuilderState.CurrentTime.Should().Be(StartState.CurrentTime.Add(new TimeSpan(2, 57, 0))); |
||||
playoutItems.Last().FinishOffset.Should().Be(playoutBuilderState.CurrentTime); |
||||
|
||||
playoutBuilderState.NextGuideGroup.Should().Be(4); |
||||
playoutBuilderState.DurationFinish.IsNone.Should().BeTrue(); |
||||
playoutBuilderState.InFlood.Should().BeFalse(); |
||||
playoutBuilderState.MultipleRemaining.IsNone.Should().BeTrue(); |
||||
playoutBuilderState.InDurationFiller.Should().BeFalse(); |
||||
playoutBuilderState.ScheduleItemIndex.Should().Be(1); |
||||
|
||||
enumerator1.State.Index.Should().Be(1); |
||||
enumerator2.State.Index.Should().Be(1); |
||||
|
||||
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[1].MediaItemId.Should().Be(2); |
||||
playoutItems[1].StartOffset.Should().Be(StartState.CurrentTime.AddMinutes(55)); |
||||
playoutItems[1].GuideGroup.Should().Be(2); |
||||
playoutItems[1].FillerKind.Should().Be(FillerKind.None); |
||||
|
||||
playoutItems[2].MediaItemId.Should().Be(1); |
||||
playoutItems[2].StartOffset.Should().Be(StartState.CurrentTime.Add(new TimeSpan(1, 50, 0))); |
||||
playoutItems[2].GuideGroup.Should().Be(3); |
||||
playoutItems[2].FillerKind.Should().Be(FillerKind.None); |
||||
|
||||
playoutItems[3].MediaItemId.Should().Be(3); |
||||
playoutItems[3].StartOffset.Should().Be(StartState.CurrentTime.Add(new TimeSpan(2, 45, 0))); |
||||
playoutItems[3].GuideGroup.Should().Be(3); |
||||
playoutItems[3].FillerKind.Should().Be(FillerKind.Tail); |
||||
|
||||
playoutItems[4].MediaItemId.Should().Be(4); |
||||
playoutItems[4].StartOffset.Should().Be(StartState.CurrentTime.Add(new TimeSpan(2, 49, 0))); |
||||
playoutItems[4].GuideGroup.Should().Be(3); |
||||
playoutItems[4].FillerKind.Should().Be(FillerKind.Tail); |
||||
|
||||
playoutItems[5].MediaItemId.Should().Be(3); |
||||
playoutItems[5].StartOffset.Should().Be(StartState.CurrentTime.Add(new TimeSpan(2, 53, 0))); |
||||
playoutItems[5].GuideGroup.Should().Be(3); |
||||
playoutItems[5].FillerKind.Should().Be(FillerKind.Tail); |
||||
} |
||||
|
||||
[Test] |
||||
public void Should_Not_Have_Gap_With_Tail_And_Fallback() |
||||
{ |
||||
Collection collectionOne = TwoItemCollection(1, 2, TimeSpan.FromMinutes(55)); |
||||
Collection collectionTwo = TwoItemCollection(3, 4, TimeSpan.FromMinutes(4)); |
||||
Collection collectionThree = TwoItemCollection(5, 6, TimeSpan.FromMinutes(1)); |
||||
|
||||
var scheduleItem = new ProgramScheduleItemFlood |
||||
{ |
||||
Id = 1, |
||||
Index = 1, |
||||
Collection = collectionOne, |
||||
CollectionId = collectionOne.Id, |
||||
StartTime = null, |
||||
PlaybackOrder = PlaybackOrder.Chronological, |
||||
TailFiller = new FillerPreset |
||||
{ |
||||
FillerKind = FillerKind.Tail, |
||||
Collection = collectionTwo, |
||||
CollectionId = collectionTwo.Id |
||||
}, |
||||
FallbackFiller = new FillerPreset |
||||
{ |
||||
FillerKind = FillerKind.Fallback, |
||||
Collection = collectionThree, |
||||
CollectionId = collectionThree.Id |
||||
} |
||||
}; |
||||
|
||||
var enumerator1 = new ChronologicalMediaCollectionEnumerator( |
||||
collectionOne.MediaItems, |
||||
new CollectionEnumeratorState()); |
||||
|
||||
var enumerator2 = new ChronologicalMediaCollectionEnumerator( |
||||
collectionTwo.MediaItems, |
||||
new CollectionEnumeratorState()); |
||||
|
||||
var enumerator3 = new ChronologicalMediaCollectionEnumerator( |
||||
collectionThree.MediaItems, |
||||
new CollectionEnumeratorState()); |
||||
|
||||
var sortedScheduleItems = new List<ProgramScheduleItem> |
||||
{ |
||||
scheduleItem, |
||||
NextScheduleItem |
||||
}; |
||||
|
||||
var scheduler = new PlayoutModeSchedulerFlood(sortedScheduleItems, new Mock<ILogger>().Object); |
||||
(PlayoutBuilderState playoutBuilderState, List<PlayoutItem> playoutItems) = scheduler.Schedule( |
||||
StartState, |
||||
CollectionEnumerators( |
||||
scheduleItem, |
||||
enumerator1, |
||||
scheduleItem.TailFiller, |
||||
enumerator2, |
||||
scheduleItem.FallbackFiller, |
||||
enumerator3), |
||||
scheduleItem, |
||||
NextScheduleItem, |
||||
HardStop); |
||||
|
||||
playoutBuilderState.CurrentTime.Should().Be(StartState.CurrentTime.AddHours(3)); |
||||
playoutItems.Last().FinishOffset.Should().Be(playoutBuilderState.CurrentTime); |
||||
|
||||
playoutBuilderState.NextGuideGroup.Should().Be(4); |
||||
playoutBuilderState.DurationFinish.IsNone.Should().BeTrue(); |
||||
playoutBuilderState.InFlood.Should().BeFalse(); |
||||
playoutBuilderState.MultipleRemaining.IsNone.Should().BeTrue(); |
||||
playoutBuilderState.InDurationFiller.Should().BeFalse(); |
||||
playoutBuilderState.ScheduleItemIndex.Should().Be(1); |
||||
|
||||
enumerator1.State.Index.Should().Be(1); |
||||
enumerator2.State.Index.Should().Be(1); |
||||
enumerator3.State.Index.Should().Be(1); |
||||
|
||||
playoutItems.Count.Should().Be(7); |
||||
|
||||
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[1].MediaItemId.Should().Be(2); |
||||
playoutItems[1].StartOffset.Should().Be(StartState.CurrentTime.AddMinutes(55)); |
||||
playoutItems[1].GuideGroup.Should().Be(2); |
||||
playoutItems[1].FillerKind.Should().Be(FillerKind.None); |
||||
|
||||
playoutItems[2].MediaItemId.Should().Be(1); |
||||
playoutItems[2].StartOffset.Should().Be(StartState.CurrentTime.Add(new TimeSpan(1, 50, 0))); |
||||
playoutItems[2].GuideGroup.Should().Be(3); |
||||
playoutItems[2].FillerKind.Should().Be(FillerKind.None); |
||||
|
||||
playoutItems[3].MediaItemId.Should().Be(3); |
||||
playoutItems[3].StartOffset.Should().Be(StartState.CurrentTime.Add(new TimeSpan(2, 45, 0))); |
||||
playoutItems[3].GuideGroup.Should().Be(3); |
||||
playoutItems[3].FillerKind.Should().Be(FillerKind.Tail); |
||||
|
||||
playoutItems[4].MediaItemId.Should().Be(4); |
||||
playoutItems[4].StartOffset.Should().Be(StartState.CurrentTime.Add(new TimeSpan(2, 49, 0))); |
||||
playoutItems[4].GuideGroup.Should().Be(3); |
||||
playoutItems[4].FillerKind.Should().Be(FillerKind.Tail); |
||||
|
||||
playoutItems[5].MediaItemId.Should().Be(3); |
||||
playoutItems[5].StartOffset.Should().Be(StartState.CurrentTime.Add(new TimeSpan(2, 53, 0))); |
||||
playoutItems[5].GuideGroup.Should().Be(3); |
||||
playoutItems[5].FillerKind.Should().Be(FillerKind.Tail); |
||||
|
||||
playoutItems[6].MediaItemId.Should().Be(5); |
||||
playoutItems[6].StartOffset.Should().Be(StartState.CurrentTime.Add(new TimeSpan(2, 57, 0))); |
||||
playoutItems[6].GuideGroup.Should().Be(3); |
||||
playoutItems[6].FillerKind.Should().Be(FillerKind.Fallback); |
||||
} |
||||
|
||||
[Test] |
||||
public void Should_Not_Have_Gap_With_Unused_Tail_And_Unused_Fallback() |
||||
{ |
||||
Collection collectionOne = TwoItemCollection(1, 2, TimeSpan.FromHours(1)); |
||||
Collection collectionTwo = TwoItemCollection(3, 4, TimeSpan.FromMinutes(4)); |
||||
Collection collectionThree = TwoItemCollection(5, 6, TimeSpan.FromMinutes(1)); |
||||
|
||||
var scheduleItem = new ProgramScheduleItemFlood |
||||
{ |
||||
Id = 1, |
||||
Index = 1, |
||||
Collection = collectionOne, |
||||
CollectionId = collectionOne.Id, |
||||
StartTime = null, |
||||
PlaybackOrder = PlaybackOrder.Chronological, |
||||
TailFiller = new FillerPreset |
||||
{ |
||||
FillerKind = FillerKind.Tail, |
||||
Collection = collectionTwo, |
||||
CollectionId = collectionTwo.Id |
||||
}, |
||||
FallbackFiller = new FillerPreset |
||||
{ |
||||
FillerKind = FillerKind.Fallback, |
||||
Collection = collectionThree, |
||||
CollectionId = collectionThree.Id |
||||
} |
||||
}; |
||||
|
||||
var enumerator1 = new ChronologicalMediaCollectionEnumerator( |
||||
collectionOne.MediaItems, |
||||
new CollectionEnumeratorState()); |
||||
|
||||
var enumerator2 = new ChronologicalMediaCollectionEnumerator( |
||||
collectionTwo.MediaItems, |
||||
new CollectionEnumeratorState()); |
||||
|
||||
var enumerator3 = new ChronologicalMediaCollectionEnumerator( |
||||
collectionThree.MediaItems, |
||||
new CollectionEnumeratorState()); |
||||
|
||||
var sortedScheduleItems = new List<ProgramScheduleItem> |
||||
{ |
||||
scheduleItem, |
||||
NextScheduleItem |
||||
}; |
||||
|
||||
var scheduler = new PlayoutModeSchedulerFlood(sortedScheduleItems, new Mock<ILogger>().Object); |
||||
(PlayoutBuilderState playoutBuilderState, List<PlayoutItem> playoutItems) = scheduler.Schedule( |
||||
StartState, |
||||
CollectionEnumerators( |
||||
scheduleItem, |
||||
enumerator1, |
||||
scheduleItem.TailFiller, |
||||
enumerator2, |
||||
scheduleItem.FallbackFiller, |
||||
enumerator3), |
||||
scheduleItem, |
||||
NextScheduleItem, |
||||
HardStop); |
||||
|
||||
playoutBuilderState.CurrentTime.Should().Be(StartState.CurrentTime.AddHours(3)); |
||||
playoutItems.Last().FinishOffset.Should().Be(playoutBuilderState.CurrentTime); |
||||
|
||||
playoutBuilderState.NextGuideGroup.Should().Be(4); |
||||
playoutBuilderState.DurationFinish.IsNone.Should().BeTrue(); |
||||
playoutBuilderState.InFlood.Should().BeFalse(); |
||||
playoutBuilderState.MultipleRemaining.IsNone.Should().BeTrue(); |
||||
playoutBuilderState.InDurationFiller.Should().BeFalse(); |
||||
playoutBuilderState.ScheduleItemIndex.Should().Be(1); |
||||
|
||||
enumerator1.State.Index.Should().Be(1); |
||||
enumerator2.State.Index.Should().Be(0); |
||||
enumerator3.State.Index.Should().Be(0); |
||||
|
||||
playoutItems.Count.Should().Be(3); |
||||
|
||||
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[1].MediaItemId.Should().Be(2); |
||||
playoutItems[1].StartOffset.Should().Be(StartState.CurrentTime.AddHours(1)); |
||||
playoutItems[1].GuideGroup.Should().Be(2); |
||||
playoutItems[1].FillerKind.Should().Be(FillerKind.None); |
||||
|
||||
playoutItems[2].MediaItemId.Should().Be(1); |
||||
playoutItems[2].StartOffset.Should().Be(StartState.CurrentTime.AddHours(2)); |
||||
playoutItems[2].GuideGroup.Should().Be(3); |
||||
playoutItems[2].FillerKind.Should().Be(FillerKind.None); |
||||
} |
||||
|
||||
protected override ProgramScheduleItem NextScheduleItem => new ProgramScheduleItemOne |
||||
{ |
||||
StartTime = TimeSpan.FromHours(3) |
||||
}; |
||||
} |
||||
} |
@ -0,0 +1,630 @@
@@ -0,0 +1,630 @@
|
||||
using System; |
||||
using System.Collections.Generic; |
||||
using System.Linq; |
||||
using ErsatzTV.Core.Domain; |
||||
using ErsatzTV.Core.Domain.Filler; |
||||
using ErsatzTV.Core.Scheduling; |
||||
using FluentAssertions; |
||||
using LanguageExt; |
||||
using Microsoft.Extensions.Logging; |
||||
using Moq; |
||||
using NUnit.Framework; |
||||
|
||||
namespace ErsatzTV.Core.Tests.Scheduling |
||||
{ |
||||
[TestFixture] |
||||
public class PlayoutModeSchedulerMultipleTests : SchedulerTestBase |
||||
{ |
||||
[Test] |
||||
public void Should_Fill_Exactly_To_Next_Schedule_Item() |
||||
{ |
||||
Collection collectionOne = TwoItemCollection(1, 2, TimeSpan.FromHours(1)); |
||||
|
||||
var scheduleItem = new ProgramScheduleItemMultiple |
||||
{ |
||||
Id = 1, |
||||
Index = 1, |
||||
CollectionType = ProgramScheduleItemCollectionType.Collection, |
||||
Collection = collectionOne, |
||||
CollectionId = collectionOne.Id, |
||||
StartTime = null, |
||||
PlaybackOrder = PlaybackOrder.Chronological, |
||||
TailFiller = null, |
||||
FallbackFiller = null, |
||||
Count = 3 |
||||
}; |
||||
|
||||
var enumerator = new ChronologicalMediaCollectionEnumerator( |
||||
collectionOne.MediaItems, |
||||
new CollectionEnumeratorState()); |
||||
|
||||
var collectionMediaItems = new Dictionary<CollectionKey, List<MediaItem>> |
||||
{ |
||||
{ CollectionKey.ForScheduleItem(scheduleItem), collectionOne.MediaItems } |
||||
}.ToMap(); |
||||
|
||||
var scheduler = new PlayoutModeSchedulerMultiple(collectionMediaItems, new Mock<ILogger>().Object); |
||||
(PlayoutBuilderState playoutBuilderState, List<PlayoutItem> playoutItems) = scheduler.Schedule( |
||||
StartState, |
||||
CollectionEnumerators(scheduleItem, enumerator), |
||||
scheduleItem, |
||||
NextScheduleItem, |
||||
HardStop); |
||||
|
||||
playoutBuilderState.CurrentTime.Should().Be(StartState.CurrentTime.AddHours(3)); |
||||
playoutItems.Last().FinishOffset.Should().Be(playoutBuilderState.CurrentTime); |
||||
|
||||
playoutBuilderState.NextGuideGroup.Should().Be(2); |
||||
playoutBuilderState.DurationFinish.IsNone.Should().BeTrue(); |
||||
playoutBuilderState.InFlood.Should().BeFalse(); |
||||
playoutBuilderState.MultipleRemaining.IsNone.Should().BeTrue(); |
||||
playoutBuilderState.InDurationFiller.Should().BeFalse(); |
||||
playoutBuilderState.ScheduleItemIndex.Should().Be(1); |
||||
|
||||
enumerator.State.Index.Should().Be(1); |
||||
|
||||
playoutItems.Count.Should().Be(3); |
||||
|
||||
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[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[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); |
||||
} |
||||
|
||||
[Test] |
||||
public void Should_Have_Gap_With_No_Tail_No_Fallback() |
||||
{ |
||||
Collection collectionOne = TwoItemCollection(1, 2, TimeSpan.FromMinutes(55)); |
||||
|
||||
var scheduleItem = new ProgramScheduleItemMultiple |
||||
{ |
||||
Id = 1, |
||||
Index = 1, |
||||
Collection = collectionOne, |
||||
CollectionId = collectionOne.Id, |
||||
StartTime = null, |
||||
PlaybackOrder = PlaybackOrder.Chronological, |
||||
TailFiller = null, |
||||
FallbackFiller = null, |
||||
Count = 3 |
||||
}; |
||||
|
||||
var enumerator = new ChronologicalMediaCollectionEnumerator( |
||||
collectionOne.MediaItems, |
||||
new CollectionEnumeratorState()); |
||||
|
||||
var collectionMediaItems = new Dictionary<CollectionKey, List<MediaItem>> |
||||
{ |
||||
{ CollectionKey.ForScheduleItem(scheduleItem), collectionOne.MediaItems } |
||||
}.ToMap(); |
||||
|
||||
var scheduler = new PlayoutModeSchedulerMultiple(collectionMediaItems, new Mock<ILogger>().Object); |
||||
(PlayoutBuilderState playoutBuilderState, List<PlayoutItem> playoutItems) = scheduler.Schedule( |
||||
StartState, |
||||
CollectionEnumerators(scheduleItem, enumerator), |
||||
scheduleItem, |
||||
NextScheduleItem, |
||||
HardStop); |
||||
|
||||
playoutBuilderState.CurrentTime.Should().Be(StartState.CurrentTime.Add(new TimeSpan(2, 45, 0))); |
||||
playoutItems.Last().FinishOffset.Should().Be(playoutBuilderState.CurrentTime); |
||||
|
||||
playoutBuilderState.NextGuideGroup.Should().Be(2); |
||||
playoutBuilderState.DurationFinish.IsNone.Should().BeTrue(); |
||||
playoutBuilderState.InFlood.Should().BeFalse(); |
||||
playoutBuilderState.MultipleRemaining.IsNone.Should().BeTrue(); |
||||
playoutBuilderState.InDurationFiller.Should().BeFalse(); |
||||
playoutBuilderState.ScheduleItemIndex.Should().Be(1); |
||||
|
||||
enumerator.State.Index.Should().Be(1); |
||||
|
||||
playoutItems.Count.Should().Be(3); |
||||
|
||||
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[1].MediaItemId.Should().Be(2); |
||||
playoutItems[1].StartOffset.Should().Be(StartState.CurrentTime.AddMinutes(55)); |
||||
playoutItems[1].GuideGroup.Should().Be(1); |
||||
playoutItems[1].FillerKind.Should().Be(FillerKind.None); |
||||
|
||||
playoutItems[2].MediaItemId.Should().Be(1); |
||||
playoutItems[2].StartOffset.Should().Be(StartState.CurrentTime.Add(new TimeSpan(1, 50, 0))); |
||||
playoutItems[2].GuideGroup.Should().Be(1); |
||||
playoutItems[2].FillerKind.Should().Be(FillerKind.None); |
||||
} |
||||
|
||||
[Test] |
||||
public void Should_Not_Have_Gap_With_Exact_Tail() |
||||
{ |
||||
Collection collectionOne = TwoItemCollection(1, 2, TimeSpan.FromMinutes(55)); |
||||
Collection collectionTwo = TwoItemCollection(3, 4, TimeSpan.FromMinutes(5)); |
||||
|
||||
var scheduleItem = new ProgramScheduleItemMultiple |
||||
{ |
||||
Id = 1, |
||||
Index = 1, |
||||
Collection = collectionOne, |
||||
CollectionId = collectionOne.Id, |
||||
StartTime = null, |
||||
PlaybackOrder = PlaybackOrder.Chronological, |
||||
TailFiller = new FillerPreset |
||||
{ |
||||
FillerKind = FillerKind.Tail, |
||||
Collection = collectionTwo, |
||||
CollectionId = collectionTwo.Id |
||||
}, |
||||
FallbackFiller = null, |
||||
Count = 3 |
||||
}; |
||||
|
||||
var enumerator1 = new ChronologicalMediaCollectionEnumerator( |
||||
collectionOne.MediaItems, |
||||
new CollectionEnumeratorState()); |
||||
|
||||
var enumerator2 = new ChronologicalMediaCollectionEnumerator( |
||||
collectionTwo.MediaItems, |
||||
new CollectionEnumeratorState()); |
||||
|
||||
var collectionMediaItems = new Dictionary<CollectionKey, List<MediaItem>> |
||||
{ |
||||
{ CollectionKey.ForScheduleItem(scheduleItem), collectionOne.MediaItems }, |
||||
{ CollectionKey.ForFillerPreset(scheduleItem.TailFiller), collectionTwo.MediaItems } |
||||
}.ToMap(); |
||||
|
||||
var scheduler = new PlayoutModeSchedulerMultiple(collectionMediaItems, new Mock<ILogger>().Object); |
||||
(PlayoutBuilderState playoutBuilderState, List<PlayoutItem> playoutItems) = scheduler.Schedule( |
||||
StartState, |
||||
CollectionEnumerators(scheduleItem, enumerator1, scheduleItem.TailFiller, enumerator2), |
||||
scheduleItem, |
||||
NextScheduleItem, |
||||
HardStop); |
||||
|
||||
playoutBuilderState.CurrentTime.Should().Be(StartState.CurrentTime.AddHours(3)); |
||||
playoutItems.Last().FinishOffset.Should().Be(playoutBuilderState.CurrentTime); |
||||
|
||||
playoutBuilderState.NextGuideGroup.Should().Be(2); |
||||
playoutBuilderState.DurationFinish.IsNone.Should().BeTrue(); |
||||
playoutBuilderState.InFlood.Should().BeFalse(); |
||||
playoutBuilderState.MultipleRemaining.IsNone.Should().BeTrue(); |
||||
playoutBuilderState.InDurationFiller.Should().BeFalse(); |
||||
playoutBuilderState.ScheduleItemIndex.Should().Be(1); |
||||
|
||||
enumerator1.State.Index.Should().Be(1); |
||||
enumerator2.State.Index.Should().Be(1); |
||||
|
||||
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[1].MediaItemId.Should().Be(2); |
||||
playoutItems[1].StartOffset.Should().Be(StartState.CurrentTime.AddMinutes(55)); |
||||
playoutItems[1].GuideGroup.Should().Be(1); |
||||
playoutItems[1].FillerKind.Should().Be(FillerKind.None); |
||||
|
||||
playoutItems[2].MediaItemId.Should().Be(1); |
||||
playoutItems[2].StartOffset.Should().Be(StartState.CurrentTime.Add(new TimeSpan(1, 50, 0))); |
||||
playoutItems[2].GuideGroup.Should().Be(1); |
||||
playoutItems[2].FillerKind.Should().Be(FillerKind.None); |
||||
|
||||
playoutItems[3].MediaItemId.Should().Be(3); |
||||
playoutItems[3].StartOffset.Should().Be(StartState.CurrentTime.Add(new TimeSpan(2, 45, 0))); |
||||
playoutItems[3].GuideGroup.Should().Be(1); |
||||
playoutItems[3].FillerKind.Should().Be(FillerKind.Tail); |
||||
|
||||
playoutItems[4].MediaItemId.Should().Be(4); |
||||
playoutItems[4].StartOffset.Should().Be(StartState.CurrentTime.Add(new TimeSpan(2, 50, 0))); |
||||
playoutItems[4].GuideGroup.Should().Be(1); |
||||
playoutItems[4].FillerKind.Should().Be(FillerKind.Tail); |
||||
|
||||
playoutItems[5].MediaItemId.Should().Be(3); |
||||
playoutItems[5].StartOffset.Should().Be(StartState.CurrentTime.Add(new TimeSpan(2, 55, 0))); |
||||
playoutItems[5].GuideGroup.Should().Be(1); |
||||
playoutItems[5].FillerKind.Should().Be(FillerKind.Tail); |
||||
} |
||||
|
||||
[Test] |
||||
public void Should_Not_Have_Gap_With_Fallback() |
||||
{ |
||||
Collection collectionOne = TwoItemCollection(1, 2, TimeSpan.FromMinutes(55)); |
||||
Collection collectionTwo = TwoItemCollection(3, 4, TimeSpan.FromMinutes(5)); |
||||
|
||||
var scheduleItem = new ProgramScheduleItemMultiple |
||||
{ |
||||
Id = 1, |
||||
Index = 1, |
||||
Collection = collectionOne, |
||||
CollectionId = collectionOne.Id, |
||||
StartTime = null, |
||||
PlaybackOrder = PlaybackOrder.Chronological, |
||||
TailFiller = null, |
||||
FallbackFiller = new FillerPreset |
||||
{ |
||||
FillerKind = FillerKind.Tail, |
||||
Collection = collectionTwo, |
||||
CollectionId = collectionTwo.Id |
||||
}, |
||||
Count = 3 |
||||
}; |
||||
|
||||
var enumerator1 = new ChronologicalMediaCollectionEnumerator( |
||||
collectionOne.MediaItems, |
||||
new CollectionEnumeratorState()); |
||||
|
||||
var enumerator2 = new ChronologicalMediaCollectionEnumerator( |
||||
collectionTwo.MediaItems, |
||||
new CollectionEnumeratorState()); |
||||
|
||||
var collectionMediaItems = new Dictionary<CollectionKey, List<MediaItem>> |
||||
{ |
||||
{ CollectionKey.ForScheduleItem(scheduleItem), collectionOne.MediaItems }, |
||||
{ CollectionKey.ForFillerPreset(scheduleItem.FallbackFiller), collectionTwo.MediaItems } |
||||
}.ToMap(); |
||||
|
||||
var scheduler = new PlayoutModeSchedulerMultiple(collectionMediaItems, new Mock<ILogger>().Object); |
||||
(PlayoutBuilderState playoutBuilderState, List<PlayoutItem> playoutItems) = scheduler.Schedule( |
||||
StartState, |
||||
CollectionEnumerators(scheduleItem, enumerator1, scheduleItem.FallbackFiller, enumerator2), |
||||
scheduleItem, |
||||
NextScheduleItem, |
||||
HardStop); |
||||
|
||||
playoutBuilderState.CurrentTime.Should().Be(StartState.CurrentTime.AddHours(3)); |
||||
playoutItems.Last().FinishOffset.Should().Be(playoutBuilderState.CurrentTime); |
||||
|
||||
playoutBuilderState.NextGuideGroup.Should().Be(2); |
||||
playoutBuilderState.DurationFinish.IsNone.Should().BeTrue(); |
||||
playoutBuilderState.InFlood.Should().BeFalse(); |
||||
playoutBuilderState.MultipleRemaining.IsNone.Should().BeTrue(); |
||||
playoutBuilderState.InDurationFiller.Should().BeFalse(); |
||||
playoutBuilderState.ScheduleItemIndex.Should().Be(1); |
||||
|
||||
enumerator1.State.Index.Should().Be(1); |
||||
enumerator2.State.Index.Should().Be(1); |
||||
|
||||
playoutItems.Count.Should().Be(4); |
||||
|
||||
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[1].MediaItemId.Should().Be(2); |
||||
playoutItems[1].StartOffset.Should().Be(StartState.CurrentTime.AddMinutes(55)); |
||||
playoutItems[1].GuideGroup.Should().Be(1); |
||||
playoutItems[1].FillerKind.Should().Be(FillerKind.None); |
||||
|
||||
playoutItems[2].MediaItemId.Should().Be(1); |
||||
playoutItems[2].StartOffset.Should().Be(StartState.CurrentTime.Add(new TimeSpan(1, 50, 0))); |
||||
playoutItems[2].GuideGroup.Should().Be(1); |
||||
playoutItems[2].FillerKind.Should().Be(FillerKind.None); |
||||
|
||||
playoutItems[3].MediaItemId.Should().Be(3); |
||||
playoutItems[3].StartOffset.Should().Be(StartState.CurrentTime.Add(new TimeSpan(2, 45, 0))); |
||||
playoutItems[3].GuideGroup.Should().Be(1); |
||||
playoutItems[3].FillerKind.Should().Be(FillerKind.Fallback); |
||||
} |
||||
|
||||
[Test] |
||||
public void Should_Have_Gap_With_Tail_No_Fallback() |
||||
{ |
||||
Collection collectionOne = TwoItemCollection(1, 2, TimeSpan.FromMinutes(55)); |
||||
Collection collectionTwo = TwoItemCollection(3, 4, TimeSpan.FromMinutes(4)); |
||||
|
||||
var scheduleItem = new ProgramScheduleItemMultiple |
||||
{ |
||||
Id = 1, |
||||
Index = 1, |
||||
Collection = collectionOne, |
||||
CollectionId = collectionOne.Id, |
||||
StartTime = null, |
||||
PlaybackOrder = PlaybackOrder.Chronological, |
||||
TailFiller = new FillerPreset |
||||
{ |
||||
FillerKind = FillerKind.Tail, |
||||
Collection = collectionTwo, |
||||
CollectionId = collectionTwo.Id |
||||
}, |
||||
FallbackFiller = null, |
||||
Count = 3 |
||||
}; |
||||
|
||||
var enumerator1 = new ChronologicalMediaCollectionEnumerator( |
||||
collectionOne.MediaItems, |
||||
new CollectionEnumeratorState()); |
||||
|
||||
var enumerator2 = new ChronologicalMediaCollectionEnumerator( |
||||
collectionTwo.MediaItems, |
||||
new CollectionEnumeratorState()); |
||||
|
||||
var collectionMediaItems = new Dictionary<CollectionKey, List<MediaItem>> |
||||
{ |
||||
{ CollectionKey.ForScheduleItem(scheduleItem), collectionOne.MediaItems }, |
||||
{ CollectionKey.ForFillerPreset(scheduleItem.TailFiller), collectionTwo.MediaItems } |
||||
}.ToMap(); |
||||
|
||||
var scheduler = new PlayoutModeSchedulerMultiple(collectionMediaItems, new Mock<ILogger>().Object); |
||||
(PlayoutBuilderState playoutBuilderState, List<PlayoutItem> playoutItems) = scheduler.Schedule( |
||||
StartState, |
||||
CollectionEnumerators(scheduleItem, enumerator1, scheduleItem.TailFiller, enumerator2), |
||||
scheduleItem, |
||||
NextScheduleItem, |
||||
HardStop); |
||||
|
||||
playoutBuilderState.CurrentTime.Should().Be(StartState.CurrentTime.Add(new TimeSpan(2, 57, 0))); |
||||
playoutItems.Last().FinishOffset.Should().Be(playoutBuilderState.CurrentTime); |
||||
|
||||
playoutBuilderState.NextGuideGroup.Should().Be(2); |
||||
playoutBuilderState.DurationFinish.IsNone.Should().BeTrue(); |
||||
playoutBuilderState.InFlood.Should().BeFalse(); |
||||
playoutBuilderState.MultipleRemaining.IsNone.Should().BeTrue(); |
||||
playoutBuilderState.InDurationFiller.Should().BeFalse(); |
||||
playoutBuilderState.ScheduleItemIndex.Should().Be(1); |
||||
|
||||
enumerator1.State.Index.Should().Be(1); |
||||
enumerator2.State.Index.Should().Be(1); |
||||
|
||||
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[1].MediaItemId.Should().Be(2); |
||||
playoutItems[1].StartOffset.Should().Be(StartState.CurrentTime.AddMinutes(55)); |
||||
playoutItems[1].GuideGroup.Should().Be(1); |
||||
playoutItems[1].FillerKind.Should().Be(FillerKind.None); |
||||
|
||||
playoutItems[2].MediaItemId.Should().Be(1); |
||||
playoutItems[2].StartOffset.Should().Be(StartState.CurrentTime.Add(new TimeSpan(1, 50, 0))); |
||||
playoutItems[2].GuideGroup.Should().Be(1); |
||||
playoutItems[2].FillerKind.Should().Be(FillerKind.None); |
||||
|
||||
playoutItems[3].MediaItemId.Should().Be(3); |
||||
playoutItems[3].StartOffset.Should().Be(StartState.CurrentTime.Add(new TimeSpan(2, 45, 0))); |
||||
playoutItems[3].GuideGroup.Should().Be(1); |
||||
playoutItems[3].FillerKind.Should().Be(FillerKind.Tail); |
||||
|
||||
playoutItems[4].MediaItemId.Should().Be(4); |
||||
playoutItems[4].StartOffset.Should().Be(StartState.CurrentTime.Add(new TimeSpan(2, 49, 0))); |
||||
playoutItems[4].GuideGroup.Should().Be(1); |
||||
playoutItems[4].FillerKind.Should().Be(FillerKind.Tail); |
||||
|
||||
playoutItems[5].MediaItemId.Should().Be(3); |
||||
playoutItems[5].StartOffset.Should().Be(StartState.CurrentTime.Add(new TimeSpan(2, 53, 0))); |
||||
playoutItems[5].GuideGroup.Should().Be(1); |
||||
playoutItems[5].FillerKind.Should().Be(FillerKind.Tail); |
||||
} |
||||
|
||||
[Test] |
||||
public void Should_Not_Have_Gap_With_Tail_And_Fallback() |
||||
{ |
||||
Collection collectionOne = TwoItemCollection(1, 2, TimeSpan.FromMinutes(55)); |
||||
Collection collectionTwo = TwoItemCollection(3, 4, TimeSpan.FromMinutes(4)); |
||||
Collection collectionThree = TwoItemCollection(5, 6, TimeSpan.FromMinutes(1)); |
||||
|
||||
var scheduleItem = new ProgramScheduleItemMultiple |
||||
{ |
||||
Id = 1, |
||||
Index = 1, |
||||
Collection = collectionOne, |
||||
CollectionId = collectionOne.Id, |
||||
StartTime = null, |
||||
PlaybackOrder = PlaybackOrder.Chronological, |
||||
TailFiller = new FillerPreset |
||||
{ |
||||
FillerKind = FillerKind.Tail, |
||||
Collection = collectionTwo, |
||||
CollectionId = collectionTwo.Id |
||||
}, |
||||
FallbackFiller = new FillerPreset |
||||
{ |
||||
FillerKind = FillerKind.Fallback, |
||||
Collection = collectionThree, |
||||
CollectionId = collectionThree.Id |
||||
}, |
||||
Count = 3 |
||||
}; |
||||
|
||||
var enumerator1 = new ChronologicalMediaCollectionEnumerator( |
||||
collectionOne.MediaItems, |
||||
new CollectionEnumeratorState()); |
||||
|
||||
var enumerator2 = new ChronologicalMediaCollectionEnumerator( |
||||
collectionTwo.MediaItems, |
||||
new CollectionEnumeratorState()); |
||||
|
||||
var enumerator3 = new ChronologicalMediaCollectionEnumerator( |
||||
collectionThree.MediaItems, |
||||
new CollectionEnumeratorState()); |
||||
|
||||
var collectionMediaItems = new Dictionary<CollectionKey, List<MediaItem>> |
||||
{ |
||||
{ CollectionKey.ForScheduleItem(scheduleItem), collectionOne.MediaItems }, |
||||
{ CollectionKey.ForFillerPreset(scheduleItem.TailFiller), collectionTwo.MediaItems }, |
||||
{ CollectionKey.ForFillerPreset(scheduleItem.FallbackFiller), collectionThree.MediaItems } |
||||
}.ToMap(); |
||||
|
||||
var scheduler = new PlayoutModeSchedulerMultiple(collectionMediaItems, new Mock<ILogger>().Object); |
||||
(PlayoutBuilderState playoutBuilderState, List<PlayoutItem> playoutItems) = scheduler.Schedule( |
||||
StartState, |
||||
CollectionEnumerators( |
||||
scheduleItem, |
||||
enumerator1, |
||||
scheduleItem.TailFiller, |
||||
enumerator2, |
||||
scheduleItem.FallbackFiller, |
||||
enumerator3), |
||||
scheduleItem, |
||||
NextScheduleItem, |
||||
HardStop); |
||||
|
||||
playoutBuilderState.CurrentTime.Should().Be(StartState.CurrentTime.AddHours(3)); |
||||
playoutItems.Last().FinishOffset.Should().Be(playoutBuilderState.CurrentTime); |
||||
|
||||
playoutBuilderState.NextGuideGroup.Should().Be(2); |
||||
playoutBuilderState.DurationFinish.IsNone.Should().BeTrue(); |
||||
playoutBuilderState.InFlood.Should().BeFalse(); |
||||
playoutBuilderState.MultipleRemaining.IsNone.Should().BeTrue(); |
||||
playoutBuilderState.InDurationFiller.Should().BeFalse(); |
||||
playoutBuilderState.ScheduleItemIndex.Should().Be(1); |
||||
|
||||
enumerator1.State.Index.Should().Be(1); |
||||
enumerator2.State.Index.Should().Be(1); |
||||
enumerator3.State.Index.Should().Be(1); |
||||
|
||||
playoutItems.Count.Should().Be(7); |
||||
|
||||
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[1].MediaItemId.Should().Be(2); |
||||
playoutItems[1].StartOffset.Should().Be(StartState.CurrentTime.AddMinutes(55)); |
||||
playoutItems[1].GuideGroup.Should().Be(1); |
||||
playoutItems[1].FillerKind.Should().Be(FillerKind.None); |
||||
|
||||
playoutItems[2].MediaItemId.Should().Be(1); |
||||
playoutItems[2].StartOffset.Should().Be(StartState.CurrentTime.Add(new TimeSpan(1, 50, 0))); |
||||
playoutItems[2].GuideGroup.Should().Be(1); |
||||
playoutItems[2].FillerKind.Should().Be(FillerKind.None); |
||||
|
||||
playoutItems[3].MediaItemId.Should().Be(3); |
||||
playoutItems[3].StartOffset.Should().Be(StartState.CurrentTime.Add(new TimeSpan(2, 45, 0))); |
||||
playoutItems[3].GuideGroup.Should().Be(1); |
||||
playoutItems[3].FillerKind.Should().Be(FillerKind.Tail); |
||||
|
||||
playoutItems[4].MediaItemId.Should().Be(4); |
||||
playoutItems[4].StartOffset.Should().Be(StartState.CurrentTime.Add(new TimeSpan(2, 49, 0))); |
||||
playoutItems[4].GuideGroup.Should().Be(1); |
||||
playoutItems[4].FillerKind.Should().Be(FillerKind.Tail); |
||||
|
||||
playoutItems[5].MediaItemId.Should().Be(3); |
||||
playoutItems[5].StartOffset.Should().Be(StartState.CurrentTime.Add(new TimeSpan(2, 53, 0))); |
||||
playoutItems[5].GuideGroup.Should().Be(1); |
||||
playoutItems[5].FillerKind.Should().Be(FillerKind.Tail); |
||||
|
||||
playoutItems[6].MediaItemId.Should().Be(5); |
||||
playoutItems[6].StartOffset.Should().Be(StartState.CurrentTime.Add(new TimeSpan(2, 57, 0))); |
||||
playoutItems[6].GuideGroup.Should().Be(1); |
||||
playoutItems[6].FillerKind.Should().Be(FillerKind.Fallback); |
||||
} |
||||
|
||||
[Test] |
||||
public void Should_Not_Have_Gap_With_Unused_Tail_And_Unused_Fallback() |
||||
{ |
||||
Collection collectionOne = TwoItemCollection(1, 2, TimeSpan.FromHours(1)); |
||||
Collection collectionTwo = TwoItemCollection(3, 4, TimeSpan.FromMinutes(4)); |
||||
Collection collectionThree = TwoItemCollection(5, 6, TimeSpan.FromMinutes(1)); |
||||
|
||||
var scheduleItem = new ProgramScheduleItemMultiple |
||||
{ |
||||
Id = 1, |
||||
Index = 1, |
||||
Collection = collectionOne, |
||||
CollectionId = collectionOne.Id, |
||||
StartTime = null, |
||||
PlaybackOrder = PlaybackOrder.Chronological, |
||||
TailFiller = new FillerPreset |
||||
{ |
||||
FillerKind = FillerKind.Tail, |
||||
Collection = collectionTwo, |
||||
CollectionId = collectionTwo.Id |
||||
}, |
||||
FallbackFiller = new FillerPreset |
||||
{ |
||||
FillerKind = FillerKind.Fallback, |
||||
Collection = collectionThree, |
||||
CollectionId = collectionThree.Id |
||||
}, |
||||
Count = 3 |
||||
}; |
||||
|
||||
var enumerator1 = new ChronologicalMediaCollectionEnumerator( |
||||
collectionOne.MediaItems, |
||||
new CollectionEnumeratorState()); |
||||
|
||||
var enumerator2 = new ChronologicalMediaCollectionEnumerator( |
||||
collectionTwo.MediaItems, |
||||
new CollectionEnumeratorState()); |
||||
|
||||
var enumerator3 = new ChronologicalMediaCollectionEnumerator( |
||||
collectionThree.MediaItems, |
||||
new CollectionEnumeratorState()); |
||||
|
||||
var collectionMediaItems = new Dictionary<CollectionKey, List<MediaItem>> |
||||
{ |
||||
{ CollectionKey.ForScheduleItem(scheduleItem), collectionOne.MediaItems }, |
||||
{ CollectionKey.ForFillerPreset(scheduleItem.TailFiller), collectionTwo.MediaItems }, |
||||
{ CollectionKey.ForFillerPreset(scheduleItem.FallbackFiller), collectionThree.MediaItems } |
||||
}.ToMap(); |
||||
|
||||
var scheduler = new PlayoutModeSchedulerMultiple(collectionMediaItems, new Mock<ILogger>().Object); |
||||
(PlayoutBuilderState playoutBuilderState, List<PlayoutItem> playoutItems) = scheduler.Schedule( |
||||
StartState, |
||||
CollectionEnumerators( |
||||
scheduleItem, |
||||
enumerator1, |
||||
scheduleItem.TailFiller, |
||||
enumerator2, |
||||
scheduleItem.FallbackFiller, |
||||
enumerator3), |
||||
scheduleItem, |
||||
NextScheduleItem, |
||||
HardStop); |
||||
|
||||
playoutBuilderState.CurrentTime.Should().Be(StartState.CurrentTime.AddHours(3)); |
||||
playoutItems.Last().FinishOffset.Should().Be(playoutBuilderState.CurrentTime); |
||||
|
||||
playoutBuilderState.NextGuideGroup.Should().Be(2); |
||||
playoutBuilderState.DurationFinish.IsNone.Should().BeTrue(); |
||||
playoutBuilderState.InFlood.Should().BeFalse(); |
||||
playoutBuilderState.MultipleRemaining.IsNone.Should().BeTrue(); |
||||
playoutBuilderState.InDurationFiller.Should().BeFalse(); |
||||
playoutBuilderState.ScheduleItemIndex.Should().Be(1); |
||||
|
||||
enumerator1.State.Index.Should().Be(1); |
||||
enumerator2.State.Index.Should().Be(0); |
||||
enumerator3.State.Index.Should().Be(0); |
||||
|
||||
playoutItems.Count.Should().Be(3); |
||||
|
||||
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[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[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); |
||||
} |
||||
|
||||
protected override ProgramScheduleItem NextScheduleItem => new ProgramScheduleItemOne |
||||
{ |
||||
StartTime = TimeSpan.FromHours(3) |
||||
}; |
||||
} |
||||
} |
@ -0,0 +1,460 @@
@@ -0,0 +1,460 @@
|
||||
using System; |
||||
using System.Collections.Generic; |
||||
using System.Linq; |
||||
using ErsatzTV.Core.Domain; |
||||
using ErsatzTV.Core.Domain.Filler; |
||||
using ErsatzTV.Core.Scheduling; |
||||
using FluentAssertions; |
||||
using Microsoft.Extensions.Logging; |
||||
using Moq; |
||||
using NUnit.Framework; |
||||
|
||||
namespace ErsatzTV.Core.Tests.Scheduling |
||||
{ |
||||
[TestFixture] |
||||
public class PlayoutModeSchedulerOneTests : SchedulerTestBase |
||||
{ |
||||
[Test] |
||||
public void Should_Have_Gap_With_No_Tail_No_Fallback() |
||||
{ |
||||
Collection collectionOne = TwoItemCollection(1, 2, TimeSpan.FromHours(1)); |
||||
|
||||
var scheduleItem = new ProgramScheduleItemOne |
||||
{ |
||||
Id = 1, |
||||
Index = 1, |
||||
Collection = collectionOne, |
||||
CollectionId = collectionOne.Id, |
||||
StartTime = null, |
||||
PlaybackOrder = PlaybackOrder.Chronological, |
||||
TailFiller = null, |
||||
FallbackFiller = null |
||||
}; |
||||
|
||||
var enumerator = new ChronologicalMediaCollectionEnumerator( |
||||
collectionOne.MediaItems, |
||||
new CollectionEnumeratorState()); |
||||
|
||||
var scheduler = new PlayoutModeSchedulerOne(new Mock<ILogger>().Object); |
||||
(PlayoutBuilderState playoutBuilderState, List<PlayoutItem> playoutItems) = scheduler.Schedule( |
||||
StartState, |
||||
CollectionEnumerators(scheduleItem, enumerator), |
||||
scheduleItem, |
||||
NextScheduleItem, |
||||
HardStop); |
||||
|
||||
playoutBuilderState.CurrentTime.Should().Be(StartState.CurrentTime.AddHours(1)); |
||||
playoutItems.Last().FinishOffset.Should().Be(playoutBuilderState.CurrentTime); |
||||
|
||||
playoutBuilderState.NextGuideGroup.Should().Be(2); |
||||
playoutBuilderState.DurationFinish.IsNone.Should().BeTrue(); |
||||
playoutBuilderState.InFlood.Should().BeFalse(); |
||||
playoutBuilderState.MultipleRemaining.IsNone.Should().BeTrue(); |
||||
playoutBuilderState.InDurationFiller.Should().BeFalse(); |
||||
playoutBuilderState.ScheduleItemIndex.Should().Be(1); |
||||
|
||||
enumerator.State.Index.Should().Be(1); |
||||
|
||||
playoutItems.Count.Should().Be(1); |
||||
|
||||
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); |
||||
} |
||||
|
||||
[Test] |
||||
public void Should_Not_Have_Gap_With_Exact_Tail() |
||||
{ |
||||
Collection collectionOne = TwoItemCollection(1, 2, new TimeSpan(2, 45, 0)); |
||||
Collection collectionTwo = TwoItemCollection(3, 4, TimeSpan.FromMinutes(5)); |
||||
|
||||
var scheduleItem = new ProgramScheduleItemOne |
||||
{ |
||||
Id = 1, |
||||
Index = 1, |
||||
Collection = collectionOne, |
||||
CollectionId = collectionOne.Id, |
||||
StartTime = null, |
||||
PlaybackOrder = PlaybackOrder.Chronological, |
||||
TailFiller = new FillerPreset |
||||
{ |
||||
FillerKind = FillerKind.Tail, |
||||
Collection = collectionTwo, |
||||
CollectionId = collectionTwo.Id |
||||
}, |
||||
FallbackFiller = null |
||||
}; |
||||
|
||||
var enumerator1 = new ChronologicalMediaCollectionEnumerator( |
||||
collectionOne.MediaItems, |
||||
new CollectionEnumeratorState()); |
||||
|
||||
var enumerator2 = new ChronologicalMediaCollectionEnumerator( |
||||
collectionTwo.MediaItems, |
||||
new CollectionEnumeratorState()); |
||||
|
||||
var scheduler = new PlayoutModeSchedulerOne(new Mock<ILogger>().Object); |
||||
(PlayoutBuilderState playoutBuilderState, List<PlayoutItem> playoutItems) = scheduler.Schedule( |
||||
StartState, |
||||
CollectionEnumerators(scheduleItem, enumerator1, scheduleItem.TailFiller, enumerator2), |
||||
scheduleItem, |
||||
NextScheduleItem, |
||||
HardStop); |
||||
|
||||
playoutBuilderState.CurrentTime.Should().Be(StartState.CurrentTime.AddHours(3)); |
||||
playoutItems.Last().FinishOffset.Should().Be(playoutBuilderState.CurrentTime); |
||||
|
||||
playoutBuilderState.NextGuideGroup.Should().Be(2); |
||||
playoutBuilderState.DurationFinish.IsNone.Should().BeTrue(); |
||||
playoutBuilderState.InFlood.Should().BeFalse(); |
||||
playoutBuilderState.MultipleRemaining.IsNone.Should().BeTrue(); |
||||
playoutBuilderState.InDurationFiller.Should().BeFalse(); |
||||
playoutBuilderState.ScheduleItemIndex.Should().Be(1); |
||||
|
||||
enumerator1.State.Index.Should().Be(1); |
||||
enumerator2.State.Index.Should().Be(1); |
||||
|
||||
playoutItems.Count.Should().Be(4); |
||||
|
||||
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[1].MediaItemId.Should().Be(3); |
||||
playoutItems[1].StartOffset.Should().Be(StartState.CurrentTime.Add(new TimeSpan(2, 45, 0))); |
||||
playoutItems[1].GuideGroup.Should().Be(1); |
||||
playoutItems[1].FillerKind.Should().Be(FillerKind.Tail); |
||||
|
||||
playoutItems[2].MediaItemId.Should().Be(4); |
||||
playoutItems[2].StartOffset.Should().Be(StartState.CurrentTime.Add(new TimeSpan(2, 50, 0))); |
||||
playoutItems[2].GuideGroup.Should().Be(1); |
||||
playoutItems[2].FillerKind.Should().Be(FillerKind.Tail); |
||||
|
||||
playoutItems[3].MediaItemId.Should().Be(3); |
||||
playoutItems[3].StartOffset.Should().Be(StartState.CurrentTime.Add(new TimeSpan(2, 55, 0))); |
||||
playoutItems[3].GuideGroup.Should().Be(1); |
||||
playoutItems[3].FillerKind.Should().Be(FillerKind.Tail); |
||||
} |
||||
|
||||
[Test] |
||||
public void Should_Not_Have_Gap_With_Fallback() |
||||
{ |
||||
Collection collectionOne = TwoItemCollection(1, 2, TimeSpan.FromHours(1)); |
||||
Collection collectionTwo = TwoItemCollection(3, 4, TimeSpan.FromMinutes(5)); |
||||
|
||||
var scheduleItem = new ProgramScheduleItemOne |
||||
{ |
||||
Id = 1, |
||||
Index = 1, |
||||
Collection = collectionOne, |
||||
CollectionId = collectionOne.Id, |
||||
StartTime = null, |
||||
PlaybackOrder = PlaybackOrder.Chronological, |
||||
TailFiller = null, |
||||
FallbackFiller = new FillerPreset |
||||
{ |
||||
FillerKind = FillerKind.Tail, |
||||
Collection = collectionTwo, |
||||
CollectionId = collectionTwo.Id |
||||
} |
||||
}; |
||||
|
||||
var enumerator1 = new ChronologicalMediaCollectionEnumerator( |
||||
collectionOne.MediaItems, |
||||
new CollectionEnumeratorState()); |
||||
|
||||
var enumerator2 = new ChronologicalMediaCollectionEnumerator( |
||||
collectionTwo.MediaItems, |
||||
new CollectionEnumeratorState()); |
||||
|
||||
var scheduler = new PlayoutModeSchedulerOne(new Mock<ILogger>().Object); |
||||
(PlayoutBuilderState playoutBuilderState, List<PlayoutItem> playoutItems) = scheduler.Schedule( |
||||
StartState, |
||||
CollectionEnumerators(scheduleItem, enumerator1, scheduleItem.FallbackFiller, enumerator2), |
||||
scheduleItem, |
||||
NextScheduleItem, |
||||
HardStop); |
||||
|
||||
playoutBuilderState.CurrentTime.Should().Be(StartState.CurrentTime.AddHours(3)); |
||||
playoutItems.Last().FinishOffset.Should().Be(playoutBuilderState.CurrentTime); |
||||
|
||||
playoutBuilderState.NextGuideGroup.Should().Be(2); |
||||
playoutBuilderState.DurationFinish.IsNone.Should().BeTrue(); |
||||
playoutBuilderState.InFlood.Should().BeFalse(); |
||||
playoutBuilderState.MultipleRemaining.IsNone.Should().BeTrue(); |
||||
playoutBuilderState.InDurationFiller.Should().BeFalse(); |
||||
playoutBuilderState.ScheduleItemIndex.Should().Be(1); |
||||
|
||||
enumerator1.State.Index.Should().Be(1); |
||||
enumerator2.State.Index.Should().Be(1); |
||||
|
||||
playoutItems.Count.Should().Be(2); |
||||
|
||||
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[1].MediaItemId.Should().Be(3); |
||||
playoutItems[1].StartOffset.Should().Be(StartState.CurrentTime.AddHours(1)); |
||||
playoutItems[1].GuideGroup.Should().Be(1); |
||||
playoutItems[1].FillerKind.Should().Be(FillerKind.Fallback); |
||||
} |
||||
|
||||
[Test] |
||||
public void Should_Have_Gap_With_Tail_No_Fallback() |
||||
{ |
||||
Collection collectionOne = TwoItemCollection(1, 2, new TimeSpan(2, 45, 0)); |
||||
Collection collectionTwo = TwoItemCollection(3, 4, TimeSpan.FromMinutes(4)); |
||||
|
||||
var scheduleItem = new ProgramScheduleItemOne |
||||
{ |
||||
Id = 1, |
||||
Index = 1, |
||||
Collection = collectionOne, |
||||
CollectionId = collectionOne.Id, |
||||
StartTime = null, |
||||
PlaybackOrder = PlaybackOrder.Chronological, |
||||
TailFiller = new FillerPreset |
||||
{ |
||||
FillerKind = FillerKind.Tail, |
||||
Collection = collectionTwo, |
||||
CollectionId = collectionTwo.Id |
||||
}, |
||||
FallbackFiller = null |
||||
}; |
||||
|
||||
var enumerator1 = new ChronologicalMediaCollectionEnumerator( |
||||
collectionOne.MediaItems, |
||||
new CollectionEnumeratorState()); |
||||
|
||||
var enumerator2 = new ChronologicalMediaCollectionEnumerator( |
||||
collectionTwo.MediaItems, |
||||
new CollectionEnumeratorState()); |
||||
|
||||
var scheduler = new PlayoutModeSchedulerOne(new Mock<ILogger>().Object); |
||||
(PlayoutBuilderState playoutBuilderState, List<PlayoutItem> playoutItems) = scheduler.Schedule( |
||||
StartState, |
||||
CollectionEnumerators(scheduleItem, enumerator1, scheduleItem.TailFiller, enumerator2), |
||||
scheduleItem, |
||||
NextScheduleItem, |
||||
HardStop); |
||||
|
||||
playoutBuilderState.CurrentTime.Should().Be(StartState.CurrentTime.Add(new TimeSpan(2, 57, 0))); |
||||
playoutItems.Last().FinishOffset.Should().Be(playoutBuilderState.CurrentTime); |
||||
|
||||
playoutBuilderState.NextGuideGroup.Should().Be(2); |
||||
playoutBuilderState.DurationFinish.IsNone.Should().BeTrue(); |
||||
playoutBuilderState.InFlood.Should().BeFalse(); |
||||
playoutBuilderState.MultipleRemaining.IsNone.Should().BeTrue(); |
||||
playoutBuilderState.InDurationFiller.Should().BeFalse(); |
||||
playoutBuilderState.ScheduleItemIndex.Should().Be(1); |
||||
|
||||
enumerator1.State.Index.Should().Be(1); |
||||
enumerator2.State.Index.Should().Be(1); |
||||
|
||||
playoutItems.Count.Should().Be(4); |
||||
|
||||
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[1].MediaItemId.Should().Be(3); |
||||
playoutItems[1].StartOffset.Should().Be(StartState.CurrentTime.Add(new TimeSpan(2, 45, 0))); |
||||
playoutItems[1].GuideGroup.Should().Be(1); |
||||
playoutItems[1].FillerKind.Should().Be(FillerKind.Tail); |
||||
|
||||
playoutItems[2].MediaItemId.Should().Be(4); |
||||
playoutItems[2].StartOffset.Should().Be(StartState.CurrentTime.Add(new TimeSpan(2, 49, 0))); |
||||
playoutItems[2].GuideGroup.Should().Be(1); |
||||
playoutItems[2].FillerKind.Should().Be(FillerKind.Tail); |
||||
|
||||
playoutItems[3].MediaItemId.Should().Be(3); |
||||
playoutItems[3].StartOffset.Should().Be(StartState.CurrentTime.Add(new TimeSpan(2, 53, 0))); |
||||
playoutItems[3].GuideGroup.Should().Be(1); |
||||
playoutItems[3].FillerKind.Should().Be(FillerKind.Tail); |
||||
} |
||||
|
||||
[Test] |
||||
public void Should_Not_Have_Gap_With_Tail_And_Fallback() |
||||
{ |
||||
Collection collectionOne = TwoItemCollection(1, 2, new TimeSpan(2, 45, 0)); |
||||
Collection collectionTwo = TwoItemCollection(3, 4, TimeSpan.FromMinutes(4)); |
||||
Collection collectionThree = TwoItemCollection(5, 6, TimeSpan.FromMinutes(1)); |
||||
|
||||
var scheduleItem = new ProgramScheduleItemOne |
||||
{ |
||||
Id = 1, |
||||
Index = 1, |
||||
Collection = collectionOne, |
||||
CollectionId = collectionOne.Id, |
||||
StartTime = null, |
||||
PlaybackOrder = PlaybackOrder.Chronological, |
||||
TailFiller = new FillerPreset |
||||
{ |
||||
FillerKind = FillerKind.Tail, |
||||
Collection = collectionTwo, |
||||
CollectionId = collectionTwo.Id |
||||
}, |
||||
FallbackFiller = new FillerPreset |
||||
{ |
||||
FillerKind = FillerKind.Fallback, |
||||
Collection = collectionThree, |
||||
CollectionId = collectionThree.Id |
||||
} |
||||
}; |
||||
|
||||
var enumerator1 = new ChronologicalMediaCollectionEnumerator( |
||||
collectionOne.MediaItems, |
||||
new CollectionEnumeratorState()); |
||||
|
||||
var enumerator2 = new ChronologicalMediaCollectionEnumerator( |
||||
collectionTwo.MediaItems, |
||||
new CollectionEnumeratorState()); |
||||
|
||||
var enumerator3 = new ChronologicalMediaCollectionEnumerator( |
||||
collectionThree.MediaItems, |
||||
new CollectionEnumeratorState()); |
||||
|
||||
var scheduler = new PlayoutModeSchedulerOne(new Mock<ILogger>().Object); |
||||
(PlayoutBuilderState playoutBuilderState, List<PlayoutItem> playoutItems) = scheduler.Schedule( |
||||
StartState, |
||||
CollectionEnumerators( |
||||
scheduleItem, |
||||
enumerator1, |
||||
scheduleItem.TailFiller, |
||||
enumerator2, |
||||
scheduleItem.FallbackFiller, |
||||
enumerator3), |
||||
scheduleItem, |
||||
NextScheduleItem, |
||||
HardStop); |
||||
|
||||
playoutBuilderState.CurrentTime.Should().Be(StartState.CurrentTime.AddHours(3)); |
||||
playoutItems.Last().FinishOffset.Should().Be(playoutBuilderState.CurrentTime); |
||||
|
||||
playoutBuilderState.NextGuideGroup.Should().Be(2); |
||||
playoutBuilderState.DurationFinish.IsNone.Should().BeTrue(); |
||||
playoutBuilderState.InFlood.Should().BeFalse(); |
||||
playoutBuilderState.MultipleRemaining.IsNone.Should().BeTrue(); |
||||
playoutBuilderState.InDurationFiller.Should().BeFalse(); |
||||
playoutBuilderState.ScheduleItemIndex.Should().Be(1); |
||||
|
||||
enumerator1.State.Index.Should().Be(1); |
||||
enumerator2.State.Index.Should().Be(1); |
||||
enumerator3.State.Index.Should().Be(1); |
||||
|
||||
playoutItems.Count.Should().Be(5); |
||||
|
||||
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[1].MediaItemId.Should().Be(3); |
||||
playoutItems[1].StartOffset.Should().Be(StartState.CurrentTime.Add(new TimeSpan(2, 45, 0))); |
||||
playoutItems[1].GuideGroup.Should().Be(1); |
||||
playoutItems[1].FillerKind.Should().Be(FillerKind.Tail); |
||||
|
||||
playoutItems[2].MediaItemId.Should().Be(4); |
||||
playoutItems[2].StartOffset.Should().Be(StartState.CurrentTime.Add(new TimeSpan(2, 49, 0))); |
||||
playoutItems[2].GuideGroup.Should().Be(1); |
||||
playoutItems[2].FillerKind.Should().Be(FillerKind.Tail); |
||||
|
||||
playoutItems[3].MediaItemId.Should().Be(3); |
||||
playoutItems[3].StartOffset.Should().Be(StartState.CurrentTime.Add(new TimeSpan(2, 53, 0))); |
||||
playoutItems[3].GuideGroup.Should().Be(1); |
||||
playoutItems[3].FillerKind.Should().Be(FillerKind.Tail); |
||||
|
||||
playoutItems[4].MediaItemId.Should().Be(5); |
||||
playoutItems[4].StartOffset.Should().Be(StartState.CurrentTime.Add(new TimeSpan(2, 57, 0))); |
||||
playoutItems[4].GuideGroup.Should().Be(1); |
||||
playoutItems[4].FillerKind.Should().Be(FillerKind.Fallback); |
||||
} |
||||
|
||||
[Test] |
||||
public void Should_Not_Have_Gap_With_Unused_Tail_And_Unused_Fallback() |
||||
{ |
||||
Collection collectionOne = TwoItemCollection(1, 2, TimeSpan.FromHours(3)); |
||||
Collection collectionTwo = TwoItemCollection(3, 4, TimeSpan.FromMinutes(4)); |
||||
Collection collectionThree = TwoItemCollection(5, 6, TimeSpan.FromMinutes(1)); |
||||
|
||||
var scheduleItem = new ProgramScheduleItemOne |
||||
{ |
||||
Id = 1, |
||||
Index = 1, |
||||
Collection = collectionOne, |
||||
CollectionId = collectionOne.Id, |
||||
StartTime = null, |
||||
PlaybackOrder = PlaybackOrder.Chronological, |
||||
TailFiller = new FillerPreset |
||||
{ |
||||
FillerKind = FillerKind.Tail, |
||||
Collection = collectionTwo, |
||||
CollectionId = collectionTwo.Id |
||||
}, |
||||
FallbackFiller = new FillerPreset |
||||
{ |
||||
FillerKind = FillerKind.Fallback, |
||||
Collection = collectionThree, |
||||
CollectionId = collectionThree.Id |
||||
} |
||||
}; |
||||
|
||||
var enumerator1 = new ChronologicalMediaCollectionEnumerator( |
||||
collectionOne.MediaItems, |
||||
new CollectionEnumeratorState()); |
||||
|
||||
var enumerator2 = new ChronologicalMediaCollectionEnumerator( |
||||
collectionTwo.MediaItems, |
||||
new CollectionEnumeratorState()); |
||||
|
||||
var enumerator3 = new ChronologicalMediaCollectionEnumerator( |
||||
collectionThree.MediaItems, |
||||
new CollectionEnumeratorState()); |
||||
|
||||
var scheduler = new PlayoutModeSchedulerOne(new Mock<ILogger>().Object); |
||||
(PlayoutBuilderState playoutBuilderState, List<PlayoutItem> playoutItems) = scheduler.Schedule( |
||||
StartState, |
||||
CollectionEnumerators( |
||||
scheduleItem, |
||||
enumerator1, |
||||
scheduleItem.TailFiller, |
||||
enumerator2, |
||||
scheduleItem.FallbackFiller, |
||||
enumerator3), |
||||
scheduleItem, |
||||
NextScheduleItem, |
||||
HardStop); |
||||
|
||||
playoutBuilderState.CurrentTime.Should().Be(StartState.CurrentTime.AddHours(3)); |
||||
playoutItems.Last().FinishOffset.Should().Be(playoutBuilderState.CurrentTime); |
||||
|
||||
playoutBuilderState.NextGuideGroup.Should().Be(2); |
||||
playoutBuilderState.DurationFinish.IsNone.Should().BeTrue(); |
||||
playoutBuilderState.InFlood.Should().BeFalse(); |
||||
playoutBuilderState.MultipleRemaining.IsNone.Should().BeTrue(); |
||||
playoutBuilderState.InDurationFiller.Should().BeFalse(); |
||||
playoutBuilderState.ScheduleItemIndex.Should().Be(1); |
||||
|
||||
enumerator1.State.Index.Should().Be(1); |
||||
enumerator2.State.Index.Should().Be(0); |
||||
enumerator3.State.Index.Should().Be(0); |
||||
|
||||
playoutItems.Count.Should().Be(1); |
||||
|
||||
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); |
||||
} |
||||
|
||||
protected override ProgramScheduleItem NextScheduleItem => new ProgramScheduleItemOne |
||||
{ |
||||
StartTime = TimeSpan.FromHours(3) |
||||
}; |
||||
} |
||||
} |
@ -0,0 +1,78 @@
@@ -0,0 +1,78 @@
|
||||
using System; |
||||
using System.Collections.Generic; |
||||
using ErsatzTV.Core.Domain; |
||||
using ErsatzTV.Core.Domain.Filler; |
||||
using ErsatzTV.Core.Interfaces.Scheduling; |
||||
using ErsatzTV.Core.Scheduling; |
||||
using LanguageExt; |
||||
|
||||
namespace ErsatzTV.Core.Tests.Scheduling |
||||
{ |
||||
public abstract class SchedulerTestBase |
||||
{ |
||||
protected static PlayoutBuilderState StartState => new( |
||||
0, |
||||
Prelude.None, |
||||
Prelude.None, |
||||
false, |
||||
false, |
||||
1, |
||||
new DateTimeOffset(new DateTime(2020, 10, 18, 0, 0, 0, DateTimeKind.Local))); |
||||
|
||||
protected virtual ProgramScheduleItem NextScheduleItem => new ProgramScheduleItemOne |
||||
{ |
||||
StartTime = null |
||||
}; |
||||
|
||||
protected static DateTimeOffset HardStop => StartState.CurrentTime.AddHours(6); |
||||
|
||||
protected static Dictionary<CollectionKey, IMediaCollectionEnumerator> CollectionEnumerators( |
||||
ProgramScheduleItem scheduleItem, IMediaCollectionEnumerator enumerator) => |
||||
new() |
||||
{ |
||||
{ CollectionKey.ForScheduleItem(scheduleItem), enumerator } |
||||
}; |
||||
|
||||
protected static Dictionary<CollectionKey, IMediaCollectionEnumerator> CollectionEnumerators( |
||||
ProgramScheduleItem scheduleItem, IMediaCollectionEnumerator enumerator1, |
||||
FillerPreset fillerPreset, IMediaCollectionEnumerator enumerator2, |
||||
FillerPreset fillerPreset2, IMediaCollectionEnumerator enumerator3) => |
||||
new() |
||||
{ |
||||
{ CollectionKey.ForScheduleItem(scheduleItem), enumerator1 }, |
||||
{ CollectionKey.ForFillerPreset(fillerPreset), enumerator2 }, |
||||
{ CollectionKey.ForFillerPreset(fillerPreset2), enumerator3 } |
||||
}; |
||||
|
||||
private static Movie TestMovie(int id, TimeSpan duration, DateTime aired) => |
||||
new() |
||||
{ |
||||
Id = id, |
||||
MovieMetadata = new List<MovieMetadata> { new() { ReleaseDate = aired } }, |
||||
MediaVersions = new List<MediaVersion> |
||||
{ |
||||
new() { Duration = duration } |
||||
} |
||||
}; |
||||
|
||||
protected static Collection TwoItemCollection(int id1, int id2, TimeSpan duration) => new() |
||||
{ |
||||
Id = id1, |
||||
Name = $"Collection of Items {id1}", |
||||
MediaItems = new List<MediaItem> |
||||
{ |
||||
TestMovie(id1, duration, new DateTime(2020, 1, 1)), |
||||
TestMovie(id2, duration, new DateTime(2020, 1, 2)) |
||||
} |
||||
}; |
||||
|
||||
protected static Dictionary<CollectionKey, IMediaCollectionEnumerator> CollectionEnumerators( |
||||
ProgramScheduleItem scheduleItem, IMediaCollectionEnumerator enumerator1, |
||||
FillerPreset fillerPreset, IMediaCollectionEnumerator enumerator2) => |
||||
new() |
||||
{ |
||||
{ CollectionKey.ForScheduleItem(scheduleItem), enumerator1 }, |
||||
{ CollectionKey.ForFillerPreset(fillerPreset), enumerator2 } |
||||
}; |
||||
} |
||||
} |
@ -0,0 +1,12 @@
@@ -0,0 +1,12 @@
|
||||
namespace ErsatzTV.Core.Domain.Filler |
||||
{ |
||||
public enum FillerKind |
||||
{ |
||||
None = 0, |
||||
PreRoll = 1, |
||||
MidRoll = 2, |
||||
PostRoll = 3, |
||||
Tail = 4, |
||||
Fallback = 5 |
||||
} |
||||
} |
@ -0,0 +1,10 @@
@@ -0,0 +1,10 @@
|
||||
namespace ErsatzTV.Core.Domain.Filler |
||||
{ |
||||
public enum FillerMode |
||||
{ |
||||
None = 0, |
||||
Duration = 1, |
||||
Count = 2, |
||||
Pad = 3 |
||||
} |
||||
} |
@ -0,0 +1,24 @@
@@ -0,0 +1,24 @@
|
||||
using System; |
||||
|
||||
namespace ErsatzTV.Core.Domain.Filler |
||||
{ |
||||
public class FillerPreset |
||||
{ |
||||
public int Id { get; set; } |
||||
public string Name { get; set; } |
||||
public FillerKind FillerKind { get; set; } |
||||
public FillerMode FillerMode { get; set; } |
||||
public TimeSpan? Duration { get; set; } |
||||
public int? Count { get; set; } |
||||
public int? PadToNearestMinute { get; set; } |
||||
public ProgramScheduleItemCollectionType CollectionType { get; set; } |
||||
public int? CollectionId { get; set; } |
||||
public Collection Collection { get; set; } |
||||
public int? MediaItemId { get; set; } |
||||
public MediaItem MediaItem { get; set; } |
||||
public int? MultiCollectionId { get; set; } |
||||
public MultiCollection MultiCollection { get; set; } |
||||
public int? SmartCollectionId { get; set; } |
||||
public SmartCollection SmartCollection { get; set; } |
||||
} |
||||
} |
@ -0,0 +1,15 @@
@@ -0,0 +1,15 @@
|
||||
using System; |
||||
|
||||
namespace ErsatzTV.Core.Domain |
||||
{ |
||||
public class MediaChapter |
||||
{ |
||||
public int Id { get; set; } |
||||
public int MediaVersionId { get; set; } |
||||
public MediaVersion MediaVersion { get; set; } |
||||
public long ChapterId { get; set; } |
||||
public TimeSpan StartTime { get; set; } |
||||
public TimeSpan EndTime { get; set; } |
||||
public string Title { get; set; } |
||||
} |
||||
} |
@ -0,0 +1,17 @@
@@ -0,0 +1,17 @@
|
||||
using System; |
||||
using System.Collections.Generic; |
||||
using ErsatzTV.Core.Domain; |
||||
using ErsatzTV.Core.Scheduling; |
||||
|
||||
namespace ErsatzTV.Core.Interfaces.Scheduling |
||||
{ |
||||
public interface IPlayoutModeScheduler<in T> where T : ProgramScheduleItem |
||||
{ |
||||
Tuple<PlayoutBuilderState, List<PlayoutItem>> Schedule( |
||||
PlayoutBuilderState playoutBuilderState, |
||||
Dictionary<CollectionKey, IMediaCollectionEnumerator> collectionEnumerators, |
||||
T scheduleItem, |
||||
ProgramScheduleItem nextScheduleItem, |
||||
DateTimeOffset hardStop); |
||||
} |
||||
} |
@ -0,0 +1,88 @@
@@ -0,0 +1,88 @@
|
||||
using System; |
||||
using ErsatzTV.Core.Domain; |
||||
using ErsatzTV.Core.Domain.Filler; |
||||
using LanguageExt; |
||||
|
||||
namespace ErsatzTV.Core.Scheduling |
||||
{ |
||||
public class CollectionKey : Record<CollectionKey> |
||||
{ |
||||
public static CollectionKey ForScheduleItem(ProgramScheduleItem item) => |
||||
item.CollectionType switch |
||||
{ |
||||
ProgramScheduleItemCollectionType.Collection => new CollectionKey |
||||
{ |
||||
CollectionType = item.CollectionType, |
||||
CollectionId = item.CollectionId |
||||
}, |
||||
ProgramScheduleItemCollectionType.TelevisionShow => new CollectionKey |
||||
{ |
||||
CollectionType = item.CollectionType, |
||||
MediaItemId = item.MediaItemId |
||||
}, |
||||
ProgramScheduleItemCollectionType.TelevisionSeason => new CollectionKey |
||||
{ |
||||
CollectionType = item.CollectionType, |
||||
MediaItemId = item.MediaItemId |
||||
}, |
||||
ProgramScheduleItemCollectionType.Artist => new CollectionKey |
||||
{ |
||||
CollectionType = item.CollectionType, |
||||
MediaItemId = item.MediaItemId |
||||
}, |
||||
ProgramScheduleItemCollectionType.MultiCollection => new CollectionKey |
||||
{ |
||||
CollectionType = item.CollectionType, |
||||
MultiCollectionId = item.MultiCollectionId |
||||
}, |
||||
ProgramScheduleItemCollectionType.SmartCollection => new CollectionKey |
||||
{ |
||||
CollectionType = item.CollectionType, |
||||
SmartCollectionId = item.SmartCollectionId |
||||
}, |
||||
_ => throw new ArgumentOutOfRangeException(nameof(item)) |
||||
}; |
||||
|
||||
public static CollectionKey ForFillerPreset(FillerPreset filler) => |
||||
filler.CollectionType switch |
||||
{ |
||||
ProgramScheduleItemCollectionType.Collection => new CollectionKey |
||||
{ |
||||
CollectionType = filler.CollectionType, |
||||
CollectionId = filler.CollectionId |
||||
}, |
||||
ProgramScheduleItemCollectionType.TelevisionShow => new CollectionKey |
||||
{ |
||||
CollectionType = filler.CollectionType, |
||||
MediaItemId = filler.MediaItemId |
||||
}, |
||||
ProgramScheduleItemCollectionType.TelevisionSeason => new CollectionKey |
||||
{ |
||||
CollectionType = filler.CollectionType, |
||||
MediaItemId = filler.MediaItemId |
||||
}, |
||||
ProgramScheduleItemCollectionType.Artist => new CollectionKey |
||||
{ |
||||
CollectionType = filler.CollectionType, |
||||
MediaItemId = filler.MediaItemId |
||||
}, |
||||
ProgramScheduleItemCollectionType.MultiCollection => new CollectionKey |
||||
{ |
||||
CollectionType = filler.CollectionType, |
||||
MultiCollectionId = filler.MultiCollectionId |
||||
}, |
||||
ProgramScheduleItemCollectionType.SmartCollection => new CollectionKey |
||||
{ |
||||
CollectionType = filler.CollectionType, |
||||
SmartCollectionId = filler.SmartCollectionId |
||||
}, |
||||
_ => throw new ArgumentOutOfRangeException(nameof(filler)) |
||||
}; |
||||
|
||||
public ProgramScheduleItemCollectionType CollectionType { get; set; } |
||||
public int? CollectionId { get; set; } |
||||
public int? MultiCollectionId { get; set; } |
||||
public int? SmartCollectionId { get; set; } |
||||
public int? MediaItemId { get; set; } |
||||
} |
||||
} |
@ -0,0 +1,18 @@
@@ -0,0 +1,18 @@
|
||||
using System; |
||||
using LanguageExt; |
||||
|
||||
namespace ErsatzTV.Core.Scheduling |
||||
{ |
||||
public record PlayoutBuilderState( |
||||
int ScheduleItemIndex, |
||||
Option<int> MultipleRemaining, |
||||
Option<DateTimeOffset> DurationFinish, |
||||
bool InFlood, |
||||
bool InDurationFiller, |
||||
int NextGuideGroup, |
||||
DateTimeOffset CurrentTime) |
||||
{ |
||||
public int IncrementGuideGroup => (NextGuideGroup + 1) % 10000; |
||||
public int DecrementGuideGroup => (NextGuideGroup - 1) % 10000; |
||||
} |
||||
} |
@ -0,0 +1,6 @@
@@ -0,0 +1,6 @@
|
||||
using System; |
||||
|
||||
namespace ErsatzTV.Core.Scheduling |
||||
{ |
||||
public record PlayoutModeBlock(DateTimeOffset StartTime, DateTimeOffset FinishTime); |
||||
} |
@ -0,0 +1,700 @@
@@ -0,0 +1,700 @@
|
||||
using System; |
||||
using System.Collections.Generic; |
||||
using System.Linq; |
||||
using ErsatzTV.Core.Domain; |
||||
using ErsatzTV.Core.Domain.Filler; |
||||
using ErsatzTV.Core.Interfaces.Scheduling; |
||||
using LanguageExt.UnsafeValueAccess; |
||||
using Microsoft.Extensions.Logging; |
||||
using static LanguageExt.Prelude; |
||||
using LanguageExt; |
||||
|
||||
namespace ErsatzTV.Core.Scheduling |
||||
{ |
||||
public abstract class PlayoutModeSchedulerBase<T> : IPlayoutModeScheduler<T> where T : ProgramScheduleItem |
||||
{ |
||||
protected readonly ILogger _logger; |
||||
|
||||
protected PlayoutModeSchedulerBase(ILogger logger) |
||||
{ |
||||
_logger = logger; |
||||
} |
||||
|
||||
public static DateTimeOffset GetStartTimeAfter( |
||||
PlayoutBuilderState state, |
||||
ProgramScheduleItem scheduleItem) |
||||
{ |
||||
DateTimeOffset startTime = state.CurrentTime; |
||||
|
||||
bool isIncomplete = scheduleItem is ProgramScheduleItemMultiple && state.MultipleRemaining.IsSome || |
||||
scheduleItem is ProgramScheduleItemDuration && state.DurationFinish.IsSome || |
||||
scheduleItem is ProgramScheduleItemFlood && state.InFlood || |
||||
scheduleItem is ProgramScheduleItemDuration && state.InDurationFiller; |
||||
|
||||
if (scheduleItem.StartType == StartType.Fixed && !isIncomplete) |
||||
{ |
||||
TimeSpan itemStartTime = scheduleItem.StartTime.GetValueOrDefault(); |
||||
DateTime date = startTime.Date; |
||||
DateTimeOffset result = new DateTimeOffset( |
||||
date.Year, |
||||
date.Month, |
||||
date.Day, |
||||
0, |
||||
0, |
||||
0, |
||||
TimeZoneInfo.Local.GetUtcOffset( |
||||
new DateTime(date.Year, date.Month, date.Day, 0, 0, 0, DateTimeKind.Local))) |
||||
.Add(itemStartTime); |
||||
|
||||
// DateTimeOffset result = startTime.Date + itemStartTime;
|
||||
// need to wrap to the next day if appropriate
|
||||
startTime = startTime.TimeOfDay > itemStartTime ? result.AddDays(1) : result; |
||||
} |
||||
|
||||
return startTime; |
||||
} |
||||
|
||||
public abstract Tuple<PlayoutBuilderState, List<PlayoutItem>> Schedule( |
||||
PlayoutBuilderState playoutBuilderState, |
||||
Dictionary<CollectionKey, IMediaCollectionEnumerator> collectionEnumerators, |
||||
T scheduleItem, |
||||
ProgramScheduleItem nextScheduleItem, |
||||
DateTimeOffset hardStop); |
||||
|
||||
protected Tuple<PlayoutBuilderState, List<PlayoutItem>> AddTailFiller( |
||||
PlayoutBuilderState playoutBuilderState, |
||||
Dictionary<CollectionKey, IMediaCollectionEnumerator> collectionEnumerators, |
||||
ProgramScheduleItem scheduleItem, |
||||
List<PlayoutItem> playoutItems, |
||||
DateTimeOffset nextItemStart) |
||||
{ |
||||
var newItems = new List<PlayoutItem>(playoutItems); |
||||
PlayoutBuilderState nextState = playoutBuilderState; |
||||
|
||||
if (scheduleItem.TailFiller != null) |
||||
{ |
||||
IMediaCollectionEnumerator enumerator = |
||||
collectionEnumerators[CollectionKey.ForFillerPreset(scheduleItem.TailFiller)]; |
||||
|
||||
while (enumerator.Current.IsSome && nextState.CurrentTime < nextItemStart) |
||||
{ |
||||
MediaItem mediaItem = enumerator.Current.ValueUnsafe(); |
||||
|
||||
TimeSpan itemDuration = DurationForMediaItem(mediaItem); |
||||
|
||||
if (nextState.CurrentTime + itemDuration > nextItemStart) |
||||
{ |
||||
_logger.LogDebug( |
||||
"Filler with duration {Duration} will go past next item start {NextItemStart}", |
||||
itemDuration, |
||||
nextItemStart); |
||||
|
||||
break; |
||||
} |
||||
|
||||
var playoutItem = new PlayoutItem |
||||
{ |
||||
MediaItemId = mediaItem.Id, |
||||
Start = nextState.CurrentTime.UtcDateTime, |
||||
Finish = nextState.CurrentTime.UtcDateTime + itemDuration, |
||||
InPoint = TimeSpan.Zero, |
||||
OutPoint = itemDuration, |
||||
FillerKind = FillerKind.Tail, |
||||
GuideGroup = nextState.NextGuideGroup |
||||
}; |
||||
|
||||
newItems.Add(playoutItem); |
||||
|
||||
nextState = nextState with |
||||
{ |
||||
CurrentTime = nextState.CurrentTime + itemDuration |
||||
}; |
||||
|
||||
enumerator.MoveNext(); |
||||
} |
||||
} |
||||
|
||||
return Tuple(nextState, newItems); |
||||
} |
||||
|
||||
protected Tuple<PlayoutBuilderState, List<PlayoutItem>> AddFallbackFiller( |
||||
PlayoutBuilderState playoutBuilderState, |
||||
Dictionary<CollectionKey, IMediaCollectionEnumerator> collectionEnumerators, |
||||
ProgramScheduleItem scheduleItem, |
||||
List<PlayoutItem> playoutItems, |
||||
DateTimeOffset nextItemStart) |
||||
{ |
||||
var newItems = new List<PlayoutItem>(playoutItems); |
||||
PlayoutBuilderState nextState = playoutBuilderState; |
||||
|
||||
if (scheduleItem.FallbackFiller != null && playoutBuilderState.CurrentTime < nextItemStart) |
||||
{ |
||||
IMediaCollectionEnumerator enumerator = |
||||
collectionEnumerators[CollectionKey.ForFillerPreset(scheduleItem.FallbackFiller)]; |
||||
|
||||
foreach (MediaItem mediaItem in enumerator.Current) |
||||
{ |
||||
var playoutItem = new PlayoutItem |
||||
{ |
||||
MediaItemId = mediaItem.Id, |
||||
Start = nextState.CurrentTime.UtcDateTime, |
||||
Finish = nextItemStart.UtcDateTime, |
||||
InPoint = TimeSpan.Zero, |
||||
OutPoint = TimeSpan.Zero, |
||||
GuideGroup = nextState.NextGuideGroup, |
||||
FillerKind = FillerKind.Fallback |
||||
}; |
||||
|
||||
newItems.Add(playoutItem); |
||||
|
||||
nextState = nextState with |
||||
{ |
||||
CurrentTime = nextItemStart.UtcDateTime |
||||
}; |
||||
|
||||
enumerator.MoveNext(); |
||||
} |
||||
} |
||||
|
||||
return Tuple(nextState, newItems); |
||||
} |
||||
|
||||
protected static TimeSpan DurationForMediaItem(MediaItem mediaItem) |
||||
{ |
||||
MediaVersion version = mediaItem switch |
||||
{ |
||||
Movie m => m.MediaVersions.Head(), |
||||
Episode e => e.MediaVersions.Head(), |
||||
MusicVideo mv => mv.MediaVersions.Head(), |
||||
OtherVideo mv => mv.MediaVersions.Head(), |
||||
_ => throw new ArgumentOutOfRangeException(nameof(mediaItem)) |
||||
}; |
||||
|
||||
return version.Duration; |
||||
} |
||||
|
||||
protected static List<MediaChapter> ChaptersForMediaItem(MediaItem mediaItem) |
||||
{ |
||||
MediaVersion version = mediaItem switch |
||||
{ |
||||
Movie m => m.MediaVersions.Head(), |
||||
Episode e => e.MediaVersions.Head(), |
||||
MusicVideo mv => mv.MediaVersions.Head(), |
||||
OtherVideo mv => mv.MediaVersions.Head(), |
||||
_ => throw new ArgumentOutOfRangeException(nameof(mediaItem)) |
||||
}; |
||||
|
||||
return version.Chapters; |
||||
} |
||||
|
||||
protected void LogScheduledItem( |
||||
ProgramScheduleItem scheduleItem, |
||||
MediaItem mediaItem, |
||||
DateTimeOffset startTime) => |
||||
_logger.LogDebug( |
||||
"Scheduling media item: {ScheduleItemNumber} / {CollectionType} / {MediaItemId} - {MediaItemTitle} / {StartTime}", |
||||
scheduleItem.Index, |
||||
scheduleItem.CollectionType, |
||||
mediaItem.Id, |
||||
PlayoutBuilder.DisplayTitle(mediaItem), |
||||
startTime); |
||||
|
||||
internal static DateTimeOffset CalculateEndTimeWithFiller( |
||||
Dictionary<CollectionKey, IMediaCollectionEnumerator> enumerators, |
||||
ProgramScheduleItem scheduleItem, |
||||
DateTimeOffset itemStartTime, |
||||
TimeSpan itemDuration, |
||||
List<MediaChapter> chapters) |
||||
{ |
||||
var allFiller = Optional(scheduleItem.PreRollFiller) |
||||
.Append(Optional(scheduleItem.MidRollFiller)) |
||||
.Append(Optional(scheduleItem.PostRollFiller)) |
||||
.ToList(); |
||||
|
||||
if (allFiller.Count(f => f.PadToNearestMinute.HasValue) > 1) |
||||
// if (allFiller.Map(f => Optional(f.PadToNearestMinute)).Sequence().Flatten().Distinct().Count() > 1)
|
||||
{ |
||||
// multiple pad-to-nearest-minute values are invalid; use no filler
|
||||
// TODO: log error?
|
||||
return itemStartTime + itemDuration; |
||||
} |
||||
|
||||
TimeSpan totalDuration = itemDuration; |
||||
foreach (FillerPreset filler in allFiller) |
||||
{ |
||||
switch (filler.FillerKind, filler.FillerMode) |
||||
{ |
||||
case (FillerKind.MidRoll, FillerMode.Duration) when filler.Duration.HasValue: |
||||
IMediaCollectionEnumerator mrde = enumerators[CollectionKey.ForFillerPreset(filler)]; |
||||
var mrdePeekOffset = 0; |
||||
for (var i = 0; i < chapters.Count - 1; i++) |
||||
{ |
||||
TimeSpan midRollDuration = filler.Duration.Value; |
||||
while (mrde.Peek(mrdePeekOffset)) |
||||
{ |
||||
foreach (MediaItem mediaItem in mrde.Peek(mrdePeekOffset)) |
||||
{ |
||||
TimeSpan currentDuration = DurationForMediaItem(mediaItem); |
||||
midRollDuration -= currentDuration; |
||||
if (midRollDuration >= TimeSpan.Zero) |
||||
{ |
||||
totalDuration += currentDuration; |
||||
mrdePeekOffset++; |
||||
} |
||||
} |
||||
|
||||
if (midRollDuration < TimeSpan.Zero) |
||||
{ |
||||
break; |
||||
} |
||||
} |
||||
} |
||||
|
||||
break; |
||||
case (FillerKind.MidRoll, FillerMode.Count) when filler.Count.HasValue: |
||||
IMediaCollectionEnumerator mrce = enumerators[CollectionKey.ForFillerPreset(filler)]; |
||||
var mrcePeekOffset = 0; |
||||
for (var i = 0; i < chapters.Count - 1; i++) |
||||
{ |
||||
for (var j = 0; j < filler.Count.Value; j++) |
||||
{ |
||||
foreach (MediaItem mediaItem in mrce.Peek(mrcePeekOffset)) |
||||
{ |
||||
totalDuration += DurationForMediaItem(mediaItem); |
||||
mrcePeekOffset++; |
||||
} |
||||
} |
||||
} |
||||
|
||||
break; |
||||
case (_, FillerMode.Duration) when filler.Duration.HasValue: |
||||
IMediaCollectionEnumerator e1 = enumerators[CollectionKey.ForFillerPreset(filler)]; |
||||
var peekOffset1 = 0; |
||||
TimeSpan duration = filler.Duration.Value; |
||||
while (e1.Peek(peekOffset1).IsSome) |
||||
{ |
||||
foreach (MediaItem mediaItem in e1.Peek(peekOffset1)) |
||||
{ |
||||
TimeSpan currentDuration = DurationForMediaItem(mediaItem); |
||||
duration -= currentDuration; |
||||
if (duration >= TimeSpan.Zero) |
||||
{ |
||||
totalDuration += currentDuration; |
||||
peekOffset1++; |
||||
} |
||||
} |
||||
|
||||
if (duration < TimeSpan.Zero) |
||||
{ |
||||
break; |
||||
} |
||||
} |
||||
|
||||
break; |
||||
case (_, FillerMode.Count) when filler.Count.HasValue: |
||||
IMediaCollectionEnumerator e2 = enumerators[CollectionKey.ForFillerPreset(filler)]; |
||||
var peekOffset2 = 0; |
||||
for (var i = 0; i < filler.Count.Value; i++) |
||||
{ |
||||
foreach (MediaItem mediaItem in e2.Peek(peekOffset2)) |
||||
{ |
||||
totalDuration += DurationForMediaItem(mediaItem); |
||||
peekOffset2++; |
||||
} |
||||
} |
||||
|
||||
break; |
||||
} |
||||
} |
||||
|
||||
foreach (FillerPreset padFiller in Optional(allFiller.FirstOrDefault(f => f.PadToNearestMinute.HasValue))) |
||||
{ |
||||
int currentMinute = (itemStartTime + totalDuration).Minute; |
||||
// ReSharper disable once PossibleInvalidOperationException
|
||||
int targetMinute = (currentMinute + padFiller.PadToNearestMinute.Value - 1) / |
||||
padFiller.PadToNearestMinute.Value * padFiller.PadToNearestMinute.Value; |
||||
|
||||
DateTimeOffset targetTime = itemStartTime + totalDuration - TimeSpan.FromMinutes(currentMinute) + |
||||
TimeSpan.FromMinutes(targetMinute); |
||||
|
||||
return new DateTimeOffset( |
||||
targetTime.Year, |
||||
targetTime.Month, |
||||
targetTime.Day, |
||||
targetTime.Hour, |
||||
targetTime.Minute, |
||||
0, |
||||
targetTime.Offset); |
||||
} |
||||
|
||||
return itemStartTime + totalDuration; |
||||
} |
||||
|
||||
protected List<PlayoutItem> AddFiller( |
||||
PlayoutBuilderState playoutBuilderState, |
||||
Dictionary<CollectionKey, IMediaCollectionEnumerator> enumerators, |
||||
ProgramScheduleItem scheduleItem, |
||||
PlayoutItem playoutItem, |
||||
List<MediaChapter> chapters) |
||||
{ |
||||
var result = new List<PlayoutItem>(); |
||||
|
||||
var allFiller = Optional(scheduleItem.PreRollFiller) |
||||
.Append(Optional(scheduleItem.MidRollFiller)) |
||||
.Append(Optional(scheduleItem.PostRollFiller)) |
||||
.ToList(); |
||||
|
||||
if (allFiller.Count(f => f.PadToNearestMinute.HasValue) > 1) |
||||
// if (allFiller.Map(f => Optional(f.PadToNearestMinute)).Sequence().Flatten().Distinct().Count() > 1)
|
||||
{ |
||||
// multiple pad-to-nearest-minute values are invalid; use no filler
|
||||
// TODO: log error?
|
||||
return new List<PlayoutItem> { playoutItem }; |
||||
} |
||||
|
||||
foreach (FillerPreset filler in allFiller.Filter( |
||||
f => f.FillerKind == FillerKind.PreRoll && f.FillerMode != FillerMode.Pad)) |
||||
{ |
||||
switch (filler.FillerMode) |
||||
{ |
||||
case FillerMode.Duration when filler.Duration.HasValue: |
||||
IMediaCollectionEnumerator e1 = enumerators[CollectionKey.ForFillerPreset(filler)]; |
||||
result.AddRange( |
||||
AddDurationFiller(playoutBuilderState, e1, filler.Duration.Value, FillerKind.PreRoll)); |
||||
break; |
||||
case FillerMode.Count when filler.Count.HasValue: |
||||
IMediaCollectionEnumerator e2 = enumerators[CollectionKey.ForFillerPreset(filler)]; |
||||
result.AddRange( |
||||
AddCountFiller(playoutBuilderState, e2, filler.Count.Value, FillerKind.PreRoll)); |
||||
break; |
||||
} |
||||
} |
||||
|
||||
if (allFiller.All(f => f.FillerKind != FillerKind.MidRoll) || !chapters.Any()) |
||||
{ |
||||
result.Add(playoutItem); |
||||
} |
||||
else |
||||
{ |
||||
foreach (FillerPreset filler in allFiller.Filter( |
||||
f => f.FillerKind == FillerKind.MidRoll && f.FillerMode != FillerMode.Pad)) |
||||
{ |
||||
switch (filler.FillerMode) |
||||
{ |
||||
case FillerMode.Duration when filler.Duration.HasValue: |
||||
IMediaCollectionEnumerator e1 = enumerators[CollectionKey.ForFillerPreset(filler)]; |
||||
for (var i = 0; i < chapters.Count; i++) |
||||
{ |
||||
result.Add(playoutItem.ForChapter(chapters[i])); |
||||
if (i < chapters.Count - 1) |
||||
{ |
||||
result.AddRange( |
||||
AddDurationFiller( |
||||
playoutBuilderState, |
||||
e1, |
||||
filler.Duration.Value, |
||||
FillerKind.MidRoll)); |
||||
} |
||||
} |
||||
|
||||
break; |
||||
case FillerMode.Count when filler.Count.HasValue: |
||||
IMediaCollectionEnumerator e2 = enumerators[CollectionKey.ForFillerPreset(filler)]; |
||||
for (var i = 0; i < chapters.Count - 1; i++) |
||||
{ |
||||
result.Add(playoutItem.ForChapter(chapters[i])); |
||||
result.AddRange( |
||||
AddCountFiller( |
||||
playoutBuilderState, |
||||
e2, |
||||
filler.Count.Value, |
||||
FillerKind.MidRoll)); |
||||
} |
||||
|
||||
break; |
||||
} |
||||
} |
||||
} |
||||
|
||||
foreach (FillerPreset filler in allFiller.Filter( |
||||
f => f.FillerKind == FillerKind.PostRoll && f.FillerMode != FillerMode.Pad)) |
||||
{ |
||||
switch (filler.FillerMode) |
||||
{ |
||||
case FillerMode.Duration when filler.Duration.HasValue: |
||||
IMediaCollectionEnumerator e1 = enumerators[CollectionKey.ForFillerPreset(filler)]; |
||||
result.AddRange( |
||||
AddDurationFiller(playoutBuilderState, e1, filler.Duration.Value, FillerKind.PostRoll)); |
||||
break; |
||||
case FillerMode.Count when filler.Count.HasValue: |
||||
IMediaCollectionEnumerator e2 = enumerators[CollectionKey.ForFillerPreset(filler)]; |
||||
result.AddRange( |
||||
AddCountFiller(playoutBuilderState, e2, filler.Count.Value, FillerKind.PostRoll)); |
||||
break; |
||||
} |
||||
} |
||||
|
||||
// after all non-padded filler has been added, figure out padding
|
||||
foreach (FillerPreset padFiller in Optional(allFiller.FirstOrDefault(f => f.PadToNearestMinute.HasValue))) |
||||
{ |
||||
var totalDuration = |
||||
TimeSpan.FromMilliseconds( |
||||
result.Sum(pi => (pi.Finish - pi.Start).TotalMilliseconds) + |
||||
chapters.Sum(c => (c.EndTime - c.StartTime).TotalMilliseconds)); |
||||
|
||||
int currentMinute = (playoutItem.StartOffset + totalDuration).Minute; |
||||
// ReSharper disable once PossibleInvalidOperationException
|
||||
int targetMinute = (currentMinute + padFiller.PadToNearestMinute.Value - 1) / |
||||
padFiller.PadToNearestMinute.Value * padFiller.PadToNearestMinute.Value; |
||||
|
||||
DateTimeOffset almostTargetTime = playoutItem.StartOffset + totalDuration - |
||||
TimeSpan.FromMinutes(currentMinute) + |
||||
TimeSpan.FromMinutes(targetMinute); |
||||
|
||||
|
||||
var targetTime = new DateTimeOffset( |
||||
almostTargetTime.Year, |
||||
almostTargetTime.Month, |
||||
almostTargetTime.Day, |
||||
almostTargetTime.Hour, |
||||
almostTargetTime.Minute, |
||||
0, |
||||
almostTargetTime.Offset); |
||||
|
||||
TimeSpan remainingToFill = targetTime - totalDuration - playoutItem.StartOffset; |
||||
|
||||
// _logger.LogInformation(
|
||||
// "Total duration {TotalDuration}; need to fill {TimeSpan} to pad properly to {TargetTime}",
|
||||
// totalDuration,
|
||||
// remainingToFill,
|
||||
// targetTime);
|
||||
|
||||
switch (padFiller.FillerKind) |
||||
{ |
||||
case FillerKind.PreRoll: |
||||
IMediaCollectionEnumerator pre1 = enumerators[CollectionKey.ForFillerPreset(padFiller)]; |
||||
result.InsertRange( |
||||
0, |
||||
AddDurationFiller( |
||||
playoutBuilderState, |
||||
pre1, |
||||
remainingToFill, |
||||
FillerKind.PreRoll)); |
||||
totalDuration = |
||||
TimeSpan.FromMilliseconds(result.Sum(pi => (pi.Finish - pi.Start).TotalMilliseconds)); |
||||
remainingToFill = targetTime - totalDuration - playoutItem.StartOffset; |
||||
if (remainingToFill > TimeSpan.Zero) |
||||
{ |
||||
result.InsertRange( |
||||
0, |
||||
FallbackFillerForPad( |
||||
playoutBuilderState, |
||||
enumerators, |
||||
scheduleItem, |
||||
remainingToFill)); |
||||
} |
||||
|
||||
break; |
||||
case FillerKind.MidRoll: |
||||
IMediaCollectionEnumerator mid1 = enumerators[CollectionKey.ForFillerPreset(padFiller)]; |
||||
var fillerQueue = new Queue<PlayoutItem>( |
||||
AddDurationFiller( |
||||
playoutBuilderState, |
||||
mid1, |
||||
remainingToFill, |
||||
FillerKind.MidRoll)); |
||||
TimeSpan average = chapters.Count == 0 |
||||
? remainingToFill |
||||
: remainingToFill / (chapters.Count - 1); |
||||
TimeSpan filled = TimeSpan.Zero; |
||||
for (var i = 0; i < chapters.Count; i++) |
||||
{ |
||||
result.Add(playoutItem.ForChapter(chapters[i])); |
||||
if (i < chapters.Count - 1) |
||||
{ |
||||
TimeSpan current = TimeSpan.Zero; |
||||
while (current < average && filled < remainingToFill) |
||||
{ |
||||
if (fillerQueue.TryDequeue(out PlayoutItem fillerItem)) |
||||
{ |
||||
result.Add(fillerItem); |
||||
current += fillerItem.Finish - fillerItem.Start; |
||||
filled += fillerItem.Finish - fillerItem.Start; |
||||
} |
||||
else |
||||
{ |
||||
TimeSpan leftInThisBreak = average - current; |
||||
TimeSpan leftOverall = remainingToFill - filled; |
||||
|
||||
TimeSpan maxThisBreak = leftOverall < leftInThisBreak |
||||
? leftOverall |
||||
: leftInThisBreak; |
||||
|
||||
Option<PlayoutItem> maybeFallback = FallbackFillerForPad( |
||||
playoutBuilderState, |
||||
enumerators, |
||||
scheduleItem, |
||||
i < chapters.Count - 1 ? maxThisBreak : leftOverall); |
||||
|
||||
foreach (PlayoutItem fallback in maybeFallback) |
||||
{ |
||||
current += fallback.Finish - fallback.Start; |
||||
filled += fallback.Finish - fallback.Start; |
||||
result.Add(fallback); |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
break; |
||||
case FillerKind.PostRoll: |
||||
IMediaCollectionEnumerator post1 = enumerators[CollectionKey.ForFillerPreset(padFiller)]; |
||||
result.AddRange( |
||||
AddDurationFiller( |
||||
playoutBuilderState, |
||||
post1, |
||||
remainingToFill, |
||||
FillerKind.PostRoll)); |
||||
totalDuration = |
||||
TimeSpan.FromMilliseconds(result.Sum(pi => (pi.Finish - pi.Start).TotalMilliseconds)); |
||||
remainingToFill = targetTime - totalDuration - playoutItem.StartOffset; |
||||
if (remainingToFill > TimeSpan.Zero) |
||||
{ |
||||
result.AddRange( |
||||
FallbackFillerForPad( |
||||
playoutBuilderState, |
||||
enumerators, |
||||
scheduleItem, |
||||
remainingToFill)); |
||||
} |
||||
|
||||
break; |
||||
} |
||||
} |
||||
|
||||
// fix times on each playout item
|
||||
DateTimeOffset currentTime = playoutItem.StartOffset; |
||||
for (var i = 0; i < result.Count; i++) |
||||
{ |
||||
PlayoutItem item = result[i]; |
||||
TimeSpan duration = item.Finish - item.Start; |
||||
item.Start = currentTime.UtcDateTime; |
||||
item.Finish = (currentTime + duration).UtcDateTime; |
||||
currentTime = item.FinishOffset; |
||||
} |
||||
|
||||
return result; |
||||
} |
||||
|
||||
private static List<PlayoutItem> AddCountFiller( |
||||
PlayoutBuilderState playoutBuilderState, |
||||
IMediaCollectionEnumerator enumerator, |
||||
int count, |
||||
FillerKind fillerKind) |
||||
{ |
||||
var result = new List<PlayoutItem>(); |
||||
|
||||
for (var i = 0; i < count; i++) |
||||
{ |
||||
foreach (MediaItem mediaItem in enumerator.Current) |
||||
{ |
||||
TimeSpan itemDuration = DurationForMediaItem(mediaItem); |
||||
|
||||
var playoutItem = new PlayoutItem |
||||
{ |
||||
MediaItemId = mediaItem.Id, |
||||
Start = new DateTime(2020, 2, 1, 0, 0, 0, DateTimeKind.Utc), |
||||
Finish = new DateTime(2020, 2, 1, 0, 0, 0, DateTimeKind.Utc) + itemDuration, |
||||
InPoint = TimeSpan.Zero, |
||||
OutPoint = itemDuration, |
||||
GuideGroup = playoutBuilderState.NextGuideGroup, |
||||
FillerKind = fillerKind |
||||
}; |
||||
|
||||
result.Add(playoutItem); |
||||
enumerator.MoveNext(); |
||||
} |
||||
} |
||||
|
||||
return result; |
||||
} |
||||
|
||||
private static List<PlayoutItem> AddDurationFiller( |
||||
PlayoutBuilderState playoutBuilderState, |
||||
IMediaCollectionEnumerator enumerator, |
||||
TimeSpan duration, |
||||
FillerKind fillerKind) |
||||
{ |
||||
var result = new List<PlayoutItem>(); |
||||
|
||||
while (enumerator.Current.IsSome) |
||||
{ |
||||
foreach (MediaItem mediaItem in enumerator.Current) |
||||
{ |
||||
// TODO: retry up to x times when item doesn't fit?
|
||||
|
||||
TimeSpan itemDuration = DurationForMediaItem(mediaItem); |
||||
duration -= itemDuration; |
||||
|
||||
if (duration >= TimeSpan.Zero) |
||||
{ |
||||
var playoutItem = new PlayoutItem |
||||
{ |
||||
MediaItemId = mediaItem.Id, |
||||
Start = new DateTime(2020, 2, 1, 0, 0, 0, DateTimeKind.Utc), |
||||
Finish = new DateTime(2020, 2, 1, 0, 0, 0, DateTimeKind.Utc) + itemDuration, |
||||
InPoint = TimeSpan.Zero, |
||||
OutPoint = itemDuration, |
||||
GuideGroup = playoutBuilderState.NextGuideGroup, |
||||
FillerKind = fillerKind |
||||
}; |
||||
|
||||
result.Add(playoutItem); |
||||
enumerator.MoveNext(); |
||||
} |
||||
} |
||||
|
||||
if (duration < TimeSpan.Zero) |
||||
{ |
||||
break; |
||||
} |
||||
} |
||||
|
||||
return result; |
||||
} |
||||
|
||||
private Option<PlayoutItem> FallbackFillerForPad( |
||||
PlayoutBuilderState playoutBuilderState, |
||||
Dictionary<CollectionKey, IMediaCollectionEnumerator> enumerators, |
||||
ProgramScheduleItem scheduleItem, |
||||
TimeSpan duration) |
||||
{ |
||||
if (scheduleItem.FallbackFiller != null) |
||||
{ |
||||
IMediaCollectionEnumerator enumerator = |
||||
enumerators[CollectionKey.ForFillerPreset(scheduleItem.FallbackFiller)]; |
||||
|
||||
foreach (MediaItem mediaItem in enumerator.Current) |
||||
{ |
||||
var result = new PlayoutItem |
||||
{ |
||||
MediaItemId = mediaItem.Id, |
||||
Start = new DateTime(2020, 2, 1, 0, 0, 0, DateTimeKind.Utc), |
||||
Finish = new DateTime(2020, 2, 1, 0, 0, 0, DateTimeKind.Utc) + duration, |
||||
InPoint = TimeSpan.Zero, |
||||
OutPoint = TimeSpan.Zero, |
||||
GuideGroup = playoutBuilderState.NextGuideGroup, |
||||
FillerKind = FillerKind.Fallback |
||||
}; |
||||
|
||||
enumerator.MoveNext(); |
||||
|
||||
return result; |
||||
} |
||||
} |
||||
|
||||
return None; |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,182 @@
@@ -0,0 +1,182 @@
|
||||
using System; |
||||
using System.Collections.Generic; |
||||
using ErsatzTV.Core.Domain; |
||||
using ErsatzTV.Core.Domain.Filler; |
||||
using ErsatzTV.Core.Interfaces.Scheduling; |
||||
using LanguageExt; |
||||
using LanguageExt.UnsafeValueAccess; |
||||
using Microsoft.Extensions.Logging; |
||||
using static LanguageExt.Prelude; |
||||
|
||||
namespace ErsatzTV.Core.Scheduling |
||||
{ |
||||
public class PlayoutModeSchedulerDuration : PlayoutModeSchedulerBase<ProgramScheduleItemDuration> |
||||
{ |
||||
public PlayoutModeSchedulerDuration(ILogger logger) : base(logger) |
||||
{ |
||||
} |
||||
|
||||
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 |
||||
}; |
||||
|
||||
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 |
||||
}; |
||||
|
||||
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, |
||||
ScheduleItemIndex = nextState.ScheduleItemIndex + 1 |
||||
}; |
||||
} |
||||
} |
||||
|
||||
// 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, |
||||
ScheduleItemIndex = nextState.ScheduleItemIndex + 1 |
||||
}; |
||||
} |
||||
|
||||
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; |
||||
} |
||||
} |
||||
|
||||
nextState = nextState with { NextGuideGroup = nextState.IncrementGuideGroup }; |
||||
|
||||
return Tuple(nextState, playoutItems); |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,147 @@
@@ -0,0 +1,147 @@
|
||||
using System; |
||||
using System.Collections.Generic; |
||||
using System.Linq; |
||||
using ErsatzTV.Core.Domain; |
||||
using ErsatzTV.Core.Domain.Filler; |
||||
using ErsatzTV.Core.Interfaces.Scheduling; |
||||
using LanguageExt.UnsafeValueAccess; |
||||
using Microsoft.Extensions.Logging; |
||||
using static LanguageExt.Prelude; |
||||
|
||||
namespace ErsatzTV.Core.Scheduling |
||||
{ |
||||
public class PlayoutModeSchedulerFlood : PlayoutModeSchedulerBase<ProgramScheduleItemFlood> |
||||
{ |
||||
private readonly List<ProgramScheduleItem> _sortedScheduleItems; |
||||
|
||||
public PlayoutModeSchedulerFlood(List<ProgramScheduleItem> sortedScheduleItems, ILogger logger) |
||||
: base(logger) |
||||
{ |
||||
_sortedScheduleItems = sortedScheduleItems; |
||||
} |
||||
|
||||
public override Tuple<PlayoutBuilderState, List<PlayoutItem>> Schedule( |
||||
PlayoutBuilderState playoutBuilderState, |
||||
Dictionary<CollectionKey, IMediaCollectionEnumerator> collectionEnumerators, |
||||
ProgramScheduleItemFlood scheduleItem, |
||||
ProgramScheduleItem nextScheduleItem, |
||||
DateTimeOffset hardStop) |
||||
{ |
||||
var playoutItems = new List<PlayoutItem>(); |
||||
|
||||
PlayoutBuilderState nextState = playoutBuilderState; |
||||
var willFinishInTime = true; |
||||
|
||||
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); |
||||
TimeSpan itemDuration = DurationForMediaItem(mediaItem); |
||||
List<MediaChapter> itemChapters = ChaptersForMediaItem(mediaItem); |
||||
|
||||
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 |
||||
}; |
||||
|
||||
ProgramScheduleItem peekScheduleItem = |
||||
_sortedScheduleItems[(nextState.ScheduleItemIndex + 1) % _sortedScheduleItems.Count]; |
||||
DateTimeOffset peekScheduleItemStart = |
||||
peekScheduleItem.StartType == StartType.Fixed |
||||
? GetStartTimeAfter(nextState, peekScheduleItem) |
||||
: DateTimeOffset.MaxValue; |
||||
|
||||
DateTimeOffset itemEndTimeWithFiller = CalculateEndTimeWithFiller( |
||||
collectionEnumerators, |
||||
scheduleItem, |
||||
itemStartTime, |
||||
itemDuration, |
||||
itemChapters); |
||||
|
||||
// if the current time is before the next schedule item, but the current finish
|
||||
// is after, we need to move on to the next schedule item
|
||||
willFinishInTime = itemStartTime > peekScheduleItemStart || |
||||
itemEndTimeWithFiller <= peekScheduleItemStart; |
||||
|
||||
if (willFinishInTime) |
||||
{ |
||||
playoutItems.AddRange( |
||||
AddFiller(nextState, collectionEnumerators, scheduleItem, playoutItem, itemChapters)); |
||||
// LogScheduledItem(scheduleItem, mediaItem, itemStartTime);
|
||||
|
||||
DateTimeOffset actualEndTime = playoutItems.Max(p => p.FinishOffset); |
||||
if (Math.Abs((itemEndTimeWithFiller - actualEndTime).TotalSeconds) > 1) |
||||
{ |
||||
_logger.LogWarning( |
||||
"Filler prediction failure: predicted {PredictedDuration} doesn't match actual {ActualDuration}", |
||||
itemEndTimeWithFiller, |
||||
actualEndTime); |
||||
|
||||
// _logger.LogWarning("Playout items: {@PlayoutItems}", playoutItems);
|
||||
} |
||||
|
||||
nextState = nextState with |
||||
{ |
||||
CurrentTime = itemEndTimeWithFiller, |
||||
InFlood = true, |
||||
NextGuideGroup = nextState.IncrementGuideGroup |
||||
}; |
||||
|
||||
contentEnumerator.MoveNext(); |
||||
} |
||||
} |
||||
|
||||
// _logger.LogDebug(
|
||||
// "Advancing to next schedule item after playout mode {PlayoutMode}",
|
||||
// "Flood");
|
||||
|
||||
nextState = nextState with |
||||
{ |
||||
ScheduleItemIndex = nextState.ScheduleItemIndex + 1, |
||||
InFlood = nextState.CurrentTime >= hardStop, |
||||
NextGuideGroup = nextState.DecrementGuideGroup |
||||
}; |
||||
|
||||
ProgramScheduleItem peekItem = |
||||
_sortedScheduleItems[nextState.ScheduleItemIndex % _sortedScheduleItems.Count]; |
||||
DateTimeOffset peekItemStart = GetStartTimeAfter(nextState, peekItem); |
||||
|
||||
if (scheduleItem.TailFiller != null) |
||||
{ |
||||
(nextState, playoutItems) = AddTailFiller( |
||||
nextState, |
||||
collectionEnumerators, |
||||
scheduleItem, |
||||
playoutItems, |
||||
peekItemStart); |
||||
} |
||||
|
||||
if (scheduleItem.FallbackFiller != null) |
||||
{ |
||||
(nextState, playoutItems) = AddFallbackFiller( |
||||
nextState, |
||||
collectionEnumerators, |
||||
scheduleItem, |
||||
playoutItems, |
||||
peekItemStart); |
||||
} |
||||
|
||||
nextState = nextState with { NextGuideGroup = nextState.IncrementGuideGroup }; |
||||
|
||||
return Tuple(nextState, playoutItems); |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,131 @@
@@ -0,0 +1,131 @@
|
||||
using System; |
||||
using System.Collections.Generic; |
||||
using ErsatzTV.Core.Domain; |
||||
using ErsatzTV.Core.Domain.Filler; |
||||
using ErsatzTV.Core.Interfaces.Scheduling; |
||||
using LanguageExt; |
||||
using LanguageExt.UnsafeValueAccess; |
||||
using Microsoft.Extensions.Logging; |
||||
using static LanguageExt.Prelude; |
||||
|
||||
namespace ErsatzTV.Core.Scheduling |
||||
{ |
||||
public class PlayoutModeSchedulerMultiple : PlayoutModeSchedulerBase<ProgramScheduleItemMultiple> |
||||
{ |
||||
private readonly Map<CollectionKey, List<MediaItem>> _collectionMediaItems; |
||||
|
||||
public PlayoutModeSchedulerMultiple(Map<CollectionKey, List<MediaItem>> collectionMediaItems, ILogger logger) |
||||
: base(logger) |
||||
{ |
||||
_collectionMediaItems = collectionMediaItems; |
||||
} |
||||
|
||||
public override Tuple<PlayoutBuilderState, List<PlayoutItem>> Schedule( |
||||
PlayoutBuilderState playoutBuilderState, |
||||
Dictionary<CollectionKey, IMediaCollectionEnumerator> collectionEnumerators, |
||||
ProgramScheduleItemMultiple scheduleItem, |
||||
ProgramScheduleItem nextScheduleItem, |
||||
DateTimeOffset hardStop) |
||||
{ |
||||
var playoutItems = new List<PlayoutItem>(); |
||||
|
||||
PlayoutBuilderState nextState = playoutBuilderState with |
||||
{ |
||||
MultipleRemaining = playoutBuilderState.MultipleRemaining.IfNone(scheduleItem.Count) |
||||
}; |
||||
|
||||
if (nextState.MultipleRemaining == 0) |
||||
{ |
||||
nextState = nextState with |
||||
{ |
||||
MultipleRemaining = _collectionMediaItems[CollectionKey.ForScheduleItem(scheduleItem)].Count |
||||
}; |
||||
} |
||||
|
||||
IMediaCollectionEnumerator contentEnumerator = |
||||
collectionEnumerators[CollectionKey.ForScheduleItem(scheduleItem)]; |
||||
while (contentEnumerator.Current.IsSome && nextState.MultipleRemaining > 0 && |
||||
nextState.CurrentTime < hardStop) |
||||
{ |
||||
MediaItem mediaItem = contentEnumerator.Current.ValueUnsafe(); |
||||
|
||||
// find when we should start this item, based on the current time
|
||||
DateTimeOffset itemStartTime = GetStartTimeAfter(nextState, scheduleItem); |
||||
|
||||
TimeSpan itemDuration = DurationForMediaItem(mediaItem); |
||||
List<MediaChapter> itemChapters = ChaptersForMediaItem(mediaItem); |
||||
|
||||
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 |
||||
}; |
||||
|
||||
// LogScheduledItem(scheduleItem, mediaItem, itemStartTime);
|
||||
DateTimeOffset itemEndTimeWithFiller = CalculateEndTimeWithFiller( |
||||
collectionEnumerators, |
||||
scheduleItem, |
||||
itemStartTime, |
||||
itemDuration, |
||||
itemChapters); |
||||
|
||||
playoutItems.AddRange( |
||||
AddFiller(nextState, collectionEnumerators, scheduleItem, playoutItem, itemChapters)); |
||||
|
||||
nextState = nextState with |
||||
{ |
||||
CurrentTime = itemEndTimeWithFiller, |
||||
MultipleRemaining = nextState.MultipleRemaining.Map(i => i - 1) |
||||
}; |
||||
|
||||
contentEnumerator.MoveNext(); |
||||
} |
||||
|
||||
if (nextState.MultipleRemaining.IfNone(-1) == 0) |
||||
{ |
||||
// _logger.LogDebug(
|
||||
// "Advancing to next schedule item after playout mode {PlayoutMode}",
|
||||
// "Multiple");
|
||||
|
||||
nextState = nextState with |
||||
{ |
||||
ScheduleItemIndex = nextState.ScheduleItemIndex + 1, |
||||
MultipleRemaining = None |
||||
}; |
||||
} |
||||
|
||||
DateTimeOffset nextItemStart = GetStartTimeAfter(nextState, nextScheduleItem); |
||||
|
||||
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 { NextGuideGroup = nextState.IncrementGuideGroup }; |
||||
|
||||
return Tuple(nextState, playoutItems); |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,108 @@
@@ -0,0 +1,108 @@
|
||||
using System; |
||||
using System.Collections.Generic; |
||||
using ErsatzTV.Core.Domain; |
||||
using ErsatzTV.Core.Domain.Filler; |
||||
using ErsatzTV.Core.Interfaces.Scheduling; |
||||
using Microsoft.Extensions.Logging; |
||||
using static LanguageExt.Prelude; |
||||
|
||||
namespace ErsatzTV.Core.Scheduling |
||||
{ |
||||
public class PlayoutModeSchedulerOne : PlayoutModeSchedulerBase<ProgramScheduleItemOne> |
||||
{ |
||||
public PlayoutModeSchedulerOne(ILogger logger) : base(logger) |
||||
{ |
||||
} |
||||
|
||||
public override Tuple<PlayoutBuilderState, List<PlayoutItem>> Schedule( |
||||
PlayoutBuilderState playoutBuilderState, |
||||
Dictionary<CollectionKey, IMediaCollectionEnumerator> collectionEnumerators, |
||||
ProgramScheduleItemOne scheduleItem, |
||||
ProgramScheduleItem nextScheduleItem, |
||||
DateTimeOffset hardStop) |
||||
{ |
||||
IMediaCollectionEnumerator contentEnumerator = |
||||
collectionEnumerators[CollectionKey.ForScheduleItem(scheduleItem)]; |
||||
foreach (MediaItem mediaItem in contentEnumerator.Current) |
||||
{ |
||||
// find when we should start this item, based on the current time
|
||||
DateTimeOffset itemStartTime = GetStartTimeAfter( |
||||
playoutBuilderState, |
||||
scheduleItem); |
||||
|
||||
TimeSpan itemDuration = DurationForMediaItem(mediaItem); |
||||
List<MediaChapter> itemChapters = ChaptersForMediaItem(mediaItem); |
||||
|
||||
var playoutItem = new PlayoutItem |
||||
{ |
||||
MediaItemId = mediaItem.Id, |
||||
Start = itemStartTime.UtcDateTime, |
||||
Finish = itemStartTime.UtcDateTime + itemDuration, |
||||
InPoint = TimeSpan.Zero, |
||||
OutPoint = itemDuration, |
||||
GuideGroup = playoutBuilderState.NextGuideGroup, |
||||
FillerKind = scheduleItem.GuideMode == GuideMode.Filler |
||||
? FillerKind.Tail |
||||
: FillerKind.None |
||||
}; |
||||
|
||||
DateTimeOffset itemEndTimeWithFiller = CalculateEndTimeWithFiller( |
||||
collectionEnumerators, |
||||
scheduleItem, |
||||
itemStartTime, |
||||
itemDuration, |
||||
itemChapters); |
||||
|
||||
List<PlayoutItem> playoutItems = AddFiller( |
||||
playoutBuilderState, |
||||
collectionEnumerators, |
||||
scheduleItem, |
||||
playoutItem, |
||||
itemChapters); |
||||
|
||||
// only play one item from collection, so always advance to the next item
|
||||
// _logger.LogDebug(
|
||||
// "Advancing to next schedule item after playout mode {PlayoutMode}",
|
||||
// "One");
|
||||
|
||||
PlayoutBuilderState nextState = playoutBuilderState with |
||||
{ |
||||
CurrentTime = itemEndTimeWithFiller, |
||||
ScheduleItemIndex = playoutBuilderState.ScheduleItemIndex + 1 |
||||
}; |
||||
|
||||
contentEnumerator.MoveNext(); |
||||
|
||||
// LogScheduledItem(scheduleItem, mediaItem, itemStartTime);
|
||||
|
||||
DateTimeOffset nextItemStart = GetStartTimeAfter(nextState, nextScheduleItem); |
||||
|
||||
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 { NextGuideGroup = nextState.IncrementGuideGroup }; |
||||
|
||||
return Tuple(nextState, playoutItems); |
||||
} |
||||
|
||||
return Tuple(playoutBuilderState, new List<PlayoutItem>()); |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,38 @@
@@ -0,0 +1,38 @@
|
||||
using ErsatzTV.Core.Domain.Filler; |
||||
using Microsoft.EntityFrameworkCore; |
||||
using Microsoft.EntityFrameworkCore.Metadata.Builders; |
||||
|
||||
namespace ErsatzTV.Infrastructure.Data.Configurations.Filler |
||||
{ |
||||
public class FillerPresetConfiguration : IEntityTypeConfiguration<FillerPreset> |
||||
{ |
||||
public void Configure(EntityTypeBuilder<FillerPreset> builder) |
||||
{ |
||||
builder.ToTable("FillerPreset"); |
||||
|
||||
builder.HasOne(i => i.Collection) |
||||
.WithMany() |
||||
.HasForeignKey(i => i.CollectionId) |
||||
.OnDelete(DeleteBehavior.Cascade) |
||||
.IsRequired(false); |
||||
|
||||
builder.HasOne(i => i.MediaItem) |
||||
.WithMany() |
||||
.HasForeignKey(i => i.MediaItemId) |
||||
.OnDelete(DeleteBehavior.Cascade) |
||||
.IsRequired(false); |
||||
|
||||
builder.HasOne(i => i.MultiCollection) |
||||
.WithMany() |
||||
.HasForeignKey(i => i.MultiCollectionId) |
||||
.OnDelete(DeleteBehavior.Cascade) |
||||
.IsRequired(false); |
||||
|
||||
builder.HasOne(i => i.SmartCollection) |
||||
.WithMany() |
||||
.HasForeignKey(i => i.SmartCollectionId) |
||||
.OnDelete(DeleteBehavior.Cascade) |
||||
.IsRequired(false); |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,11 @@
@@ -0,0 +1,11 @@
|
||||
using ErsatzTV.Core.Domain; |
||||
using Microsoft.EntityFrameworkCore; |
||||
using Microsoft.EntityFrameworkCore.Metadata.Builders; |
||||
|
||||
namespace ErsatzTV.Infrastructure.Data.Configurations |
||||
{ |
||||
public class MediaChapterConfiguration : IEntityTypeConfiguration<MediaChapter> |
||||
{ |
||||
public void Configure(EntityTypeBuilder<MediaChapter> builder) => builder.ToTable("MediaChapter"); |
||||
} |
||||
} |
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,58 @@
@@ -0,0 +1,58 @@
|
||||
using System; |
||||
using Microsoft.EntityFrameworkCore.Migrations; |
||||
|
||||
namespace ErsatzTV.Infrastructure.Migrations |
||||
{ |
||||
public partial class Add_MediaChapter : Migration |
||||
{ |
||||
protected override void Up(MigrationBuilder migrationBuilder) |
||||
{ |
||||
migrationBuilder.CreateTable( |
||||
name: "MediaChapter", |
||||
columns: table => new |
||||
{ |
||||
Id = table.Column<int>(type: "INTEGER", nullable: false) |
||||
.Annotation("Sqlite:Autoincrement", true), |
||||
MediaVersionId = table.Column<int>(type: "INTEGER", nullable: false), |
||||
ChapterId = table.Column<long>(type: "INTEGER", nullable: false), |
||||
StartTime = table.Column<TimeSpan>(type: "TEXT", nullable: false), |
||||
EndTime = table.Column<TimeSpan>(type: "TEXT", nullable: false), |
||||
Title = table.Column<string>(type: "TEXT", nullable: true) |
||||
}, |
||||
constraints: table => |
||||
{ |
||||
table.PrimaryKey("PK_MediaChapter", x => x.Id); |
||||
table.ForeignKey( |
||||
name: "FK_MediaChapter_MediaVersion_MediaVersionId", |
||||
column: x => x.MediaVersionId, |
||||
principalTable: "MediaVersion", |
||||
principalColumn: "Id", |
||||
onDelete: ReferentialAction.Cascade); |
||||
}); |
||||
|
||||
migrationBuilder.CreateIndex( |
||||
name: "IX_MediaChapter_MediaVersionId", |
||||
table: "MediaChapter", |
||||
column: "MediaVersionId"); |
||||
|
||||
migrationBuilder.Sql("UPDATE MediaVersion SET DateUpdated = '0001-01-01 00:00:00'"); |
||||
migrationBuilder.Sql("UPDATE LibraryFolder SET Etag = NULL"); |
||||
migrationBuilder.Sql("UPDATE EmbyMovie SET Etag = NULL"); |
||||
migrationBuilder.Sql("UPDATE EmbyShow SET Etag = NULL"); |
||||
migrationBuilder.Sql("UPDATE EmbySeason SET Etag = NULL"); |
||||
migrationBuilder.Sql("UPDATE EmbyEpisode SET Etag = NULL"); |
||||
migrationBuilder.Sql("UPDATE JellyfinMovie SET Etag = NULL"); |
||||
migrationBuilder.Sql("UPDATE JellyfinShow SET Etag = NULL"); |
||||
migrationBuilder.Sql("UPDATE JellyfinSeason SET Etag = NULL"); |
||||
migrationBuilder.Sql("UPDATE JellyfinEpisode SET Etag = NULL"); |
||||
migrationBuilder.Sql("UPDATE LibraryPath SET LastScan = '0001-01-01 00:00:00'"); |
||||
migrationBuilder.Sql("UPDATE Library SET LastScan = '0001-01-01 00:00:00'"); |
||||
} |
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder) |
||||
{ |
||||
migrationBuilder.DropTable( |
||||
name: "MediaChapter"); |
||||
} |
||||
} |
||||
} |
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,256 @@
@@ -0,0 +1,256 @@
|
||||
using System; |
||||
using Microsoft.EntityFrameworkCore.Migrations; |
||||
|
||||
namespace ErsatzTV.Infrastructure.Migrations |
||||
{ |
||||
public partial class Add_FillerPreset : Migration |
||||
{ |
||||
protected override void Up(MigrationBuilder migrationBuilder) |
||||
{ |
||||
migrationBuilder.DropForeignKey( |
||||
name: "FK_ProgramScheduleDurationItem_SmartCollection_TailSmartCollectionId", |
||||
table: "ProgramScheduleDurationItem"); |
||||
|
||||
migrationBuilder.DropForeignKey( |
||||
name: "FK_ProgramScheduleItem_SmartCollection_SmartCollectionId", |
||||
table: "ProgramScheduleItem"); |
||||
|
||||
migrationBuilder.AddColumn<int>( |
||||
name: "FallbackFillerId", |
||||
table: "ProgramScheduleItem", |
||||
type: "INTEGER", |
||||
nullable: true); |
||||
|
||||
migrationBuilder.AddColumn<int>( |
||||
name: "MidRollFillerId", |
||||
table: "ProgramScheduleItem", |
||||
type: "INTEGER", |
||||
nullable: true); |
||||
|
||||
migrationBuilder.AddColumn<int>( |
||||
name: "PostRollFillerId", |
||||
table: "ProgramScheduleItem", |
||||
type: "INTEGER", |
||||
nullable: true); |
||||
|
||||
migrationBuilder.AddColumn<int>( |
||||
name: "PreRollFillerId", |
||||
table: "ProgramScheduleItem", |
||||
type: "INTEGER", |
||||
nullable: true); |
||||
|
||||
migrationBuilder.CreateTable( |
||||
name: "FillerPreset", |
||||
columns: table => new |
||||
{ |
||||
Id = table.Column<int>(type: "INTEGER", nullable: false) |
||||
.Annotation("Sqlite:Autoincrement", true), |
||||
Name = table.Column<string>(type: "TEXT", nullable: true), |
||||
FillerKind = table.Column<int>(type: "INTEGER", nullable: false), |
||||
FillerMode = table.Column<int>(type: "INTEGER", nullable: false), |
||||
Duration = table.Column<TimeSpan>(type: "TEXT", nullable: true), |
||||
Count = table.Column<int>(type: "INTEGER", nullable: true), |
||||
PadToNearestMinute = table.Column<int>(type: "INTEGER", nullable: true), |
||||
CollectionType = table.Column<int>(type: "INTEGER", nullable: false), |
||||
CollectionId = table.Column<int>(type: "INTEGER", nullable: true), |
||||
MediaItemId = table.Column<int>(type: "INTEGER", nullable: true), |
||||
MultiCollectionId = table.Column<int>(type: "INTEGER", nullable: true), |
||||
SmartCollectionId = table.Column<int>(type: "INTEGER", nullable: true) |
||||
}, |
||||
constraints: table => |
||||
{ |
||||
table.PrimaryKey("PK_FillerPreset", x => x.Id); |
||||
table.ForeignKey( |
||||
name: "FK_FillerPreset_Collection_CollectionId", |
||||
column: x => x.CollectionId, |
||||
principalTable: "Collection", |
||||
principalColumn: "Id", |
||||
onDelete: ReferentialAction.Cascade); |
||||
table.ForeignKey( |
||||
name: "FK_FillerPreset_MediaItem_MediaItemId", |
||||
column: x => x.MediaItemId, |
||||
principalTable: "MediaItem", |
||||
principalColumn: "Id", |
||||
onDelete: ReferentialAction.Cascade); |
||||
table.ForeignKey( |
||||
name: "FK_FillerPreset_MultiCollection_MultiCollectionId", |
||||
column: x => x.MultiCollectionId, |
||||
principalTable: "MultiCollection", |
||||
principalColumn: "Id", |
||||
onDelete: ReferentialAction.Cascade); |
||||
table.ForeignKey( |
||||
name: "FK_FillerPreset_SmartCollection_SmartCollectionId", |
||||
column: x => x.SmartCollectionId, |
||||
principalTable: "SmartCollection", |
||||
principalColumn: "Id", |
||||
onDelete: ReferentialAction.Cascade); |
||||
}); |
||||
|
||||
migrationBuilder.CreateIndex( |
||||
name: "IX_ProgramScheduleItem_FallbackFillerId", |
||||
table: "ProgramScheduleItem", |
||||
column: "FallbackFillerId"); |
||||
|
||||
migrationBuilder.CreateIndex( |
||||
name: "IX_ProgramScheduleItem_MidRollFillerId", |
||||
table: "ProgramScheduleItem", |
||||
column: "MidRollFillerId"); |
||||
|
||||
migrationBuilder.CreateIndex( |
||||
name: "IX_ProgramScheduleItem_PostRollFillerId", |
||||
table: "ProgramScheduleItem", |
||||
column: "PostRollFillerId"); |
||||
|
||||
migrationBuilder.CreateIndex( |
||||
name: "IX_ProgramScheduleItem_PreRollFillerId", |
||||
table: "ProgramScheduleItem", |
||||
column: "PreRollFillerId"); |
||||
|
||||
migrationBuilder.CreateIndex( |
||||
name: "IX_FillerPreset_CollectionId", |
||||
table: "FillerPreset", |
||||
column: "CollectionId"); |
||||
|
||||
migrationBuilder.CreateIndex( |
||||
name: "IX_FillerPreset_MediaItemId", |
||||
table: "FillerPreset", |
||||
column: "MediaItemId"); |
||||
|
||||
migrationBuilder.CreateIndex( |
||||
name: "IX_FillerPreset_MultiCollectionId", |
||||
table: "FillerPreset", |
||||
column: "MultiCollectionId"); |
||||
|
||||
migrationBuilder.CreateIndex( |
||||
name: "IX_FillerPreset_SmartCollectionId", |
||||
table: "FillerPreset", |
||||
column: "SmartCollectionId"); |
||||
|
||||
migrationBuilder.AddForeignKey( |
||||
name: "FK_ProgramScheduleDurationItem_SmartCollection_TailSmartCollectionId", |
||||
table: "ProgramScheduleDurationItem", |
||||
column: "TailSmartCollectionId", |
||||
principalTable: "SmartCollection", |
||||
principalColumn: "Id", |
||||
onDelete: ReferentialAction.Cascade); |
||||
|
||||
migrationBuilder.AddForeignKey( |
||||
name: "FK_ProgramScheduleItem_FillerPreset_FallbackFillerId", |
||||
table: "ProgramScheduleItem", |
||||
column: "FallbackFillerId", |
||||
principalTable: "FillerPreset", |
||||
principalColumn: "Id", |
||||
onDelete: ReferentialAction.Cascade); |
||||
|
||||
migrationBuilder.AddForeignKey( |
||||
name: "FK_ProgramScheduleItem_FillerPreset_MidRollFillerId", |
||||
table: "ProgramScheduleItem", |
||||
column: "MidRollFillerId", |
||||
principalTable: "FillerPreset", |
||||
principalColumn: "Id", |
||||
onDelete: ReferentialAction.Cascade); |
||||
|
||||
migrationBuilder.AddForeignKey( |
||||
name: "FK_ProgramScheduleItem_FillerPreset_PostRollFillerId", |
||||
table: "ProgramScheduleItem", |
||||
column: "PostRollFillerId", |
||||
principalTable: "FillerPreset", |
||||
principalColumn: "Id", |
||||
onDelete: ReferentialAction.Cascade); |
||||
|
||||
migrationBuilder.AddForeignKey( |
||||
name: "FK_ProgramScheduleItem_FillerPreset_PreRollFillerId", |
||||
table: "ProgramScheduleItem", |
||||
column: "PreRollFillerId", |
||||
principalTable: "FillerPreset", |
||||
principalColumn: "Id", |
||||
onDelete: ReferentialAction.Cascade); |
||||
|
||||
migrationBuilder.AddForeignKey( |
||||
name: "FK_ProgramScheduleItem_SmartCollection_SmartCollectionId", |
||||
table: "ProgramScheduleItem", |
||||
column: "SmartCollectionId", |
||||
principalTable: "SmartCollection", |
||||
principalColumn: "Id", |
||||
onDelete: ReferentialAction.Cascade); |
||||
} |
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder) |
||||
{ |
||||
migrationBuilder.DropForeignKey( |
||||
name: "FK_ProgramScheduleDurationItem_SmartCollection_TailSmartCollectionId", |
||||
table: "ProgramScheduleDurationItem"); |
||||
|
||||
migrationBuilder.DropForeignKey( |
||||
name: "FK_ProgramScheduleItem_FillerPreset_FallbackFillerId", |
||||
table: "ProgramScheduleItem"); |
||||
|
||||
migrationBuilder.DropForeignKey( |
||||
name: "FK_ProgramScheduleItem_FillerPreset_MidRollFillerId", |
||||
table: "ProgramScheduleItem"); |
||||
|
||||
migrationBuilder.DropForeignKey( |
||||
name: "FK_ProgramScheduleItem_FillerPreset_PostRollFillerId", |
||||
table: "ProgramScheduleItem"); |
||||
|
||||
migrationBuilder.DropForeignKey( |
||||
name: "FK_ProgramScheduleItem_FillerPreset_PreRollFillerId", |
||||
table: "ProgramScheduleItem"); |
||||
|
||||
migrationBuilder.DropForeignKey( |
||||
name: "FK_ProgramScheduleItem_SmartCollection_SmartCollectionId", |
||||
table: "ProgramScheduleItem"); |
||||
|
||||
migrationBuilder.DropTable( |
||||
name: "FillerPreset"); |
||||
|
||||
migrationBuilder.DropIndex( |
||||
name: "IX_ProgramScheduleItem_FallbackFillerId", |
||||
table: "ProgramScheduleItem"); |
||||
|
||||
migrationBuilder.DropIndex( |
||||
name: "IX_ProgramScheduleItem_MidRollFillerId", |
||||
table: "ProgramScheduleItem"); |
||||
|
||||
migrationBuilder.DropIndex( |
||||
name: "IX_ProgramScheduleItem_PostRollFillerId", |
||||
table: "ProgramScheduleItem"); |
||||
|
||||
migrationBuilder.DropIndex( |
||||
name: "IX_ProgramScheduleItem_PreRollFillerId", |
||||
table: "ProgramScheduleItem"); |
||||
|
||||
migrationBuilder.DropColumn( |
||||
name: "FallbackFillerId", |
||||
table: "ProgramScheduleItem"); |
||||
|
||||
migrationBuilder.DropColumn( |
||||
name: "MidRollFillerId", |
||||
table: "ProgramScheduleItem"); |
||||
|
||||
migrationBuilder.DropColumn( |
||||
name: "PostRollFillerId", |
||||
table: "ProgramScheduleItem"); |
||||
|
||||
migrationBuilder.DropColumn( |
||||
name: "PreRollFillerId", |
||||
table: "ProgramScheduleItem"); |
||||
|
||||
migrationBuilder.AddForeignKey( |
||||
name: "FK_ProgramScheduleDurationItem_SmartCollection_TailSmartCollectionId", |
||||
table: "ProgramScheduleDurationItem", |
||||
column: "TailSmartCollectionId", |
||||
principalTable: "SmartCollection", |
||||
principalColumn: "Id", |
||||
onDelete: ReferentialAction.Restrict); |
||||
|
||||
migrationBuilder.AddForeignKey( |
||||
name: "FK_ProgramScheduleItem_SmartCollection_SmartCollectionId", |
||||
table: "ProgramScheduleItem", |
||||
column: "SmartCollectionId", |
||||
principalTable: "SmartCollection", |
||||
principalColumn: "Id", |
||||
onDelete: ReferentialAction.Restrict); |
||||
} |
||||
} |
||||
} |
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,140 @@
@@ -0,0 +1,140 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations; |
||||
|
||||
namespace ErsatzTV.Infrastructure.Migrations |
||||
{ |
||||
public partial class Add_ProgramScheduleItem_TailFiller : Migration |
||||
{ |
||||
protected override void Up(MigrationBuilder migrationBuilder) |
||||
{ |
||||
migrationBuilder.DropForeignKey( |
||||
name: "FK_ProgramScheduleItem_FillerPreset_FallbackFillerId", |
||||
table: "ProgramScheduleItem"); |
||||
|
||||
migrationBuilder.DropForeignKey( |
||||
name: "FK_ProgramScheduleItem_FillerPreset_MidRollFillerId", |
||||
table: "ProgramScheduleItem"); |
||||
|
||||
migrationBuilder.DropForeignKey( |
||||
name: "FK_ProgramScheduleItem_FillerPreset_PostRollFillerId", |
||||
table: "ProgramScheduleItem"); |
||||
|
||||
migrationBuilder.DropForeignKey( |
||||
name: "FK_ProgramScheduleItem_FillerPreset_PreRollFillerId", |
||||
table: "ProgramScheduleItem"); |
||||
|
||||
migrationBuilder.AddColumn<int>( |
||||
name: "TailFillerId", |
||||
table: "ProgramScheduleItem", |
||||
type: "INTEGER", |
||||
nullable: true); |
||||
|
||||
migrationBuilder.CreateIndex( |
||||
name: "IX_ProgramScheduleItem_TailFillerId", |
||||
table: "ProgramScheduleItem", |
||||
column: "TailFillerId"); |
||||
|
||||
migrationBuilder.AddForeignKey( |
||||
name: "FK_ProgramScheduleItem_FillerPreset_FallbackFillerId", |
||||
table: "ProgramScheduleItem", |
||||
column: "FallbackFillerId", |
||||
principalTable: "FillerPreset", |
||||
principalColumn: "Id", |
||||
onDelete: ReferentialAction.SetNull); |
||||
|
||||
migrationBuilder.AddForeignKey( |
||||
name: "FK_ProgramScheduleItem_FillerPreset_MidRollFillerId", |
||||
table: "ProgramScheduleItem", |
||||
column: "MidRollFillerId", |
||||
principalTable: "FillerPreset", |
||||
principalColumn: "Id", |
||||
onDelete: ReferentialAction.SetNull); |
||||
|
||||
migrationBuilder.AddForeignKey( |
||||
name: "FK_ProgramScheduleItem_FillerPreset_PostRollFillerId", |
||||
table: "ProgramScheduleItem", |
||||
column: "PostRollFillerId", |
||||
principalTable: "FillerPreset", |
||||
principalColumn: "Id", |
||||
onDelete: ReferentialAction.SetNull); |
||||
|
||||
migrationBuilder.AddForeignKey( |
||||
name: "FK_ProgramScheduleItem_FillerPreset_PreRollFillerId", |
||||
table: "ProgramScheduleItem", |
||||
column: "PreRollFillerId", |
||||
principalTable: "FillerPreset", |
||||
principalColumn: "Id", |
||||
onDelete: ReferentialAction.SetNull); |
||||
|
||||
migrationBuilder.AddForeignKey( |
||||
name: "FK_ProgramScheduleItem_FillerPreset_TailFillerId", |
||||
table: "ProgramScheduleItem", |
||||
column: "TailFillerId", |
||||
principalTable: "FillerPreset", |
||||
principalColumn: "Id", |
||||
onDelete: ReferentialAction.SetNull); |
||||
} |
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder) |
||||
{ |
||||
migrationBuilder.DropForeignKey( |
||||
name: "FK_ProgramScheduleItem_FillerPreset_FallbackFillerId", |
||||
table: "ProgramScheduleItem"); |
||||
|
||||
migrationBuilder.DropForeignKey( |
||||
name: "FK_ProgramScheduleItem_FillerPreset_MidRollFillerId", |
||||
table: "ProgramScheduleItem"); |
||||
|
||||
migrationBuilder.DropForeignKey( |
||||
name: "FK_ProgramScheduleItem_FillerPreset_PostRollFillerId", |
||||
table: "ProgramScheduleItem"); |
||||
|
||||
migrationBuilder.DropForeignKey( |
||||
name: "FK_ProgramScheduleItem_FillerPreset_PreRollFillerId", |
||||
table: "ProgramScheduleItem"); |
||||
|
||||
migrationBuilder.DropForeignKey( |
||||
name: "FK_ProgramScheduleItem_FillerPreset_TailFillerId", |
||||
table: "ProgramScheduleItem"); |
||||
|
||||
migrationBuilder.DropIndex( |
||||
name: "IX_ProgramScheduleItem_TailFillerId", |
||||
table: "ProgramScheduleItem"); |
||||
|
||||
migrationBuilder.DropColumn( |
||||
name: "TailFillerId", |
||||
table: "ProgramScheduleItem"); |
||||
|
||||
migrationBuilder.AddForeignKey( |
||||
name: "FK_ProgramScheduleItem_FillerPreset_FallbackFillerId", |
||||
table: "ProgramScheduleItem", |
||||
column: "FallbackFillerId", |
||||
principalTable: "FillerPreset", |
||||
principalColumn: "Id", |
||||
onDelete: ReferentialAction.Cascade); |
||||
|
||||
migrationBuilder.AddForeignKey( |
||||
name: "FK_ProgramScheduleItem_FillerPreset_MidRollFillerId", |
||||
table: "ProgramScheduleItem", |
||||
column: "MidRollFillerId", |
||||
principalTable: "FillerPreset", |
||||
principalColumn: "Id", |
||||
onDelete: ReferentialAction.Cascade); |
||||
|
||||
migrationBuilder.AddForeignKey( |
||||
name: "FK_ProgramScheduleItem_FillerPreset_PostRollFillerId", |
||||
table: "ProgramScheduleItem", |
||||
column: "PostRollFillerId", |
||||
principalTable: "FillerPreset", |
||||
principalColumn: "Id", |
||||
onDelete: ReferentialAction.Cascade); |
||||
|
||||
migrationBuilder.AddForeignKey( |
||||
name: "FK_ProgramScheduleItem_FillerPreset_PreRollFillerId", |
||||
table: "ProgramScheduleItem", |
||||
column: "PreRollFillerId", |
||||
principalTable: "FillerPreset", |
||||
principalColumn: "Id", |
||||
onDelete: ReferentialAction.Cascade); |
||||
} |
||||
} |
||||
} |
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,35 @@
@@ -0,0 +1,35 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations; |
||||
|
||||
namespace ErsatzTV.Infrastructure.Migrations |
||||
{ |
||||
public partial class Migrate_TailFiller_FillerPreset : Migration |
||||
{ |
||||
protected override void Up(MigrationBuilder migrationBuilder) |
||||
{ |
||||
migrationBuilder.Sql( |
||||
@"
|
||||
insert into FillerPreset (Name, FillerKind, FillerMode, CollectionType, MediaItemId, CollectionId, MultiCollectionId, SmartCollectionId) |
||||
select |
||||
'Migrated_Filler_' || TailCollectionType || '_' || ifnull(TailCollectionId, '') || '_' || ifnull(TailMediaItemId, '') || '_' || ifnull(TailMultiCollectionId, '') || '_' || ifnull(TailSmartCollectionId, ''), |
||||
4, |
||||
0, |
||||
TailCollectionType, |
||||
TailMediaItemId, |
||||
TailCollectionId, |
||||
TailMultiCollectionId, |
||||
TailSmartCollectionId |
||||
from (select distinct TailCollectionType, TailCollectionId, TailMediaItemId, TailMultiCollectionId, TailSmartCollectionId from ProgramScheduleDurationItem)");
|
||||
|
||||
migrationBuilder.Sql( |
||||
@"
|
||||
update ProgramScheduleItem |
||||
set TailFillerId = FPID |
||||
from (select fp.Id as FPID, psdi.Id as PSDIID from FillerPreset fp inner join ProgramScheduleDurationItem psdi where TailCollectionType = CollectionType and TailCollectionId is CollectionId and TailMediaItemId is MediaItemId and TailMultiCollectionId is MultiCollectionId and TailSmartCollectionId is SmartCollectionId) as whatever |
||||
where PSDIID = ProgramScheduleItem.Id");
|
||||
} |
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder) |
||||
{ |
||||
} |
||||
} |
||||
} |
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,148 @@
@@ -0,0 +1,148 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations; |
||||
|
||||
namespace ErsatzTV.Infrastructure.Migrations |
||||
{ |
||||
public partial class Remove_DurationTailFiller : Migration |
||||
{ |
||||
protected override void Up(MigrationBuilder migrationBuilder) |
||||
{ |
||||
migrationBuilder.DropForeignKey( |
||||
name: "FK_ProgramScheduleDurationItem_Collection_TailCollectionId", |
||||
table: "ProgramScheduleDurationItem"); |
||||
|
||||
migrationBuilder.DropForeignKey( |
||||
name: "FK_ProgramScheduleDurationItem_MediaItem_TailMediaItemId", |
||||
table: "ProgramScheduleDurationItem"); |
||||
|
||||
migrationBuilder.DropForeignKey( |
||||
name: "FK_ProgramScheduleDurationItem_MultiCollection_TailMultiCollectionId", |
||||
table: "ProgramScheduleDurationItem"); |
||||
|
||||
migrationBuilder.DropForeignKey( |
||||
name: "FK_ProgramScheduleDurationItem_SmartCollection_TailSmartCollectionId", |
||||
table: "ProgramScheduleDurationItem"); |
||||
|
||||
migrationBuilder.DropIndex( |
||||
name: "IX_ProgramScheduleDurationItem_TailCollectionId", |
||||
table: "ProgramScheduleDurationItem"); |
||||
|
||||
migrationBuilder.DropIndex( |
||||
name: "IX_ProgramScheduleDurationItem_TailMediaItemId", |
||||
table: "ProgramScheduleDurationItem"); |
||||
|
||||
migrationBuilder.DropIndex( |
||||
name: "IX_ProgramScheduleDurationItem_TailMultiCollectionId", |
||||
table: "ProgramScheduleDurationItem"); |
||||
|
||||
migrationBuilder.DropIndex( |
||||
name: "IX_ProgramScheduleDurationItem_TailSmartCollectionId", |
||||
table: "ProgramScheduleDurationItem"); |
||||
|
||||
migrationBuilder.DropColumn( |
||||
name: "TailCollectionId", |
||||
table: "ProgramScheduleDurationItem"); |
||||
|
||||
migrationBuilder.DropColumn( |
||||
name: "TailCollectionType", |
||||
table: "ProgramScheduleDurationItem"); |
||||
|
||||
migrationBuilder.DropColumn( |
||||
name: "TailMediaItemId", |
||||
table: "ProgramScheduleDurationItem"); |
||||
|
||||
migrationBuilder.DropColumn( |
||||
name: "TailMultiCollectionId", |
||||
table: "ProgramScheduleDurationItem"); |
||||
|
||||
migrationBuilder.DropColumn( |
||||
name: "TailSmartCollectionId", |
||||
table: "ProgramScheduleDurationItem"); |
||||
} |
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder) |
||||
{ |
||||
migrationBuilder.AddColumn<int>( |
||||
name: "TailCollectionId", |
||||
table: "ProgramScheduleDurationItem", |
||||
type: "INTEGER", |
||||
nullable: true); |
||||
|
||||
migrationBuilder.AddColumn<int>( |
||||
name: "TailCollectionType", |
||||
table: "ProgramScheduleDurationItem", |
||||
type: "INTEGER", |
||||
nullable: false, |
||||
defaultValue: 0); |
||||
|
||||
migrationBuilder.AddColumn<int>( |
||||
name: "TailMediaItemId", |
||||
table: "ProgramScheduleDurationItem", |
||||
type: "INTEGER", |
||||
nullable: true); |
||||
|
||||
migrationBuilder.AddColumn<int>( |
||||
name: "TailMultiCollectionId", |
||||
table: "ProgramScheduleDurationItem", |
||||
type: "INTEGER", |
||||
nullable: true); |
||||
|
||||
migrationBuilder.AddColumn<int>( |
||||
name: "TailSmartCollectionId", |
||||
table: "ProgramScheduleDurationItem", |
||||
type: "INTEGER", |
||||
nullable: true); |
||||
|
||||
migrationBuilder.CreateIndex( |
||||
name: "IX_ProgramScheduleDurationItem_TailCollectionId", |
||||
table: "ProgramScheduleDurationItem", |
||||
column: "TailCollectionId"); |
||||
|
||||
migrationBuilder.CreateIndex( |
||||
name: "IX_ProgramScheduleDurationItem_TailMediaItemId", |
||||
table: "ProgramScheduleDurationItem", |
||||
column: "TailMediaItemId"); |
||||
|
||||
migrationBuilder.CreateIndex( |
||||
name: "IX_ProgramScheduleDurationItem_TailMultiCollectionId", |
||||
table: "ProgramScheduleDurationItem", |
||||
column: "TailMultiCollectionId"); |
||||
|
||||
migrationBuilder.CreateIndex( |
||||
name: "IX_ProgramScheduleDurationItem_TailSmartCollectionId", |
||||
table: "ProgramScheduleDurationItem", |
||||
column: "TailSmartCollectionId"); |
||||
|
||||
migrationBuilder.AddForeignKey( |
||||
name: "FK_ProgramScheduleDurationItem_Collection_TailCollectionId", |
||||
table: "ProgramScheduleDurationItem", |
||||
column: "TailCollectionId", |
||||
principalTable: "Collection", |
||||
principalColumn: "Id", |
||||
onDelete: ReferentialAction.Cascade); |
||||
|
||||
migrationBuilder.AddForeignKey( |
||||
name: "FK_ProgramScheduleDurationItem_MediaItem_TailMediaItemId", |
||||
table: "ProgramScheduleDurationItem", |
||||
column: "TailMediaItemId", |
||||
principalTable: "MediaItem", |
||||
principalColumn: "Id", |
||||
onDelete: ReferentialAction.Cascade); |
||||
|
||||
migrationBuilder.AddForeignKey( |
||||
name: "FK_ProgramScheduleDurationItem_MultiCollection_TailMultiCollectionId", |
||||
table: "ProgramScheduleDurationItem", |
||||
column: "TailMultiCollectionId", |
||||
principalTable: "MultiCollection", |
||||
principalColumn: "Id", |
||||
onDelete: ReferentialAction.Cascade); |
||||
|
||||
migrationBuilder.AddForeignKey( |
||||
name: "FK_ProgramScheduleDurationItem_SmartCollection_TailSmartCollectionId", |
||||
table: "ProgramScheduleDurationItem", |
||||
column: "TailSmartCollectionId", |
||||
principalTable: "SmartCollection", |
||||
principalColumn: "Id", |
||||
onDelete: ReferentialAction.Cascade); |
||||
} |
||||
} |
||||
} |
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,24 @@
@@ -0,0 +1,24 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations; |
||||
|
||||
namespace ErsatzTV.Infrastructure.Migrations |
||||
{ |
||||
public partial class Add_PlayoutItemIsFallback : Migration |
||||
{ |
||||
protected override void Up(MigrationBuilder migrationBuilder) |
||||
{ |
||||
migrationBuilder.AddColumn<bool>( |
||||
name: "IsFallback", |
||||
table: "PlayoutItem", |
||||
type: "INTEGER", |
||||
nullable: false, |
||||
defaultValue: false); |
||||
} |
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder) |
||||
{ |
||||
migrationBuilder.DropColumn( |
||||
name: "IsFallback", |
||||
table: "PlayoutItem"); |
||||
} |
||||
} |
||||
} |
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue