Browse Source

scheduling bug fixes (#1238)

pull/1239/head
Jason Dove 3 years ago committed by GitHub
parent
commit
a684dcced9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 7
      CHANGELOG.md
  2. 34
      ErsatzTV.Core/Scheduling/GroupedMediaItem.cs
  3. 40
      ErsatzTV.Core/Scheduling/MediaItemsForCollection.cs
  4. 6
      ErsatzTV.Core/Scheduling/MultiCollectionGrouper.cs
  5. 2
      ErsatzTV.Core/Scheduling/ShuffleInOrderCollectionEnumerator.cs
  6. 2
      ErsatzTV.Core/Scheduling/ShuffledMediaCollectionEnumerator.cs
  7. 10
      ErsatzTV.Infrastructure/Data/Repositories/MediaCollectionRepository.cs
  8. 2
      ErsatzTV.Infrastructure/Scheduling/MultiEpisodeShuffleCollectionEnumerator.cs
  9. 3
      ErsatzTV/Pages/Playouts.razor

7
CHANGELOG.md

@ -6,6 +6,13 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
## [Unreleased] ## [Unreleased]
### Fixed ### Fixed
- Limit `HLS Direct` streams to realtime speed - Limit `HLS Direct` streams to realtime speed
- Fix `Reset Playout` button to use worker thread instead of UI thread
- This fixes potential UI hangs and database concurrency bugs
### Changed
- Remove duplicate items from smart collections before scheduling
- i.e. shows no longer need to be filtered out if search results also include episodes
- Certain multi-collection scenarios may still include duplicates across multiple collections
## [0.7.7-beta] - 2023-04-07 ## [0.7.7-beta] - 2023-04-07
### Added ### Added

34
ErsatzTV.Core/Scheduling/GroupedMediaItem.cs

@ -4,7 +4,7 @@ using ErsatzTV.Core.Domain;
namespace ErsatzTV.Core.Scheduling; namespace ErsatzTV.Core.Scheduling;
[DebuggerDisplay("{" + nameof(First) + "}")] [DebuggerDisplay("{" + nameof(First) + "}")]
public class GroupedMediaItem public class GroupedMediaItem : IEquatable<GroupedMediaItem>
{ {
public GroupedMediaItem() public GroupedMediaItem()
{ {
@ -16,10 +16,36 @@ public class GroupedMediaItem
Additional = additional ?? new List<MediaItem>(); Additional = additional ?? new List<MediaItem>();
} }
public MediaItem First { get; set; } public MediaItem First { get; init; }
public List<MediaItem> Additional { get; set; } public List<MediaItem> Additional { get; protected init; }
public static IList<MediaItem> FlattenGroups(GroupedMediaItem[] copy, int mediaItemCount) public bool Equals(GroupedMediaItem other) =>
Equals(First.Id, other?.First.Id) && Equals(Additional?.Count, other?.Additional?.Count) &&
Equals(Additional?.Map(x => x.Id), other?.Additional?.Map(x => x.Id));
public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj))
{
return false;
}
if (ReferenceEquals(this, obj))
{
return true;
}
if (obj.GetType() != GetType())
{
return false;
}
return Equals((GroupedMediaItem)obj);
}
public override int GetHashCode() => HashCode.Combine(First.Id, Additional?.Map(x => x.Id));
public static IList<MediaItem> FlattenGroups(IEnumerable<GroupedMediaItem> copy, int mediaItemCount)
{ {
var result = new MediaItem[mediaItemCount]; var result = new MediaItem[mediaItemCount];
var i = 0; var i = 0;

40
ErsatzTV.Core/Scheduling/MediaItemsForCollection.cs

@ -11,36 +11,34 @@ public static class MediaItemsForCollection
IArtistRepository artistRepository, IArtistRepository artistRepository,
CollectionKey collectionKey) CollectionKey collectionKey)
{ {
var result = new List<MediaItem>();
switch (collectionKey.CollectionType) switch (collectionKey.CollectionType)
{ {
case ProgramScheduleItemCollectionType.Collection: case ProgramScheduleItemCollectionType.Collection:
List<MediaItem> collectionItems = result.AddRange(await mediaCollectionRepository.GetItems(collectionKey.CollectionId ?? 0));
await mediaCollectionRepository.GetItems(collectionKey.CollectionId ?? 0); break;
return collectionItems;
case ProgramScheduleItemCollectionType.TelevisionShow: case ProgramScheduleItemCollectionType.TelevisionShow:
List<Episode> showItems = result.AddRange(await televisionRepository.GetShowItems(collectionKey.MediaItemId ?? 0));
await televisionRepository.GetShowItems(collectionKey.MediaItemId ?? 0); break;
return showItems.Cast<MediaItem>().ToList();
case ProgramScheduleItemCollectionType.TelevisionSeason: case ProgramScheduleItemCollectionType.TelevisionSeason:
List<Episode> seasonItems = result.AddRange(await televisionRepository.GetSeasonItems(collectionKey.MediaItemId ?? 0));
await televisionRepository.GetSeasonItems(collectionKey.MediaItemId ?? 0); break;
return seasonItems.Cast<MediaItem>().ToList();
case ProgramScheduleItemCollectionType.Artist: case ProgramScheduleItemCollectionType.Artist:
List<MusicVideo> artistItems = result.AddRange(await artistRepository.GetArtistItems(collectionKey.MediaItemId ?? 0));
await artistRepository.GetArtistItems(collectionKey.MediaItemId ?? 0); break;
return artistItems.Cast<MediaItem>().ToList();
case ProgramScheduleItemCollectionType.MultiCollection: case ProgramScheduleItemCollectionType.MultiCollection:
List<MediaItem> multiCollectionItems = result.AddRange(
await mediaCollectionRepository.GetMultiCollectionItems( await mediaCollectionRepository.GetMultiCollectionItems(collectionKey.MultiCollectionId ?? 0));
collectionKey.MultiCollectionId ?? 0); break;
return multiCollectionItems;
case ProgramScheduleItemCollectionType.SmartCollection: case ProgramScheduleItemCollectionType.SmartCollection:
List<MediaItem> smartCollectionItems = result.AddRange(
await mediaCollectionRepository.GetSmartCollectionItems( await mediaCollectionRepository.GetSmartCollectionItems(collectionKey.SmartCollectionId ?? 0));
collectionKey.SmartCollectionId ?? 0); break;
return smartCollectionItems;
default: default:
return new List<MediaItem>(); throw new ArgumentOutOfRangeException();
} }
return result.DistinctBy(x => x.Id).ToList();
} }
} }

6
ErsatzTV.Core/Scheduling/MultiCollectionGrouper.cs

@ -1,8 +1,8 @@
namespace ErsatzTV.Core.Scheduling; namespace ErsatzTV.Core.Scheduling;
public class MultiCollectionGrouper public static class MultiCollectionGrouper
{ {
public static List<GroupedMediaItem> GroupMediaItems(IList<CollectionWithItems> collections) public static List<GroupedMediaItem> GroupMediaItems(IEnumerable<CollectionWithItems> collections)
{ {
var result = new List<GroupedMediaItem>(); var result = new List<GroupedMediaItem>();
@ -18,6 +18,6 @@ public class MultiCollectionGrouper
} }
} }
return result; return result.Distinct().ToList();
} }
} }

2
ErsatzTV.Core/Scheduling/ShuffleInOrderCollectionEnumerator.cs

@ -52,7 +52,7 @@ public class ShuffleInOrderCollectionEnumerator : IMediaCollectionEnumerator
State.Seed = _random.Next(); State.Seed = _random.Next();
_random = new Random(State.Seed); _random = new Random(State.Seed);
_shuffled = Shuffle(_collections, _random); _shuffled = Shuffle(_collections, _random);
} while (_collections.Count > 1 && Current == tail); } while (_collections.Count > 1 && Current.Map(x => x.Id) == tail.Map(x => x.Id));
} }
else else
{ {

2
ErsatzTV.Core/Scheduling/ShuffledMediaCollectionEnumerator.cs

@ -49,7 +49,7 @@ public class ShuffledMediaCollectionEnumerator : IMediaCollectionEnumerator
State.Seed = _random.Next(); State.Seed = _random.Next();
_random = new CloneableRandom(State.Seed); _random = new CloneableRandom(State.Seed);
_shuffled = Shuffle(_mediaItems, _random); _shuffled = Shuffle(_mediaItems, _random);
} while (_mediaItems.Count > 1 && Current == tail); } while (_mediaItems.Count > 1 && Current.Map(x => x.Id) == tail.Map(x => x.Id));
} }
else else
{ {

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

@ -86,7 +86,7 @@ public class MediaCollectionRepository : IMediaCollectionRepository
} }
} }
return result.Distinct().ToList(); return result.DistinctBy(x => x.Id).ToList();
} }
public async Task<List<MediaItem>> GetSmartCollectionItems(int id) public async Task<List<MediaItem>> GetSmartCollectionItems(int id)
@ -150,7 +150,7 @@ public class MediaCollectionRepository : IMediaCollectionRepository
result.AddRange(await GetSongItems(dbContext, songIds)); result.AddRange(await GetSongItems(dbContext, songIds));
} }
return result; return result.DistinctBy(x => x.Id).ToList();
} }
public async Task<List<CollectionWithItems>> GetMultiCollectionCollections(int id) public async Task<List<CollectionWithItems>> GetMultiCollectionCollections(int id)
@ -221,14 +221,14 @@ public class MediaCollectionRepository : IMediaCollectionRepository
// remove duplicate items from ungrouped collections // remove duplicate items from ungrouped collections
var toRemoveFrom = result.Filter(c => !c.ScheduleAsGroup).ToList(); var toRemoveFrom = result.Filter(c => !c.ScheduleAsGroup).ToList();
var toRemove = result.Filter(c => c.ScheduleAsGroup) var scheduleAsGroupItemIds = result.Filter(c => c.ScheduleAsGroup)
.SelectMany(c => c.MediaItems.Map(i => i.Id)) .SelectMany(c => c.MediaItems.Map(i => i.Id))
.Distinct() .Distinct()
.ToList(); .ToHashSet();
foreach (CollectionWithItems collection in toRemoveFrom) foreach (CollectionWithItems collection in toRemoveFrom)
{ {
collection.MediaItems.RemoveAll(mi => toRemove.Contains(mi.Id)); collection.MediaItems.RemoveAll(mi => scheduleAsGroupItemIds.Contains(mi.Id));
} }
return result; return result;

2
ErsatzTV.Infrastructure/Scheduling/MultiEpisodeShuffleCollectionEnumerator.cs

@ -93,7 +93,7 @@ public class MultiEpisodeShuffleCollectionEnumerator : IMediaCollectionEnumerato
State.Seed = _random.Next(); State.Seed = _random.Next();
_random = new CloneableRandom(State.Seed); _random = new CloneableRandom(State.Seed);
_shuffled = Shuffle(_random); _shuffled = Shuffle(_random);
} while (_mediaItemCount > 1 && Current == tail); } while (_mediaItemCount > 1 && Current.Map(x => x.Id) == tail.Map(x => x.Id));
} }
else else
{ {

3
ErsatzTV/Pages/Playouts.razor

@ -5,6 +5,7 @@
@implements IDisposable @implements IDisposable
@inject IDialogService _dialog @inject IDialogService _dialog
@inject IMediator _mediator @inject IMediator _mediator
@inject ChannelWriter<IBackgroundServiceRequest> WorkerChannel;
<MudContainer MaxWidth="MaxWidth.ExtraLarge" Class="pt-8"> <MudContainer MaxWidth="MaxWidth.ExtraLarge" Class="pt-8">
<MudButton Variant="Variant.Filled" Color="Color.Primary" Link="playouts/add"> <MudButton Variant="Variant.Filled" Color="Color.Primary" Link="playouts/add">
@ -176,7 +177,7 @@
private async Task ResetPlayout(PlayoutNameViewModel playout) private async Task ResetPlayout(PlayoutNameViewModel playout)
{ {
await _mediator.Send(new BuildPlayout(playout.PlayoutId, PlayoutBuildMode.Reset), _cts.Token); await WorkerChannel.WriteAsync(new BuildPlayout(playout.PlayoutId, PlayoutBuildMode.Reset), _cts.Token);
if (_table != null) if (_table != null)
{ {
await _table.ReloadServerData(); await _table.ReloadServerData();

Loading…
Cancel
Save