Browse Source

add playlist item play all option (#1694)

pull/1696/head
Jason Dove 2 years ago committed by GitHub
parent
commit
e19b639527
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 7
      CHANGELOG.md
  2. 96
      ErsatzTV.Application/MediaCollections/Commands/PreviewPlaylistPlayoutHandler.cs
  3. 1
      ErsatzTV.Application/MediaCollections/Commands/ReplacePlaylistItem.cs
  4. 1
      ErsatzTV.Application/MediaCollections/Commands/ReplacePlaylistItemsHandler.cs
  5. 1
      ErsatzTV.Application/MediaCollections/Mapper.cs
  6. 1
      ErsatzTV.Application/MediaCollections/PlaylistItemViewModel.cs
  7. 3
      ErsatzTV.Core.Tests/Fakes/FakeMediaCollectionRepository.cs
  8. 1
      ErsatzTV.Core/Domain/Collection/PlaylistItem.cs
  9. 1
      ErsatzTV.Core/Interfaces/Repositories/IMediaCollectionRepository.cs
  10. 3
      ErsatzTV.Core/Interfaces/Scheduling/IPlayoutBuilder.cs
  11. 6
      ErsatzTV.Core/Scheduling/BlockScheduling/BlockPlayoutBuilder.cs
  12. 23
      ErsatzTV.Core/Scheduling/PlaylistEnumerator.cs
  13. 31
      ErsatzTV.Core/Scheduling/PlayoutBuilder.cs
  14. 5774
      ErsatzTV.Infrastructure.MySql/Migrations/20240428175755_Add_PlaylistItemPlayAll.Designer.cs
  15. 29
      ErsatzTV.Infrastructure.MySql/Migrations/20240428175755_Add_PlaylistItemPlayAll.cs
  16. 3
      ErsatzTV.Infrastructure.MySql/Migrations/TvContextModelSnapshot.cs
  17. 5613
      ErsatzTV.Infrastructure.Sqlite/Migrations/20240428155744_Add_PlaylistItemPlayAll.Designer.cs
  18. 29
      ErsatzTV.Infrastructure.Sqlite/Migrations/20240428155744_Add_PlaylistItemPlayAll.cs
  19. 3
      ErsatzTV.Infrastructure.Sqlite/Migrations/TvContextModelSnapshot.cs
  20. 124
      ErsatzTV.Infrastructure/Data/Repositories/MediaCollectionRepository.cs
  21. 9
      ErsatzTV/Pages/PlaylistEditor.razor
  22. 2
      ErsatzTV/ViewModels/PlaylistItemEditViewModel.cs

7
CHANGELOG.md

@ -50,8 +50,13 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). @@ -50,8 +50,13 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Songs
- Images
- 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
- 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
### Fixed

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

@ -1,7 +1,7 @@ @@ -1,7 +1,7 @@
using System.Diagnostics.CodeAnalysis;
using ErsatzTV.Application.Scheduling;
using ErsatzTV.Core.Domain;
using ErsatzTV.Core.Domain.Scheduling;
using ErsatzTV.Core.Interfaces.Repositories;
using ErsatzTV.Core.Interfaces.Scheduling;
using ErsatzTV.Core.Scheduling;
using ErsatzTV.Infrastructure.Data;
@ -13,7 +13,8 @@ namespace ErsatzTV.Application.MediaCollections; @@ -13,7 +13,8 @@ namespace ErsatzTV.Application.MediaCollections;
[SuppressMessage("ReSharper", "SuggestBaseTypeForParameterInConstructor")]
public class PreviewPlaylistPlayoutHandler(
IDbContextFactory<TvContext> dbContextFactory,
IBlockPlayoutPreviewBuilder blockPlayoutBuilder)
IMediaCollectionRepository mediaCollectionRepository,
IPlayoutBuilder playoutBuilder)
: IRequestHandler<PreviewPlaylistPlayout, List<PlayoutItemPreviewViewModel>>
{
public async Task<List<PlayoutItemPreviewViewModel>> Handle(
@ -22,21 +23,6 @@ public class PreviewPlaylistPlayoutHandler( @@ -22,21 +23,6 @@ public class PreviewPlaylistPlayoutHandler(
{
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
{
Channel = new Channel(Guid.NewGuid())
@ -45,21 +31,38 @@ public class PreviewPlaylistPlayoutHandler( @@ -45,21 +31,38 @@ public class PreviewPlaylistPlayoutHandler(
Name = "Playlist Preview"
},
Items = [],
ProgramSchedulePlayoutType = ProgramSchedulePlayoutType.Block,
ProgramSchedulePlayoutType = ProgramSchedulePlayoutType.Flood,
PlayoutHistory = [],
Templates =
[
new PlayoutTemplate
{
DaysOfWeek = PlayoutTemplate.AllDaysOfWeek(),
DaysOfMonth = PlayoutTemplate.AllDaysOfMonth(),
MonthsOfYear = PlayoutTemplate.AllMonthsOfYear(),
Template = template
}
]
ProgramSchedule = new ProgramSchedule
{
Items = [MapToScheduleItem(request)]
},
ProgramScheduleAlternates = [],
FillGroupIndices = []
};
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
foreach (PlayoutItem playoutItem in playout.Items)
@ -91,29 +94,32 @@ public class PreviewPlaylistPlayoutHandler( @@ -91,29 +94,32 @@ public class PreviewPlaylistPlayoutHandler(
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()
{
Name = request.Name,
Minutes = 6 * 60,
StopScheduling = BlockStopScheduling.AfterDurationEnd,
Items = request.Items.Map(MapToBlockItem).ToList()
CollectionType = ProgramScheduleItemCollectionType.Playlist,
Playlist = new Playlist
{
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()
{
Id = id,
Index = request.Index,
CollectionType = request.CollectionType,
CollectionId = request.CollectionId,
MultiCollectionId = request.MultiCollectionId,
SmartCollectionId = request.SmartCollectionId,
MediaItemId = request.MediaItemId,
PlaybackOrder = request.PlaybackOrder
Index = item.Index,
CollectionType = item.CollectionType,
CollectionId = item.CollectionId,
MediaItemId = item.MediaItemId,
MultiCollectionId = item.MultiCollectionId,
SmartCollectionId = item.SmartCollectionId,
PlaybackOrder = item.PlaybackOrder,
PlayAll = item.PlayAll
};
}

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

@ -10,4 +10,5 @@ public record ReplacePlaylistItem( @@ -10,4 +10,5 @@ public record ReplacePlaylistItem(
int? SmartCollectionId,
int? MediaItemId,
PlaybackOrder PlaybackOrder,
bool PlayAll,
bool IncludeInProgramGuide);

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

@ -46,6 +46,7 @@ public class ReplacePlaylistItemsHandler(IDbContextFactory<TvContext> dbContextF @@ -46,6 +46,7 @@ public class ReplacePlaylistItemsHandler(IDbContextFactory<TvContext> dbContextF
SmartCollectionId = item.SmartCollectionId,
MediaItemId = item.MediaItemId,
PlaybackOrder = item.PlaybackOrder,
PlayAll = item.PlayAll,
IncludeInProgramGuide = item.IncludeInProgramGuide
};

1
ErsatzTV.Application/MediaCollections/Mapper.cs

@ -78,5 +78,6 @@ internal static class Mapper @@ -78,5 +78,6 @@ internal static class Mapper
_ => null
},
playlistItem.PlaybackOrder,
playlistItem.PlayAll,
playlistItem.IncludeInProgramGuide);
}

1
ErsatzTV.Application/MediaCollections/PlaylistItemViewModel.cs

@ -12,4 +12,5 @@ public record PlaylistItemViewModel( @@ -12,4 +12,5 @@ public record PlaylistItemViewModel(
SmartCollectionViewModel SmartCollection,
NamedMediaItemViewModel MediaItem,
PlaybackOrder PlaybackOrder,
bool PlayAll,
bool IncludeInProgramGuide);

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

@ -13,6 +13,9 @@ public class FakeMediaCollectionRepository : IMediaCollectionRepository @@ -13,6 +13,9 @@ public class FakeMediaCollectionRepository : IMediaCollectionRepository
public Task<Dictionary<PlaylistItem, List<MediaItem>>> GetPlaylistItemMap(int playlistId) =>
throw new NotSupportedException();
public Task<Dictionary<PlaylistItem, List<MediaItem>>> GetPlaylistItemMap(Playlist playlist) =>
throw new NotSupportedException();
public Task<Option<Collection>> GetCollectionWithCollectionItemsUntracked(int id) =>
throw new NotSupportedException();

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

@ -16,5 +16,6 @@ public class PlaylistItem @@ -16,5 +16,6 @@ public class PlaylistItem
public int? SmartCollectionId { get; set; }
public SmartCollection SmartCollection { get; set; }
public PlaybackOrder PlaybackOrder { get; set; }
public bool PlayAll { get; set; }
public bool IncludeInProgramGuide { get; set; }
}

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

@ -6,6 +6,7 @@ namespace ErsatzTV.Core.Interfaces.Repositories; @@ -6,6 +6,7 @@ namespace ErsatzTV.Core.Interfaces.Repositories;
public interface IMediaCollectionRepository
{
Task<Dictionary<PlaylistItem, List<MediaItem>>> GetPlaylistItemMap(int playlistId);
Task<Dictionary<PlaylistItem, List<MediaItem>>> GetPlaylistItemMap(Playlist playlist);
Task<Option<Collection>> GetCollectionWithCollectionItemsUntracked(int id);
Task<List<MediaItem>> GetItems(int id);
Task<List<MediaItem>> GetMultiCollectionItems(int id);

3
ErsatzTV.Core/Interfaces/Scheduling/IPlayoutBuilder.cs

@ -5,5 +5,8 @@ namespace ErsatzTV.Core.Interfaces.Scheduling; @@ -5,5 +5,8 @@ namespace ErsatzTV.Core.Interfaces.Scheduling;
public interface IPlayoutBuilder
{
public bool TrimStart { get; set; }
public Playlist DebugPlaylist { get; set; }
Task<Playout> Build(Playout playout, PlayoutBuildMode mode, CancellationToken cancellationToken);
}

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

@ -56,6 +56,12 @@ public class BlockPlayoutBuilder( @@ -56,6 +56,12 @@ public class BlockPlayoutBuilder(
// get all collection items for the playout
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);
Dictionary<PlayoutItem, BlockKey> itemBlockKeys =

23
ErsatzTV.Core/Scheduling/PlaylistEnumerator.cs

@ -8,6 +8,7 @@ namespace ErsatzTV.Core.Scheduling; @@ -8,6 +8,7 @@ namespace ErsatzTV.Core.Scheduling;
public class PlaylistEnumerator : IMediaCollectionEnumerator
{
private IList<IMediaCollectionEnumerator> _sortedEnumerators;
private IList<bool> _playAll;
private int _enumeratorIndex;
private System.Collections.Generic.HashSet<int> _idsToIncludeInEPG;
@ -20,6 +21,7 @@ public class PlaylistEnumerator : IMediaCollectionEnumerator @@ -20,6 +21,7 @@ public class PlaylistEnumerator : IMediaCollectionEnumerator
var result = new PlaylistEnumerator
{
_sortedEnumerators = [],
_playAll = [],
_idsToIncludeInEPG = [],
Count = LCM(playlistItemMap.Values.Map(v => v.Count)) * playlistItemMap.Count
};
@ -42,6 +44,7 @@ public class PlaylistEnumerator : IMediaCollectionEnumerator @@ -42,6 +44,7 @@ public class PlaylistEnumerator : IMediaCollectionEnumerator
if (enumeratorMap.TryGetValue(collectionKey, out IMediaCollectionEnumerator enumerator))
{
result._sortedEnumerators.Add(enumerator);
result._playAll.Add(playlistItem.PlayAll);
continue;
}
@ -92,6 +95,7 @@ public class PlaylistEnumerator : IMediaCollectionEnumerator @@ -92,6 +95,7 @@ public class PlaylistEnumerator : IMediaCollectionEnumerator
{
enumeratorMap.Add(collectionKey, enumerator);
result._sortedEnumerators.Add(enumerator);
result._playAll.Add(playlistItem.PlayAll);
}
}
@ -103,11 +107,16 @@ public class PlaylistEnumerator : IMediaCollectionEnumerator @@ -103,11 +107,16 @@ public class PlaylistEnumerator : IMediaCollectionEnumerator
result.State = new CollectionEnumeratorState { Seed = state.Seed };
result._enumeratorIndex = 0;
while (result.State.Index < state.Index)
// TODO: how do we end up with index > count?
if (state.Index < result.Count)
{
result.MoveNext();
while (result.State.Index < state.Index)
{
result.MoveNext();
}
}
return result;
}
@ -142,7 +151,13 @@ public class PlaylistEnumerator : IMediaCollectionEnumerator @@ -142,7 +151,13 @@ public class PlaylistEnumerator : IMediaCollectionEnumerator
public void 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;
}

31
ErsatzTV.Core/Scheduling/PlayoutBuilder.cs

@ -5,6 +5,7 @@ using ErsatzTV.Core.Interfaces.Metadata; @@ -5,6 +5,7 @@ using ErsatzTV.Core.Interfaces.Metadata;
using ErsatzTV.Core.Interfaces.Repositories;
using ErsatzTV.Core.Interfaces.Scheduling;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Newtonsoft.Json;
using Map = LanguageExt.Map;
@ -18,10 +19,11 @@ public class PlayoutBuilder : IPlayoutBuilder @@ -18,10 +19,11 @@ public class PlayoutBuilder : IPlayoutBuilder
private readonly IArtistRepository _artistRepository;
private readonly IConfigElementRepository _configElementRepository;
private readonly ILocalFileSystem _localFileSystem;
private readonly ILogger<PlayoutBuilder> _logger;
private ILogger<PlayoutBuilder> _logger;
private readonly IMediaCollectionRepository _mediaCollectionRepository;
private readonly IMultiEpisodeShuffleCollectionEnumeratorFactory _multiEpisodeFactory;
private readonly ITelevisionRepository _televisionRepository;
private Playlist _debugPlaylist;
public PlayoutBuilder(
IConfigElementRepository configElementRepository,
@ -41,6 +43,21 @@ public class PlayoutBuilder : IPlayoutBuilder @@ -41,6 +43,21 @@ public class PlayoutBuilder : IPlayoutBuilder
_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)
{
if (playout.ProgramSchedulePlayoutType is not ProgramSchedulePlayoutType.Flood)
@ -368,8 +385,11 @@ public class PlayoutBuilder : IPlayoutBuilder @@ -368,8 +385,11 @@ public class PlayoutBuilder : IPlayoutBuilder
cancellationToken);
}
// remove old items
playout.Items.RemoveAll(old => old.FinishOffset < trimBefore);
if (TrimStart)
{
// remove old items
playout.Items.RemoveAll(old => old.FinishOffset < trimBefore);
}
// check for future items that aren't grouped inside range
var futureItems = playout.Items.Filter(i => i.StartOffset > trimAfter).ToList();
@ -944,8 +964,9 @@ public class PlayoutBuilder : IPlayoutBuilder @@ -944,8 +964,9 @@ public class PlayoutBuilder : IPlayoutBuilder
{
foreach (int playlistId in Optional(collectionKey.PlaylistId))
{
Dictionary<PlaylistItem, List<MediaItem>> playlistItemMap =
await _mediaCollectionRepository.GetPlaylistItemMap(playlistId);
Dictionary<PlaylistItem, List<MediaItem>> playlistItemMap = DebugPlaylist is not null
? await _mediaCollectionRepository.GetPlaylistItemMap(DebugPlaylist)
: await _mediaCollectionRepository.GetPlaylistItemMap(playlistId);
return await PlaylistEnumerator.Create(
_mediaCollectionRepository,

5774
ErsatzTV.Infrastructure.MySql/Migrations/20240428175755_Add_PlaylistItemPlayAll.Designer.cs generated

File diff suppressed because it is too large Load Diff

29
ErsatzTV.Infrastructure.MySql/Migrations/20240428175755_Add_PlaylistItemPlayAll.cs

@ -0,0 +1,29 @@ @@ -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");
}
}
}

3
ErsatzTV.Infrastructure.MySql/Migrations/TvContextModelSnapshot.cs

@ -1649,6 +1649,9 @@ namespace ErsatzTV.Infrastructure.MySql.Migrations @@ -1649,6 +1649,9 @@ namespace ErsatzTV.Infrastructure.MySql.Migrations
b.Property<int?>("MultiCollectionId")
.HasColumnType("int");
b.Property<bool>("PlayAll")
.HasColumnType("tinyint(1)");
b.Property<int>("PlaybackOrder")
.HasColumnType("int");

5613
ErsatzTV.Infrastructure.Sqlite/Migrations/20240428155744_Add_PlaylistItemPlayAll.Designer.cs generated

File diff suppressed because it is too large Load Diff

29
ErsatzTV.Infrastructure.Sqlite/Migrations/20240428155744_Add_PlaylistItemPlayAll.cs

@ -0,0 +1,29 @@ @@ -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");
}
}
}

3
ErsatzTV.Infrastructure.Sqlite/Migrations/TvContextModelSnapshot.cs

@ -1564,6 +1564,9 @@ namespace ErsatzTV.Infrastructure.Sqlite.Migrations @@ -1564,6 +1564,9 @@ namespace ErsatzTV.Infrastructure.Sqlite.Migrations
b.Property<int?>("MultiCollectionId")
.HasColumnType("INTEGER");
b.Property<bool>("PlayAll")
.HasColumnType("INTEGER");
b.Property<int>("PlaybackOrder")
.HasColumnType("INTEGER");

124
ErsatzTV.Infrastructure/Data/Repositories/MediaCollectionRepository.cs

@ -156,6 +156,130 @@ public class MediaCollectionRepository : IMediaCollectionRepository @@ -156,6 +156,130 @@ public class MediaCollectionRepository : IMediaCollectionRepository
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)
{
await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync();

9
ErsatzTV/Pages/PlaylistEditor.razor

@ -35,6 +35,7 @@ @@ -35,6 +35,7 @@
<col/>
<col/>
<col/>
<col/>
<col style="width: 60px;"/>
<col style="width: 60px;"/>
<col style="width: 60px;"/>
@ -44,6 +45,7 @@ @@ -44,6 +45,7 @@
<MudTh>Item Type</MudTh>
<MudTh>Item</MudTh>
<MudTh>Playback Order</MudTh>
<MudTh>Play All</MudTh>
<MudTh>Show In EPG</MudTh>
<MudTh/>
<MudTh/>
@ -66,6 +68,9 @@ @@ -66,6 +68,9 @@
@(context.PlaybackOrder > 0 ? context.PlaybackOrder : "")
</MudText>
</MudTd>
<MudTd>
<MudCheckBox T="bool" Value="@context.PlayAll" ValueChanged="@(e => UpdatePlayAll(context, e))"/>
</MudTd>
<MudTd>
<MudCheckBox T="bool" Value="@context.IncludeInProgramGuide" ValueChanged="@(e => UpdateEPG(context, e))"/>
</MudTd>
@ -405,6 +410,7 @@ @@ -405,6 +410,7 @@
SmartCollection = item.SmartCollection,
MediaItem = item.MediaItem,
PlaybackOrder = item.PlaybackOrder,
PlayAll = item.PlayAll,
IncludeInProgramGuide = item.IncludeInProgramGuide
};
@ -432,6 +438,7 @@ @@ -432,6 +438,7 @@
MultiCollection = item.MultiCollection,
SmartCollection = item.SmartCollection,
MediaItem = item.MediaItem,
PlayAll = item.PlayAll,
IncludeInProgramGuide = item.IncludeInProgramGuide
};
@ -490,6 +497,7 @@ @@ -490,6 +497,7 @@
item.SmartCollection?.Id,
item.MediaItem?.MediaItemId,
item.PlaybackOrder,
item.PlayAll,
item.IncludeInProgramGuide)).ToList();
return new ReplacePlaylistItems(Id, _playlist.Name, items);
@ -503,4 +511,5 @@ @@ -503,4 +511,5 @@
private static void UpdateEPG(PlaylistItemEditViewModel context, bool includeInProgramGuide) => context.IncludeInProgramGuide = includeInProgramGuide;
private static void UpdatePlayAll(PlaylistItemEditViewModel context, bool playAll) => context.PlayAll = playAll;
}

2
ErsatzTV/ViewModels/PlaylistItemEditViewModel.cs

@ -81,6 +81,8 @@ public class PlaylistItemEditViewModel : INotifyPropertyChanged @@ -81,6 +81,8 @@ public class PlaylistItemEditViewModel : INotifyPropertyChanged
public PlaybackOrder PlaybackOrder { get; set; }
public bool PlayAll { get; set; }
public bool IncludeInProgramGuide { get; set; }
public event PropertyChangedEventHandler PropertyChanged;

Loading…
Cancel
Save