Browse Source

add rerun collection type (#2421)

* rename collection type

* split collections into separate pages

* add rerun collection types, migration, editor

* add rerun to classic schedule items

* rerun plumbing in classic playout builder

* start to implement rerun enumerator

* add scheduledAt to enumerator movenext

* maintain rerun history in db

* fix shuffle

* fix rerun allowed playback orders

* fix updating rerun collections

* update changelog; fix editing

* update changelog
pull/2422/head
Jason Dove 4 months ago committed by GitHub
parent
commit
245165c9d9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 12
      CHANGELOG.md
  2. 2
      ErsatzTV.Application/Filler/Commands/CreateFillerPreset.cs
  3. 2
      ErsatzTV.Application/Filler/Commands/UpdateFillerPreset.cs
  4. 2
      ErsatzTV.Application/Filler/FillerPresetViewModel.cs
  5. 2
      ErsatzTV.Application/MediaCollections/Commands/AddEpisodeToPlaylistHandler.cs
  6. 22
      ErsatzTV.Application/MediaCollections/Commands/AddItemsToPlaylistHandler.cs
  7. 2
      ErsatzTV.Application/MediaCollections/Commands/AddMovieToPlaylistHandler.cs
  8. 2
      ErsatzTV.Application/MediaCollections/Commands/AddSeasonToPlaylistHandler.cs
  9. 2
      ErsatzTV.Application/MediaCollections/Commands/AddShowToPlaylistHandler.cs
  10. 16
      ErsatzTV.Application/MediaCollections/Commands/CreateRerunCollection.cs
  11. 62
      ErsatzTV.Application/MediaCollections/Commands/CreateRerunCollectionHandler.cs
  12. 5
      ErsatzTV.Application/MediaCollections/Commands/DeleteRerunCollection.cs
  13. 41
      ErsatzTV.Application/MediaCollections/Commands/DeleteRerunCollectionHandler.cs
  14. 2
      ErsatzTV.Application/MediaCollections/Commands/PreviewPlaylistPlayoutHandler.cs
  15. 2
      ErsatzTV.Application/MediaCollections/Commands/ReplacePlaylistItem.cs
  16. 26
      ErsatzTV.Application/MediaCollections/Commands/ReplacePlaylistItemsHandler.cs
  17. 8
      ErsatzTV.Application/MediaCollections/Commands/TraktCommandBase.cs
  18. 17
      ErsatzTV.Application/MediaCollections/Commands/UpdateRerunCollection.cs
  19. 69
      ErsatzTV.Application/MediaCollections/Commands/UpdateRerunCollectionHandler.cs
  20. 26
      ErsatzTV.Application/MediaCollections/Mapper.cs
  21. 2
      ErsatzTV.Application/MediaCollections/MediaCollectionViewModel.cs
  22. 3
      ErsatzTV.Application/MediaCollections/PagedRerunCollectionsViewModel.cs
  23. 2
      ErsatzTV.Application/MediaCollections/PlaylistItemViewModel.cs
  24. 3
      ErsatzTV.Application/MediaCollections/Queries/GetPagedRerunCollections.cs
  25. 26
      ErsatzTV.Application/MediaCollections/Queries/GetPagedRerunCollectionsHandler.cs
  26. 10
      ErsatzTV.Application/MediaCollections/Queries/GetPagedSmartCollectionsHandler.cs
  27. 3
      ErsatzTV.Application/MediaCollections/Queries/GetRerunCollectionById.cs
  28. 36
      ErsatzTV.Application/MediaCollections/Queries/GetRerunCollectionByIdHandler.cs
  29. 15
      ErsatzTV.Application/MediaCollections/RerunCollectionViewModel.cs
  30. 16
      ErsatzTV.Application/Playouts/Commands/BuildPlayoutHandler.cs
  31. 6
      ErsatzTV.Application/ProgramSchedules/Commands/AddProgramScheduleItem.cs
  32. 3
      ErsatzTV.Application/ProgramSchedules/Commands/IProgramScheduleItemRequest.cs
  33. 34
      ErsatzTV.Application/ProgramSchedules/Commands/ProgramScheduleItemCommandBase.cs
  34. 3
      ErsatzTV.Application/ProgramSchedules/Commands/ReplaceProgramScheduleItems.cs
  35. 4
      ErsatzTV.Application/ProgramSchedules/Commands/ReplaceProgramScheduleItemsHandler.cs
  36. 12
      ErsatzTV.Application/ProgramSchedules/Mapper.cs
  37. 4
      ErsatzTV.Application/ProgramSchedules/ProgramScheduleItemDurationViewModel.cs
  38. 4
      ErsatzTV.Application/ProgramSchedules/ProgramScheduleItemFloodViewModel.cs
  39. 4
      ErsatzTV.Application/ProgramSchedules/ProgramScheduleItemMultipleViewModel.cs
  40. 4
      ErsatzTV.Application/ProgramSchedules/ProgramScheduleItemOneViewModel.cs
  41. 19
      ErsatzTV.Application/ProgramSchedules/ProgramScheduleItemViewModel.cs
  42. 7
      ErsatzTV.Application/ProgramSchedules/Queries/GetProgramScheduleItemsHandler.cs
  43. 2
      ErsatzTV.Application/Scheduling/BlockItemViewModel.cs
  44. 2
      ErsatzTV.Application/Scheduling/Commands/ReplaceBlockItem.cs
  45. 14
      ErsatzTV.Application/Scheduling/Commands/ReplaceBlockItemsHandler.cs
  46. 4
      ErsatzTV.Application/Scheduling/Commands/UpdateDeco.cs
  47. 12
      ErsatzTV.Application/Scheduling/Commands/UpdateDecoHandler.cs
  48. 4
      ErsatzTV.Application/Scheduling/DecoViewModel.cs
  49. 5
      ErsatzTV.Application/Search/Queries/SearchRerunCollections.cs
  50. 26
      ErsatzTV.Application/Search/Queries/SearchRerunCollectionsHandler.cs
  51. 2
      ErsatzTV.Application/Streaming/Queries/GetPlayoutItemProcessByChannelNumberHandler.cs
  52. 4
      ErsatzTV.Core.Tests/Fakes/FakeMediaCollectionRepository.cs
  53. 6
      ErsatzTV.Core.Tests/Scheduling/BlockScheduling/BlockPlayoutChangeDetectionTests.cs
  54. 6
      ErsatzTV.Core.Tests/Scheduling/ChronologicalContentTests.cs
  55. 6
      ErsatzTV.Core.Tests/Scheduling/ClassicScheduling/ContinuePlayoutTests.cs
  56. 22
      ErsatzTV.Core.Tests/Scheduling/ClassicScheduling/NewPlayoutTests.cs
  57. 10
      ErsatzTV.Core.Tests/Scheduling/ClassicScheduling/PlayoutBuilderTestBase.cs
  58. 4
      ErsatzTV.Core.Tests/Scheduling/ClassicScheduling/RefreshPlayoutTests.cs
  59. 6
      ErsatzTV.Core.Tests/Scheduling/CustomOrderContentTests.cs
  60. 36
      ErsatzTV.Core.Tests/Scheduling/PlaylistEnumeratorTests.cs
  61. 6
      ErsatzTV.Core.Tests/Scheduling/PlayoutModeSchedulerMultipleTests.cs
  62. 6
      ErsatzTV.Core.Tests/Scheduling/RandomizedContentTests.cs
  63. 2
      ErsatzTV.Core.Tests/Scheduling/ScheduleIntegrationTests.cs
  64. 8
      ErsatzTV.Core.Tests/Scheduling/SeasonEpisodeContentTests.cs
  65. 10
      ErsatzTV.Core.Tests/Scheduling/ShuffledContentTests.cs
  66. 2
      ErsatzTV.Core/Domain/Collection/PlaylistItem.cs
  67. 21
      ErsatzTV.Core/Domain/Collection/RerunCollection.cs
  68. 4
      ErsatzTV.Core/Domain/CollectionType.cs
  69. 1
      ErsatzTV.Core/Domain/ConfigElementKey.cs
  70. 2
      ErsatzTV.Core/Domain/Filler/FillerPreset.cs
  71. 4
      ErsatzTV.Core/Domain/PlayoutProgramScheduleAnchor.cs
  72. 4
      ErsatzTV.Core/Domain/ProgramScheduleItem.cs
  73. 2
      ErsatzTV.Core/Domain/Scheduling/BlockItem.cs
  74. 4
      ErsatzTV.Core/Domain/Scheduling/Deco.cs
  75. 13
      ErsatzTV.Core/Domain/Scheduling/RerunHistory.cs
  76. 2
      ErsatzTV.Core/Interfaces/Repositories/IMediaCollectionRepository.cs
  77. 2
      ErsatzTV.Core/Interfaces/Scheduling/IMediaCollectionEnumerator.cs
  78. 35
      ErsatzTV.Core/Interfaces/Scheduling/IRerunHelper.cs
  79. 2
      ErsatzTV.Core/Scheduling/BlockScheduling/BlockPlayoutBuilder.cs
  80. 14
      ErsatzTV.Core/Scheduling/BlockScheduling/BlockPlayoutFillerBuilder.cs
  81. 2
      ErsatzTV.Core/Scheduling/BlockScheduling/BlockPlayoutShuffledMediaCollectionEnumerator.cs
  82. 5
      ErsatzTV.Core/Scheduling/ChronologicalMediaCollectionEnumerator.cs
  83. 125
      ErsatzTV.Core/Scheduling/CollectionKey.cs
  84. 5
      ErsatzTV.Core/Scheduling/CustomOrderCollectionEnumerator.cs
  85. 20
      ErsatzTV.Core/Scheduling/Engine/MarathonHelper.cs
  86. 2
      ErsatzTV.Core/Scheduling/Engine/PlaylistHelper.cs
  87. 16
      ErsatzTV.Core/Scheduling/Engine/SchedulingEngine.cs
  88. 2
      ErsatzTV.Core/Scheduling/HistoryDetails.cs
  89. 34
      ErsatzTV.Core/Scheduling/MediaItemsForCollection.cs
  90. 6
      ErsatzTV.Core/Scheduling/PlaylistEnumerator.cs
  91. 4
      ErsatzTV.Core/Scheduling/PlayoutBuildResult.cs
  92. 41
      ErsatzTV.Core/Scheduling/PlayoutBuilder.cs
  93. 22
      ErsatzTV.Core/Scheduling/PlayoutModeSchedulerBase.cs
  94. 6
      ErsatzTV.Core/Scheduling/PlayoutModeSchedulerDuration.cs
  95. 2
      ErsatzTV.Core/Scheduling/PlayoutModeSchedulerFlood.cs
  96. 2
      ErsatzTV.Core/Scheduling/PlayoutModeSchedulerMultiple.cs
  97. 2
      ErsatzTV.Core/Scheduling/PlayoutModeSchedulerOne.cs
  98. 6
      ErsatzTV.Core/Scheduling/RandomizedMediaCollectionEnumerator.cs
  99. 6
      ErsatzTV.Core/Scheduling/RandomizedRotatingMediaCollectionEnumerator.cs
  100. 4
      ErsatzTV.Core/Scheduling/SeasonEpisodeMediaCollectionEnumerator.cs
  101. Some files were not shown because too many files have changed in this diff Show More

12
CHANGELOG.md

@ -4,6 +4,18 @@ All notable changes to this project will be documented in this file. @@ -4,6 +4,18 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
## [Unreleased]
### Added
- Add new collection type `Rerun Collection`
- This collection type will show up as *two* collection types in classic schedules
- `Rerun (First Run)`
- `Rerun (Rerun)`
- The playback order for each of these collection types can be set on the rerun collection itself
- e.g. `Season, Episode` order for first run, `Shuffle` for rerun
- When a first run item is added to a playout, it will immediately be made available in the rerun collection
- Rerun history is currently scoped to the playout, and only supported in classic schedules
- This means resetting the playout will reset the rerun history
- Items will still be scheduled from the rerun collection if it is used before the first run collection
- Otherwise, the rerun collection would be considered "empty" which prevents the playout build altogether
## [25.6.0] - 2025-09-14
### Added

2
ErsatzTV.Application/Filler/Commands/CreateFillerPreset.cs

@ -12,7 +12,7 @@ public record CreateFillerPreset( @@ -12,7 +12,7 @@ public record CreateFillerPreset(
int? Count,
int? PadToNearestMinute,
bool AllowWatermarks,
ProgramScheduleItemCollectionType CollectionType,
CollectionType CollectionType,
int? CollectionId,
int? MediaItemId,
int? MultiCollectionId,

2
ErsatzTV.Application/Filler/Commands/UpdateFillerPreset.cs

@ -13,7 +13,7 @@ public record UpdateFillerPreset( @@ -13,7 +13,7 @@ public record UpdateFillerPreset(
int? Count,
int? PadToNearestMinute,
bool AllowWatermarks,
ProgramScheduleItemCollectionType CollectionType,
CollectionType CollectionType,
int? CollectionId,
int? MediaItemId,
int? MultiCollectionId,

2
ErsatzTV.Application/Filler/FillerPresetViewModel.cs

@ -13,7 +13,7 @@ public record FillerPresetViewModel( @@ -13,7 +13,7 @@ public record FillerPresetViewModel(
int? Count,
int? PadToNearestMinute,
bool AllowWatermarks,
ProgramScheduleItemCollectionType CollectionType,
CollectionType CollectionType,
int? CollectionId,
int? MediaItemId,
int? MultiCollectionId,

2
ErsatzTV.Application/MediaCollections/Commands/AddEpisodeToPlaylistHandler.cs

@ -25,7 +25,7 @@ public class AddEpisodeToPlaylistHandler(IDbContextFactory<TvContext> dbContextF @@ -25,7 +25,7 @@ public class AddEpisodeToPlaylistHandler(IDbContextFactory<TvContext> dbContextF
var playlistItem = new PlaylistItem
{
Index = index,
CollectionType = ProgramScheduleItemCollectionType.Episode,
CollectionType = CollectionType.Episode,
MediaItemId = parameters.Episode.Id,
PlaybackOrder = PlaybackOrder.Shuffle,
IncludeInProgramGuide = true

22
ErsatzTV.Application/MediaCollections/Commands/AddItemsToPlaylistHandler.cs

@ -36,22 +36,22 @@ public class AddItemsToPlaylistHandler : IRequestHandler<AddItemsToPlaylist, Eit @@ -36,22 +36,22 @@ public class AddItemsToPlaylistHandler : IRequestHandler<AddItemsToPlaylist, Eit
AddItemsToPlaylist request,
CancellationToken cancellationToken)
{
var allItems = new Dictionary<ProgramScheduleItemCollectionType, List<int>>
var allItems = new Dictionary<CollectionType, List<int>>
{
{ ProgramScheduleItemCollectionType.Movie, request.MovieIds },
{ ProgramScheduleItemCollectionType.TelevisionShow, request.ShowIds },
{ ProgramScheduleItemCollectionType.TelevisionSeason, request.SeasonIds },
{ ProgramScheduleItemCollectionType.Episode, request.EpisodeIds },
{ ProgramScheduleItemCollectionType.Artist, request.ArtistIds },
{ ProgramScheduleItemCollectionType.MusicVideo, request.MusicVideoIds },
{ ProgramScheduleItemCollectionType.OtherVideo, request.OtherVideoIds },
{ ProgramScheduleItemCollectionType.Song, request.SongIds },
{ ProgramScheduleItemCollectionType.Image, request.ImageIds }
{ CollectionType.Movie, request.MovieIds },
{ CollectionType.TelevisionShow, request.ShowIds },
{ CollectionType.TelevisionSeason, request.SeasonIds },
{ CollectionType.Episode, request.EpisodeIds },
{ CollectionType.Artist, request.ArtistIds },
{ CollectionType.MusicVideo, request.MusicVideoIds },
{ CollectionType.OtherVideo, request.OtherVideoIds },
{ CollectionType.Song, request.SongIds },
{ CollectionType.Image, request.ImageIds }
};
int index = playlist.Items.Count > 0 ? playlist.Items.Max(i => i.Index) + 1 : 0;
foreach ((ProgramScheduleItemCollectionType collectionType, List<int> ids) in allItems)
foreach ((CollectionType collectionType, List<int> ids) in allItems)
{
foreach (int id in ids)
{

2
ErsatzTV.Application/MediaCollections/Commands/AddMovieToPlaylistHandler.cs

@ -25,7 +25,7 @@ public class AddMovieToPlaylistHandler(IDbContextFactory<TvContext> dbContextFac @@ -25,7 +25,7 @@ public class AddMovieToPlaylistHandler(IDbContextFactory<TvContext> dbContextFac
var playlistItem = new PlaylistItem
{
Index = index,
CollectionType = ProgramScheduleItemCollectionType.Movie,
CollectionType = CollectionType.Movie,
MediaItemId = parameters.Movie.Id,
PlaybackOrder = PlaybackOrder.Shuffle,
IncludeInProgramGuide = true

2
ErsatzTV.Application/MediaCollections/Commands/AddSeasonToPlaylistHandler.cs

@ -25,7 +25,7 @@ public class AddSeasonToPlaylistHandler(IDbContextFactory<TvContext> dbContextFa @@ -25,7 +25,7 @@ public class AddSeasonToPlaylistHandler(IDbContextFactory<TvContext> dbContextFa
var playlistItem = new PlaylistItem
{
Index = index,
CollectionType = ProgramScheduleItemCollectionType.TelevisionSeason,
CollectionType = CollectionType.TelevisionSeason,
MediaItemId = parameters.Season.Id,
PlaybackOrder = PlaybackOrder.Shuffle,
IncludeInProgramGuide = true

2
ErsatzTV.Application/MediaCollections/Commands/AddShowToPlaylistHandler.cs

@ -25,7 +25,7 @@ public class AddShowToPlaylistHandler(IDbContextFactory<TvContext> dbContextFact @@ -25,7 +25,7 @@ public class AddShowToPlaylistHandler(IDbContextFactory<TvContext> dbContextFact
var playlistItem = new PlaylistItem
{
Index = index,
CollectionType = ProgramScheduleItemCollectionType.TelevisionShow,
CollectionType = CollectionType.TelevisionShow,
MediaItemId = parameters.Show.Id,
PlaybackOrder = PlaybackOrder.Shuffle,
IncludeInProgramGuide = true

16
ErsatzTV.Application/MediaCollections/Commands/CreateRerunCollection.cs

@ -0,0 +1,16 @@ @@ -0,0 +1,16 @@
using ErsatzTV.Application.MediaItems;
using ErsatzTV.Core;
using ErsatzTV.Core.Domain;
namespace ErsatzTV.Application.MediaCollections;
public record CreateRerunCollection(
string Name,
CollectionType CollectionType,
MediaCollectionViewModel Collection,
MultiCollectionViewModel MultiCollection,
SmartCollectionViewModel SmartCollection,
NamedMediaItemViewModel MediaItem,
PlaybackOrder FirstRunPlaybackOrder,
PlaybackOrder RerunPlaybackOrder)
: IRequest<Either<BaseError, RerunCollectionViewModel>>;

62
ErsatzTV.Application/MediaCollections/Commands/CreateRerunCollectionHandler.cs

@ -0,0 +1,62 @@ @@ -0,0 +1,62 @@
using ErsatzTV.Core;
using ErsatzTV.Core.Domain;
using ErsatzTV.Infrastructure.Data;
using Microsoft.EntityFrameworkCore;
using static ErsatzTV.Application.MediaCollections.Mapper;
namespace ErsatzTV.Application.MediaCollections;
public class CreateRerunCollectionHandler(IDbContextFactory<TvContext> dbContextFactory) :
IRequestHandler<CreateRerunCollection, Either<BaseError, RerunCollectionViewModel>>
{
public async Task<Either<BaseError, RerunCollectionViewModel>> Handle(
CreateRerunCollection request,
CancellationToken cancellationToken)
{
await using TvContext dbContext = await dbContextFactory.CreateDbContextAsync(cancellationToken);
Validation<BaseError, RerunCollection> validation = await Validate(dbContext, request);
return await validation.Apply(c => PersistCollection(dbContext, c));
}
private static async Task<RerunCollectionViewModel> PersistCollection(
TvContext dbContext,
RerunCollection collection)
{
await dbContext.RerunCollections.AddAsync(collection);
await dbContext.SaveChangesAsync();
return ProjectToViewModel(collection);
}
private static Task<Validation<BaseError, RerunCollection>> Validate(
TvContext dbContext,
CreateRerunCollection request) =>
ValidateName(dbContext, request).MapT(name => new RerunCollection
{
Name = name,
CollectionType = request.CollectionType,
CollectionId = request.Collection?.Id,
MultiCollectionId = request.MultiCollection?.Id,
SmartCollectionId = request.SmartCollection?.Id,
MediaItemId = request.MediaItem?.MediaItemId,
FirstRunPlaybackOrder = request.FirstRunPlaybackOrder,
RerunPlaybackOrder = request.RerunPlaybackOrder
});
private static async Task<Validation<BaseError, string>> ValidateName(
TvContext dbContext,
CreateRerunCollection createCollection)
{
List<string> allNames = await dbContext.RerunCollections
.Map(c => c.Name)
.ToListAsync();
Validation<BaseError, string> result1 = createCollection.NotEmpty(c => c.Name)
.Bind(_ => createCollection.NotLongerThan(50)(c => c.Name));
var result2 = Optional(createCollection.Name)
.Where(name => !allNames.Contains(name))
.ToValidation<BaseError>("Rerun collection name must be unique");
return (result1, result2).Apply((_, _) => createCollection.Name);
}
}

5
ErsatzTV.Application/MediaCollections/Commands/DeleteRerunCollection.cs

@ -0,0 +1,5 @@ @@ -0,0 +1,5 @@
using ErsatzTV.Core;
namespace ErsatzTV.Application.MediaCollections;
public record DeleteRerunCollection(int RerunCollectionId) : IRequest<Either<BaseError, Unit>>;

41
ErsatzTV.Application/MediaCollections/Commands/DeleteRerunCollectionHandler.cs

@ -0,0 +1,41 @@ @@ -0,0 +1,41 @@
using ErsatzTV.Core;
using ErsatzTV.Core.Domain;
using ErsatzTV.Infrastructure.Data;
using ErsatzTV.Infrastructure.Extensions;
using Microsoft.EntityFrameworkCore;
namespace ErsatzTV.Application.MediaCollections;
public class DeleteRerunCollectionHandler(IDbContextFactory<TvContext> dbContextFactory)
: IRequestHandler<DeleteRerunCollection, Either<BaseError, Unit>>
{
public async Task<Either<BaseError, Unit>> Handle(
DeleteRerunCollection request,
CancellationToken cancellationToken)
{
await using TvContext dbContext = await dbContextFactory.CreateDbContextAsync(cancellationToken);
Validation<BaseError, RerunCollection> validation = await RerunCollectionMustExist(
dbContext,
request,
cancellationToken);
return await validation.Apply(c => DoDeletion(dbContext, c, cancellationToken));
}
private static async Task<Unit> DoDeletion(
TvContext dbContext,
RerunCollection rerunCollection,
CancellationToken cancellationToken)
{
dbContext.RerunCollections.Remove(rerunCollection);
await dbContext.SaveChangesAsync(cancellationToken);
return Unit.Default;
}
private static Task<Validation<BaseError, RerunCollection>> RerunCollectionMustExist(
TvContext dbContext,
DeleteRerunCollection request,
CancellationToken cancellationToken) =>
dbContext.RerunCollections
.SelectOneAsync(c => c.Id, c => c.Id == request.RerunCollectionId, cancellationToken)
.Map(o => o.ToValidation<BaseError>($"Rerun collection {request.RerunCollectionId} does not exist."));
}

2
ErsatzTV.Application/MediaCollections/Commands/PreviewPlaylistPlayoutHandler.cs

@ -120,7 +120,7 @@ public class PreviewPlaylistPlayoutHandler( @@ -120,7 +120,7 @@ public class PreviewPlaylistPlayoutHandler(
private static ProgramScheduleItemFlood MapToScheduleItem(PreviewPlaylistPlayout request) =>
new()
{
CollectionType = ProgramScheduleItemCollectionType.Playlist,
CollectionType = CollectionType.Playlist,
Playlist = new Playlist
{
Items = request.Data.Items.OrderBy(i => i.Index).Map(MapToPlaylistItem).ToList()

2
ErsatzTV.Application/MediaCollections/Commands/ReplacePlaylistItem.cs

@ -4,7 +4,7 @@ namespace ErsatzTV.Application.MediaCollections; @@ -4,7 +4,7 @@ namespace ErsatzTV.Application.MediaCollections;
public record ReplacePlaylistItem(
int Index,
ProgramScheduleItemCollectionType CollectionType,
CollectionType CollectionType,
int? CollectionId,
int? MultiCollectionId,
int? SmartCollectionId,

26
ErsatzTV.Application/MediaCollections/Commands/ReplacePlaylistItemsHandler.cs

@ -78,61 +78,61 @@ public class ReplacePlaylistItemsHandler(IDbContextFactory<TvContext> dbContextF @@ -78,61 +78,61 @@ public class ReplacePlaylistItemsHandler(IDbContextFactory<TvContext> dbContextF
{
switch (item.CollectionType)
{
case ProgramScheduleItemCollectionType.Collection:
case CollectionType.Collection:
if (item.CollectionId is null)
{
return BaseError.New("[Collection] is required for collection type 'Collection'");
}
break;
case ProgramScheduleItemCollectionType.TelevisionShow:
case CollectionType.TelevisionShow:
if (item.MediaItemId is null)
{
return BaseError.New("[MediaItem] is required for collection type 'TelevisionShow'");
}
break;
case ProgramScheduleItemCollectionType.TelevisionSeason:
case CollectionType.TelevisionSeason:
if (item.MediaItemId is null)
{
return BaseError.New("[MediaItem] is required for collection type 'TelevisionSeason'");
}
break;
case ProgramScheduleItemCollectionType.Artist:
case CollectionType.Artist:
if (item.MediaItemId is null)
{
return BaseError.New("[MediaItem] is required for collection type 'Artist'");
}
break;
case ProgramScheduleItemCollectionType.MultiCollection:
case CollectionType.MultiCollection:
if (item.MultiCollectionId is null)
{
return BaseError.New("[MultiCollection] is required for collection type 'MultiCollection'");
}
break;
case ProgramScheduleItemCollectionType.SmartCollection:
case CollectionType.SmartCollection:
if (item.SmartCollectionId is null)
{
return BaseError.New("[SmartCollection] is required for collection type 'SmartCollection'");
}
break;
case ProgramScheduleItemCollectionType.Movie:
case ProgramScheduleItemCollectionType.Episode:
case ProgramScheduleItemCollectionType.MusicVideo:
case ProgramScheduleItemCollectionType.OtherVideo:
case ProgramScheduleItemCollectionType.Song:
case ProgramScheduleItemCollectionType.Image:
case CollectionType.Movie:
case CollectionType.Episode:
case CollectionType.MusicVideo:
case CollectionType.OtherVideo:
case CollectionType.Song:
case CollectionType.Image:
if (item.MediaItemId is null)
{
return BaseError.New($"[MediaItem] is required for type '{item.CollectionType}'");
}
break;
case ProgramScheduleItemCollectionType.FakeCollection:
case CollectionType.FakeCollection:
default:
return BaseError.New("[CollectionType] is invalid");
}

8
ErsatzTV.Application/MediaCollections/Commands/TraktCommandBase.cs

@ -203,10 +203,10 @@ public abstract class TraktCommandBase @@ -203,10 +203,10 @@ public abstract class TraktCommandBase
playlistItem.CollectionType = item.Kind switch
{
TraktListItemKind.Movie => ProgramScheduleItemCollectionType.Movie,
TraktListItemKind.Show => ProgramScheduleItemCollectionType.TelevisionShow,
TraktListItemKind.Season => ProgramScheduleItemCollectionType.TelevisionSeason,
_ => ProgramScheduleItemCollectionType.Episode
TraktListItemKind.Movie => CollectionType.Movie,
TraktListItemKind.Show => CollectionType.TelevisionShow,
TraktListItemKind.Season => CollectionType.TelevisionSeason,
_ => CollectionType.Episode
};
playlistItem.MediaItemId = item.MediaItemId;

17
ErsatzTV.Application/MediaCollections/Commands/UpdateRerunCollection.cs

@ -0,0 +1,17 @@ @@ -0,0 +1,17 @@
using ErsatzTV.Application.MediaItems;
using ErsatzTV.Core;
using ErsatzTV.Core.Domain;
namespace ErsatzTV.Application.MediaCollections;
public record UpdateRerunCollection(
int RerunCollectionId,
string Name,
CollectionType CollectionType,
MediaCollectionViewModel Collection,
MultiCollectionViewModel MultiCollection,
SmartCollectionViewModel SmartCollection,
NamedMediaItemViewModel MediaItem,
PlaybackOrder FirstRunPlaybackOrder,
PlaybackOrder RerunPlaybackOrder)
: IRequest<Either<BaseError, Unit>>;

69
ErsatzTV.Application/MediaCollections/Commands/UpdateRerunCollectionHandler.cs

@ -0,0 +1,69 @@ @@ -0,0 +1,69 @@
using System.Threading.Channels;
using ErsatzTV.Application.Playouts;
using ErsatzTV.Core;
using ErsatzTV.Core.Domain;
using ErsatzTV.Core.Interfaces.Repositories;
using ErsatzTV.Core.Scheduling;
using ErsatzTV.Infrastructure.Data;
using ErsatzTV.Infrastructure.Extensions;
using Microsoft.EntityFrameworkCore;
namespace ErsatzTV.Application.MediaCollections;
public class UpdateRerunCollectionHandler(
IDbContextFactory<TvContext> dbContextFactory,
IMediaCollectionRepository mediaCollectionRepository,
ChannelWriter<IBackgroundServiceRequest> channel)
: IRequestHandler<UpdateRerunCollection, Either<BaseError, Unit>>
{
public async Task<Either<BaseError, Unit>> Handle(
UpdateRerunCollection request,
CancellationToken cancellationToken)
{
await using TvContext dbContext = await dbContextFactory.CreateDbContextAsync(cancellationToken);
Validation<BaseError, RerunCollection> validation = await Validate(dbContext, request, cancellationToken);
return await validation.Apply(c => ApplyUpdateRequest(dbContext, c, request, cancellationToken));
}
private async Task<Unit> ApplyUpdateRequest(
TvContext dbContext,
RerunCollection c,
UpdateRerunCollection request,
CancellationToken cancellationToken)
{
c.Name = request.Name;
c.CollectionType = request.CollectionType;
c.CollectionId = request.Collection?.Id;
c.MultiCollectionId = request.MultiCollection?.Id;
c.SmartCollectionId = request.SmartCollection?.Id;
c.MediaItemId = request.MediaItem?.MediaItemId;
c.FirstRunPlaybackOrder = request.FirstRunPlaybackOrder;
c.RerunPlaybackOrder = request.RerunPlaybackOrder;
// rebuild playouts
if (await dbContext.SaveChangesAsync(cancellationToken) > 0)
{
// refresh all playouts that use this rerun collection
foreach (int playoutId in await mediaCollectionRepository.PlayoutIdsUsingRerunCollection(
request.RerunCollectionId))
{
await channel.WriteAsync(new BuildPlayout(playoutId, PlayoutBuildMode.Refresh), cancellationToken);
}
}
return Unit.Default;
}
private static Task<Validation<BaseError, RerunCollection>> Validate(
TvContext dbContext,
UpdateRerunCollection request,
CancellationToken cancellationToken) => RerunCollectionMustExist(dbContext, request, cancellationToken);
private static Task<Validation<BaseError, RerunCollection>> RerunCollectionMustExist(
TvContext dbContext,
UpdateRerunCollection updateCollection,
CancellationToken cancellationToken) =>
dbContext.RerunCollections
.SelectOneAsync(c => c.Id, c => c.Id == updateCollection.RerunCollectionId, cancellationToken)
.Map(o => o.ToValidation<BaseError>("Rerun collection does not exist."));
}

26
ErsatzTV.Application/MediaCollections/Mapper.cs

@ -7,7 +7,7 @@ internal static class Mapper @@ -7,7 +7,7 @@ internal static class Mapper
{
internal static MediaCollectionViewModel ProjectToViewModel(Collection collection) =>
new(
ProgramScheduleItemCollectionType.Collection,
CollectionType.Collection,
collection.Id,
collection.Name,
collection.UseCustomPlaybackOrder,
@ -23,6 +23,30 @@ internal static class Mapper @@ -23,6 +23,30 @@ internal static class Mapper
internal static SmartCollectionViewModel ProjectToViewModel(SmartCollection collection) =>
new(collection.Id, collection.Name, collection.Query);
internal static RerunCollectionViewModel ProjectToViewModel(RerunCollection collection) =>
new(
collection.Id,
collection.Name,
collection.CollectionType,
collection.Collection is not null ? ProjectToViewModel(collection.Collection) : null,
collection.MultiCollection is not null ? ProjectToViewModel(collection.MultiCollection) : null,
collection.SmartCollection is not null ? ProjectToViewModel(collection.SmartCollection) : null,
collection.MediaItem switch
{
Show show => MediaItems.Mapper.ProjectToViewModel(show),
Season season => MediaItems.Mapper.ProjectToViewModel(season),
Artist artist => MediaItems.Mapper.ProjectToViewModel(artist),
Movie movie => MediaItems.Mapper.ProjectToViewModel(movie),
Episode episode => MediaItems.Mapper.ProjectToViewModel(episode),
MusicVideo musicVideo => MediaItems.Mapper.ProjectToViewModel(musicVideo),
OtherVideo otherVideo => MediaItems.Mapper.ProjectToViewModel(otherVideo),
Song song => MediaItems.Mapper.ProjectToViewModel(song),
Image image => MediaItems.Mapper.ProjectToViewModel(image),
_ => null
},
collection.FirstRunPlaybackOrder,
collection.RerunPlaybackOrder);
internal static TraktListViewModel ProjectToViewModel(TraktList traktList) =>
new(
traktList.Id,

2
ErsatzTV.Application/MediaCollections/MediaCollectionViewModel.cs

@ -4,7 +4,7 @@ using ErsatzTV.Core.Domain; @@ -4,7 +4,7 @@ using ErsatzTV.Core.Domain;
namespace ErsatzTV.Application.MediaCollections;
public record MediaCollectionViewModel(
ProgramScheduleItemCollectionType CollectionType,
CollectionType CollectionType,
int Id,
string Name,
bool UseCustomPlaybackOrder,

3
ErsatzTV.Application/MediaCollections/PagedRerunCollectionsViewModel.cs

@ -0,0 +1,3 @@ @@ -0,0 +1,3 @@
namespace ErsatzTV.Application.MediaCollections;
public record PagedRerunCollectionsViewModel(int TotalCount, List<RerunCollectionViewModel> Page);

2
ErsatzTV.Application/MediaCollections/PlaylistItemViewModel.cs

@ -6,7 +6,7 @@ namespace ErsatzTV.Application.MediaCollections; @@ -6,7 +6,7 @@ namespace ErsatzTV.Application.MediaCollections;
public record PlaylistItemViewModel(
int Id,
int Index,
ProgramScheduleItemCollectionType CollectionType,
CollectionType CollectionType,
MediaCollectionViewModel Collection,
MultiCollectionViewModel MultiCollection,
SmartCollectionViewModel SmartCollection,

3
ErsatzTV.Application/MediaCollections/Queries/GetPagedRerunCollections.cs

@ -0,0 +1,3 @@ @@ -0,0 +1,3 @@
namespace ErsatzTV.Application.MediaCollections;
public record GetPagedRerunCollections(int PageNum, int PageSize) : IRequest<PagedRerunCollectionsViewModel>;

26
ErsatzTV.Application/MediaCollections/Queries/GetPagedRerunCollectionsHandler.cs

@ -0,0 +1,26 @@ @@ -0,0 +1,26 @@
using ErsatzTV.Infrastructure.Data;
using Microsoft.EntityFrameworkCore;
using static ErsatzTV.Application.MediaCollections.Mapper;
namespace ErsatzTV.Application.MediaCollections;
public class GetPagedRerunCollectionsHandler(IDbContextFactory<TvContext> dbContextFactory)
: IRequestHandler<GetPagedRerunCollections, PagedRerunCollectionsViewModel>
{
public async Task<PagedRerunCollectionsViewModel> Handle(
GetPagedRerunCollections request,
CancellationToken cancellationToken)
{
await using TvContext dbContext = await dbContextFactory.CreateDbContextAsync(cancellationToken);
int count = await dbContext.RerunCollections.CountAsync(cancellationToken);
List<RerunCollectionViewModel> page = await dbContext.RerunCollections
.AsNoTracking()
.OrderBy(f => EF.Functions.Collate(f.Name, TvContext.CaseInsensitiveCollation))
.Skip(request.PageNum * request.PageSize)
.Take(request.PageSize)
.ToListAsync(cancellationToken)
.Map(list => list.Map(ProjectToViewModel).ToList());
return new PagedRerunCollectionsViewModel(count, page);
}
}

10
ErsatzTV.Application/MediaCollections/Queries/GetPagedSmartCollectionsHandler.cs

@ -4,18 +4,14 @@ using static ErsatzTV.Application.MediaCollections.Mapper; @@ -4,18 +4,14 @@ using static ErsatzTV.Application.MediaCollections.Mapper;
namespace ErsatzTV.Application.MediaCollections;
public class GetPagedSmartCollectionsHandler : IRequestHandler<GetPagedSmartCollections, PagedSmartCollectionsViewModel>
public class GetPagedSmartCollectionsHandler(IDbContextFactory<TvContext> dbContextFactory)
: IRequestHandler<GetPagedSmartCollections, PagedSmartCollectionsViewModel>
{
private readonly IDbContextFactory<TvContext> _dbContextFactory;
public GetPagedSmartCollectionsHandler(IDbContextFactory<TvContext> dbContextFactory) =>
_dbContextFactory = dbContextFactory;
public async Task<PagedSmartCollectionsViewModel> Handle(
GetPagedSmartCollections request,
CancellationToken cancellationToken)
{
await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(cancellationToken);
await using TvContext dbContext = await dbContextFactory.CreateDbContextAsync(cancellationToken);
int count = await dbContext.SmartCollections.CountAsync(cancellationToken);
List<SmartCollectionViewModel> page = await dbContext.SmartCollections
.AsNoTracking()

3
ErsatzTV.Application/MediaCollections/Queries/GetRerunCollectionById.cs

@ -0,0 +1,3 @@ @@ -0,0 +1,3 @@
namespace ErsatzTV.Application.MediaCollections;
public record GetRerunCollectionById(int Id) : IRequest<Option<RerunCollectionViewModel>>;

36
ErsatzTV.Application/MediaCollections/Queries/GetRerunCollectionByIdHandler.cs

@ -0,0 +1,36 @@ @@ -0,0 +1,36 @@
using ErsatzTV.Core.Domain;
using ErsatzTV.Infrastructure.Data;
using ErsatzTV.Infrastructure.Extensions;
using Microsoft.EntityFrameworkCore;
using static ErsatzTV.Application.MediaCollections.Mapper;
namespace ErsatzTV.Application.MediaCollections;
public class GetRerunCollectionByIdHandler(IDbContextFactory<TvContext> dbContextFactory) :
IRequestHandler<GetRerunCollectionById, Option<RerunCollectionViewModel>>
{
public async Task<Option<RerunCollectionViewModel>> Handle(
GetRerunCollectionById request,
CancellationToken cancellationToken)
{
await using TvContext dbContext = await dbContextFactory.CreateDbContextAsync(cancellationToken);
return await dbContext.RerunCollections
.AsNoTracking()
.Include(c => c.Collection)
.Include(c => c.MultiCollection)
.Include(c => c.SmartCollection)
.Include(i => i.MediaItem)
.ThenInclude(i => (i as Movie).MovieMetadata)
.Include(i => i.MediaItem)
.ThenInclude(i => (i as Season).SeasonMetadata)
.Include(i => i.MediaItem)
.ThenInclude(i => (i as Season).Show)
.ThenInclude(s => s.ShowMetadata)
.Include(i => i.MediaItem)
.ThenInclude(i => (i as Show).ShowMetadata)
.Include(i => i.MediaItem)
.ThenInclude(i => (i as Artist).ArtistMetadata)
.SelectOneAsync(c => c.Id, c => c.Id == request.Id, cancellationToken)
.MapT(ProjectToViewModel);
}
}

15
ErsatzTV.Application/MediaCollections/RerunCollectionViewModel.cs

@ -0,0 +1,15 @@ @@ -0,0 +1,15 @@
using ErsatzTV.Application.MediaItems;
using ErsatzTV.Core.Domain;
namespace ErsatzTV.Application.MediaCollections;
public record RerunCollectionViewModel(
int Id,
string Name,
CollectionType CollectionType,
MediaCollectionViewModel Collection,
MultiCollectionViewModel MultiCollection,
SmartCollectionViewModel SmartCollection,
NamedMediaItemViewModel MediaItem,
PlaybackOrder FirstRunPlaybackOrder,
PlaybackOrder RerunPlaybackOrder);

16
ErsatzTV.Application/Playouts/Commands/BuildPlayoutHandler.cs

@ -28,6 +28,7 @@ public class BuildPlayoutHandler : IRequestHandler<BuildPlayout, Either<BaseErro @@ -28,6 +28,7 @@ public class BuildPlayoutHandler : IRequestHandler<BuildPlayout, Either<BaseErro
private readonly IFFmpegSegmenterService _ffmpegSegmenterService;
private readonly IPlayoutBuilder _playoutBuilder;
private readonly IPlayoutTimeShifter _playoutTimeShifter;
private readonly IRerunHelper _rerunHelper;
private readonly ChannelWriter<IBackgroundServiceRequest> _workerChannel;
private readonly ISequentialPlayoutBuilder _sequentialPlayoutBuilder;
private readonly IScriptedPlayoutBuilder _scriptedPlayoutBuilder;
@ -44,6 +45,7 @@ public class BuildPlayoutHandler : IRequestHandler<BuildPlayout, Either<BaseErro @@ -44,6 +45,7 @@ public class BuildPlayoutHandler : IRequestHandler<BuildPlayout, Either<BaseErro
IFFmpegSegmenterService ffmpegSegmenterService,
IEntityLocker entityLocker,
IPlayoutTimeShifter playoutTimeShifter,
IRerunHelper rerunHelper,
ChannelWriter<IBackgroundServiceRequest> workerChannel)
{
_client = client;
@ -57,6 +59,7 @@ public class BuildPlayoutHandler : IRequestHandler<BuildPlayout, Either<BaseErro @@ -57,6 +59,7 @@ public class BuildPlayoutHandler : IRequestHandler<BuildPlayout, Either<BaseErro
_ffmpegSegmenterService = ffmpegSegmenterService;
_entityLocker = entityLocker;
_playoutTimeShifter = playoutTimeShifter;
_rerunHelper = rerunHelper;
_workerChannel = workerChannel;
}
@ -160,6 +163,19 @@ public class BuildPlayoutHandler : IRequestHandler<BuildPlayout, Either<BaseErro @@ -160,6 +163,19 @@ public class BuildPlayoutHandler : IRequestHandler<BuildPlayout, Either<BaseErro
var changeCount = 0;
if (result.RerunHistoryToRemove.Count > 0)
{
changeCount += await dbContext.RerunHistory
.Where(rh => result.RerunHistoryToRemove.Contains(rh.Id))
.ExecuteDeleteAsync(cancellationToken);
}
if (result.AddedRerunHistory.Count > 0)
{
changeCount += 1;
await dbContext.BulkInsertAsync(result.AddedRerunHistory, cancellationToken: cancellationToken);
}
if (result.ClearItems)
{
changeCount += await dbContext.PlayoutItems

6
ErsatzTV.Application/ProgramSchedules/Commands/AddProgramScheduleItem.cs

@ -10,10 +10,11 @@ public record AddProgramScheduleItem( @@ -10,10 +10,11 @@ public record AddProgramScheduleItem(
TimeSpan? StartTime,
FixedStartTimeBehavior? FixedStartTimeBehavior,
PlayoutMode PlayoutMode,
ProgramScheduleItemCollectionType CollectionType,
CollectionType CollectionType,
int? CollectionId,
int? MultiCollectionId,
int? SmartCollectionId,
int? RerunCollectionId,
int? MediaItemId,
int? PlaylistId,
PlaybackOrder PlaybackOrder,
@ -42,7 +43,7 @@ public record AddProgramScheduleItem( @@ -42,7 +43,7 @@ public record AddProgramScheduleItem(
ChannelSubtitleMode? SubtitleMode) : IRequest<Either<BaseError, ProgramScheduleItemViewModel>>,
IProgramScheduleItemRequest
{
public static AddProgramScheduleItem ForMediaItem(int scheduleId, ProgramScheduleItemCollectionType collectionType, int mediaItemId)
public static AddProgramScheduleItem ForMediaItem(int scheduleId, CollectionType collectionType, int mediaItemId)
=> new(
scheduleId,
StartType.Dynamic,
@ -53,6 +54,7 @@ public record AddProgramScheduleItem( @@ -53,6 +54,7 @@ public record AddProgramScheduleItem(
CollectionId: null,
MultiCollectionId: null,
SmartCollectionId: null,
RerunCollectionId: null,
MediaItemId: mediaItemId,
PlaylistId: null,
PlaybackOrder.Shuffle,

3
ErsatzTV.Application/ProgramSchedules/Commands/IProgramScheduleItemRequest.cs

@ -7,10 +7,11 @@ public interface IProgramScheduleItemRequest @@ -7,10 +7,11 @@ public interface IProgramScheduleItemRequest
{
TimeSpan? StartTime { get; }
FixedStartTimeBehavior? FixedStartTimeBehavior { get; }
ProgramScheduleItemCollectionType CollectionType { get; }
CollectionType CollectionType { get; }
int? CollectionId { get; }
int? MultiCollectionId { get; }
int? SmartCollectionId { get; }
int? RerunCollectionId { get; }
int? MediaItemId { get; }
int? PlaylistId { get; }
PlayoutMode PlayoutMode { get; }

34
ErsatzTV.Application/ProgramSchedules/Commands/ProgramScheduleItemCommandBase.cs

@ -73,7 +73,7 @@ public abstract class ProgramScheduleItemCommandBase @@ -73,7 +73,7 @@ public abstract class ProgramScheduleItemCommandBase
break;
case PlayoutMode.Multiple:
if (item.MultipleMode is MultipleMode.PlaylistItemSize &&
item.CollectionType is not ProgramScheduleItemCollectionType.Playlist)
item.CollectionType is not CollectionType.Playlist)
{
return BaseError.New(
"[MultipleMode] cannot be [PlaylistItemSize] when collection is not a playlist");
@ -126,49 +126,63 @@ public abstract class ProgramScheduleItemCommandBase @@ -126,49 +126,63 @@ public abstract class ProgramScheduleItemCommandBase
{
switch (item.CollectionType)
{
case ProgramScheduleItemCollectionType.Collection:
case CollectionType.Collection:
if (item.CollectionId is null)
{
return BaseError.New("[Collection] is required for collection type 'Collection'");
}
break;
case ProgramScheduleItemCollectionType.TelevisionShow:
case CollectionType.TelevisionShow:
if (item.MediaItemId is null)
{
return BaseError.New("[MediaItem] is required for collection type 'TelevisionShow'");
}
break;
case ProgramScheduleItemCollectionType.TelevisionSeason:
case CollectionType.TelevisionSeason:
if (item.MediaItemId is null)
{
return BaseError.New("[MediaItem] is required for collection type 'TelevisionSeason'");
}
break;
case ProgramScheduleItemCollectionType.Artist:
case CollectionType.Artist:
if (item.MediaItemId is null)
{
return BaseError.New("[MediaItem] is required for collection type 'Artist'");
}
break;
case ProgramScheduleItemCollectionType.MultiCollection:
case CollectionType.MultiCollection:
if (item.MultiCollectionId is null)
{
return BaseError.New("[MultiCollection] is required for collection type 'MultiCollection'");
}
break;
case ProgramScheduleItemCollectionType.SmartCollection:
case CollectionType.SmartCollection:
if (item.SmartCollectionId is null)
{
return BaseError.New("[SmartCollection] is required for collection type 'SmartCollection'");
}
break;
case ProgramScheduleItemCollectionType.Playlist:
case CollectionType.RerunFirstRun:
if (item.RerunCollectionId is null)
{
return BaseError.New("[RerunCollection] is required for collection type 'RerunFirstRun'");
}
break;
case CollectionType.RerunRerun:
if (item.RerunCollectionId is null)
{
return BaseError.New("[RerunCollection] is required for collection type 'RerunRerun'");
}
break;
case CollectionType.Playlist:
if (item.PlaylistId is null)
{
return BaseError.New("[Playlist] is required for collection type 'Playlist'");
@ -199,6 +213,7 @@ public abstract class ProgramScheduleItemCommandBase @@ -199,6 +213,7 @@ public abstract class ProgramScheduleItemCommandBase
CollectionId = item.CollectionId,
MultiCollectionId = item.MultiCollectionId,
SmartCollectionId = item.SmartCollectionId,
RerunCollectionId = item.RerunCollectionId,
MediaItemId = item.MediaItemId,
PlaylistId = item.PlaylistId,
PlaybackOrder = item.PlaybackOrder,
@ -229,6 +244,7 @@ public abstract class ProgramScheduleItemCommandBase @@ -229,6 +244,7 @@ public abstract class ProgramScheduleItemCommandBase
CollectionId = item.CollectionId,
MultiCollectionId = item.MultiCollectionId,
SmartCollectionId = item.SmartCollectionId,
RerunCollectionId = item.RerunCollectionId,
MediaItemId = item.MediaItemId,
PlaylistId = item.PlaylistId,
PlaybackOrder = item.PlaybackOrder,
@ -259,6 +275,7 @@ public abstract class ProgramScheduleItemCommandBase @@ -259,6 +275,7 @@ public abstract class ProgramScheduleItemCommandBase
CollectionId = item.CollectionId,
MultiCollectionId = item.MultiCollectionId,
SmartCollectionId = item.SmartCollectionId,
RerunCollectionId = item.RerunCollectionId,
MediaItemId = item.MediaItemId,
PlaylistId = item.PlaylistId,
PlaybackOrder = item.PlaybackOrder,
@ -291,6 +308,7 @@ public abstract class ProgramScheduleItemCommandBase @@ -291,6 +308,7 @@ public abstract class ProgramScheduleItemCommandBase
CollectionId = item.CollectionId,
MultiCollectionId = item.MultiCollectionId,
SmartCollectionId = item.SmartCollectionId,
RerunCollectionId = item.RerunCollectionId,
MediaItemId = item.MediaItemId,
PlaylistId = item.PlaylistId,
PlaybackOrder = item.PlaybackOrder,

3
ErsatzTV.Application/ProgramSchedules/Commands/ReplaceProgramScheduleItems.cs

@ -10,10 +10,11 @@ public record ReplaceProgramScheduleItem( @@ -10,10 +10,11 @@ public record ReplaceProgramScheduleItem(
TimeSpan? StartTime,
FixedStartTimeBehavior? FixedStartTimeBehavior,
PlayoutMode PlayoutMode,
ProgramScheduleItemCollectionType CollectionType,
CollectionType CollectionType,
int? CollectionId,
int? MultiCollectionId,
int? SmartCollectionId,
int? RerunCollectionId,
int? MediaItemId,
int? PlaylistId,
PlaybackOrder PlaybackOrder,

4
ErsatzTV.Application/ProgramSchedules/Commands/ReplaceProgramScheduleItemsHandler.cs

@ -120,6 +120,7 @@ public class ReplaceProgramScheduleItemsHandler : ProgramScheduleItemCommandBase @@ -120,6 +120,7 @@ public class ReplaceProgramScheduleItemsHandler : ProgramScheduleItemCommandBase
item.MediaItemId,
item.MultiCollectionId,
item.SmartCollectionId,
item.RerunCollectionId,
item.PlaylistId);
if (keyOrders.TryGetValue(key, out System.Collections.Generic.HashSet<PlaybackOrder> playbackOrders))
@ -140,10 +141,11 @@ public class ReplaceProgramScheduleItemsHandler : ProgramScheduleItemCommandBase @@ -140,10 +141,11 @@ public class ReplaceProgramScheduleItemsHandler : ProgramScheduleItemCommandBase
}
private sealed record CollectionKey(
ProgramScheduleItemCollectionType CollectionType,
CollectionType CollectionType,
int? CollectionId,
int? MediaItemId,
int? MultiCollectionId,
int? SmartCollectionId,
int? RerunCollectionId,
int? PlaylistId);
}

12
ErsatzTV.Application/ProgramSchedules/Mapper.cs

@ -34,6 +34,9 @@ internal static class Mapper @@ -34,6 +34,9 @@ internal static class Mapper
duration.SmartCollection != null
? MediaCollections.Mapper.ProjectToViewModel(duration.SmartCollection)
: null,
duration.RerunCollection != null
? MediaCollections.Mapper.ProjectToViewModel(duration.RerunCollection)
: null,
duration.Playlist != null
? MediaCollections.Mapper.ProjectToViewModel(duration.Playlist)
: null,
@ -95,6 +98,9 @@ internal static class Mapper @@ -95,6 +98,9 @@ internal static class Mapper
flood.SmartCollection != null
? MediaCollections.Mapper.ProjectToViewModel(flood.SmartCollection)
: null,
flood.RerunCollection != null
? MediaCollections.Mapper.ProjectToViewModel(flood.RerunCollection)
: null,
flood.Playlist != null
? MediaCollections.Mapper.ProjectToViewModel(flood.Playlist)
: null,
@ -153,6 +159,9 @@ internal static class Mapper @@ -153,6 +159,9 @@ internal static class Mapper
multiple.SmartCollection != null
? MediaCollections.Mapper.ProjectToViewModel(multiple.SmartCollection)
: null,
multiple.RerunCollection != null
? MediaCollections.Mapper.ProjectToViewModel(multiple.RerunCollection)
: null,
multiple.Playlist != null
? MediaCollections.Mapper.ProjectToViewModel(multiple.Playlist)
: null,
@ -213,6 +222,9 @@ internal static class Mapper @@ -213,6 +222,9 @@ internal static class Mapper
one.SmartCollection != null
? MediaCollections.Mapper.ProjectToViewModel(one.SmartCollection)
: null,
one.RerunCollection != null
? MediaCollections.Mapper.ProjectToViewModel(one.RerunCollection)
: null,
one.Playlist != null
? MediaCollections.Mapper.ProjectToViewModel(one.Playlist)
: null,

4
ErsatzTV.Application/ProgramSchedules/ProgramScheduleItemDurationViewModel.cs

@ -16,10 +16,11 @@ public record ProgramScheduleItemDurationViewModel : ProgramScheduleItemViewMode @@ -16,10 +16,11 @@ public record ProgramScheduleItemDurationViewModel : ProgramScheduleItemViewMode
StartType startType,
TimeSpan? startTime,
FixedStartTimeBehavior? fixedStartTimeBehavior,
ProgramScheduleItemCollectionType collectionType,
CollectionType collectionType,
MediaCollectionViewModel collection,
MultiCollectionViewModel multiCollection,
SmartCollectionViewModel smartCollection,
RerunCollectionViewModel rerunCollection,
PlaylistViewModel playlist,
NamedMediaItemViewModel mediaItem,
PlaybackOrder playbackOrder,
@ -54,6 +55,7 @@ public record ProgramScheduleItemDurationViewModel : ProgramScheduleItemViewMode @@ -54,6 +55,7 @@ public record ProgramScheduleItemDurationViewModel : ProgramScheduleItemViewMode
collection,
multiCollection,
smartCollection,
rerunCollection,
playlist,
mediaItem,
playbackOrder,

4
ErsatzTV.Application/ProgramSchedules/ProgramScheduleItemFloodViewModel.cs

@ -16,10 +16,11 @@ public record ProgramScheduleItemFloodViewModel : ProgramScheduleItemViewModel @@ -16,10 +16,11 @@ public record ProgramScheduleItemFloodViewModel : ProgramScheduleItemViewModel
StartType startType,
TimeSpan? startTime,
FixedStartTimeBehavior? fixedStartTimeBehavior,
ProgramScheduleItemCollectionType collectionType,
CollectionType collectionType,
MediaCollectionViewModel collection,
MultiCollectionViewModel multiCollection,
SmartCollectionViewModel smartCollection,
RerunCollectionViewModel rerunCollection,
PlaylistViewModel playlist,
NamedMediaItemViewModel mediaItem,
PlaybackOrder playbackOrder,
@ -51,6 +52,7 @@ public record ProgramScheduleItemFloodViewModel : ProgramScheduleItemViewModel @@ -51,6 +52,7 @@ public record ProgramScheduleItemFloodViewModel : ProgramScheduleItemViewModel
collection,
multiCollection,
smartCollection,
rerunCollection,
playlist,
mediaItem,
playbackOrder,

4
ErsatzTV.Application/ProgramSchedules/ProgramScheduleItemMultipleViewModel.cs

@ -16,10 +16,11 @@ public record ProgramScheduleItemMultipleViewModel : ProgramScheduleItemViewMode @@ -16,10 +16,11 @@ public record ProgramScheduleItemMultipleViewModel : ProgramScheduleItemViewMode
StartType startType,
TimeSpan? startTime,
FixedStartTimeBehavior? fixedStartTimeBehavior,
ProgramScheduleItemCollectionType collectionType,
CollectionType collectionType,
MediaCollectionViewModel collection,
MultiCollectionViewModel multiCollection,
SmartCollectionViewModel smartCollection,
RerunCollectionViewModel rerunCollection,
PlaylistViewModel playlist,
NamedMediaItemViewModel mediaItem,
PlaybackOrder playbackOrder,
@ -53,6 +54,7 @@ public record ProgramScheduleItemMultipleViewModel : ProgramScheduleItemViewMode @@ -53,6 +54,7 @@ public record ProgramScheduleItemMultipleViewModel : ProgramScheduleItemViewMode
collection,
multiCollection,
smartCollection,
rerunCollection,
playlist,
mediaItem,
playbackOrder,

4
ErsatzTV.Application/ProgramSchedules/ProgramScheduleItemOneViewModel.cs

@ -16,10 +16,11 @@ public record ProgramScheduleItemOneViewModel : ProgramScheduleItemViewModel @@ -16,10 +16,11 @@ public record ProgramScheduleItemOneViewModel : ProgramScheduleItemViewModel
StartType startType,
TimeSpan? startTime,
FixedStartTimeBehavior? fixedStartTimeBehavior,
ProgramScheduleItemCollectionType collectionType,
CollectionType collectionType,
MediaCollectionViewModel collection,
MultiCollectionViewModel multiCollection,
SmartCollectionViewModel smartCollection,
RerunCollectionViewModel rerunCollection,
PlaylistViewModel playlist,
NamedMediaItemViewModel mediaItem,
PlaybackOrder playbackOrder,
@ -51,6 +52,7 @@ public record ProgramScheduleItemOneViewModel : ProgramScheduleItemViewModel @@ -51,6 +52,7 @@ public record ProgramScheduleItemOneViewModel : ProgramScheduleItemViewModel
collection,
multiCollection,
smartCollection,
rerunCollection,
playlist,
mediaItem,
playbackOrder,

19
ErsatzTV.Application/ProgramSchedules/ProgramScheduleItemViewModel.cs

@ -15,10 +15,11 @@ public abstract record ProgramScheduleItemViewModel( @@ -15,10 +15,11 @@ public abstract record ProgramScheduleItemViewModel(
TimeSpan? StartTime,
FixedStartTimeBehavior? FixedStartTimeBehavior,
PlayoutMode PlayoutMode,
ProgramScheduleItemCollectionType CollectionType,
CollectionType CollectionType,
MediaCollectionViewModel Collection,
MultiCollectionViewModel MultiCollection,
SmartCollectionViewModel SmartCollection,
RerunCollectionViewModel RerunCollection,
PlaylistViewModel Playlist,
NamedMediaItemViewModel MediaItem,
PlaybackOrder PlaybackOrder,
@ -43,19 +44,21 @@ public abstract record ProgramScheduleItemViewModel( @@ -43,19 +44,21 @@ public abstract record ProgramScheduleItemViewModel(
{
public string Name => CollectionType switch
{
ProgramScheduleItemCollectionType.Collection => Collection?.Name,
ProgramScheduleItemCollectionType.TelevisionShow =>
CollectionType.Collection => Collection?.Name,
CollectionType.TelevisionShow =>
MediaItem?.Name, // $"{TelevisionShow?.Title} ({TelevisionShow?.Year})",
ProgramScheduleItemCollectionType.TelevisionSeason =>
CollectionType.TelevisionSeason =>
MediaItem?.Name, // $"{TelevisionSeason?.Title} ({TelevisionSeason?.Plot})",
ProgramScheduleItemCollectionType.Artist =>
CollectionType.Artist =>
MediaItem?.Name,
ProgramScheduleItemCollectionType.MultiCollection =>
CollectionType.MultiCollection =>
MultiCollection?.Name,
ProgramScheduleItemCollectionType.SmartCollection =>
CollectionType.SmartCollection =>
SmartCollection?.Name,
ProgramScheduleItemCollectionType.Playlist =>
CollectionType.Playlist =>
Playlist?.Name,
CollectionType.RerunFirstRun or CollectionType.RerunRerun =>
RerunCollection?.Name,
_ => string.Empty
};
}

7
ErsatzTV.Application/ProgramSchedules/Queries/GetProgramScheduleItemsHandler.cs

@ -23,6 +23,7 @@ public class GetProgramScheduleItemsHandler(IDbContextFactory<TvContext> dbConte @@ -23,6 +23,7 @@ public class GetProgramScheduleItemsHandler(IDbContextFactory<TvContext> dbConte
.Include(i => i.Collection)
.Include(i => i.MultiCollection)
.Include(i => i.SmartCollection)
.Include(i => i.RerunCollection)
.Include(i => i.Playlist)
.Include(i => i.MediaItem)
.ThenInclude(i => (i as Movie).MovieMetadata)
@ -74,6 +75,12 @@ public class GetProgramScheduleItemsHandler(IDbContextFactory<TvContext> dbConte @@ -74,6 +75,12 @@ public class GetProgramScheduleItemsHandler(IDbContextFactory<TvContext> dbConte
{
item = item with { FillWithGroupMode = FillWithGroupMode.None };
}
if (item.CollectionType is CollectionType.Playlist or CollectionType.RerunFirstRun
or CollectionType.RerunRerun)
{
item = item with { PlaybackOrder = PlaybackOrder.None };
}
}
return item;

2
ErsatzTV.Application/Scheduling/BlockItemViewModel.cs

@ -7,7 +7,7 @@ namespace ErsatzTV.Application.Scheduling; @@ -7,7 +7,7 @@ namespace ErsatzTV.Application.Scheduling;
public record BlockItemViewModel(
int Id,
int Index,
ProgramScheduleItemCollectionType CollectionType,
CollectionType CollectionType,
MediaCollectionViewModel Collection,
MultiCollectionViewModel MultiCollection,
SmartCollectionViewModel SmartCollection,

2
ErsatzTV.Application/Scheduling/Commands/ReplaceBlockItem.cs

@ -4,7 +4,7 @@ namespace ErsatzTV.Application.Scheduling; @@ -4,7 +4,7 @@ namespace ErsatzTV.Application.Scheduling;
public record ReplaceBlockItem(
int Index,
ProgramScheduleItemCollectionType CollectionType,
CollectionType CollectionType,
int? CollectionId,
int? MultiCollectionId,
int? SmartCollectionId,

14
ErsatzTV.Application/Scheduling/Commands/ReplaceBlockItemsHandler.cs

@ -89,49 +89,49 @@ public class ReplaceBlockItemsHandler(IDbContextFactory<TvContext> dbContextFact @@ -89,49 +89,49 @@ public class ReplaceBlockItemsHandler(IDbContextFactory<TvContext> dbContextFact
{
switch (item.CollectionType)
{
case ProgramScheduleItemCollectionType.Collection:
case CollectionType.Collection:
if (item.CollectionId is null)
{
return BaseError.New("[Collection] is required for collection type 'Collection'");
}
break;
case ProgramScheduleItemCollectionType.TelevisionShow:
case CollectionType.TelevisionShow:
if (item.MediaItemId is null)
{
return BaseError.New("[MediaItem] is required for collection type 'TelevisionShow'");
}
break;
case ProgramScheduleItemCollectionType.TelevisionSeason:
case CollectionType.TelevisionSeason:
if (item.MediaItemId is null)
{
return BaseError.New("[MediaItem] is required for collection type 'TelevisionSeason'");
}
break;
case ProgramScheduleItemCollectionType.Artist:
case CollectionType.Artist:
if (item.MediaItemId is null)
{
return BaseError.New("[MediaItem] is required for collection type 'Artist'");
}
break;
case ProgramScheduleItemCollectionType.MultiCollection:
case CollectionType.MultiCollection:
if (item.MultiCollectionId is null)
{
return BaseError.New("[MultiCollection] is required for collection type 'MultiCollection'");
}
break;
case ProgramScheduleItemCollectionType.SmartCollection:
case CollectionType.SmartCollection:
if (item.SmartCollectionId is null)
{
return BaseError.New("[SmartCollection] is required for collection type 'SmartCollection'");
}
break;
case ProgramScheduleItemCollectionType.FakeCollection:
case CollectionType.FakeCollection:
default:
return BaseError.New("[CollectionType] is invalid");
}

4
ErsatzTV.Application/Scheduling/Commands/UpdateDeco.cs

@ -15,14 +15,14 @@ public record UpdateDeco( @@ -15,14 +15,14 @@ public record UpdateDeco(
List<int> GraphicsElementIds,
bool UseGraphicsElementsDuringFiller,
DecoMode DefaultFillerMode,
ProgramScheduleItemCollectionType DefaultFillerCollectionType,
CollectionType DefaultFillerCollectionType,
int? DefaultFillerCollectionId,
int? DefaultFillerMediaItemId,
int? DefaultFillerMultiCollectionId,
int? DefaultFillerSmartCollectionId,
bool DefaultFillerTrimToFit,
DecoMode DeadAirFallbackMode,
ProgramScheduleItemCollectionType DeadAirFallbackCollectionType,
CollectionType DeadAirFallbackCollectionType,
int? DeadAirFallbackCollectionId,
int? DeadAirFallbackMediaItemId,
int? DeadAirFallbackMultiCollectionId,

12
ErsatzTV.Application/Scheduling/Commands/UpdateDecoHandler.cs

@ -92,13 +92,13 @@ public class UpdateDecoHandler(IDbContextFactory<TvContext> dbContextFactory) @@ -92,13 +92,13 @@ public class UpdateDecoHandler(IDbContextFactory<TvContext> dbContextFactory)
{
switch (request.DefaultFillerCollectionType)
{
case ProgramScheduleItemCollectionType.Collection:
case CollectionType.Collection:
existing.DefaultFillerCollectionId = request.DefaultFillerCollectionId;
break;
case ProgramScheduleItemCollectionType.MultiCollection:
case CollectionType.MultiCollection:
existing.DefaultFillerMultiCollectionId = request.DefaultFillerMultiCollectionId;
break;
case ProgramScheduleItemCollectionType.SmartCollection:
case CollectionType.SmartCollection:
existing.DefaultFillerSmartCollectionId = request.DefaultFillerSmartCollectionId;
break;
default:
@ -120,13 +120,13 @@ public class UpdateDecoHandler(IDbContextFactory<TvContext> dbContextFactory) @@ -120,13 +120,13 @@ public class UpdateDecoHandler(IDbContextFactory<TvContext> dbContextFactory)
{
switch (request.DeadAirFallbackCollectionType)
{
case ProgramScheduleItemCollectionType.Collection:
case CollectionType.Collection:
existing.DeadAirFallbackCollectionId = request.DeadAirFallbackCollectionId;
break;
case ProgramScheduleItemCollectionType.MultiCollection:
case CollectionType.MultiCollection:
existing.DeadAirFallbackMultiCollectionId = request.DeadAirFallbackMultiCollectionId;
break;
case ProgramScheduleItemCollectionType.SmartCollection:
case CollectionType.SmartCollection:
existing.DeadAirFallbackSmartCollectionId = request.DeadAirFallbackSmartCollectionId;
break;
default:

4
ErsatzTV.Application/Scheduling/DecoViewModel.cs

@ -17,14 +17,14 @@ public record DecoViewModel( @@ -17,14 +17,14 @@ public record DecoViewModel(
List<GraphicsElementViewModel> GraphicsElements,
bool UseGraphicsElementsDuringFiller,
DecoMode DefaultFillerMode,
ProgramScheduleItemCollectionType DefaultFillerCollectionType,
CollectionType DefaultFillerCollectionType,
int? DefaultFillerCollectionId,
int? DefaultFillerMediaItemId,
int? DefaultFillerMultiCollectionId,
int? DefaultFillerSmartCollectionId,
bool DefaultFillerTrimToFit,
DecoMode DeadAirFallbackMode,
ProgramScheduleItemCollectionType DeadAirFallbackCollectionType,
CollectionType DeadAirFallbackCollectionType,
int? DeadAirFallbackCollectionId,
int? DeadAirFallbackMediaItemId,
int? DeadAirFallbackMultiCollectionId,

5
ErsatzTV.Application/Search/Queries/SearchRerunCollections.cs

@ -0,0 +1,5 @@ @@ -0,0 +1,5 @@
using ErsatzTV.Application.MediaCollections;
namespace ErsatzTV.Application.Search;
public record SearchRerunCollections(string Query) : IRequest<List<RerunCollectionViewModel>>;

26
ErsatzTV.Application/Search/Queries/SearchRerunCollectionsHandler.cs

@ -0,0 +1,26 @@ @@ -0,0 +1,26 @@
using ErsatzTV.Application.MediaCollections;
using ErsatzTV.Infrastructure.Data;
using Microsoft.EntityFrameworkCore;
using static ErsatzTV.Application.MediaCollections.Mapper;
namespace ErsatzTV.Application.Search;
public class SearchRerunCollectionsHandler(IDbContextFactory<TvContext> dbContextFactory)
: IRequestHandler<SearchRerunCollections, List<RerunCollectionViewModel>>
{
public async Task<List<RerunCollectionViewModel>> Handle(
SearchRerunCollections request,
CancellationToken cancellationToken)
{
await using TvContext dbContext = await dbContextFactory.CreateDbContextAsync(cancellationToken);
return await dbContext.RerunCollections
.AsNoTracking()
.Where(c => EF.Functions.Like(
EF.Functions.Collate(c.Name, TvContext.CaseInsensitiveCollation),
$"%{request.Query}%"))
.OrderBy(c => EF.Functions.Collate(c.Name, TvContext.CaseInsensitiveCollation))
.Take(10)
.ToListAsync(cancellationToken)
.Map(list => list.Map(ProjectToViewModel).ToList());
}
}

2
ErsatzTV.Application/Streaming/Queries/GetPlayoutItemProcessByChannelNumberHandler.cs

@ -849,7 +849,7 @@ public class GetPlayoutItemProcessByChannelNumberHandler : FFmpegProcessHandler< @@ -849,7 +849,7 @@ public class GetPlayoutItemProcessByChannelNumberHandler : FFmpegProcessHandler<
private sealed record DisableDeadAirFallback : DeadAirFallbackResult;
private sealed record CustomDeadAirFallback(
ProgramScheduleItemCollectionType CollectionType,
CollectionType CollectionType,
int? CollectionId,
int? MediaItemId,
int? MultiCollectionId,

4
ErsatzTV.Core.Tests/Fakes/FakeMediaCollectionRepository.cs

@ -42,6 +42,7 @@ public class FakeMediaCollectionRepository : IMediaCollectionRepository @@ -42,6 +42,7 @@ public class FakeMediaCollectionRepository : IMediaCollectionRepository
CancellationToken cancellationToken) =>
throw new NotSupportedException();
public Task<List<MediaItem>> GetRerunCollectionItems(int id, CancellationToken cancellationToken) => throw new NotSupportedException();
public Task<List<MediaItem>> GetShowItemsByShowGuids(List<string> guids) => throw new NotSupportedException();
public Task<List<MediaItem>> GetPlaylistItems(int id, CancellationToken cancellationToken) => throw new NotSupportedException();
public Task<List<Movie>> GetMovie(int id) => throw new NotSupportedException();
@ -69,6 +70,9 @@ public class FakeMediaCollectionRepository : IMediaCollectionRepository @@ -69,6 +70,9 @@ public class FakeMediaCollectionRepository : IMediaCollectionRepository
public Task<List<int>> PlayoutIdsUsingSmartCollection(int smartCollectionId) =>
throw new NotSupportedException();
public Task<List<int>> PlayoutIdsUsingRerunCollection(int rerunCollectionId) =>
throw new NotSupportedException();
public Task<bool> IsCustomPlaybackOrder(int collectionId) => false.AsTask();
public Task<Option<string>> GetNameFromKey(CollectionKey emptyCollection, CancellationToken cancellationToken) => Option<string>.None.AsTask();

6
ErsatzTV.Core.Tests/Scheduling/BlockScheduling/BlockPlayoutChangeDetectionTests.cs

@ -161,7 +161,7 @@ public static class BlockPlayoutChangeDetectionTests @@ -161,7 +161,7 @@ public static class BlockPlayoutChangeDetectionTests
Id = 1,
Index = 1,
BlockId = 1,
CollectionType = ProgramScheduleItemCollectionType.TelevisionShow,
CollectionType = CollectionType.TelevisionShow,
MediaItemId = 1,
PlaybackOrder = PlaybackOrder.Chronological
}
@ -179,7 +179,7 @@ public static class BlockPlayoutChangeDetectionTests @@ -179,7 +179,7 @@ public static class BlockPlayoutChangeDetectionTests
Id = 2,
Index = 1,
BlockId = 2,
CollectionType = ProgramScheduleItemCollectionType.TelevisionShow,
CollectionType = CollectionType.TelevisionShow,
MediaItemId = 2,
PlaybackOrder = PlaybackOrder.Chronological
}
@ -197,7 +197,7 @@ public static class BlockPlayoutChangeDetectionTests @@ -197,7 +197,7 @@ public static class BlockPlayoutChangeDetectionTests
Id = 3,
Index = 1,
BlockId = 3,
CollectionType = ProgramScheduleItemCollectionType.TelevisionShow,
CollectionType = CollectionType.TelevisionShow,
MediaItemId = 3,
PlaybackOrder = PlaybackOrder.Chronological
}

6
ErsatzTV.Core.Tests/Scheduling/ChronologicalContentTests.cs

@ -25,7 +25,7 @@ public class ChronologicalContentTests @@ -25,7 +25,7 @@ public class ChronologicalContentTests
{
chronologicalContent.Current.IsSome.ShouldBeTrue();
chronologicalContent.Current.Map(x => x.Id).IfNone(-1).ShouldBe(i);
chronologicalContent.MoveNext();
chronologicalContent.MoveNext(Option<DateTimeOffset>.None);
}
}
@ -40,7 +40,7 @@ public class ChronologicalContentTests @@ -40,7 +40,7 @@ public class ChronologicalContentTests
for (var i = 0; i < 10; i++)
{
chronologicalContent.State.Index.ShouldBe(i % 10);
chronologicalContent.MoveNext();
chronologicalContent.MoveNext(Option<DateTimeOffset>.None);
}
}
@ -57,7 +57,7 @@ public class ChronologicalContentTests @@ -57,7 +57,7 @@ public class ChronologicalContentTests
chronologicalContent.Current.IsSome.ShouldBeTrue();
chronologicalContent.Current.Map(x => x.Id).IfNone(-1).ShouldBe(i);
chronologicalContent.State.Index.ShouldBe(i - 1);
chronologicalContent.MoveNext();
chronologicalContent.MoveNext(Option<DateTimeOffset>.None);
}
}

6
ErsatzTV.Core.Tests/Scheduling/ClassicScheduling/ContinuePlayoutTests.cs

@ -541,6 +541,7 @@ public class ContinuePlayoutTests : PlayoutBuilderTestBase @@ -541,6 +541,7 @@ public class ContinuePlayoutTests : PlayoutBuilderTestBase
IMultiEpisodeShuffleCollectionEnumeratorFactory factory =
Substitute.For<IMultiEpisodeShuffleCollectionEnumeratorFactory>();
ILocalFileSystem localFileSystem = Substitute.For<ILocalFileSystem>();
IRerunHelper rerunHelper = Substitute.For<IRerunHelper>();
var builder = new PlayoutBuilder(
configRepo,
fakeRepository,
@ -548,6 +549,7 @@ public class ContinuePlayoutTests : PlayoutBuilderTestBase @@ -548,6 +549,7 @@ public class ContinuePlayoutTests : PlayoutBuilderTestBase
artistRepo,
factory,
localFileSystem,
rerunHelper,
Logger);
DateTimeOffset start = HoursAfterMidnight(0);
@ -661,6 +663,7 @@ public class ContinuePlayoutTests : PlayoutBuilderTestBase @@ -661,6 +663,7 @@ public class ContinuePlayoutTests : PlayoutBuilderTestBase
IMultiEpisodeShuffleCollectionEnumeratorFactory factory =
Substitute.For<IMultiEpisodeShuffleCollectionEnumeratorFactory>();
ILocalFileSystem localFileSystem = Substitute.For<ILocalFileSystem>();
IRerunHelper rerunHelper = Substitute.For<IRerunHelper>();
var builder = new PlayoutBuilder(
configRepo,
fakeRepository,
@ -668,6 +671,7 @@ public class ContinuePlayoutTests : PlayoutBuilderTestBase @@ -668,6 +671,7 @@ public class ContinuePlayoutTests : PlayoutBuilderTestBase
artistRepo,
factory,
localFileSystem,
rerunHelper,
Logger);
DateTimeOffset start = HoursAfterMidnight(0);
@ -783,6 +787,7 @@ public class ContinuePlayoutTests : PlayoutBuilderTestBase @@ -783,6 +787,7 @@ public class ContinuePlayoutTests : PlayoutBuilderTestBase
IMultiEpisodeShuffleCollectionEnumeratorFactory factory =
Substitute.For<IMultiEpisodeShuffleCollectionEnumeratorFactory>();
ILocalFileSystem localFileSystem = Substitute.For<ILocalFileSystem>();
IRerunHelper rerunHelper = Substitute.For<IRerunHelper>();
var builder = new PlayoutBuilder(
configRepo,
fakeRepository,
@ -790,6 +795,7 @@ public class ContinuePlayoutTests : PlayoutBuilderTestBase @@ -790,6 +795,7 @@ public class ContinuePlayoutTests : PlayoutBuilderTestBase
artistRepo,
factory,
localFileSystem,
rerunHelper,
Logger);
PlayoutBuildResult result = await builder.Build(

22
ErsatzTV.Core.Tests/Scheduling/ClassicScheduling/NewPlayoutTests.cs

@ -550,6 +550,7 @@ public class NewPlayoutTests : PlayoutBuilderTestBase @@ -550,6 +550,7 @@ public class NewPlayoutTests : PlayoutBuilderTestBase
IMultiEpisodeShuffleCollectionEnumeratorFactory factory =
Substitute.For<IMultiEpisodeShuffleCollectionEnumeratorFactory>();
ILocalFileSystem localFileSystem = Substitute.For<ILocalFileSystem>();
IRerunHelper rerunHelper = Substitute.For<IRerunHelper>();
var builder = new PlayoutBuilder(
configRepo,
fakeRepository,
@ -557,6 +558,7 @@ public class NewPlayoutTests : PlayoutBuilderTestBase @@ -557,6 +558,7 @@ public class NewPlayoutTests : PlayoutBuilderTestBase
artistRepo,
factory,
localFileSystem,
rerunHelper,
Logger);
DateTimeOffset start = HoursAfterMidnight(0);
@ -664,6 +666,7 @@ public class NewPlayoutTests : PlayoutBuilderTestBase @@ -664,6 +666,7 @@ public class NewPlayoutTests : PlayoutBuilderTestBase
IMultiEpisodeShuffleCollectionEnumeratorFactory factory =
Substitute.For<IMultiEpisodeShuffleCollectionEnumeratorFactory>();
ILocalFileSystem localFileSystem = Substitute.For<ILocalFileSystem>();
IRerunHelper rerunHelper = Substitute.For<IRerunHelper>();
var builder = new PlayoutBuilder(
configRepo,
fakeRepository,
@ -671,6 +674,7 @@ public class NewPlayoutTests : PlayoutBuilderTestBase @@ -671,6 +674,7 @@ public class NewPlayoutTests : PlayoutBuilderTestBase
artistRepo,
factory,
localFileSystem,
rerunHelper,
Logger);
DateTimeOffset start = HoursAfterMidnight(0);
@ -829,6 +833,7 @@ public class NewPlayoutTests : PlayoutBuilderTestBase @@ -829,6 +833,7 @@ public class NewPlayoutTests : PlayoutBuilderTestBase
IMultiEpisodeShuffleCollectionEnumeratorFactory factory =
Substitute.For<IMultiEpisodeShuffleCollectionEnumeratorFactory>();
ILocalFileSystem localFileSystem = Substitute.For<ILocalFileSystem>();
IRerunHelper rerunHelper = Substitute.For<IRerunHelper>();
var builder = new PlayoutBuilder(
configRepo,
fakeRepository,
@ -836,6 +841,7 @@ public class NewPlayoutTests : PlayoutBuilderTestBase @@ -836,6 +841,7 @@ public class NewPlayoutTests : PlayoutBuilderTestBase
artistRepo,
factory,
localFileSystem,
rerunHelper,
Logger);
DateTimeOffset start = HoursAfterMidnight(0);
@ -953,6 +959,7 @@ public class NewPlayoutTests : PlayoutBuilderTestBase @@ -953,6 +959,7 @@ public class NewPlayoutTests : PlayoutBuilderTestBase
IMultiEpisodeShuffleCollectionEnumeratorFactory factory =
Substitute.For<IMultiEpisodeShuffleCollectionEnumeratorFactory>();
ILocalFileSystem localFileSystem = Substitute.For<ILocalFileSystem>();
IRerunHelper rerunHelper = Substitute.For<IRerunHelper>();
var builder = new PlayoutBuilder(
configRepo,
fakeRepository,
@ -960,6 +967,7 @@ public class NewPlayoutTests : PlayoutBuilderTestBase @@ -960,6 +967,7 @@ public class NewPlayoutTests : PlayoutBuilderTestBase
artistRepo,
factory,
localFileSystem,
rerunHelper,
Logger);
DateTimeOffset start = HoursAfterMidnight(0);
@ -1076,6 +1084,7 @@ public class NewPlayoutTests : PlayoutBuilderTestBase @@ -1076,6 +1084,7 @@ public class NewPlayoutTests : PlayoutBuilderTestBase
IMultiEpisodeShuffleCollectionEnumeratorFactory factory =
Substitute.For<IMultiEpisodeShuffleCollectionEnumeratorFactory>();
ILocalFileSystem localFileSystem = Substitute.For<ILocalFileSystem>();
IRerunHelper rerunHelper = Substitute.For<IRerunHelper>();
var builder = new PlayoutBuilder(
configRepo,
fakeRepository,
@ -1083,6 +1092,7 @@ public class NewPlayoutTests : PlayoutBuilderTestBase @@ -1083,6 +1092,7 @@ public class NewPlayoutTests : PlayoutBuilderTestBase
artistRepo,
factory,
localFileSystem,
rerunHelper,
Logger);
DateTimeOffset start = HoursAfterMidnight(0);
@ -1200,6 +1210,7 @@ public class NewPlayoutTests : PlayoutBuilderTestBase @@ -1200,6 +1210,7 @@ public class NewPlayoutTests : PlayoutBuilderTestBase
IMultiEpisodeShuffleCollectionEnumeratorFactory factory =
Substitute.For<IMultiEpisodeShuffleCollectionEnumeratorFactory>();
ILocalFileSystem localFileSystem = Substitute.For<ILocalFileSystem>();
IRerunHelper rerunHelper = Substitute.For<IRerunHelper>();
var builder = new PlayoutBuilder(
configRepo,
fakeRepository,
@ -1207,6 +1218,7 @@ public class NewPlayoutTests : PlayoutBuilderTestBase @@ -1207,6 +1218,7 @@ public class NewPlayoutTests : PlayoutBuilderTestBase
artistRepo,
factory,
localFileSystem,
rerunHelper,
Logger);
DateTimeOffset start = HoursAfterMidnight(0);
@ -1329,6 +1341,7 @@ public class NewPlayoutTests : PlayoutBuilderTestBase @@ -1329,6 +1341,7 @@ public class NewPlayoutTests : PlayoutBuilderTestBase
IMultiEpisodeShuffleCollectionEnumeratorFactory factory =
Substitute.For<IMultiEpisodeShuffleCollectionEnumeratorFactory>();
ILocalFileSystem localFileSystem = Substitute.For<ILocalFileSystem>();
IRerunHelper rerunHelper = Substitute.For<IRerunHelper>();
var builder = new PlayoutBuilder(
configRepo,
fakeRepository,
@ -1336,6 +1349,7 @@ public class NewPlayoutTests : PlayoutBuilderTestBase @@ -1336,6 +1349,7 @@ public class NewPlayoutTests : PlayoutBuilderTestBase
artistRepo,
factory,
localFileSystem,
rerunHelper,
Logger);
DateTimeOffset start = HoursAfterMidnight(0);
@ -1458,6 +1472,7 @@ public class NewPlayoutTests : PlayoutBuilderTestBase @@ -1458,6 +1472,7 @@ public class NewPlayoutTests : PlayoutBuilderTestBase
IMultiEpisodeShuffleCollectionEnumeratorFactory factory =
Substitute.For<IMultiEpisodeShuffleCollectionEnumeratorFactory>();
ILocalFileSystem localFileSystem = Substitute.For<ILocalFileSystem>();
IRerunHelper rerunHelper = Substitute.For<IRerunHelper>();
var builder = new PlayoutBuilder(
configRepo,
fakeRepository,
@ -1465,6 +1480,7 @@ public class NewPlayoutTests : PlayoutBuilderTestBase @@ -1465,6 +1480,7 @@ public class NewPlayoutTests : PlayoutBuilderTestBase
artistRepo,
factory,
localFileSystem,
rerunHelper,
Logger);
DateTimeOffset start = HoursAfterMidnight(0);
@ -1596,6 +1612,7 @@ public class NewPlayoutTests : PlayoutBuilderTestBase @@ -1596,6 +1612,7 @@ public class NewPlayoutTests : PlayoutBuilderTestBase
IMultiEpisodeShuffleCollectionEnumeratorFactory factory =
Substitute.For<IMultiEpisodeShuffleCollectionEnumeratorFactory>();
ILocalFileSystem localFileSystem = Substitute.For<ILocalFileSystem>();
IRerunHelper rerunHelper = Substitute.For<IRerunHelper>();
var builder = new PlayoutBuilder(
configRepo,
fakeRepository,
@ -1603,6 +1620,7 @@ public class NewPlayoutTests : PlayoutBuilderTestBase @@ -1603,6 +1620,7 @@ public class NewPlayoutTests : PlayoutBuilderTestBase
artistRepo,
factory,
localFileSystem,
rerunHelper,
Logger);
DateTimeOffset start = HoursAfterMidnight(0);
@ -1727,6 +1745,7 @@ public class NewPlayoutTests : PlayoutBuilderTestBase @@ -1727,6 +1745,7 @@ public class NewPlayoutTests : PlayoutBuilderTestBase
IMultiEpisodeShuffleCollectionEnumeratorFactory factory =
Substitute.For<IMultiEpisodeShuffleCollectionEnumeratorFactory>();
ILocalFileSystem localFileSystem = Substitute.For<ILocalFileSystem>();
IRerunHelper rerunHelper = Substitute.For<IRerunHelper>();
var builder = new PlayoutBuilder(
configRepo,
fakeRepository,
@ -1734,6 +1753,7 @@ public class NewPlayoutTests : PlayoutBuilderTestBase @@ -1734,6 +1753,7 @@ public class NewPlayoutTests : PlayoutBuilderTestBase
artistRepo,
factory,
localFileSystem,
rerunHelper,
Logger);
DateTimeOffset start = HoursAfterMidnight(0);
@ -1824,6 +1844,7 @@ public class NewPlayoutTests : PlayoutBuilderTestBase @@ -1824,6 +1844,7 @@ public class NewPlayoutTests : PlayoutBuilderTestBase
IMultiEpisodeShuffleCollectionEnumeratorFactory factory =
Substitute.For<IMultiEpisodeShuffleCollectionEnumeratorFactory>();
ILocalFileSystem localFileSystem = Substitute.For<ILocalFileSystem>();
IRerunHelper rerunHelper = Substitute.For<IRerunHelper>();
var builder = new PlayoutBuilder(
configRepo,
fakeRepository,
@ -1831,6 +1852,7 @@ public class NewPlayoutTests : PlayoutBuilderTestBase @@ -1831,6 +1852,7 @@ public class NewPlayoutTests : PlayoutBuilderTestBase
artistRepo,
factory,
localFileSystem,
rerunHelper,
Logger);
DateTimeOffset start = HoursAfterMidnight(0);

10
ErsatzTV.Core.Tests/Scheduling/ClassicScheduling/PlayoutBuilderTestBase.cs

@ -66,6 +66,7 @@ public abstract class PlayoutBuilderTestBase @@ -66,6 +66,7 @@ public abstract class PlayoutBuilderTestBase
IMultiEpisodeShuffleCollectionEnumeratorFactory factory =
Substitute.For<IMultiEpisodeShuffleCollectionEnumeratorFactory>();
ILocalFileSystem localFileSystem = Substitute.For<ILocalFileSystem>();
IRerunHelper rerunHelper = Substitute.For<IRerunHelper>();
var builder = new PlayoutBuilder(
configRepo,
collectionRepo,
@ -73,6 +74,7 @@ public abstract class PlayoutBuilderTestBase @@ -73,6 +74,7 @@ public abstract class PlayoutBuilderTestBase
artistRepo,
factory,
localFileSystem,
rerunHelper,
Logger);
var items = new List<ProgramScheduleItem> { Flood(mediaCollection, playbackOrder) };
@ -120,7 +122,7 @@ public abstract class PlayoutBuilderTestBase @@ -120,7 +122,7 @@ public abstract class PlayoutBuilderTestBase
{
Id = 1,
Index = 1,
CollectionType = ProgramScheduleItemCollectionType.Collection,
CollectionType = CollectionType.Collection,
Collection = mediaCollection,
CollectionId = mediaCollection.Id,
StartTime = null,
@ -135,7 +137,7 @@ public abstract class PlayoutBuilderTestBase @@ -135,7 +137,7 @@ public abstract class PlayoutBuilderTestBase
{
Id = 1,
Index = 1,
CollectionType = ProgramScheduleItemCollectionType.SmartCollection,
CollectionType = CollectionType.SmartCollection,
SmartCollection = smartCollection,
SmartCollectionId = smartCollection.Id,
StartTime = null,
@ -143,7 +145,7 @@ public abstract class PlayoutBuilderTestBase @@ -143,7 +145,7 @@ public abstract class PlayoutBuilderTestBase
FallbackFiller = new FillerPreset
{
Id = 1,
CollectionType = ProgramScheduleItemCollectionType.SmartCollection,
CollectionType = CollectionType.SmartCollection,
SmartCollection = fillerCollection,
SmartCollectionId = fillerCollection.Id,
FillerKind = FillerKind.Fallback
@ -180,6 +182,7 @@ public abstract class PlayoutBuilderTestBase @@ -180,6 +182,7 @@ public abstract class PlayoutBuilderTestBase
IMultiEpisodeShuffleCollectionEnumeratorFactory factory =
Substitute.For<IMultiEpisodeShuffleCollectionEnumeratorFactory>();
ILocalFileSystem localFileSystem = Substitute.For<ILocalFileSystem>();
IRerunHelper rerunHelper = Substitute.For<IRerunHelper>();
var builder = new PlayoutBuilder(
configRepo,
collectionRepo,
@ -187,6 +190,7 @@ public abstract class PlayoutBuilderTestBase @@ -187,6 +190,7 @@ public abstract class PlayoutBuilderTestBase
artistRepo,
factory,
localFileSystem,
rerunHelper,
Logger);
var items = new List<ProgramScheduleItem> { Flood(mediaCollection, fillerCollection, playbackOrder) };

4
ErsatzTV.Core.Tests/Scheduling/ClassicScheduling/RefreshPlayoutTests.cs

@ -77,7 +77,7 @@ public class RefreshPlayoutTests : PlayoutBuilderTestBase @@ -77,7 +77,7 @@ public class RefreshPlayoutTests : PlayoutBuilderTestBase
AnchorDate = HoursAfterMidnight(24).UtcDateTime,
Collection = collectionOne,
CollectionId = collectionOne.Id,
CollectionType = ProgramScheduleItemCollectionType.Collection,
CollectionType = CollectionType.Collection,
EnumeratorState = new CollectionEnumeratorState
{
Index = 1,
@ -102,6 +102,7 @@ public class RefreshPlayoutTests : PlayoutBuilderTestBase @@ -102,6 +102,7 @@ public class RefreshPlayoutTests : PlayoutBuilderTestBase
IMultiEpisodeShuffleCollectionEnumeratorFactory factory =
Substitute.For<IMultiEpisodeShuffleCollectionEnumeratorFactory>();
ILocalFileSystem localFileSystem = Substitute.For<ILocalFileSystem>();
IRerunHelper rerunHelper = Substitute.For<IRerunHelper>();
var builder = new PlayoutBuilder(
configRepo,
fakeRepository,
@ -109,6 +110,7 @@ public class RefreshPlayoutTests : PlayoutBuilderTestBase @@ -109,6 +110,7 @@ public class RefreshPlayoutTests : PlayoutBuilderTestBase
artistRepo,
factory,
localFileSystem,
rerunHelper,
Logger);
DateTimeOffset start = HoursAfterMidnight(24);

6
ErsatzTV.Core.Tests/Scheduling/CustomOrderContentTests.cs

@ -25,7 +25,7 @@ public class CustomOrderContentTests @@ -25,7 +25,7 @@ public class CustomOrderContentTests
{
customOrderContent.Current.IsSome.ShouldBeTrue();
customOrderContent.Current.Map(x => x.Id).IfNone(-1).ShouldBe(i);
customOrderContent.MoveNext();
customOrderContent.MoveNext(Option<DateTimeOffset>.None);
}
}
@ -41,7 +41,7 @@ public class CustomOrderContentTests @@ -41,7 +41,7 @@ public class CustomOrderContentTests
for (var i = 0; i < 10; i++)
{
customOrderContent.State.Index.ShouldBe(i % 10);
customOrderContent.MoveNext();
customOrderContent.MoveNext(Option<DateTimeOffset>.None);
}
}
@ -59,7 +59,7 @@ public class CustomOrderContentTests @@ -59,7 +59,7 @@ public class CustomOrderContentTests
customOrderContent.Current.IsSome.ShouldBeTrue();
customOrderContent.Current.Map(x => x.Id).IfNone(-1).ShouldBe(i);
customOrderContent.State.Index.ShouldBe(5 - i + 5); // 5 through 10
customOrderContent.MoveNext();
customOrderContent.MoveNext(Option<DateTimeOffset>.None);
}
}

36
ErsatzTV.Core.Tests/Scheduling/PlaylistEnumeratorTests.cs

@ -27,7 +27,7 @@ public class PlaylistEnumeratorTests @@ -27,7 +27,7 @@ public class PlaylistEnumeratorTests
Id = 1,
PlaybackOrder = PlaybackOrder.Chronological,
PlayAll = false,
CollectionType = ProgramScheduleItemCollectionType.Collection,
CollectionType = CollectionType.Collection,
CollectionId = 1
},
[FakeMovie(10)]
@ -38,7 +38,7 @@ public class PlaylistEnumeratorTests @@ -38,7 +38,7 @@ public class PlaylistEnumeratorTests
Id = 2,
PlaybackOrder = PlaybackOrder.Chronological,
PlayAll = true,
CollectionType = ProgramScheduleItemCollectionType.Collection,
CollectionType = CollectionType.Collection,
CollectionId = 2
},
[FakeMovie(20), FakeMovie(21)]
@ -49,7 +49,7 @@ public class PlaylistEnumeratorTests @@ -49,7 +49,7 @@ public class PlaylistEnumeratorTests
Id = 3,
PlaybackOrder = PlaybackOrder.Chronological,
PlayAll = false,
CollectionType = ProgramScheduleItemCollectionType.Collection,
CollectionType = CollectionType.Collection,
CollectionId = 3
},
[FakeMovie(30), FakeMovie(31)]
@ -67,11 +67,11 @@ public class PlaylistEnumeratorTests @@ -67,11 +67,11 @@ public class PlaylistEnumeratorTests
var items = new List<int>();
items.AddRange(enumerator.Current.Map(mi => mi.Id));
enumerator.MoveNext();
enumerator.MoveNext(Option<DateTimeOffset>.None);
while (enumerator.State.Index > 0)
{
items.AddRange(enumerator.Current.Map(mi => mi.Id));
enumerator.MoveNext();
enumerator.MoveNext(Option<DateTimeOffset>.None);
}
items.Count.ShouldBe(8);
@ -95,7 +95,7 @@ public class PlaylistEnumeratorTests @@ -95,7 +95,7 @@ public class PlaylistEnumeratorTests
Id = 1,
PlaybackOrder = PlaybackOrder.Chronological,
PlayAll = false,
CollectionType = ProgramScheduleItemCollectionType.Collection,
CollectionType = CollectionType.Collection,
CollectionId = 1
},
[FakeMovie(10)]
@ -106,7 +106,7 @@ public class PlaylistEnumeratorTests @@ -106,7 +106,7 @@ public class PlaylistEnumeratorTests
Id = 2,
PlaybackOrder = PlaybackOrder.Chronological,
PlayAll = false,
CollectionType = ProgramScheduleItemCollectionType.Collection,
CollectionType = CollectionType.Collection,
CollectionId = 2
},
[FakeMovie(20), FakeMovie(21)]
@ -117,7 +117,7 @@ public class PlaylistEnumeratorTests @@ -117,7 +117,7 @@ public class PlaylistEnumeratorTests
Id = 3,
PlaybackOrder = PlaybackOrder.Chronological,
PlayAll = true,
CollectionType = ProgramScheduleItemCollectionType.Collection,
CollectionType = CollectionType.Collection,
CollectionId = 3
},
[FakeMovie(30), FakeMovie(31)]
@ -135,11 +135,11 @@ public class PlaylistEnumeratorTests @@ -135,11 +135,11 @@ public class PlaylistEnumeratorTests
var items = new List<int>();
items.AddRange(enumerator.Current.Map(mi => mi.Id));
enumerator.MoveNext();
enumerator.MoveNext(Option<DateTimeOffset>.None);
while (enumerator.State.Index > 0)
{
items.AddRange(enumerator.Current.Map(mi => mi.Id));
enumerator.MoveNext();
enumerator.MoveNext(Option<DateTimeOffset>.None);
}
items.Count.ShouldBe(8);
@ -161,7 +161,7 @@ public class PlaylistEnumeratorTests @@ -161,7 +161,7 @@ public class PlaylistEnumeratorTests
Index = 0,
PlaybackOrder = PlaybackOrder.Chronological,
PlayAll = false,
CollectionType = ProgramScheduleItemCollectionType.Collection,
CollectionType = CollectionType.Collection,
CollectionId = 1
},
[FakeMovie(10)]
@ -173,7 +173,7 @@ public class PlaylistEnumeratorTests @@ -173,7 +173,7 @@ public class PlaylistEnumeratorTests
Index = 1,
PlaybackOrder = PlaybackOrder.Chronological,
PlayAll = true,
CollectionType = ProgramScheduleItemCollectionType.Collection,
CollectionType = CollectionType.Collection,
CollectionId = 2
},
[FakeMovie(20), FakeMovie(21)]
@ -185,7 +185,7 @@ public class PlaylistEnumeratorTests @@ -185,7 +185,7 @@ public class PlaylistEnumeratorTests
Index = 2,
PlaybackOrder = PlaybackOrder.Chronological,
PlayAll = false,
CollectionType = ProgramScheduleItemCollectionType.Collection,
CollectionType = CollectionType.Collection,
CollectionId = 3
},
[FakeMovie(30)]
@ -206,7 +206,7 @@ public class PlaylistEnumeratorTests @@ -206,7 +206,7 @@ public class PlaylistEnumeratorTests
for (var i = 0; i < 4; i++)
{
items.AddRange(enumerator.Current.Map(mi => mi.Id));
enumerator.MoveNext();
enumerator.MoveNext(Option<DateTimeOffset>.None);
}
// with seed 1, shuffle order of (1,2,3) is (2,3,1)
@ -231,7 +231,7 @@ public class PlaylistEnumeratorTests @@ -231,7 +231,7 @@ public class PlaylistEnumeratorTests
PlaybackOrder = PlaybackOrder.Chronological,
PlayAll = false,
Count = 2,
CollectionType = ProgramScheduleItemCollectionType.Collection,
CollectionType = CollectionType.Collection,
CollectionId = 1
},
[FakeMovie(10), FakeMovie(11), FakeMovie(12)]
@ -243,7 +243,7 @@ public class PlaylistEnumeratorTests @@ -243,7 +243,7 @@ public class PlaylistEnumeratorTests
Index = 1,
PlaybackOrder = PlaybackOrder.Chronological,
PlayAll = false,
CollectionType = ProgramScheduleItemCollectionType.Collection,
CollectionType = CollectionType.Collection,
CollectionId = 2
},
[FakeMovie(20)]
@ -255,7 +255,7 @@ public class PlaylistEnumeratorTests @@ -255,7 +255,7 @@ public class PlaylistEnumeratorTests
Index = 2,
PlaybackOrder = PlaybackOrder.Chronological,
PlayAll = false,
CollectionType = ProgramScheduleItemCollectionType.Collection,
CollectionType = CollectionType.Collection,
CollectionId = 3
},
[FakeMovie(30)]
@ -276,7 +276,7 @@ public class PlaylistEnumeratorTests @@ -276,7 +276,7 @@ public class PlaylistEnumeratorTests
for (var i = 0; i < 4; i++)
{
items.AddRange(enumerator.Current.Map(mi => mi.Id));
enumerator.MoveNext();
enumerator.MoveNext(Option<DateTimeOffset>.None);
}
// with seed 1, shuffle order of (1,2,3) is (2,3,1)

6
ErsatzTV.Core.Tests/Scheduling/PlayoutModeSchedulerMultipleTests.cs

@ -25,7 +25,7 @@ public class PlayoutModeSchedulerMultipleTests : SchedulerTestBase @@ -25,7 +25,7 @@ public class PlayoutModeSchedulerMultipleTests : SchedulerTestBase
{
Id = 1,
Index = 1,
CollectionType = ProgramScheduleItemCollectionType.Collection,
CollectionType = CollectionType.Collection,
Collection = collectionOne,
CollectionId = collectionOne.Id,
StartTime = TimeSpan.FromHours(1),
@ -127,7 +127,7 @@ public class PlayoutModeSchedulerMultipleTests : SchedulerTestBase @@ -127,7 +127,7 @@ public class PlayoutModeSchedulerMultipleTests : SchedulerTestBase
{
Id = 1,
Index = 1,
CollectionType = ProgramScheduleItemCollectionType.Collection,
CollectionType = CollectionType.Collection,
Collection = collectionOne,
CollectionId = collectionOne.Id,
StartTime = TimeSpan.FromHours(1),
@ -199,7 +199,7 @@ public class PlayoutModeSchedulerMultipleTests : SchedulerTestBase @@ -199,7 +199,7 @@ public class PlayoutModeSchedulerMultipleTests : SchedulerTestBase
{
Id = 1,
Index = 1,
CollectionType = ProgramScheduleItemCollectionType.Collection,
CollectionType = CollectionType.Collection,
Collection = collectionOne,
CollectionId = collectionOne.Id,
StartTime = null,

6
ErsatzTV.Core.Tests/Scheduling/RandomizedContentTests.cs

@ -37,7 +37,7 @@ public class RandomizedContentTests @@ -37,7 +37,7 @@ public class RandomizedContentTests
randomizedContent.Current.IsSome.ShouldBeTrue();
randomizedContent.Current.Do(c => list.Add(c.Id));
randomizedContent.MoveNext();
randomizedContent.MoveNext(Option<DateTimeOffset>.None);
}
list.ShouldNotBe([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
@ -56,7 +56,7 @@ public class RandomizedContentTests @@ -56,7 +56,7 @@ public class RandomizedContentTests
{
randomizedContent.State.Index.ShouldBe(i);
randomizedContent.MoveNext();
randomizedContent.MoveNext(Option<DateTimeOffset>.None);
}
}
@ -75,7 +75,7 @@ public class RandomizedContentTests @@ -75,7 +75,7 @@ public class RandomizedContentTests
randomizedContent.Current.Map(c => c.Id).IfNone(-1).ShouldBe(_expected[i - 2]);
randomizedContent.State.Index.ShouldBe(i);
randomizedContent.MoveNext();
randomizedContent.MoveNext(Option<DateTimeOffset>.None);
}
}

2
ErsatzTV.Core.Tests/Scheduling/ScheduleIntegrationTests.cs

@ -125,6 +125,7 @@ public class ScheduleIntegrationTests @@ -125,6 +125,7 @@ public class ScheduleIntegrationTests
new ArtistRepository(factory),
Substitute.For<IMultiEpisodeShuffleCollectionEnumeratorFactory>(),
Substitute.For<ILocalFileSystem>(),
Substitute.For<IRerunHelper>(),
provider.GetRequiredService<ILogger<PlayoutBuilder>>());
{
@ -320,6 +321,7 @@ public class ScheduleIntegrationTests @@ -320,6 +321,7 @@ public class ScheduleIntegrationTests
new ArtistRepository(factory),
Substitute.For<IMultiEpisodeShuffleCollectionEnumeratorFactory>(),
Substitute.For<ILocalFileSystem>(),
Substitute.For<IRerunHelper>(),
provider.GetRequiredService<ILogger<PlayoutBuilder>>());
for (var i = 0; i <= 24 * 4; i++)

8
ErsatzTV.Core.Tests/Scheduling/SeasonEpisodeContentTests.cs

@ -25,7 +25,7 @@ public class SeasonEpisodeContentTests @@ -25,7 +25,7 @@ public class SeasonEpisodeContentTests
{
chronologicalContent.Current.IsSome.ShouldBeTrue();
chronologicalContent.Current.Map(x => x.Id).IfNone(-1).ShouldBe(i);
chronologicalContent.MoveNext();
chronologicalContent.MoveNext(Option<DateTimeOffset>.None);
}
}
@ -40,7 +40,7 @@ public class SeasonEpisodeContentTests @@ -40,7 +40,7 @@ public class SeasonEpisodeContentTests
for (var i = 0; i < 10; i++)
{
chronologicalContent.State.Index.ShouldBe(i % 10);
chronologicalContent.MoveNext();
chronologicalContent.MoveNext(Option<DateTimeOffset>.None);
}
}
@ -57,7 +57,7 @@ public class SeasonEpisodeContentTests @@ -57,7 +57,7 @@ public class SeasonEpisodeContentTests
chronologicalContent.Current.IsSome.ShouldBeTrue();
chronologicalContent.Current.Map(x => x.Id).IfNone(-1).ShouldBe(i);
chronologicalContent.State.Index.ShouldBe(i - 1);
chronologicalContent.MoveNext();
chronologicalContent.MoveNext(Option<DateTimeOffset>.None);
}
}
@ -89,7 +89,7 @@ public class SeasonEpisodeContentTests @@ -89,7 +89,7 @@ public class SeasonEpisodeContentTests
for (var i = 0; i < 16; i++)
{
chronologicalContent.State.Index.ShouldBe(i % 8);
chronologicalContent.MoveNext();
chronologicalContent.MoveNext(Option<DateTimeOffset>.None);
}
}

10
ErsatzTV.Core.Tests/Scheduling/ShuffledContentTests.cs

@ -32,7 +32,7 @@ public class ShuffledContentTests @@ -32,7 +32,7 @@ public class ShuffledContentTests
{
shuffledContent.Current.IsSome.ShouldBeTrue();
shuffledContent.Current.Do(x => list.Add(x.Id));
shuffledContent.MoveNext();
shuffledContent.MoveNext(Option<DateTimeOffset>.None);
}
for (var i = 0; i < list.Count - 1; i++)
@ -59,7 +59,7 @@ public class ShuffledContentTests @@ -59,7 +59,7 @@ public class ShuffledContentTests
{
shuffledContent.Current.IsSome.ShouldBeTrue();
shuffledContent.Current.Do(x => list.Add(x.Id));
shuffledContent.MoveNext();
shuffledContent.MoveNext(Option<DateTimeOffset>.None);
}
list.ShouldBe([1, 1, 1, 1, 1, 1, 1, 1, 1, 1]);
@ -80,7 +80,7 @@ public class ShuffledContentTests @@ -80,7 +80,7 @@ public class ShuffledContentTests
{
shuffledContent.Current.IsSome.ShouldBeTrue();
shuffledContent.Current.Do(x => list.Add(x.Id));
shuffledContent.MoveNext();
shuffledContent.MoveNext(Option<DateTimeOffset>.None);
}
list.ShouldNotBe([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
@ -99,7 +99,7 @@ public class ShuffledContentTests @@ -99,7 +99,7 @@ public class ShuffledContentTests
for (var i = 0; i < 10; i++)
{
shuffledContent.State.Index.ShouldBe(i);
shuffledContent.MoveNext();
shuffledContent.MoveNext(Option<DateTimeOffset>.None);
}
}
@ -117,7 +117,7 @@ public class ShuffledContentTests @@ -117,7 +117,7 @@ public class ShuffledContentTests
shuffledContent.Current.IsSome.ShouldBeTrue();
shuffledContent.Current.Map(x => x.Id).IfNone(-1).ShouldBe(i);
shuffledContent.State.Index.ShouldBe(i - 1);
shuffledContent.MoveNext();
shuffledContent.MoveNext(Option<DateTimeOffset>.None);
}
}

2
ErsatzTV.Core/Domain/Collection/PlaylistItem.cs

@ -6,7 +6,7 @@ public class PlaylistItem @@ -6,7 +6,7 @@ public class PlaylistItem
public int Index { get; set; }
public int PlaylistId { get; set; }
public Playlist Playlist { get; set; }
public ProgramScheduleItemCollectionType CollectionType { get; set; }
public CollectionType CollectionType { get; set; }
public int? CollectionId { get; set; }
public Collection Collection { get; set; }
public int? MediaItemId { get; set; }

21
ErsatzTV.Core/Domain/Collection/RerunCollection.cs

@ -0,0 +1,21 @@ @@ -0,0 +1,21 @@
using System.Diagnostics.CodeAnalysis;
namespace ErsatzTV.Core.Domain;
[SuppressMessage("Naming", "CA1711:Identifiers should not have incorrect suffix")]
public class RerunCollection
{
public int Id { get; set; }
public string Name { get; set; }
public CollectionType 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; }
public PlaybackOrder FirstRunPlaybackOrder { get; set; }
public PlaybackOrder RerunPlaybackOrder { get; set; }
}

4
ErsatzTV.Core/Domain/ProgramScheduleItemCollectionType.cs → ErsatzTV.Core/Domain/CollectionType.cs

@ -1,6 +1,6 @@ @@ -1,6 +1,6 @@
namespace ErsatzTV.Core.Domain;
public enum ProgramScheduleItemCollectionType
public enum CollectionType
{
Collection = 0,
TelevisionShow = 1,
@ -9,6 +9,8 @@ public enum ProgramScheduleItemCollectionType @@ -9,6 +9,8 @@ public enum ProgramScheduleItemCollectionType
MultiCollection = 4,
SmartCollection = 5,
Playlist = 6,
RerunFirstRun = 7,
RerunRerun = 8,
Movie = 10,
Episode = 20,

1
ErsatzTV.Core/Domain/ConfigElementKey.cs

@ -34,6 +34,7 @@ public class ConfigElementKey @@ -34,6 +34,7 @@ public class ConfigElementKey
public static ConfigElementKey CollectionsPageSize => new("pages.collections.page_size");
public static ConfigElementKey MultiCollectionsPageSize => new("pages.multi_collections.page_size");
public static ConfigElementKey SmartCollectionsPageSize => new("pages.smart_collections.page_size");
public static ConfigElementKey RerunCollectionsPageSize => new("pages.rerun_collections.page_size");
public static ConfigElementKey SchedulesPageSize => new("pages.schedules.page_size");
public static ConfigElementKey SchedulesDetailPageSize => new("pages.schedules.detail_page_size");
public static ConfigElementKey PlayoutsPageSize => new("pages.playouts.page_size");

2
ErsatzTV.Core/Domain/Filler/FillerPreset.cs

@ -10,7 +10,7 @@ public class FillerPreset @@ -10,7 +10,7 @@ public class FillerPreset
public int? Count { get; set; }
public int? PadToNearestMinute { get; set; }
public bool AllowWatermarks { get; set; }
public ProgramScheduleItemCollectionType CollectionType { get; set; }
public CollectionType CollectionType { get; set; }
public int? CollectionId { get; set; }
public Collection Collection { get; set; }
public int? MediaItemId { get; set; }

4
ErsatzTV.Core/Domain/PlayoutProgramScheduleAnchor.cs

@ -16,13 +16,15 @@ public class PlayoutProgramScheduleAnchor @@ -16,13 +16,15 @@ public class PlayoutProgramScheduleAnchor
? new DateTimeOffset(AnchorDate.Value, TimeSpan.Zero).ToLocalTime()
: null;
public ProgramScheduleItemCollectionType CollectionType { get; set; }
public CollectionType CollectionType { get; set; }
public int? CollectionId { get; set; }
public Collection Collection { get; set; }
public int? MultiCollectionId { get; set; }
public MultiCollection MultiCollection { get; set; }
public int? SmartCollectionId { get; set; }
public SmartCollection SmartCollection { get; set; }
public int? RerunCollectionId { get; set; }
public RerunCollection RerunCollection { get; set; }
public int? MediaItemId { get; set; }
public MediaItem MediaItem { get; set; }
public int? PlaylistId { get; set; }

4
ErsatzTV.Core/Domain/ProgramScheduleItem.cs

@ -11,7 +11,7 @@ public abstract class ProgramScheduleItem @@ -11,7 +11,7 @@ public abstract class ProgramScheduleItem
public StartType StartType => StartTime.HasValue ? StartType.Fixed : StartType.Dynamic;
public TimeSpan? StartTime { get; set; }
public FixedStartTimeBehavior? FixedStartTimeBehavior { get; set; }
public ProgramScheduleItemCollectionType CollectionType { get; set; }
public CollectionType CollectionType { get; set; }
public GuideMode GuideMode { get; set; }
public string CustomTitle { get; set; }
public int ProgramScheduleId { get; set; }
@ -27,6 +27,8 @@ public abstract class ProgramScheduleItem @@ -27,6 +27,8 @@ public abstract class ProgramScheduleItem
public MultiCollection MultiCollection { get; set; }
public int? SmartCollectionId { get; set; }
public SmartCollection SmartCollection { get; set; }
public int? RerunCollectionId { get; set; }
public RerunCollection RerunCollection { get; set; }
public int? PlaylistId { get; set; }
public Playlist Playlist { get; set; }
public string FakeCollectionKey { get; set; }

2
ErsatzTV.Core/Domain/Scheduling/BlockItem.cs

@ -6,7 +6,7 @@ public class BlockItem @@ -6,7 +6,7 @@ public class BlockItem
public int Index { get; set; }
public int BlockId { get; set; }
public Block Block { get; set; }
public ProgramScheduleItemCollectionType CollectionType { get; set; }
public CollectionType CollectionType { get; set; }
public int? CollectionId { get; set; }
public Collection Collection { get; set; }
public int? MediaItemId { get; set; }

4
ErsatzTV.Core/Domain/Scheduling/Deco.cs

@ -22,7 +22,7 @@ public class Deco @@ -22,7 +22,7 @@ public class Deco
// default filler
public DecoMode DefaultFillerMode { get; set; }
public ProgramScheduleItemCollectionType DefaultFillerCollectionType { get; set; }
public CollectionType DefaultFillerCollectionType { get; set; }
public int? DefaultFillerCollectionId { get; set; }
public Collection DefaultFillerCollection { get; set; }
public int? DefaultFillerMediaItemId { get; set; }
@ -35,7 +35,7 @@ public class Deco @@ -35,7 +35,7 @@ public class Deco
// dead air fallback
public DecoMode DeadAirFallbackMode { get; set; }
public ProgramScheduleItemCollectionType DeadAirFallbackCollectionType { get; set; }
public CollectionType DeadAirFallbackCollectionType { get; set; }
public int? DeadAirFallbackCollectionId { get; set; }
public Collection DeadAirFallbackCollection { get; set; }
public int? DeadAirFallbackMediaItemId { get; set; }

13
ErsatzTV.Core/Domain/Scheduling/RerunHistory.cs

@ -0,0 +1,13 @@ @@ -0,0 +1,13 @@
namespace ErsatzTV.Core.Domain.Scheduling;
public class RerunHistory
{
public int Id { get; set; }
public int PlayoutId { get; set; }
public Playout Playout { get; set; }
public int RerunCollectionId { get; set; }
public RerunCollection RerunCollection { get; set; }
public int MediaItemId { get; set; }
public MediaItem MediaItem { get; set; }
public DateTime When { get; set; }
}

2
ErsatzTV.Core/Interfaces/Repositories/IMediaCollectionRepository.cs

@ -29,6 +29,7 @@ public interface IMediaCollectionRepository @@ -29,6 +29,7 @@ public interface IMediaCollectionRepository
string query,
string smartCollectionName,
CancellationToken cancellationToken);
Task<List<MediaItem>> GetRerunCollectionItems(int id, CancellationToken cancellationToken);
Task<List<MediaItem>> GetShowItemsByShowGuids(List<string> guids);
Task<List<MediaItem>> GetPlaylistItems(int id, CancellationToken cancellationToken);
Task<List<Movie>> GetMovie(int id);
@ -48,6 +49,7 @@ public interface IMediaCollectionRepository @@ -48,6 +49,7 @@ public interface IMediaCollectionRepository
Task<List<int>> PlayoutIdsUsingCollection(int collectionId);
Task<List<int>> PlayoutIdsUsingMultiCollection(int multiCollectionId);
Task<List<int>> PlayoutIdsUsingSmartCollection(int smartCollectionId);
Task<List<int>> PlayoutIdsUsingRerunCollection(int rerunCollectionId);
Task<bool> IsCustomPlaybackOrder(int collectionId);
Task<Option<string>> GetNameFromKey(CollectionKey emptyCollection, CancellationToken cancellationToken);
List<CollectionWithItems> GroupIntoFakeCollections(List<MediaItem> items, string fakeKey = null);

2
ErsatzTV.Core/Interfaces/Scheduling/IMediaCollectionEnumerator.cs

@ -10,5 +10,5 @@ public interface IMediaCollectionEnumerator @@ -10,5 +10,5 @@ public interface IMediaCollectionEnumerator
int Count { get; }
Option<TimeSpan> MinimumDuration { get; }
void ResetState(CollectionEnumeratorState state);
void MoveNext();
void MoveNext(Option<DateTimeOffset> scheduledAt);
}

35
ErsatzTV.Core/Interfaces/Scheduling/IRerunHelper.cs

@ -0,0 +1,35 @@ @@ -0,0 +1,35 @@
using ErsatzTV.Core.Domain;
using ErsatzTV.Core.Domain.Scheduling;
using ErsatzTV.Core.Scheduling;
namespace ErsatzTV.Core.Interfaces.Scheduling;
public interface IRerunHelper
{
bool ClearHistory { get; set; }
Task InitWithMediaItems(
int playoutId,
CollectionKey collectionKey,
List<MediaItem> mediaItems,
CancellationToken cancellationToken);
IMediaCollectionEnumerator CreateEnumerator(
CollectionKey collectionKey,
CollectionEnumeratorState state,
CancellationToken cancellationToken);
bool IsFirstRun(CollectionKey collectionKey, int mediaItemId);
bool IsRerun(CollectionKey collectionKey, int mediaItemId);
int FirstRunCount(CollectionKey collectionKey);
int RerunCount(CollectionKey collectionKey);
void AddToHistory(CollectionKey collectionKey, int mediaItemId, DateTimeOffset scheduledAt);
System.Collections.Generic.HashSet<int> GetHistoryToRemove();
List<RerunHistory> GetHistoryToAdd();
}

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

@ -242,7 +242,7 @@ public class BlockPlayoutBuilder( @@ -242,7 +242,7 @@ public class BlockPlayoutBuilder(
result.AddedHistory.Add(nextHistory);
currentTime += itemDuration;
enumerator.MoveNext();
enumerator.MoveNext(playoutItem.StartOffset);
}
if (pastTime)

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

@ -177,7 +177,7 @@ public class BlockPlayoutFillerBuilder( @@ -177,7 +177,7 @@ public class BlockPlayoutFillerBuilder(
result.AddedHistory.Add(nextHistory);
current += itemDuration;
enumerator.MoveNext();
enumerator.MoveNext(filler.StartOffset);
}
if (pastTime)
@ -227,17 +227,17 @@ public class BlockPlayoutFillerBuilder( @@ -227,17 +227,17 @@ public class BlockPlayoutFillerBuilder(
{
switch (deco.DefaultFillerCollectionType)
{
case ProgramScheduleItemCollectionType.Collection:
case CollectionType.Collection:
return deco.DefaultFillerCollectionId.HasValue;
case ProgramScheduleItemCollectionType.TelevisionShow:
case CollectionType.TelevisionShow:
return deco.DefaultFillerMediaItemId.HasValue;
case ProgramScheduleItemCollectionType.TelevisionSeason:
case CollectionType.TelevisionSeason:
return deco.DefaultFillerMediaItemId.HasValue;
case ProgramScheduleItemCollectionType.Artist:
case CollectionType.Artist:
return deco.DefaultFillerMediaItemId.HasValue;
case ProgramScheduleItemCollectionType.MultiCollection:
case CollectionType.MultiCollection:
return deco.DefaultFillerMultiCollectionId.HasValue;
case ProgramScheduleItemCollectionType.SmartCollection:
case CollectionType.SmartCollection:
return deco.DefaultFillerSmartCollectionId.HasValue;
default:
return false;

2
ErsatzTV.Core/Scheduling/BlockScheduling/BlockPlayoutShuffledMediaCollectionEnumerator.cs

@ -45,7 +45,7 @@ public class BlockPlayoutShuffledMediaCollectionEnumerator : IMediaCollectionEnu @@ -45,7 +45,7 @@ public class BlockPlayoutShuffledMediaCollectionEnumerator : IMediaCollectionEnu
public Option<MediaItem> Current => _shuffled.Any() ? _shuffled[State.Index % _mediaItemCount] : None;
public Option<bool> CurrentIncludeInProgramGuide { get; }
public void MoveNext()
public void MoveNext(Option<DateTimeOffset> scheduledAt)
{
State.Index++;
if (State.Index % _mediaItemCount == 0)

5
ErsatzTV.Core/Scheduling/ChronologicalMediaCollectionEnumerator.cs

@ -32,7 +32,7 @@ public sealed class ChronologicalMediaCollectionEnumerator : IMediaCollectionEnu @@ -32,7 +32,7 @@ public sealed class ChronologicalMediaCollectionEnumerator : IMediaCollectionEnu
while (State.Index < state.Index)
{
MoveNext();
MoveNext(Option<DateTimeOffset>.None);
}
}
@ -45,7 +45,8 @@ public sealed class ChronologicalMediaCollectionEnumerator : IMediaCollectionEnu @@ -45,7 +45,8 @@ public sealed class ChronologicalMediaCollectionEnumerator : IMediaCollectionEnu
public Option<MediaItem> Current => _sortedMediaItems.Count != 0 ? _sortedMediaItems[State.Index] : None;
public Option<bool> CurrentIncludeInProgramGuide { get; }
public void MoveNext() => State.Index = (State.Index + 1) % _sortedMediaItems.Count;
public void MoveNext(Option<DateTimeOffset> scheduledAt) =>
State.Index = (State.Index + 1) % _sortedMediaItems.Count;
public Option<TimeSpan> MinimumDuration => _lazyMinimumDuration.Value;

125
ErsatzTV.Core/Scheduling/CollectionKey.cs

@ -6,10 +6,11 @@ namespace ErsatzTV.Core.Scheduling; @@ -6,10 +6,11 @@ namespace ErsatzTV.Core.Scheduling;
public class CollectionKey : Record<CollectionKey>
{
public ProgramScheduleItemCollectionType CollectionType { get; set; }
public CollectionType CollectionType { get; set; }
public int? CollectionId { get; set; }
public int? MultiCollectionId { get; set; }
public int? SmartCollectionId { get; set; }
public int? RerunCollectionId { get; set; }
public int? MediaItemId { get; set; }
public int? PlaylistId { get; set; }
public string FakeCollectionKey { get; set; }
@ -17,71 +18,71 @@ public class CollectionKey : Record<CollectionKey> @@ -17,71 +18,71 @@ public class CollectionKey : Record<CollectionKey>
public static CollectionKey ForPlaylistItem(PlaylistItem item) =>
item.CollectionType switch
{
ProgramScheduleItemCollectionType.Collection => new CollectionKey
CollectionType.Collection => new CollectionKey
{
CollectionType = item.CollectionType,
CollectionId = item.CollectionId
},
ProgramScheduleItemCollectionType.TelevisionShow => new CollectionKey
CollectionType.TelevisionShow => new CollectionKey
{
CollectionType = item.CollectionType,
MediaItemId = item.MediaItemId
},
ProgramScheduleItemCollectionType.TelevisionSeason => new CollectionKey
CollectionType.TelevisionSeason => new CollectionKey
{
CollectionType = item.CollectionType,
MediaItemId = item.MediaItemId
},
ProgramScheduleItemCollectionType.Artist => new CollectionKey
CollectionType.Artist => new CollectionKey
{
CollectionType = item.CollectionType,
MediaItemId = item.MediaItemId
},
ProgramScheduleItemCollectionType.MultiCollection => new CollectionKey
CollectionType.MultiCollection => new CollectionKey
{
CollectionType = item.CollectionType,
MultiCollectionId = item.MultiCollectionId
},
ProgramScheduleItemCollectionType.SmartCollection => new CollectionKey
CollectionType.SmartCollection => new CollectionKey
{
CollectionType = item.CollectionType,
SmartCollectionId = item.SmartCollectionId
},
ProgramScheduleItemCollectionType.FakeCollection => new CollectionKey
CollectionType.FakeCollection => new CollectionKey
{
CollectionType = item.CollectionType
},
ProgramScheduleItemCollectionType.FakePlaylistItem => new CollectionKey
CollectionType.FakePlaylistItem => new CollectionKey
{
CollectionType = item.CollectionType,
CollectionId = item.CollectionId
},
ProgramScheduleItemCollectionType.Movie => new CollectionKey
CollectionType.Movie => new CollectionKey
{
CollectionType = item.CollectionType,
MediaItemId = item.MediaItemId
},
ProgramScheduleItemCollectionType.Episode => new CollectionKey
CollectionType.Episode => new CollectionKey
{
CollectionType = item.CollectionType,
MediaItemId = item.MediaItemId
},
ProgramScheduleItemCollectionType.MusicVideo => new CollectionKey
CollectionType.MusicVideo => new CollectionKey
{
CollectionType = item.CollectionType,
MediaItemId = item.MediaItemId
},
ProgramScheduleItemCollectionType.OtherVideo => new CollectionKey
CollectionType.OtherVideo => new CollectionKey
{
CollectionType = item.CollectionType,
MediaItemId = item.MediaItemId
},
ProgramScheduleItemCollectionType.Song => new CollectionKey
CollectionType.Song => new CollectionKey
{
CollectionType = item.CollectionType,
MediaItemId = item.MediaItemId
},
ProgramScheduleItemCollectionType.Image => new CollectionKey
CollectionType.Image => new CollectionKey
{
CollectionType = item.CollectionType,
MediaItemId = item.MediaItemId
@ -92,66 +93,66 @@ public class CollectionKey : Record<CollectionKey> @@ -92,66 +93,66 @@ public class CollectionKey : Record<CollectionKey>
public static CollectionKey ForBlockItem(BlockItem item) =>
item.CollectionType switch
{
ProgramScheduleItemCollectionType.Collection => new CollectionKey
CollectionType.Collection => new CollectionKey
{
CollectionType = item.CollectionType,
CollectionId = item.CollectionId
},
ProgramScheduleItemCollectionType.TelevisionShow => new CollectionKey
CollectionType.TelevisionShow => new CollectionKey
{
CollectionType = item.CollectionType,
MediaItemId = item.MediaItemId
},
ProgramScheduleItemCollectionType.TelevisionSeason => new CollectionKey
CollectionType.TelevisionSeason => new CollectionKey
{
CollectionType = item.CollectionType,
MediaItemId = item.MediaItemId
},
ProgramScheduleItemCollectionType.Artist => new CollectionKey
CollectionType.Artist => new CollectionKey
{
CollectionType = item.CollectionType,
MediaItemId = item.MediaItemId
},
ProgramScheduleItemCollectionType.MultiCollection => new CollectionKey
CollectionType.MultiCollection => new CollectionKey
{
CollectionType = item.CollectionType,
MultiCollectionId = item.MultiCollectionId
},
ProgramScheduleItemCollectionType.SmartCollection => new CollectionKey
CollectionType.SmartCollection => new CollectionKey
{
CollectionType = item.CollectionType,
SmartCollectionId = item.SmartCollectionId
},
ProgramScheduleItemCollectionType.FakeCollection => new CollectionKey
CollectionType.FakeCollection => new CollectionKey
{
CollectionType = item.CollectionType
},
ProgramScheduleItemCollectionType.Movie => new CollectionKey
CollectionType.Movie => new CollectionKey
{
CollectionType = item.CollectionType,
MediaItemId = item.MediaItemId
},
ProgramScheduleItemCollectionType.Episode => new CollectionKey
CollectionType.Episode => new CollectionKey
{
CollectionType = item.CollectionType,
MediaItemId = item.MediaItemId
},
ProgramScheduleItemCollectionType.MusicVideo => new CollectionKey
CollectionType.MusicVideo => new CollectionKey
{
CollectionType = item.CollectionType,
MediaItemId = item.MediaItemId
},
ProgramScheduleItemCollectionType.OtherVideo => new CollectionKey
CollectionType.OtherVideo => new CollectionKey
{
CollectionType = item.CollectionType,
MediaItemId = item.MediaItemId
},
ProgramScheduleItemCollectionType.Song => new CollectionKey
CollectionType.Song => new CollectionKey
{
CollectionType = item.CollectionType,
MediaItemId = item.MediaItemId
},
ProgramScheduleItemCollectionType.Image => new CollectionKey
CollectionType.Image => new CollectionKey
{
CollectionType = item.CollectionType,
MediaItemId = item.MediaItemId
@ -162,66 +163,66 @@ public class CollectionKey : Record<CollectionKey> @@ -162,66 +163,66 @@ public class CollectionKey : Record<CollectionKey>
public static CollectionKey ForDecoDefaultFiller(Deco deco) =>
deco.DefaultFillerCollectionType switch
{
ProgramScheduleItemCollectionType.Collection => new CollectionKey
CollectionType.Collection => new CollectionKey
{
CollectionType = deco.DefaultFillerCollectionType,
CollectionId = deco.DefaultFillerCollectionId
},
ProgramScheduleItemCollectionType.TelevisionShow => new CollectionKey
CollectionType.TelevisionShow => new CollectionKey
{
CollectionType = deco.DefaultFillerCollectionType,
MediaItemId = deco.DefaultFillerMediaItemId
},
ProgramScheduleItemCollectionType.TelevisionSeason => new CollectionKey
CollectionType.TelevisionSeason => new CollectionKey
{
CollectionType = deco.DefaultFillerCollectionType,
MediaItemId = deco.DefaultFillerMediaItemId
},
ProgramScheduleItemCollectionType.Artist => new CollectionKey
CollectionType.Artist => new CollectionKey
{
CollectionType = deco.DefaultFillerCollectionType,
MediaItemId = deco.DefaultFillerMediaItemId
},
ProgramScheduleItemCollectionType.MultiCollection => new CollectionKey
CollectionType.MultiCollection => new CollectionKey
{
CollectionType = deco.DefaultFillerCollectionType,
MultiCollectionId = deco.DefaultFillerMultiCollectionId
},
ProgramScheduleItemCollectionType.SmartCollection => new CollectionKey
CollectionType.SmartCollection => new CollectionKey
{
CollectionType = deco.DefaultFillerCollectionType,
SmartCollectionId = deco.DefaultFillerSmartCollectionId
},
ProgramScheduleItemCollectionType.FakeCollection => new CollectionKey
CollectionType.FakeCollection => new CollectionKey
{
CollectionType = deco.DefaultFillerCollectionType
},
ProgramScheduleItemCollectionType.Movie => new CollectionKey
CollectionType.Movie => new CollectionKey
{
CollectionType = deco.DefaultFillerCollectionType,
MediaItemId = deco.DefaultFillerMediaItemId
},
ProgramScheduleItemCollectionType.Episode => new CollectionKey
CollectionType.Episode => new CollectionKey
{
CollectionType = deco.DefaultFillerCollectionType,
MediaItemId = deco.DefaultFillerMediaItemId
},
ProgramScheduleItemCollectionType.MusicVideo => new CollectionKey
CollectionType.MusicVideo => new CollectionKey
{
CollectionType = deco.DefaultFillerCollectionType,
MediaItemId = deco.DefaultFillerMediaItemId
},
ProgramScheduleItemCollectionType.OtherVideo => new CollectionKey
CollectionType.OtherVideo => new CollectionKey
{
CollectionType = deco.DefaultFillerCollectionType,
MediaItemId = deco.DefaultFillerMediaItemId
},
ProgramScheduleItemCollectionType.Song => new CollectionKey
CollectionType.Song => new CollectionKey
{
CollectionType = deco.DefaultFillerCollectionType,
MediaItemId = deco.DefaultFillerMediaItemId
},
ProgramScheduleItemCollectionType.Image => new CollectionKey
CollectionType.Image => new CollectionKey
{
CollectionType = deco.DefaultFillerCollectionType,
MediaItemId = deco.DefaultFillerMediaItemId
@ -232,49 +233,61 @@ public class CollectionKey : Record<CollectionKey> @@ -232,49 +233,61 @@ public class CollectionKey : Record<CollectionKey>
public static CollectionKey ForScheduleItem(ProgramScheduleItem item) =>
item.CollectionType switch
{
ProgramScheduleItemCollectionType.Collection => new CollectionKey
CollectionType.Collection => new CollectionKey
{
CollectionType = item.CollectionType,
CollectionId = item.CollectionId,
FakeCollectionKey = item.FakeCollectionKey
},
ProgramScheduleItemCollectionType.TelevisionShow => new CollectionKey
CollectionType.TelevisionShow => new CollectionKey
{
CollectionType = item.CollectionType,
MediaItemId = item.MediaItemId,
FakeCollectionKey = item.FakeCollectionKey
},
ProgramScheduleItemCollectionType.TelevisionSeason => new CollectionKey
CollectionType.TelevisionSeason => new CollectionKey
{
CollectionType = item.CollectionType,
MediaItemId = item.MediaItemId,
FakeCollectionKey = item.FakeCollectionKey
},
ProgramScheduleItemCollectionType.Artist => new CollectionKey
CollectionType.Artist => new CollectionKey
{
CollectionType = item.CollectionType,
MediaItemId = item.MediaItemId,
FakeCollectionKey = item.FakeCollectionKey
},
ProgramScheduleItemCollectionType.MultiCollection => new CollectionKey
CollectionType.MultiCollection => new CollectionKey
{
CollectionType = item.CollectionType,
MultiCollectionId = item.MultiCollectionId,
FakeCollectionKey = item.FakeCollectionKey
},
ProgramScheduleItemCollectionType.SmartCollection => new CollectionKey
CollectionType.SmartCollection => new CollectionKey
{
CollectionType = item.CollectionType,
SmartCollectionId = item.SmartCollectionId,
FakeCollectionKey = item.FakeCollectionKey
},
ProgramScheduleItemCollectionType.Playlist => new CollectionKey
CollectionType.RerunFirstRun => new CollectionKey
{
CollectionType = item.CollectionType,
RerunCollectionId = item.RerunCollectionId,
FakeCollectionKey = item.FakeCollectionKey
},
CollectionType.RerunRerun => new CollectionKey
{
CollectionType = item.CollectionType,
RerunCollectionId = item.RerunCollectionId,
FakeCollectionKey = item.FakeCollectionKey
},
CollectionType.Playlist => new CollectionKey
{
CollectionType = item.CollectionType,
PlaylistId = item.PlaylistId,
FakeCollectionKey = item.FakeCollectionKey
},
ProgramScheduleItemCollectionType.FakeCollection => new CollectionKey
CollectionType.FakeCollection => new CollectionKey
{
CollectionType = item.CollectionType,
FakeCollectionKey = item.FakeCollectionKey
@ -285,37 +298,37 @@ public class CollectionKey : Record<CollectionKey> @@ -285,37 +298,37 @@ public class CollectionKey : Record<CollectionKey>
public static CollectionKey ForFillerPreset(FillerPreset filler) =>
filler.CollectionType switch
{
ProgramScheduleItemCollectionType.Collection => new CollectionKey
CollectionType.Collection => new CollectionKey
{
CollectionType = filler.CollectionType,
CollectionId = filler.CollectionId
},
ProgramScheduleItemCollectionType.TelevisionShow => new CollectionKey
CollectionType.TelevisionShow => new CollectionKey
{
CollectionType = filler.CollectionType,
MediaItemId = filler.MediaItemId
},
ProgramScheduleItemCollectionType.TelevisionSeason => new CollectionKey
CollectionType.TelevisionSeason => new CollectionKey
{
CollectionType = filler.CollectionType,
MediaItemId = filler.MediaItemId
},
ProgramScheduleItemCollectionType.Artist => new CollectionKey
CollectionType.Artist => new CollectionKey
{
CollectionType = filler.CollectionType,
MediaItemId = filler.MediaItemId
},
ProgramScheduleItemCollectionType.MultiCollection => new CollectionKey
CollectionType.MultiCollection => new CollectionKey
{
CollectionType = filler.CollectionType,
MultiCollectionId = filler.MultiCollectionId
},
ProgramScheduleItemCollectionType.SmartCollection => new CollectionKey
CollectionType.SmartCollection => new CollectionKey
{
CollectionType = filler.CollectionType,
SmartCollectionId = filler.SmartCollectionId
},
ProgramScheduleItemCollectionType.Playlist => new CollectionKey
CollectionType.Playlist => new CollectionKey
{
CollectionType = filler.CollectionType,
PlaylistId = filler.PlaylistId

5
ErsatzTV.Core/Scheduling/CustomOrderCollectionEnumerator.cs

@ -28,7 +28,7 @@ public class CustomOrderCollectionEnumerator : IMediaCollectionEnumerator @@ -28,7 +28,7 @@ public class CustomOrderCollectionEnumerator : IMediaCollectionEnumerator
State = new CollectionEnumeratorState { Seed = state.Seed };
while (State.Index < state.Index)
{
MoveNext();
MoveNext(Option<DateTimeOffset>.None);
}
}
@ -41,7 +41,8 @@ public class CustomOrderCollectionEnumerator : IMediaCollectionEnumerator @@ -41,7 +41,8 @@ public class CustomOrderCollectionEnumerator : IMediaCollectionEnumerator
public Option<MediaItem> Current => _sortedMediaItems.Count != 0 ? _sortedMediaItems[State.Index] : None;
public Option<bool> CurrentIncludeInProgramGuide { get; }
public void MoveNext() => State.Index = (State.Index + 1) % _sortedMediaItems.Count;
public void MoveNext(Option<DateTimeOffset> scheduledAt) =>
State.Index = (State.Index + 1) % _sortedMediaItems.Count;
public Option<TimeSpan> MinimumDuration => _lazyMinimumDuration.Value;

20
ErsatzTV.Core/Scheduling/Engine/MarathonHelper.cs

@ -138,55 +138,55 @@ public class MarathonHelper(IMediaCollectionRepository mediaCollectionRepository @@ -138,55 +138,55 @@ public class MarathonHelper(IMediaCollectionRepository mediaCollectionRepository
mediaItem switch
{
Episode e => new GroupKey(
ProgramScheduleItemCollectionType.TelevisionShow,
CollectionType.TelevisionShow,
null,
null,
null,
e.Season?.ShowId ?? 0),
_ => new GroupKey(ProgramScheduleItemCollectionType.TelevisionShow, null, null, null, 0)
_ => new GroupKey(CollectionType.TelevisionShow, null, null, null, 0)
};
private static GroupKey MediaItemKeyBySeason(MediaItem mediaItem) =>
mediaItem switch
{
Episode e => new GroupKey(
ProgramScheduleItemCollectionType.TelevisionSeason,
CollectionType.TelevisionSeason,
null,
null,
null,
e.SeasonId),
_ => new GroupKey(ProgramScheduleItemCollectionType.TelevisionSeason, null, null, null, 0)
_ => new GroupKey(CollectionType.TelevisionSeason, null, null, null, 0)
};
private static GroupKey MediaItemKeyByArtist(MediaItem mediaItem) =>
mediaItem switch
{
MusicVideo mv => new GroupKey(
ProgramScheduleItemCollectionType.Artist,
CollectionType.Artist,
null,
null,
null,
mv.ArtistId),
_ => new GroupKey(ProgramScheduleItemCollectionType.Artist, null, null, null, 0)
_ => new GroupKey(CollectionType.Artist, null, null, null, 0)
};
private static GroupKey MediaItemKeyByAlbum(MediaItem mediaItem) =>
mediaItem switch
{
Song s => new GroupKey(
ProgramScheduleItemCollectionType.Collection,
CollectionType.Collection,
s.SongMetadata.HeadOrNone().Map(sm => sm.Album.GetStableHashCode()).IfNone(0),
null,
null,
null),
MusicVideo mv => new GroupKey(
ProgramScheduleItemCollectionType.Collection,
CollectionType.Collection,
mv.MusicVideoMetadata.HeadOrNone()
.Map(mvm => $"{mv.ArtistId}-${mvm.Album}".GetStableHashCode()).IfNone(0),
null,
null,
null),
_ => new GroupKey(ProgramScheduleItemCollectionType.Collection, 0, null, null, null)
_ => new GroupKey(CollectionType.Collection, 0, null, null, null)
};
private static PlaylistItem GroupToPlaylistItem(
@ -211,7 +211,7 @@ public class MarathonHelper(IMediaCollectionRepository mediaCollectionRepository @@ -211,7 +211,7 @@ public class MarathonHelper(IMediaCollectionRepository mediaCollectionRepository
};
private record GroupKey(
ProgramScheduleItemCollectionType CollectionType,
CollectionType CollectionType,
int? CollectionId,
int? MultiCollectionId,
int? SmartCollectionId,

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

@ -38,7 +38,7 @@ public class PlaylistHelper(IMediaCollectionRepository mediaCollectionRepository @@ -38,7 +38,7 @@ public class PlaylistHelper(IMediaCollectionRepository mediaCollectionRepository
{
Index = playlistIndex,
CollectionType = ProgramScheduleItemCollectionType.FakePlaylistItem,
CollectionType = CollectionType.FakePlaylistItem,
CollectionId = playlistIndex,
PlayAll = false,

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

@ -779,13 +779,13 @@ public class SchedulingEngine( @@ -779,13 +779,13 @@ public class SchedulingEngine(
remainingToFill -= itemDuration;
_state.CurrentTime += itemDuration;
enumeratorDetails.Enumerator.MoveNext();
enumeratorDetails.Enumerator.MoveNext(playoutItem.StartOffset);
}
else if (discardAttempts > 0)
{
// item won't fit; try the next one
discardAttempts--;
enumeratorDetails.Enumerator.MoveNext();
enumeratorDetails.Enumerator.MoveNext(Option<DateTimeOffset>.None);
}
else if (trim)
{
@ -806,7 +806,7 @@ public class SchedulingEngine( @@ -806,7 +806,7 @@ public class SchedulingEngine(
remainingToFill = TimeSpan.Zero;
_state.CurrentTime = targetTime;
enumeratorDetails.Enumerator.MoveNext();
enumeratorDetails.Enumerator.MoveNext(playoutItem.StartOffset);
}
else if (maybeFallbackEnumeratorDetails.IsSome)
{
@ -836,7 +836,7 @@ public class SchedulingEngine( @@ -836,7 +836,7 @@ public class SchedulingEngine(
_state.AddedHistory.Add(history);
}
fallbackEnumeratorDetails.Enumerator.MoveNext();
fallbackEnumeratorDetails.Enumerator.MoveNext(playoutItem.StartOffset);
}
}
}
@ -959,7 +959,7 @@ public class SchedulingEngine( @@ -959,7 +959,7 @@ public class SchedulingEngine(
_state.AddedHistory.Add(history);
}
enumeratorDetails.Enumerator.MoveNext();
enumeratorDetails.Enumerator.MoveNext(playoutItem.StartOffset);
result = true;
}
@ -1077,7 +1077,7 @@ public class SchedulingEngine( @@ -1077,7 +1077,7 @@ public class SchedulingEngine(
for (var i = 0; i < count; i++)
{
enumeratorDetails.Enumerator.MoveNext();
enumeratorDetails.Enumerator.MoveNext(Option<DateTimeOffset>.None);
}
}
@ -1115,7 +1115,7 @@ public class SchedulingEngine( @@ -1115,7 +1115,7 @@ public class SchedulingEngine(
}
}
enumeratorDetails.Enumerator.MoveNext();
enumeratorDetails.Enumerator.MoveNext(Option<DateTimeOffset>.None);
}
}
}
@ -1312,7 +1312,7 @@ public class SchedulingEngine( @@ -1312,7 +1312,7 @@ public class SchedulingEngine(
// only move next at the end, because that may also move
// the enumerator index
playlistEnumerator.MoveNext();
playlistEnumerator.MoveNext(Option<DateTimeOffset>.None);
}
}

2
ErsatzTV.Core/Scheduling/HistoryDetails.cs

@ -210,7 +210,7 @@ internal static class HistoryDetails @@ -210,7 +210,7 @@ internal static class HistoryDetails
if (!current)
{
enumerator.MoveNext();
enumerator.MoveNext(Option<DateTimeOffset>.None);
}
}
}

34
ErsatzTV.Core/Scheduling/MediaItemsForCollection.cs

@ -18,77 +18,83 @@ public static class MediaItemsForCollection @@ -18,77 +18,83 @@ public static class MediaItemsForCollection
switch (collectionKey.CollectionType)
{
case ProgramScheduleItemCollectionType.Collection:
case CollectionType.Collection:
result.AddRange(await mediaCollectionRepository.GetItems(collectionKey.CollectionId ?? 0));
break;
case ProgramScheduleItemCollectionType.TelevisionShow:
case CollectionType.TelevisionShow:
result.AddRange(await televisionRepository.GetShowItems(collectionKey.MediaItemId ?? 0));
break;
case ProgramScheduleItemCollectionType.TelevisionSeason:
case CollectionType.TelevisionSeason:
result.AddRange(await televisionRepository.GetSeasonItems(collectionKey.MediaItemId ?? 0));
break;
case ProgramScheduleItemCollectionType.Artist:
case CollectionType.Artist:
result.AddRange(await artistRepository.GetArtistItems(collectionKey.MediaItemId ?? 0));
break;
case ProgramScheduleItemCollectionType.MultiCollection:
case CollectionType.MultiCollection:
result.AddRange(
await mediaCollectionRepository.GetMultiCollectionItems(
collectionKey.MultiCollectionId ?? 0,
cancellationToken));
break;
case ProgramScheduleItemCollectionType.SmartCollection:
case CollectionType.SmartCollection:
result.AddRange(
await mediaCollectionRepository.GetSmartCollectionItems(
collectionKey.SmartCollectionId ?? 0,
cancellationToken));
break;
case ProgramScheduleItemCollectionType.Playlist:
case CollectionType.RerunFirstRun or CollectionType.RerunRerun:
result.AddRange(
await mediaCollectionRepository.GetRerunCollectionItems(
collectionKey.RerunCollectionId ?? 0,
cancellationToken));
break;
case CollectionType.Playlist:
result.AddRange(
await mediaCollectionRepository.GetPlaylistItems(collectionKey.PlaylistId ?? 0, cancellationToken));
break;
case ProgramScheduleItemCollectionType.Movie:
case CollectionType.Movie:
foreach (int mediaItemId in Optional(collectionKey.MediaItemId))
{
result.AddRange(await mediaCollectionRepository.GetMovie(mediaItemId));
}
break;
case ProgramScheduleItemCollectionType.Episode:
case CollectionType.Episode:
foreach (int mediaItemId in Optional(collectionKey.MediaItemId))
{
result.AddRange(await mediaCollectionRepository.GetEpisode(mediaItemId));
}
break;
case ProgramScheduleItemCollectionType.MusicVideo:
case CollectionType.MusicVideo:
foreach (int mediaItemId in Optional(collectionKey.MediaItemId))
{
result.AddRange(await mediaCollectionRepository.GetMusicVideo(mediaItemId));
}
break;
case ProgramScheduleItemCollectionType.OtherVideo:
case CollectionType.OtherVideo:
foreach (int mediaItemId in Optional(collectionKey.MediaItemId))
{
result.AddRange(await mediaCollectionRepository.GetOtherVideo(mediaItemId));
}
break;
case ProgramScheduleItemCollectionType.Song:
case CollectionType.Song:
foreach (int mediaItemId in Optional(collectionKey.MediaItemId))
{
result.AddRange(await mediaCollectionRepository.GetSong(mediaItemId));
}
break;
case ProgramScheduleItemCollectionType.Image:
case CollectionType.Image:
foreach (int mediaItemId in Optional(collectionKey.MediaItemId))
{
result.AddRange(await mediaCollectionRepository.GetImage(mediaItemId));
}
break;
case ProgramScheduleItemCollectionType.RemoteStream:
case CollectionType.RemoteStream:
foreach (int mediaItemId in Optional(collectionKey.MediaItemId))
{
result.AddRange(await mediaCollectionRepository.GetRemoteStream(mediaItemId));

6
ErsatzTV.Core/Scheduling/PlaylistEnumerator.cs

@ -58,14 +58,14 @@ public class PlaylistEnumerator : IMediaCollectionEnumerator @@ -58,14 +58,14 @@ public class PlaylistEnumerator : IMediaCollectionEnumerator
public Option<TimeSpan> MinimumDuration { get; private set; }
public void MoveNext()
public void MoveNext(Option<DateTimeOffset> scheduledAt)
{
foreach (MediaItem maybeMediaItem in _sortedEnumerators[EnumeratorIndex].Enumerator.Current)
{
_remainingMediaItemIds.Remove(maybeMediaItem.Id);
}
_sortedEnumerators[EnumeratorIndex].Enumerator.MoveNext();
_sortedEnumerators[EnumeratorIndex].Enumerator.MoveNext(scheduledAt);
_itemsTakenFromCurrent++;
bool shouldSwitchEnumerator = _batchSize.Match(
@ -238,7 +238,7 @@ public class PlaylistEnumerator : IMediaCollectionEnumerator @@ -238,7 +238,7 @@ public class PlaylistEnumerator : IMediaCollectionEnumerator
while (result.State.Index < state.Index)
{
result.MoveNext();
result.MoveNext(Option<DateTimeOffset>.None);
// previous state is no longer valid; playlist now has fewer items
if (result.State.Index == 0)

4
ErsatzTV.Core/Scheduling/PlayoutBuildResult.cs

@ -11,6 +11,8 @@ public record PlayoutBuildResult( @@ -11,6 +11,8 @@ public record PlayoutBuildResult(
System.Collections.Generic.HashSet<int> ItemsToRemove,
List<PlayoutHistory> AddedHistory,
System.Collections.Generic.HashSet<int> HistoryToRemove,
List<RerunHistory> AddedRerunHistory,
System.Collections.Generic.HashSet<int> RerunHistoryToRemove,
Option<DateTimeOffset> TimeShiftTo)
{
public static PlayoutBuildResult Empty =>
@ -22,5 +24,7 @@ public record PlayoutBuildResult( @@ -22,5 +24,7 @@ public record PlayoutBuildResult(
[],
[],
[],
[],
[],
Option<DateTimeOffset>.None);
}

41
ErsatzTV.Core/Scheduling/PlayoutBuilder.cs

@ -21,6 +21,7 @@ public class PlayoutBuilder : IPlayoutBuilder @@ -21,6 +21,7 @@ public class PlayoutBuilder : IPlayoutBuilder
private readonly IArtistRepository _artistRepository;
private readonly IConfigElementRepository _configElementRepository;
private readonly ILocalFileSystem _localFileSystem;
private readonly IRerunHelper _rerunHelper;
private readonly IMediaCollectionRepository _mediaCollectionRepository;
private readonly IMultiEpisodeShuffleCollectionEnumeratorFactory _multiEpisodeFactory;
private readonly ITelevisionRepository _televisionRepository;
@ -34,6 +35,7 @@ public class PlayoutBuilder : IPlayoutBuilder @@ -34,6 +35,7 @@ public class PlayoutBuilder : IPlayoutBuilder
IArtistRepository artistRepository,
IMultiEpisodeShuffleCollectionEnumeratorFactory multiEpisodeFactory,
ILocalFileSystem localFileSystem,
IRerunHelper rerunHelper,
ILogger<PlayoutBuilder> logger)
{
_configElementRepository = configElementRepository;
@ -42,6 +44,7 @@ public class PlayoutBuilder : IPlayoutBuilder @@ -42,6 +44,7 @@ public class PlayoutBuilder : IPlayoutBuilder
_artistRepository = artistRepository;
_multiEpisodeFactory = multiEpisodeFactory;
_localFileSystem = localFileSystem;
_rerunHelper = rerunHelper;
_logger = logger;
}
@ -89,6 +92,12 @@ public class PlayoutBuilder : IPlayoutBuilder @@ -89,6 +92,12 @@ public class PlayoutBuilder : IPlayoutBuilder
// }
result = await Build(playout, referenceData, result, mode, parameters, cancellationToken);
result = result with
{
RerunHistoryToRemove = _rerunHelper.GetHistoryToRemove(),
AddedRerunHistory = _rerunHelper.GetHistoryToAdd()
};
}
return result;
@ -181,6 +190,9 @@ public class PlayoutBuilder : IPlayoutBuilder @@ -181,6 +190,9 @@ public class PlayoutBuilder : IPlayoutBuilder
var smartCollectionIds =
playout.ProgramScheduleAnchors.Map(a => Optional(a.SmartCollectionId)).Somes().ToHashSet();
var rerunCollectionIds =
playout.ProgramScheduleAnchors.Map(a => Optional(a.RerunCollectionId)).Somes().ToHashSet();
var mediaItemIds = playout.ProgramScheduleAnchors.Map(a => Optional(a.MediaItemId)).Somes().ToHashSet();
playout.ProgramScheduleAnchors.Clear();
@ -206,6 +218,13 @@ public class PlayoutBuilder : IPlayoutBuilder @@ -206,6 +218,13 @@ public class PlayoutBuilder : IPlayoutBuilder
playout.ProgramScheduleAnchors.Add(minAnchor);
}
foreach (int rerunCollectionId in rerunCollectionIds)
{
PlayoutProgramScheduleAnchor minAnchor = allAnchors.Filter(a => a.RerunCollectionId == rerunCollectionId)
.MinBy(a => a.AnchorDateOffset.IfNone(DateTimeOffset.MaxValue).Ticks);
playout.ProgramScheduleAnchors.Add(minAnchor);
}
foreach (int mediaItemId in mediaItemIds)
{
PlayoutProgramScheduleAnchor minAnchor = allAnchors.Filter(a => a.MediaItemId == mediaItemId)
@ -261,6 +280,7 @@ public class PlayoutBuilder : IPlayoutBuilder @@ -261,6 +280,7 @@ public class PlayoutBuilder : IPlayoutBuilder
referenceData.Channel.Name);
result = result with { ClearItems = true };
_rerunHelper.ClearHistory = true;
playout.Anchor = null;
playout.ProgramScheduleAnchors.Clear();
playout.OnDemandCheckpoint = null;
@ -610,19 +630,19 @@ public class PlayoutBuilder : IPlayoutBuilder @@ -610,19 +630,19 @@ public class PlayoutBuilder : IPlayoutBuilder
{
var (showId, _, _) when showId > 0 => new CollectionKey
{
CollectionType = ProgramScheduleItemCollectionType.TelevisionShow,
CollectionType = CollectionType.TelevisionShow,
MediaItemId = showId,
FakeCollectionKey = collectionKeyString
},
var (_, artistId, _) when artistId > 0 => new CollectionKey
{
CollectionType = ProgramScheduleItemCollectionType.Artist,
CollectionType = CollectionType.Artist,
MediaItemId = artistId,
FakeCollectionKey = collectionKeyString
},
var (_, _, k) when k is not null => new CollectionKey
{
CollectionType = ProgramScheduleItemCollectionType.FakeCollection,
CollectionType = CollectionType.FakeCollection,
FakeCollectionKey = collectionKeyString
},
var (_, _, _) => null
@ -1145,6 +1165,7 @@ public class PlayoutBuilder : IPlayoutBuilder @@ -1145,6 +1165,7 @@ public class PlayoutBuilder : IPlayoutBuilder
&& a.MediaItemId == collectionKey.MediaItemId
&& a.FakeCollectionKey == collectionKey.FakeCollectionKey
&& a.SmartCollectionId == collectionKey.SmartCollectionId
&& a.RerunCollectionId == collectionKey.RerunCollectionId
&& a.MultiCollectionId == collectionKey.MultiCollectionId
&& a.PlaylistId == collectionKey.PlaylistId
&& a.AnchorDate is null);
@ -1165,6 +1186,7 @@ public class PlayoutBuilder : IPlayoutBuilder @@ -1165,6 +1186,7 @@ public class PlayoutBuilder : IPlayoutBuilder
CollectionId = collectionKey.CollectionId,
MultiCollectionId = collectionKey.MultiCollectionId,
SmartCollectionId = collectionKey.SmartCollectionId,
RerunCollectionId = collectionKey.RerunCollectionId,
MediaItemId = collectionKey.MediaItemId,
PlaylistId = collectionKey.PlaylistId,
FakeCollectionKey = collectionKey.FakeCollectionKey,
@ -1207,6 +1229,7 @@ public class PlayoutBuilder : IPlayoutBuilder @@ -1207,6 +1229,7 @@ public class PlayoutBuilder : IPlayoutBuilder
&& a.CollectionId == collectionKey.CollectionId
&& a.MultiCollectionId == collectionKey.MultiCollectionId
&& a.SmartCollectionId == collectionKey.SmartCollectionId
&& a.RerunCollectionId == collectionKey.RerunCollectionId
&& a.MediaItemId == collectionKey.MediaItemId
&& a.PlaylistId == collectionKey.PlaylistId);
@ -1223,7 +1246,13 @@ public class PlayoutBuilder : IPlayoutBuilder @@ -1223,7 +1246,13 @@ public class PlayoutBuilder : IPlayoutBuilder
state ??= new CollectionEnumeratorState { Seed = Random.Next(), Index = 0 };
if (collectionKey.CollectionType is ProgramScheduleItemCollectionType.Playlist)
if (collectionKey.CollectionType is CollectionType.RerunFirstRun or CollectionType.RerunRerun)
{
await _rerunHelper.InitWithMediaItems(playout.Id, collectionKey, mediaItems, cancellationToken);
return _rerunHelper.CreateEnumerator(collectionKey, state, cancellationToken);
}
if (collectionKey.CollectionType is CollectionType.Playlist)
{
foreach (int playlistId in Optional(collectionKey.PlaylistId))
{
@ -1243,7 +1272,7 @@ public class PlayoutBuilder : IPlayoutBuilder @@ -1243,7 +1272,7 @@ public class PlayoutBuilder : IPlayoutBuilder
int collectionId = collectionKey.CollectionId ?? 0;
if (collectionKey.CollectionType == ProgramScheduleItemCollectionType.Collection &&
if (collectionKey.CollectionType == CollectionType.Collection &&
await _mediaCollectionRepository.IsCustomPlaybackOrder(collectionId))
{
Option<Collection> maybeCollectionWithItems =
@ -1297,7 +1326,7 @@ public class PlayoutBuilder : IPlayoutBuilder @@ -1297,7 +1326,7 @@ public class PlayoutBuilder : IPlayoutBuilder
activeSchedule.RandomStartPoint,
cancellationToken);
case PlaybackOrder.MultiEpisodeShuffle when
collectionKey.CollectionType == ProgramScheduleItemCollectionType.TelevisionShow &&
collectionKey.CollectionType == CollectionType.TelevisionShow &&
collectionKey.MediaItemId.HasValue:
foreach (Show show in await _televisionRepository.GetShow(collectionKey.MediaItemId.Value, cancellationToken))
{

22
ErsatzTV.Core/Scheduling/PlayoutModeSchedulerBase.cs

@ -178,7 +178,7 @@ public abstract class PlayoutModeSchedulerBase<T> : IPlayoutModeScheduler<T> whe @@ -178,7 +178,7 @@ public abstract class PlayoutModeSchedulerBase<T> : IPlayoutModeScheduler<T> whe
CurrentTime = nextState.CurrentTime + itemDuration
};
enumerator.MoveNext();
enumerator.MoveNext(playoutItem.StartOffset);
}
}
@ -223,7 +223,7 @@ public abstract class PlayoutModeSchedulerBase<T> : IPlayoutModeScheduler<T> whe @@ -223,7 +223,7 @@ public abstract class PlayoutModeSchedulerBase<T> : IPlayoutModeScheduler<T> whe
CurrentTime = nextItemStart.UtcDateTime
};
enumerator.MoveNext();
enumerator.MoveNext(playoutItem.StartOffset);
}
}
@ -365,9 +365,9 @@ public abstract class PlayoutModeSchedulerBase<T> : IPlayoutModeScheduler<T> whe @@ -365,9 +365,9 @@ public abstract class PlayoutModeSchedulerBase<T> : IPlayoutModeScheduler<T> whe
}
// convert playlist filler
if (allFiller.Any(f => f.CollectionType is ProgramScheduleItemCollectionType.Playlist))
if (allFiller.Any(f => f.CollectionType is CollectionType.Playlist))
{
var toRemove = allFiller.Filter(f => f.CollectionType is ProgramScheduleItemCollectionType.Playlist)
var toRemove = allFiller.Filter(f => f.CollectionType is CollectionType.Playlist)
.ToList();
allFiller.RemoveAll(toRemove.Contains);
@ -800,7 +800,9 @@ public abstract class PlayoutModeSchedulerBase<T> : IPlayoutModeScheduler<T> whe @@ -800,7 +800,9 @@ public abstract class PlayoutModeSchedulerBase<T> : IPlayoutModeScheduler<T> whe
};
result.Add(playoutItem);
enumerator.MoveNext();
// TODO: this won't work with reruns
enumerator.MoveNext(Option<DateTimeOffset>.None);
}
}
@ -845,7 +847,9 @@ public abstract class PlayoutModeSchedulerBase<T> : IPlayoutModeScheduler<T> whe @@ -845,7 +847,9 @@ public abstract class PlayoutModeSchedulerBase<T> : IPlayoutModeScheduler<T> whe
remainingToFill -= itemDuration;
result.Add(playoutItem);
enumerator.MoveNext();
// TODO: this won't work with reruns
enumerator.MoveNext(Option<DateTimeOffset>.None);
}
else
{
@ -857,7 +861,8 @@ public abstract class PlayoutModeSchedulerBase<T> : IPlayoutModeScheduler<T> whe @@ -857,7 +861,8 @@ public abstract class PlayoutModeSchedulerBase<T> : IPlayoutModeScheduler<T> whe
remainingToFill);
}
enumerator.MoveNext();
// TODO: this won't work with reruns
enumerator.MoveNext(Option<DateTimeOffset>.None);
}
}
}
@ -892,7 +897,8 @@ public abstract class PlayoutModeSchedulerBase<T> : IPlayoutModeScheduler<T> whe @@ -892,7 +897,8 @@ public abstract class PlayoutModeSchedulerBase<T> : IPlayoutModeScheduler<T> whe
DisableWatermarks = !scheduleItem.FallbackFiller.AllowWatermarks
};
enumerator.MoveNext();
// TODO: this won't work with reruns
enumerator.MoveNext(Option<DateTimeOffset>.None);
return result;
}

6
ErsatzTV.Core/Scheduling/PlayoutModeSchedulerDuration.cs

@ -110,7 +110,7 @@ public class PlayoutModeSchedulerDuration : PlayoutModeSchedulerBase<ProgramSche @@ -110,7 +110,7 @@ public class PlayoutModeSchedulerDuration : PlayoutModeSchedulerBase<ProgramSche
itemDuration,
scheduleItem.PlayoutDuration);
contentEnumerator.MoveNext();
contentEnumerator.MoveNext(Option<DateTimeOffset>.None);
continue;
}
@ -137,7 +137,7 @@ public class PlayoutModeSchedulerDuration : PlayoutModeSchedulerBase<ProgramSche @@ -137,7 +137,7 @@ public class PlayoutModeSchedulerDuration : PlayoutModeSchedulerBase<ProgramSche
itemDuration,
remainingDuration);
contentEnumerator.MoveNext();
contentEnumerator.MoveNext(Option<DateTimeOffset>.None);
}
}
else
@ -235,7 +235,7 @@ public class PlayoutModeSchedulerDuration : PlayoutModeSchedulerBase<ProgramSche @@ -235,7 +235,7 @@ public class PlayoutModeSchedulerDuration : PlayoutModeSchedulerBase<ProgramSche
: nextState.NextGuideGroup
};
contentEnumerator.MoveNext();
contentEnumerator.MoveNext(itemStartTime);
}
else
{

2
ErsatzTV.Core/Scheduling/PlayoutModeSchedulerFlood.cs

@ -145,7 +145,7 @@ public class PlayoutModeSchedulerFlood : PlayoutModeSchedulerBase<ProgramSchedul @@ -145,7 +145,7 @@ public class PlayoutModeSchedulerFlood : PlayoutModeSchedulerBase<ProgramSchedul
: nextState.NextGuideGroup
};
contentEnumerator.MoveNext();
contentEnumerator.MoveNext(itemStartTime);
}
else
{

2
ErsatzTV.Core/Scheduling/PlayoutModeSchedulerMultiple.cs

@ -156,7 +156,7 @@ public class PlayoutModeSchedulerMultiple : PlayoutModeSchedulerBase<ProgramSche @@ -156,7 +156,7 @@ public class PlayoutModeSchedulerMultiple : PlayoutModeSchedulerBase<ProgramSche
: nextState.NextGuideGroup
};
contentEnumerator.MoveNext();
contentEnumerator.MoveNext(playoutItem.StartOffset);
}
if (nextState.MultipleRemaining.IfNone(-1) == 0)

2
ErsatzTV.Core/Scheduling/PlayoutModeSchedulerOne.cs

@ -96,7 +96,7 @@ public class PlayoutModeSchedulerOne : PlayoutModeSchedulerBase<ProgramScheduleI @@ -96,7 +96,7 @@ public class PlayoutModeSchedulerOne : PlayoutModeSchedulerBase<ProgramScheduleI
};
nextState.ScheduleItemsEnumerator.MoveNext();
contentEnumerator.MoveNext();
contentEnumerator.MoveNext(itemStartTime);
// LogScheduledItem(scheduleItem, mediaItem, itemStartTime);

6
ErsatzTV.Core/Scheduling/RandomizedMediaCollectionEnumerator.cs

@ -26,13 +26,13 @@ public class RandomizedMediaCollectionEnumerator : IMediaCollectionEnumerator @@ -26,13 +26,13 @@ public class RandomizedMediaCollectionEnumerator : IMediaCollectionEnumerator
// because _index defaults to 0
if (State.Index == state.Index)
{
MoveNext();
MoveNext(Option<DateTimeOffset>.None);
}
else
{
while (State.Index <= state.Index)
{
MoveNext();
MoveNext(Option<DateTimeOffset>.None);
}
}
}
@ -46,7 +46,7 @@ public class RandomizedMediaCollectionEnumerator : IMediaCollectionEnumerator @@ -46,7 +46,7 @@ public class RandomizedMediaCollectionEnumerator : IMediaCollectionEnumerator
public Option<MediaItem> Current => _mediaItems.Any() ? _mediaItems[_index] : None;
public Option<bool> CurrentIncludeInProgramGuide { get; }
public void MoveNext()
public void MoveNext(Option<DateTimeOffset> scheduledAt)
{
if (_mediaItems.Count == 0)
{

6
ErsatzTV.Core/Scheduling/RandomizedRotatingMediaCollectionEnumerator.cs

@ -50,13 +50,13 @@ public class RandomizedRotatingMediaCollectionEnumerator : IMediaCollectionEnume @@ -50,13 +50,13 @@ public class RandomizedRotatingMediaCollectionEnumerator : IMediaCollectionEnume
// because _index defaults to 0
if (State.Index == state.Index)
{
MoveNext();
MoveNext(Option<DateTimeOffset>.None);
}
else
{
while (State.Index <= state.Index)
{
MoveNext();
MoveNext(Option<DateTimeOffset>.None);
}
}
}
@ -70,7 +70,7 @@ public class RandomizedRotatingMediaCollectionEnumerator : IMediaCollectionEnume @@ -70,7 +70,7 @@ public class RandomizedRotatingMediaCollectionEnumerator : IMediaCollectionEnume
public Option<MediaItem> Current => _mediaItems.Any() ? _mediaItems[_index] : None;
public Option<bool> CurrentIncludeInProgramGuide { get; }
public void MoveNext()
public void MoveNext(Option<DateTimeOffset> scheduledAt)
{
var groups = _groupMedia.Keys.ToList();
if (groups.Count == 0)

4
ErsatzTV.Core/Scheduling/SeasonEpisodeMediaCollectionEnumerator.cs

@ -31,7 +31,7 @@ public sealed class SeasonEpisodeMediaCollectionEnumerator : IMediaCollectionEnu @@ -31,7 +31,7 @@ public sealed class SeasonEpisodeMediaCollectionEnumerator : IMediaCollectionEnu
while (State.Index < state.Index)
{
MoveNext();
MoveNext(Option<DateTimeOffset>.None);
}
}
@ -44,7 +44,7 @@ public sealed class SeasonEpisodeMediaCollectionEnumerator : IMediaCollectionEnu @@ -44,7 +44,7 @@ public sealed class SeasonEpisodeMediaCollectionEnumerator : IMediaCollectionEnu
public Option<MediaItem> Current => _sortedMediaItems.Count != 0 ? _sortedMediaItems[State.Index] : None;
public Option<bool> CurrentIncludeInProgramGuide { get; }
public void MoveNext()
public void MoveNext(Option<DateTimeOffset> scheduledAt)
{
if (_sortedMediaItems.Count == 0)
{

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save