add playlist item play all option (#1694)

This commit is contained in:
Jason Dove
2024-04-28 13:00:22 -05:00
committed by GitHub
parent a6d5df3ca6
commit e19b639527
22 changed files with 11707 additions and 56 deletions
+6 -1
View File
@@ -50,8 +50,13 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Songs - Songs
- Images - Images
- Playlists can be added to schedules as a schedule item - Playlists can be added to schedules as a schedule item
- Each time through the playlist, one item will be scheduled from each playlist item - Each time through the playlist, one item will be scheduled from each playlist item (if `Play All` is unchecked)
- NB: This does not mean every collection will always schedule one item; the normal flood playout restrictions like duration and fixed start times still apply here - NB: This does not mean every collection will always schedule one item; the normal flood playout restrictions like duration and fixed start times still apply here
- If `Play All` is checked, that playlist item will play all of its items each time through the playlist
- This can be helpful if you want to play entire collections in a specific order, e.g.
- Every episode from Show 1 Season 2
- Every episode from Show 2 Season 3
- Every episode from Show 1 Season 3
- Playlist items with fewer media items will be re-shuffled (if applicable) before those with more media items - Playlist items with fewer media items will be re-shuffled (if applicable) before those with more media items
### Fixed ### Fixed
@@ -1,7 +1,7 @@
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using ErsatzTV.Application.Scheduling; using ErsatzTV.Application.Scheduling;
using ErsatzTV.Core.Domain; using ErsatzTV.Core.Domain;
using ErsatzTV.Core.Domain.Scheduling; using ErsatzTV.Core.Interfaces.Repositories;
using ErsatzTV.Core.Interfaces.Scheduling; using ErsatzTV.Core.Interfaces.Scheduling;
using ErsatzTV.Core.Scheduling; using ErsatzTV.Core.Scheduling;
using ErsatzTV.Infrastructure.Data; using ErsatzTV.Infrastructure.Data;
@@ -13,7 +13,8 @@ namespace ErsatzTV.Application.MediaCollections;
[SuppressMessage("ReSharper", "SuggestBaseTypeForParameterInConstructor")] [SuppressMessage("ReSharper", "SuggestBaseTypeForParameterInConstructor")]
public class PreviewPlaylistPlayoutHandler( public class PreviewPlaylistPlayoutHandler(
IDbContextFactory<TvContext> dbContextFactory, IDbContextFactory<TvContext> dbContextFactory,
IBlockPlayoutPreviewBuilder blockPlayoutBuilder) IMediaCollectionRepository mediaCollectionRepository,
IPlayoutBuilder playoutBuilder)
: IRequestHandler<PreviewPlaylistPlayout, List<PlayoutItemPreviewViewModel>> : IRequestHandler<PreviewPlaylistPlayout, List<PlayoutItemPreviewViewModel>>
{ {
public async Task<List<PlayoutItemPreviewViewModel>> Handle( public async Task<List<PlayoutItemPreviewViewModel>> Handle(
@@ -22,21 +23,6 @@ public class PreviewPlaylistPlayoutHandler(
{ {
await using TvContext dbContext = await dbContextFactory.CreateDbContextAsync(cancellationToken); await using TvContext dbContext = await dbContextFactory.CreateDbContextAsync(cancellationToken);
// TODO: consider using flood playout instead
var template = new Template
{
Items = []
};
template.Items.Add(
new TemplateItem
{
Block = MapToBlock(request.Data),
StartTime = TimeSpan.Zero,
Template = template
});
var playout = new Playout var playout = new Playout
{ {
Channel = new Channel(Guid.NewGuid()) Channel = new Channel(Guid.NewGuid())
@@ -45,21 +31,38 @@ public class PreviewPlaylistPlayoutHandler(
Name = "Playlist Preview" Name = "Playlist Preview"
}, },
Items = [], Items = [],
ProgramSchedulePlayoutType = ProgramSchedulePlayoutType.Block, ProgramSchedulePlayoutType = ProgramSchedulePlayoutType.Flood,
PlayoutHistory = [], PlayoutHistory = [],
Templates = ProgramSchedule = new ProgramSchedule
[ {
new PlayoutTemplate Items = [MapToScheduleItem(request)]
{ },
DaysOfWeek = PlayoutTemplate.AllDaysOfWeek(), ProgramScheduleAlternates = [],
DaysOfMonth = PlayoutTemplate.AllDaysOfMonth(), FillGroupIndices = []
MonthsOfYear = PlayoutTemplate.AllMonthsOfYear(),
Template = template
}
]
}; };
await blockPlayoutBuilder.Build(playout, PlayoutBuildMode.Reset, cancellationToken); // TODO: make an explicit method to preview, this is ugly
playoutBuilder.TrimStart = false;
playoutBuilder.DebugPlaylist = playout.ProgramSchedule.Items[0].Playlist;
await playoutBuilder.Build(playout, PlayoutBuildMode.Reset, cancellationToken);
int maxItems = 0;
Dictionary<PlaylistItem, List<MediaItem>> map =
await mediaCollectionRepository.GetPlaylistItemMap(playout.ProgramSchedule.Items[0].Playlist);
foreach (PlaylistItem item in playout.ProgramSchedule.Items[0].Playlist.Items)
{
if (item.PlayAll)
{
maxItems += map[item].Count;
}
else
{
maxItems += 1;
}
}
// limit preview to once through the playlist
playout.Items = playout.Items.Take(maxItems).ToList();
// load playout item details for title // load playout item details for title
foreach (PlayoutItem playoutItem in playout.Items) foreach (PlayoutItem playoutItem in playout.Items)
@@ -91,29 +94,32 @@ public class PreviewPlaylistPlayoutHandler(
playoutItem.MediaItem = mediaItem; playoutItem.MediaItem = mediaItem;
} }
} }
return playout.Items.Map(Scheduling.Mapper.ProjectToViewModel).ToList(); return playout.Items.OrderBy(i => i.StartOffset).Map(Scheduling.Mapper.ProjectToViewModel).ToList();
} }
private static Block MapToBlock(ReplacePlaylistItems request) => private static ProgramScheduleItemFlood MapToScheduleItem(PreviewPlaylistPlayout request) =>
new() new()
{ {
Name = request.Name, CollectionType = ProgramScheduleItemCollectionType.Playlist,
Minutes = 6 * 60, Playlist = new Playlist
StopScheduling = BlockStopScheduling.AfterDurationEnd, {
Items = request.Items.Map(MapToBlockItem).ToList() Items = request.Data.Items.OrderBy(i => i.Index).Map(MapToPlaylistItem).ToList()
},
PlaylistId = request.Data.PlaylistId,
PlaybackOrder = PlaybackOrder.Shuffle
}; };
private static BlockItem MapToBlockItem(int id, ReplacePlaylistItem request) => private static PlaylistItem MapToPlaylistItem(ReplacePlaylistItem item) =>
new() new()
{ {
Id = id, Index = item.Index,
Index = request.Index, CollectionType = item.CollectionType,
CollectionType = request.CollectionType, CollectionId = item.CollectionId,
CollectionId = request.CollectionId, MediaItemId = item.MediaItemId,
MultiCollectionId = request.MultiCollectionId, MultiCollectionId = item.MultiCollectionId,
SmartCollectionId = request.SmartCollectionId, SmartCollectionId = item.SmartCollectionId,
MediaItemId = request.MediaItemId, PlaybackOrder = item.PlaybackOrder,
PlaybackOrder = request.PlaybackOrder PlayAll = item.PlayAll
}; };
} }
@@ -10,4 +10,5 @@ public record ReplacePlaylistItem(
int? SmartCollectionId, int? SmartCollectionId,
int? MediaItemId, int? MediaItemId,
PlaybackOrder PlaybackOrder, PlaybackOrder PlaybackOrder,
bool PlayAll,
bool IncludeInProgramGuide); bool IncludeInProgramGuide);
@@ -46,6 +46,7 @@ public class ReplacePlaylistItemsHandler(IDbContextFactory<TvContext> dbContextF
SmartCollectionId = item.SmartCollectionId, SmartCollectionId = item.SmartCollectionId,
MediaItemId = item.MediaItemId, MediaItemId = item.MediaItemId,
PlaybackOrder = item.PlaybackOrder, PlaybackOrder = item.PlaybackOrder,
PlayAll = item.PlayAll,
IncludeInProgramGuide = item.IncludeInProgramGuide IncludeInProgramGuide = item.IncludeInProgramGuide
}; };
@@ -78,5 +78,6 @@ internal static class Mapper
_ => null _ => null
}, },
playlistItem.PlaybackOrder, playlistItem.PlaybackOrder,
playlistItem.PlayAll,
playlistItem.IncludeInProgramGuide); playlistItem.IncludeInProgramGuide);
} }
@@ -12,4 +12,5 @@ public record PlaylistItemViewModel(
SmartCollectionViewModel SmartCollection, SmartCollectionViewModel SmartCollection,
NamedMediaItemViewModel MediaItem, NamedMediaItemViewModel MediaItem,
PlaybackOrder PlaybackOrder, PlaybackOrder PlaybackOrder,
bool PlayAll,
bool IncludeInProgramGuide); bool IncludeInProgramGuide);
@@ -13,6 +13,9 @@ public class FakeMediaCollectionRepository : IMediaCollectionRepository
public Task<Dictionary<PlaylistItem, List<MediaItem>>> GetPlaylistItemMap(int playlistId) => public Task<Dictionary<PlaylistItem, List<MediaItem>>> GetPlaylistItemMap(int playlistId) =>
throw new NotSupportedException(); throw new NotSupportedException();
public Task<Dictionary<PlaylistItem, List<MediaItem>>> GetPlaylistItemMap(Playlist playlist) =>
throw new NotSupportedException();
public Task<Option<Collection>> GetCollectionWithCollectionItemsUntracked(int id) => public Task<Option<Collection>> GetCollectionWithCollectionItemsUntracked(int id) =>
throw new NotSupportedException(); throw new NotSupportedException();
@@ -16,5 +16,6 @@ public class PlaylistItem
public int? SmartCollectionId { get; set; } public int? SmartCollectionId { get; set; }
public SmartCollection SmartCollection { get; set; } public SmartCollection SmartCollection { get; set; }
public PlaybackOrder PlaybackOrder { get; set; } public PlaybackOrder PlaybackOrder { get; set; }
public bool PlayAll { get; set; }
public bool IncludeInProgramGuide { get; set; } public bool IncludeInProgramGuide { get; set; }
} }
@@ -6,6 +6,7 @@ namespace ErsatzTV.Core.Interfaces.Repositories;
public interface IMediaCollectionRepository public interface IMediaCollectionRepository
{ {
Task<Dictionary<PlaylistItem, List<MediaItem>>> GetPlaylistItemMap(int playlistId); Task<Dictionary<PlaylistItem, List<MediaItem>>> GetPlaylistItemMap(int playlistId);
Task<Dictionary<PlaylistItem, List<MediaItem>>> GetPlaylistItemMap(Playlist playlist);
Task<Option<Collection>> GetCollectionWithCollectionItemsUntracked(int id); Task<Option<Collection>> GetCollectionWithCollectionItemsUntracked(int id);
Task<List<MediaItem>> GetItems(int id); Task<List<MediaItem>> GetItems(int id);
Task<List<MediaItem>> GetMultiCollectionItems(int id); Task<List<MediaItem>> GetMultiCollectionItems(int id);
@@ -5,5 +5,8 @@ namespace ErsatzTV.Core.Interfaces.Scheduling;
public interface IPlayoutBuilder public interface IPlayoutBuilder
{ {
public bool TrimStart { get; set; }
public Playlist DebugPlaylist { get; set; }
Task<Playout> Build(Playout playout, PlayoutBuildMode mode, CancellationToken cancellationToken); Task<Playout> Build(Playout playout, PlayoutBuildMode mode, CancellationToken cancellationToken);
} }
@@ -56,6 +56,12 @@ public class BlockPlayoutBuilder(
// get all collection items for the playout // get all collection items for the playout
Map<CollectionKey, List<MediaItem>> collectionMediaItems = await GetCollectionMediaItems(blocksToSchedule); Map<CollectionKey, List<MediaItem>> collectionMediaItems = await GetCollectionMediaItems(blocksToSchedule);
if (collectionMediaItems.Values.All(v => v.Count == 0))
{
logger.LogWarning("There are no media items to schedule");
return playout;
}
Map<CollectionKey, string> collectionEtags = GetCollectionEtags(collectionMediaItems); Map<CollectionKey, string> collectionEtags = GetCollectionEtags(collectionMediaItems);
Dictionary<PlayoutItem, BlockKey> itemBlockKeys = Dictionary<PlayoutItem, BlockKey> itemBlockKeys =
+20 -5
View File
@@ -8,6 +8,7 @@ namespace ErsatzTV.Core.Scheduling;
public class PlaylistEnumerator : IMediaCollectionEnumerator public class PlaylistEnumerator : IMediaCollectionEnumerator
{ {
private IList<IMediaCollectionEnumerator> _sortedEnumerators; private IList<IMediaCollectionEnumerator> _sortedEnumerators;
private IList<bool> _playAll;
private int _enumeratorIndex; private int _enumeratorIndex;
private System.Collections.Generic.HashSet<int> _idsToIncludeInEPG; private System.Collections.Generic.HashSet<int> _idsToIncludeInEPG;
@@ -20,6 +21,7 @@ public class PlaylistEnumerator : IMediaCollectionEnumerator
var result = new PlaylistEnumerator var result = new PlaylistEnumerator
{ {
_sortedEnumerators = [], _sortedEnumerators = [],
_playAll = [],
_idsToIncludeInEPG = [], _idsToIncludeInEPG = [],
Count = LCM(playlistItemMap.Values.Map(v => v.Count)) * playlistItemMap.Count Count = LCM(playlistItemMap.Values.Map(v => v.Count)) * playlistItemMap.Count
}; };
@@ -42,6 +44,7 @@ public class PlaylistEnumerator : IMediaCollectionEnumerator
if (enumeratorMap.TryGetValue(collectionKey, out IMediaCollectionEnumerator enumerator)) if (enumeratorMap.TryGetValue(collectionKey, out IMediaCollectionEnumerator enumerator))
{ {
result._sortedEnumerators.Add(enumerator); result._sortedEnumerators.Add(enumerator);
result._playAll.Add(playlistItem.PlayAll);
continue; continue;
} }
@@ -92,6 +95,7 @@ public class PlaylistEnumerator : IMediaCollectionEnumerator
{ {
enumeratorMap.Add(collectionKey, enumerator); enumeratorMap.Add(collectionKey, enumerator);
result._sortedEnumerators.Add(enumerator); result._sortedEnumerators.Add(enumerator);
result._playAll.Add(playlistItem.PlayAll);
} }
} }
@@ -103,11 +107,16 @@ public class PlaylistEnumerator : IMediaCollectionEnumerator
result.State = new CollectionEnumeratorState { Seed = state.Seed }; result.State = new CollectionEnumeratorState { Seed = state.Seed };
result._enumeratorIndex = 0; result._enumeratorIndex = 0;
while (result.State.Index < state.Index)
{
result.MoveNext();
}
// TODO: how do we end up with index > count?
if (state.Index < result.Count)
{
while (result.State.Index < state.Index)
{
result.MoveNext();
}
}
return result; return result;
} }
@@ -142,7 +151,13 @@ public class PlaylistEnumerator : IMediaCollectionEnumerator
public void MoveNext() public void MoveNext()
{ {
_sortedEnumerators[_enumeratorIndex].MoveNext(); _sortedEnumerators[_enumeratorIndex].MoveNext();
_enumeratorIndex = (_enumeratorIndex + 1) % _sortedEnumerators.Count;
// if we aren't playing all, or if we just finished playing all, move to the next enumerator
if (!_playAll[_enumeratorIndex] || _sortedEnumerators[_enumeratorIndex].State.Index == 0)
{
_enumeratorIndex = (_enumeratorIndex + 1) % _sortedEnumerators.Count;
}
State.Index = (State.Index + 1) % Count; State.Index = (State.Index + 1) % Count;
} }
+26 -5
View File
@@ -5,6 +5,7 @@ using ErsatzTV.Core.Interfaces.Metadata;
using ErsatzTV.Core.Interfaces.Repositories; using ErsatzTV.Core.Interfaces.Repositories;
using ErsatzTV.Core.Interfaces.Scheduling; using ErsatzTV.Core.Interfaces.Scheduling;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Newtonsoft.Json; using Newtonsoft.Json;
using Map = LanguageExt.Map; using Map = LanguageExt.Map;
@@ -18,10 +19,11 @@ public class PlayoutBuilder : IPlayoutBuilder
private readonly IArtistRepository _artistRepository; private readonly IArtistRepository _artistRepository;
private readonly IConfigElementRepository _configElementRepository; private readonly IConfigElementRepository _configElementRepository;
private readonly ILocalFileSystem _localFileSystem; private readonly ILocalFileSystem _localFileSystem;
private readonly ILogger<PlayoutBuilder> _logger; private ILogger<PlayoutBuilder> _logger;
private readonly IMediaCollectionRepository _mediaCollectionRepository; private readonly IMediaCollectionRepository _mediaCollectionRepository;
private readonly IMultiEpisodeShuffleCollectionEnumeratorFactory _multiEpisodeFactory; private readonly IMultiEpisodeShuffleCollectionEnumeratorFactory _multiEpisodeFactory;
private readonly ITelevisionRepository _televisionRepository; private readonly ITelevisionRepository _televisionRepository;
private Playlist _debugPlaylist;
public PlayoutBuilder( public PlayoutBuilder(
IConfigElementRepository configElementRepository, IConfigElementRepository configElementRepository,
@@ -41,6 +43,21 @@ public class PlayoutBuilder : IPlayoutBuilder
_logger = logger; _logger = logger;
} }
public bool TrimStart { get; set; } = true;
public Playlist DebugPlaylist
{
get => _debugPlaylist;
set
{
if (value is not null)
{
_debugPlaylist = value;
_logger = NullLogger<PlayoutBuilder>.Instance;
}
}
}
public async Task<Playout> Build(Playout playout, PlayoutBuildMode mode, CancellationToken cancellationToken) public async Task<Playout> Build(Playout playout, PlayoutBuildMode mode, CancellationToken cancellationToken)
{ {
if (playout.ProgramSchedulePlayoutType is not ProgramSchedulePlayoutType.Flood) if (playout.ProgramSchedulePlayoutType is not ProgramSchedulePlayoutType.Flood)
@@ -368,8 +385,11 @@ public class PlayoutBuilder : IPlayoutBuilder
cancellationToken); cancellationToken);
} }
// remove old items if (TrimStart)
playout.Items.RemoveAll(old => old.FinishOffset < trimBefore); {
// remove old items
playout.Items.RemoveAll(old => old.FinishOffset < trimBefore);
}
// check for future items that aren't grouped inside range // check for future items that aren't grouped inside range
var futureItems = playout.Items.Filter(i => i.StartOffset > trimAfter).ToList(); var futureItems = playout.Items.Filter(i => i.StartOffset > trimAfter).ToList();
@@ -944,8 +964,9 @@ public class PlayoutBuilder : IPlayoutBuilder
{ {
foreach (int playlistId in Optional(collectionKey.PlaylistId)) foreach (int playlistId in Optional(collectionKey.PlaylistId))
{ {
Dictionary<PlaylistItem, List<MediaItem>> playlistItemMap = Dictionary<PlaylistItem, List<MediaItem>> playlistItemMap = DebugPlaylist is not null
await _mediaCollectionRepository.GetPlaylistItemMap(playlistId); ? await _mediaCollectionRepository.GetPlaylistItemMap(DebugPlaylist)
: await _mediaCollectionRepository.GetPlaylistItemMap(playlistId);
return await PlaylistEnumerator.Create( return await PlaylistEnumerator.Create(
_mediaCollectionRepository, _mediaCollectionRepository,
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,29 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace ErsatzTV.Infrastructure.MySql.Migrations
{
/// <inheritdoc />
public partial class Add_PlaylistItemPlayAll : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<bool>(
name: "PlayAll",
table: "PlaylistItem",
type: "tinyint(1)",
nullable: false,
defaultValue: false);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "PlayAll",
table: "PlaylistItem");
}
}
}
@@ -1649,6 +1649,9 @@ namespace ErsatzTV.Infrastructure.MySql.Migrations
b.Property<int?>("MultiCollectionId") b.Property<int?>("MultiCollectionId")
.HasColumnType("int"); .HasColumnType("int");
b.Property<bool>("PlayAll")
.HasColumnType("tinyint(1)");
b.Property<int>("PlaybackOrder") b.Property<int>("PlaybackOrder")
.HasColumnType("int"); .HasColumnType("int");
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,29 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace ErsatzTV.Infrastructure.Sqlite.Migrations
{
/// <inheritdoc />
public partial class Add_PlaylistItemPlayAll : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<bool>(
name: "PlayAll",
table: "PlaylistItem",
type: "INTEGER",
nullable: false,
defaultValue: false);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "PlayAll",
table: "PlaylistItem");
}
}
}
@@ -1564,6 +1564,9 @@ namespace ErsatzTV.Infrastructure.Sqlite.Migrations
b.Property<int?>("MultiCollectionId") b.Property<int?>("MultiCollectionId")
.HasColumnType("INTEGER"); .HasColumnType("INTEGER");
b.Property<bool>("PlayAll")
.HasColumnType("INTEGER");
b.Property<int>("PlaybackOrder") b.Property<int>("PlaybackOrder")
.HasColumnType("INTEGER"); .HasColumnType("INTEGER");
@@ -156,6 +156,130 @@ public class MediaCollectionRepository : IMediaCollectionRepository
return result; return result;
} }
public async Task<Dictionary<PlaylistItem, List<MediaItem>>> GetPlaylistItemMap(Playlist playlist)
{
await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync();
var result = new Dictionary<PlaylistItem, List<MediaItem>>();
foreach (PlaylistItem playlistItem in playlist.Items)
{
var mediaItems = new List<MediaItem>();
switch (playlistItem.CollectionType)
{
case ProgramScheduleItemCollectionType.Collection:
foreach (int collectionId in Optional(playlistItem.CollectionId))
{
mediaItems.AddRange(await GetMovieItems(dbContext, collectionId));
mediaItems.AddRange(await GetShowItems(dbContext, collectionId));
mediaItems.AddRange(await GetSeasonItems(dbContext, collectionId));
mediaItems.AddRange(await GetEpisodeItems(dbContext, collectionId));
mediaItems.AddRange(await GetArtistItems(dbContext, collectionId));
mediaItems.AddRange(await GetMusicVideoItems(dbContext, collectionId));
mediaItems.AddRange(await GetOtherVideoItems(dbContext, collectionId));
mediaItems.AddRange(await GetSongItems(dbContext, collectionId));
mediaItems.AddRange(await GetImageItems(dbContext, collectionId));
}
break;
case ProgramScheduleItemCollectionType.TelevisionShow:
foreach (int mediaItemId in Optional(playlistItem.MediaItemId))
{
mediaItems.AddRange(await GetShowItemsFromShowId(dbContext, mediaItemId));
}
break;
case ProgramScheduleItemCollectionType.TelevisionSeason:
foreach (int mediaItemId in Optional(playlistItem.MediaItemId))
{
mediaItems.AddRange(await GetSeasonItemsFromSeasonId(dbContext, mediaItemId));
}
break;
case ProgramScheduleItemCollectionType.Artist:
foreach (int mediaItemId in Optional(playlistItem.MediaItemId))
{
mediaItems.AddRange(await GetArtistItemsFromArtistId(dbContext, mediaItemId));
}
break;
case ProgramScheduleItemCollectionType.MultiCollection:
foreach (int multiCollectionId in Optional(playlistItem.MultiCollectionId))
{
mediaItems.AddRange(await GetMultiCollectionItems(multiCollectionId));
}
break;
case ProgramScheduleItemCollectionType.SmartCollection:
foreach (int smartCollectionId in Optional(playlistItem.SmartCollectionId))
{
mediaItems.AddRange(await GetSmartCollectionItems(smartCollectionId));
}
break;
case ProgramScheduleItemCollectionType.Movie:
foreach (int mediaItemId in Optional(playlistItem.MediaItemId))
{
mediaItems.AddRange(await GetMovieItems(dbContext, [mediaItemId]));
}
break;
case ProgramScheduleItemCollectionType.Episode:
foreach (int mediaItemId in Optional(playlistItem.MediaItemId))
{
mediaItems.AddRange(await GetEpisodeItems(dbContext, [mediaItemId]));
}
break;
case ProgramScheduleItemCollectionType.MusicVideo:
foreach (int mediaItemId in Optional(playlistItem.MediaItemId))
{
mediaItems.AddRange(await GetMusicVideoItems(dbContext, [mediaItemId]));
}
break;
case ProgramScheduleItemCollectionType.OtherVideo:
foreach (int mediaItemId in Optional(playlistItem.MediaItemId))
{
mediaItems.AddRange(await GetOtherVideoItems(dbContext, [mediaItemId]));
}
break;
case ProgramScheduleItemCollectionType.Song:
foreach (int mediaItemId in Optional(playlistItem.MediaItemId))
{
mediaItems.AddRange(await GetSongItems(dbContext, [mediaItemId]));
}
break;
case ProgramScheduleItemCollectionType.Image:
foreach (int mediaItemId in Optional(playlistItem.MediaItemId))
{
mediaItems.AddRange(await GetImageItems(dbContext, [mediaItemId]));
}
break;
}
result.Add(playlistItem, mediaItems);
}
return result;
}
public async Task<Option<Collection>> GetCollectionWithCollectionItemsUntracked(int id) public async Task<Option<Collection>> GetCollectionWithCollectionItemsUntracked(int id)
{ {
await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(); await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync();
+9
View File
@@ -35,6 +35,7 @@
<col/> <col/>
<col/> <col/>
<col/> <col/>
<col/>
<col style="width: 60px;"/> <col style="width: 60px;"/>
<col style="width: 60px;"/> <col style="width: 60px;"/>
<col style="width: 60px;"/> <col style="width: 60px;"/>
@@ -44,6 +45,7 @@
<MudTh>Item Type</MudTh> <MudTh>Item Type</MudTh>
<MudTh>Item</MudTh> <MudTh>Item</MudTh>
<MudTh>Playback Order</MudTh> <MudTh>Playback Order</MudTh>
<MudTh>Play All</MudTh>
<MudTh>Show In EPG</MudTh> <MudTh>Show In EPG</MudTh>
<MudTh/> <MudTh/>
<MudTh/> <MudTh/>
@@ -66,6 +68,9 @@
@(context.PlaybackOrder > 0 ? context.PlaybackOrder : "") @(context.PlaybackOrder > 0 ? context.PlaybackOrder : "")
</MudText> </MudText>
</MudTd> </MudTd>
<MudTd>
<MudCheckBox T="bool" Value="@context.PlayAll" ValueChanged="@(e => UpdatePlayAll(context, e))"/>
</MudTd>
<MudTd> <MudTd>
<MudCheckBox T="bool" Value="@context.IncludeInProgramGuide" ValueChanged="@(e => UpdateEPG(context, e))"/> <MudCheckBox T="bool" Value="@context.IncludeInProgramGuide" ValueChanged="@(e => UpdateEPG(context, e))"/>
</MudTd> </MudTd>
@@ -405,6 +410,7 @@
SmartCollection = item.SmartCollection, SmartCollection = item.SmartCollection,
MediaItem = item.MediaItem, MediaItem = item.MediaItem,
PlaybackOrder = item.PlaybackOrder, PlaybackOrder = item.PlaybackOrder,
PlayAll = item.PlayAll,
IncludeInProgramGuide = item.IncludeInProgramGuide IncludeInProgramGuide = item.IncludeInProgramGuide
}; };
@@ -432,6 +438,7 @@
MultiCollection = item.MultiCollection, MultiCollection = item.MultiCollection,
SmartCollection = item.SmartCollection, SmartCollection = item.SmartCollection,
MediaItem = item.MediaItem, MediaItem = item.MediaItem,
PlayAll = item.PlayAll,
IncludeInProgramGuide = item.IncludeInProgramGuide IncludeInProgramGuide = item.IncludeInProgramGuide
}; };
@@ -490,6 +497,7 @@
item.SmartCollection?.Id, item.SmartCollection?.Id,
item.MediaItem?.MediaItemId, item.MediaItem?.MediaItemId,
item.PlaybackOrder, item.PlaybackOrder,
item.PlayAll,
item.IncludeInProgramGuide)).ToList(); item.IncludeInProgramGuide)).ToList();
return new ReplacePlaylistItems(Id, _playlist.Name, items); return new ReplacePlaylistItems(Id, _playlist.Name, items);
@@ -503,4 +511,5 @@
private static void UpdateEPG(PlaylistItemEditViewModel context, bool includeInProgramGuide) => context.IncludeInProgramGuide = includeInProgramGuide; private static void UpdateEPG(PlaylistItemEditViewModel context, bool includeInProgramGuide) => context.IncludeInProgramGuide = includeInProgramGuide;
private static void UpdatePlayAll(PlaylistItemEditViewModel context, bool playAll) => context.PlayAll = playAll;
} }
@@ -81,6 +81,8 @@ public class PlaylistItemEditViewModel : INotifyPropertyChanged
public PlaybackOrder PlaybackOrder { get; set; } public PlaybackOrder PlaybackOrder { get; set; }
public bool PlayAll { get; set; }
public bool IncludeInProgramGuide { get; set; } public bool IncludeInProgramGuide { get; set; }
public event PropertyChangedEventHandler PropertyChanged; public event PropertyChangedEventHandler PropertyChanged;