support filling with groups of song artists (#1537)
This commit is contained in:
@@ -25,5 +25,6 @@ public class PlayoutProgramScheduleAnchor
|
|||||||
public SmartCollection SmartCollection { get; set; }
|
public SmartCollection SmartCollection { get; set; }
|
||||||
public int? MediaItemId { get; set; }
|
public int? MediaItemId { get; set; }
|
||||||
public MediaItem MediaItem { get; set; }
|
public MediaItem MediaItem { get; set; }
|
||||||
|
public string FakeCollectionKey { get; set; }
|
||||||
public CollectionEnumeratorState EnumeratorState { get; set; }
|
public CollectionEnumeratorState EnumeratorState { get; set; }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ public abstract class ProgramScheduleItem
|
|||||||
public MultiCollection MultiCollection { get; set; }
|
public MultiCollection MultiCollection { get; set; }
|
||||||
public int? SmartCollectionId { get; set; }
|
public int? SmartCollectionId { get; set; }
|
||||||
public SmartCollection SmartCollection { get; set; }
|
public SmartCollection SmartCollection { get; set; }
|
||||||
|
public string FakeCollectionKey { get; set; }
|
||||||
public PlaybackOrder PlaybackOrder { get; set; }
|
public PlaybackOrder PlaybackOrder { get; set; }
|
||||||
public int? PreRollFillerId { get; set; }
|
public int? PreRollFillerId { get; set; }
|
||||||
public FillerPreset PreRollFiller { get; set; }
|
public FillerPreset PreRollFiller { get; set; }
|
||||||
|
|||||||
@@ -7,5 +7,7 @@ public enum ProgramScheduleItemCollectionType
|
|||||||
TelevisionSeason = 2,
|
TelevisionSeason = 2,
|
||||||
Artist = 3,
|
Artist = 3,
|
||||||
MultiCollection = 4,
|
MultiCollection = 4,
|
||||||
SmartCollection = 5
|
SmartCollection = 5,
|
||||||
|
|
||||||
|
FakeCollection = 100
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ public class CollectionKey : Record<CollectionKey>
|
|||||||
public int? MultiCollectionId { get; set; }
|
public int? MultiCollectionId { get; set; }
|
||||||
public int? SmartCollectionId { get; set; }
|
public int? SmartCollectionId { get; set; }
|
||||||
public int? MediaItemId { get; set; }
|
public int? MediaItemId { get; set; }
|
||||||
|
public string FakeCollectionKey { get; set; }
|
||||||
|
|
||||||
public static CollectionKey ForScheduleItem(ProgramScheduleItem item) =>
|
public static CollectionKey ForScheduleItem(ProgramScheduleItem item) =>
|
||||||
item.CollectionType switch
|
item.CollectionType switch
|
||||||
@@ -44,6 +45,11 @@ public class CollectionKey : Record<CollectionKey>
|
|||||||
CollectionType = item.CollectionType,
|
CollectionType = item.CollectionType,
|
||||||
SmartCollectionId = item.SmartCollectionId
|
SmartCollectionId = item.SmartCollectionId
|
||||||
},
|
},
|
||||||
|
ProgramScheduleItemCollectionType.FakeCollection => new CollectionKey
|
||||||
|
{
|
||||||
|
CollectionType = item.CollectionType,
|
||||||
|
FakeCollectionKey = item.FakeCollectionKey
|
||||||
|
},
|
||||||
_ => throw new ArgumentOutOfRangeException(nameof(item))
|
_ => throw new ArgumentOutOfRangeException(nameof(item))
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ namespace ErsatzTV.Core.Scheduling;
|
|||||||
public record CollectionWithItems(
|
public record CollectionWithItems(
|
||||||
int ShowId,
|
int ShowId,
|
||||||
int ArtistId,
|
int ArtistId,
|
||||||
|
string Key,
|
||||||
List<MediaItem> MediaItems,
|
List<MediaItem> MediaItems,
|
||||||
bool ScheduleAsGroup,
|
bool ScheduleAsGroup,
|
||||||
PlaybackOrder PlaybackOrder,
|
PlaybackOrder PlaybackOrder,
|
||||||
|
|||||||
@@ -438,10 +438,9 @@ public class PlayoutBuilder : IPlayoutBuilder
|
|||||||
_artistRepository,
|
_artistRepository,
|
||||||
collectionKey);
|
collectionKey);
|
||||||
var fakeCollections = _mediaCollectionRepository.GroupIntoFakeCollections(mediaItems)
|
var fakeCollections = _mediaCollectionRepository.GroupIntoFakeCollections(mediaItems)
|
||||||
.Filter(c => c.ShowId > 0 || c.ArtistId > 0)
|
.Filter(c => c.ShowId > 0 || c.ArtistId > 0 || !string.IsNullOrWhiteSpace(c.Key))
|
||||||
.ToList();
|
.ToList();
|
||||||
List<ProgramScheduleItem> fakeScheduleItems = []
|
List<ProgramScheduleItem> fakeScheduleItems = [];
|
||||||
;
|
|
||||||
|
|
||||||
// this will be used to clone a schedule item
|
// this will be used to clone a schedule item
|
||||||
MethodInfo generic = typeof(JsonConvert).GetMethods()
|
MethodInfo generic = typeof(JsonConvert).GetMethods()
|
||||||
@@ -451,18 +450,36 @@ public class PlayoutBuilder : IPlayoutBuilder
|
|||||||
|
|
||||||
foreach (CollectionWithItems fakeCollection in fakeCollections)
|
foreach (CollectionWithItems fakeCollection in fakeCollections)
|
||||||
{
|
{
|
||||||
var key = new CollectionKey
|
CollectionKey key = (fakeCollection.ShowId, fakeCollection.ArtistId, fakeCollection.Key) switch
|
||||||
{
|
{
|
||||||
CollectionType = fakeCollection.ShowId > 0
|
var (showId, _, _) when showId > 0 => new CollectionKey
|
||||||
? ProgramScheduleItemCollectionType.TelevisionShow
|
{
|
||||||
: ProgramScheduleItemCollectionType.Artist,
|
CollectionType = ProgramScheduleItemCollectionType.TelevisionShow,
|
||||||
MediaItemId = fakeCollection.ShowId > 0 ? fakeCollection.ShowId : fakeCollection.ArtistId
|
MediaItemId = showId
|
||||||
|
},
|
||||||
|
var (_, artistId, _) when artistId > 0 => new CollectionKey
|
||||||
|
{
|
||||||
|
CollectionType = ProgramScheduleItemCollectionType.Artist,
|
||||||
|
MediaItemId = artistId
|
||||||
|
},
|
||||||
|
var (_, _, k) when k is not null => new CollectionKey
|
||||||
|
{
|
||||||
|
CollectionType = ProgramScheduleItemCollectionType.FakeCollection,
|
||||||
|
FakeCollectionKey = k
|
||||||
|
},
|
||||||
|
var (_, _, _) => null
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (key is null)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
string serialized = JsonConvert.SerializeObject(scheduleItem);
|
string serialized = JsonConvert.SerializeObject(scheduleItem);
|
||||||
var copyScheduleItem = generic.Invoke(this, [serialized]) as ProgramScheduleItem;
|
var copyScheduleItem = generic.Invoke(this, [serialized]) as ProgramScheduleItem;
|
||||||
copyScheduleItem.CollectionType = key.CollectionType;
|
copyScheduleItem.CollectionType = key.CollectionType;
|
||||||
copyScheduleItem.MediaItemId = key.MediaItemId;
|
copyScheduleItem.MediaItemId = key.MediaItemId;
|
||||||
|
copyScheduleItem.FakeCollectionKey = key.FakeCollectionKey;
|
||||||
fakeScheduleItems.Add(copyScheduleItem);
|
fakeScheduleItems.Add(copyScheduleItem);
|
||||||
|
|
||||||
IMediaCollectionEnumerator enumerator = await GetMediaCollectionEnumerator(
|
IMediaCollectionEnumerator enumerator = await GetMediaCollectionEnumerator(
|
||||||
@@ -669,11 +686,7 @@ public class PlayoutBuilder : IPlayoutBuilder
|
|||||||
}
|
}
|
||||||
|
|
||||||
// build program schedule anchors
|
// build program schedule anchors
|
||||||
playout.ProgramScheduleAnchors = BuildProgramScheduleAnchors(
|
playout.ProgramScheduleAnchors = BuildProgramScheduleAnchors(playout, collectionEnumerators, saveAnchorDate);
|
||||||
playout,
|
|
||||||
activeSchedule,
|
|
||||||
collectionEnumerators,
|
|
||||||
saveAnchorDate);
|
|
||||||
|
|
||||||
// build fill group indices
|
// build fill group indices
|
||||||
playout.FillGroupIndices = BuildFillGroupIndices(playout, scheduleItemsFillGroupEnumerators);
|
playout.FillGroupIndices = BuildFillGroupIndices(playout, scheduleItemsFillGroupEnumerators);
|
||||||
@@ -817,7 +830,6 @@ public class PlayoutBuilder : IPlayoutBuilder
|
|||||||
|
|
||||||
private static List<PlayoutProgramScheduleAnchor> BuildProgramScheduleAnchors(
|
private static List<PlayoutProgramScheduleAnchor> BuildProgramScheduleAnchors(
|
||||||
Playout playout,
|
Playout playout,
|
||||||
ProgramSchedule activeSchedule,
|
|
||||||
Dictionary<CollectionKey, IMediaCollectionEnumerator> collectionEnumerators,
|
Dictionary<CollectionKey, IMediaCollectionEnumerator> collectionEnumerators,
|
||||||
bool saveAnchorDate)
|
bool saveAnchorDate)
|
||||||
{
|
{
|
||||||
@@ -829,6 +841,7 @@ public class PlayoutBuilder : IPlayoutBuilder
|
|||||||
a => a.CollectionType == collectionKey.CollectionType
|
a => a.CollectionType == collectionKey.CollectionType
|
||||||
&& a.CollectionId == collectionKey.CollectionId
|
&& a.CollectionId == collectionKey.CollectionId
|
||||||
&& a.MediaItemId == collectionKey.MediaItemId
|
&& a.MediaItemId == collectionKey.MediaItemId
|
||||||
|
&& a.FakeCollectionKey == collectionKey.FakeCollectionKey
|
||||||
&& a.SmartCollectionId == collectionKey.SmartCollectionId
|
&& a.SmartCollectionId == collectionKey.SmartCollectionId
|
||||||
&& a.MultiCollectionId == collectionKey.MultiCollectionId
|
&& a.MultiCollectionId == collectionKey.MultiCollectionId
|
||||||
&& a.AnchorDate is null);
|
&& a.AnchorDate is null);
|
||||||
@@ -850,6 +863,7 @@ public class PlayoutBuilder : IPlayoutBuilder
|
|||||||
MultiCollectionId = collectionKey.MultiCollectionId,
|
MultiCollectionId = collectionKey.MultiCollectionId,
|
||||||
SmartCollectionId = collectionKey.SmartCollectionId,
|
SmartCollectionId = collectionKey.SmartCollectionId,
|
||||||
MediaItemId = collectionKey.MediaItemId,
|
MediaItemId = collectionKey.MediaItemId,
|
||||||
|
FakeCollectionKey = collectionKey.FakeCollectionKey,
|
||||||
EnumeratorState = maybeEnumeratorState[collectionKey]
|
EnumeratorState = maybeEnumeratorState[collectionKey]
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Generated
+4527
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,40 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace ErsatzTV.Infrastructure.MySql.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class AddFakeCollectionGroup : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.AddColumn<string>(
|
||||||
|
name: "FakeCollectionKey",
|
||||||
|
table: "ProgramScheduleItem",
|
||||||
|
type: "longtext",
|
||||||
|
nullable: true)
|
||||||
|
.Annotation("MySql:CharSet", "utf8mb4");
|
||||||
|
|
||||||
|
migrationBuilder.AddColumn<string>(
|
||||||
|
name: "FakeCollectionKey",
|
||||||
|
table: "PlayoutProgramScheduleAnchor",
|
||||||
|
type: "longtext",
|
||||||
|
nullable: true)
|
||||||
|
.Annotation("MySql:CharSet", "utf8mb4");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "FakeCollectionKey",
|
||||||
|
table: "ProgramScheduleItem");
|
||||||
|
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "FakeCollectionKey",
|
||||||
|
table: "PlayoutProgramScheduleAnchor");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -16,7 +16,7 @@ namespace ErsatzTV.Infrastructure.MySql.Migrations
|
|||||||
{
|
{
|
||||||
#pragma warning disable 612, 618
|
#pragma warning disable 612, 618
|
||||||
modelBuilder
|
modelBuilder
|
||||||
.HasAnnotation("ProductVersion", "7.0.14")
|
.HasAnnotation("ProductVersion", "8.0.0")
|
||||||
.HasAnnotation("Relational:MaxIdentifierLength", 64);
|
.HasAnnotation("Relational:MaxIdentifierLength", 64);
|
||||||
|
|
||||||
modelBuilder.Entity("ErsatzTV.Core.Domain.Actor", b =>
|
modelBuilder.Entity("ErsatzTV.Core.Domain.Actor", b =>
|
||||||
@@ -1511,6 +1511,9 @@ namespace ErsatzTV.Infrastructure.MySql.Migrations
|
|||||||
b.Property<int>("CollectionType")
|
b.Property<int>("CollectionType")
|
||||||
.HasColumnType("int");
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<string>("FakeCollectionKey")
|
||||||
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
b.Property<int?>("MediaItemId")
|
b.Property<int?>("MediaItemId")
|
||||||
.HasColumnType("int");
|
.HasColumnType("int");
|
||||||
|
|
||||||
@@ -1700,6 +1703,9 @@ namespace ErsatzTV.Infrastructure.MySql.Migrations
|
|||||||
b.Property<string>("CustomTitle")
|
b.Property<string>("CustomTitle")
|
||||||
.HasColumnType("longtext");
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b.Property<string>("FakeCollectionKey")
|
||||||
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
b.Property<int?>("FallbackFillerId")
|
b.Property<int?>("FallbackFillerId")
|
||||||
.HasColumnType("int");
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
|||||||
Generated
+4525
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,38 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace ErsatzTV.Infrastructure.Sqlite.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class AddFakeCollectionGroup : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.AddColumn<string>(
|
||||||
|
name: "FakeCollectionKey",
|
||||||
|
table: "ProgramScheduleItem",
|
||||||
|
type: "TEXT",
|
||||||
|
nullable: true);
|
||||||
|
|
||||||
|
migrationBuilder.AddColumn<string>(
|
||||||
|
name: "FakeCollectionKey",
|
||||||
|
table: "PlayoutProgramScheduleAnchor",
|
||||||
|
type: "TEXT",
|
||||||
|
nullable: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "FakeCollectionKey",
|
||||||
|
table: "ProgramScheduleItem");
|
||||||
|
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "FakeCollectionKey",
|
||||||
|
table: "PlayoutProgramScheduleAnchor");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -15,7 +15,7 @@ namespace ErsatzTV.Infrastructure.Sqlite.Migrations
|
|||||||
protected override void BuildModel(ModelBuilder modelBuilder)
|
protected override void BuildModel(ModelBuilder modelBuilder)
|
||||||
{
|
{
|
||||||
#pragma warning disable 612, 618
|
#pragma warning disable 612, 618
|
||||||
modelBuilder.HasAnnotation("ProductVersion", "7.0.14");
|
modelBuilder.HasAnnotation("ProductVersion", "8.0.0");
|
||||||
|
|
||||||
modelBuilder.Entity("ErsatzTV.Core.Domain.Actor", b =>
|
modelBuilder.Entity("ErsatzTV.Core.Domain.Actor", b =>
|
||||||
{
|
{
|
||||||
@@ -1509,6 +1509,9 @@ namespace ErsatzTV.Infrastructure.Sqlite.Migrations
|
|||||||
b.Property<int>("CollectionType")
|
b.Property<int>("CollectionType")
|
||||||
.HasColumnType("INTEGER");
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("FakeCollectionKey")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
b.Property<int?>("MediaItemId")
|
b.Property<int?>("MediaItemId")
|
||||||
.HasColumnType("INTEGER");
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
@@ -1698,6 +1701,9 @@ namespace ErsatzTV.Infrastructure.Sqlite.Migrations
|
|||||||
b.Property<string>("CustomTitle")
|
b.Property<string>("CustomTitle")
|
||||||
.HasColumnType("TEXT");
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("FakeCollectionKey")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
b.Property<int?>("FallbackFillerId")
|
b.Property<int?>("FallbackFillerId")
|
||||||
.HasColumnType("INTEGER");
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
|||||||
@@ -190,6 +190,7 @@ public class MediaCollectionRepository : IMediaCollectionRepository
|
|||||||
new CollectionWithItems(
|
new CollectionWithItems(
|
||||||
multiCollectionItem.CollectionId,
|
multiCollectionItem.CollectionId,
|
||||||
multiCollectionItem.CollectionId,
|
multiCollectionItem.CollectionId,
|
||||||
|
null,
|
||||||
sortedItems,
|
sortedItems,
|
||||||
multiCollectionItem.ScheduleAsGroup,
|
multiCollectionItem.ScheduleAsGroup,
|
||||||
multiCollectionItem.PlaybackOrder,
|
multiCollectionItem.PlaybackOrder,
|
||||||
@@ -202,6 +203,7 @@ public class MediaCollectionRepository : IMediaCollectionRepository
|
|||||||
new CollectionWithItems(
|
new CollectionWithItems(
|
||||||
multiCollectionItem.CollectionId,
|
multiCollectionItem.CollectionId,
|
||||||
multiCollectionItem.CollectionId,
|
multiCollectionItem.CollectionId,
|
||||||
|
null,
|
||||||
items,
|
items,
|
||||||
multiCollectionItem.ScheduleAsGroup,
|
multiCollectionItem.ScheduleAsGroup,
|
||||||
multiCollectionItem.PlaybackOrder,
|
multiCollectionItem.PlaybackOrder,
|
||||||
@@ -217,6 +219,7 @@ public class MediaCollectionRepository : IMediaCollectionRepository
|
|||||||
new CollectionWithItems(
|
new CollectionWithItems(
|
||||||
multiCollectionSmartItem.SmartCollectionId,
|
multiCollectionSmartItem.SmartCollectionId,
|
||||||
multiCollectionSmartItem.SmartCollectionId,
|
multiCollectionSmartItem.SmartCollectionId,
|
||||||
|
null,
|
||||||
items,
|
items,
|
||||||
multiCollectionSmartItem.ScheduleAsGroup,
|
multiCollectionSmartItem.ScheduleAsGroup,
|
||||||
multiCollectionSmartItem.PlaybackOrder,
|
multiCollectionSmartItem.PlaybackOrder,
|
||||||
@@ -357,6 +360,7 @@ public class MediaCollectionRepository : IMediaCollectionRepository
|
|||||||
new CollectionWithItems(
|
new CollectionWithItems(
|
||||||
showId,
|
showId,
|
||||||
0,
|
0,
|
||||||
|
null,
|
||||||
list,
|
list,
|
||||||
true,
|
true,
|
||||||
PlaybackOrder.Chronological,
|
PlaybackOrder.Chronological,
|
||||||
@@ -384,6 +388,7 @@ public class MediaCollectionRepository : IMediaCollectionRepository
|
|||||||
new CollectionWithItems(
|
new CollectionWithItems(
|
||||||
0,
|
0,
|
||||||
artistId,
|
artistId,
|
||||||
|
null,
|
||||||
list,
|
list,
|
||||||
true,
|
true,
|
||||||
PlaybackOrder.Chronological,
|
PlaybackOrder.Chronological,
|
||||||
@@ -418,12 +423,13 @@ public class MediaCollectionRepository : IMediaCollectionRepository
|
|||||||
songArtistCollections[key] = list;
|
songArtistCollections[key] = list;
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach ((int _, List<MediaItem> list) in songArtistCollections)
|
foreach ((int index, List<MediaItem> list) in songArtistCollections)
|
||||||
{
|
{
|
||||||
result.Add(
|
result.Add(
|
||||||
new CollectionWithItems(
|
new CollectionWithItems(
|
||||||
id,
|
id,
|
||||||
id,
|
id,
|
||||||
|
allArtists[index],
|
||||||
list,
|
list,
|
||||||
true,
|
true,
|
||||||
PlaybackOrder.Chronological,
|
PlaybackOrder.Chronological,
|
||||||
@@ -436,6 +442,7 @@ public class MediaCollectionRepository : IMediaCollectionRepository
|
|||||||
new CollectionWithItems(
|
new CollectionWithItems(
|
||||||
id,
|
id,
|
||||||
id,
|
id,
|
||||||
|
null,
|
||||||
items.OfType<Movie>().Cast<MediaItem>().ToList(),
|
items.OfType<Movie>().Cast<MediaItem>().ToList(),
|
||||||
true,
|
true,
|
||||||
PlaybackOrder.Chronological,
|
PlaybackOrder.Chronological,
|
||||||
@@ -446,6 +453,7 @@ public class MediaCollectionRepository : IMediaCollectionRepository
|
|||||||
new CollectionWithItems(
|
new CollectionWithItems(
|
||||||
id,
|
id,
|
||||||
id,
|
id,
|
||||||
|
null,
|
||||||
items.OfType<OtherVideo>().Cast<MediaItem>().ToList(),
|
items.OfType<OtherVideo>().Cast<MediaItem>().ToList(),
|
||||||
true,
|
true,
|
||||||
PlaybackOrder.Chronological,
|
PlaybackOrder.Chronological,
|
||||||
|
|||||||
Reference in New Issue
Block a user