fix filler playout crash (#517)
This commit is contained in:
@@ -12,6 +12,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||
- Properly split song genre tags
|
||||
- Properly display all songs that have an identical album and title
|
||||
- Fix channel logo and watermark uploads
|
||||
- Fix regression introduced with `v0.2.4-alpha` that caused some filler edge cases to crash the playout builder
|
||||
|
||||
### Added
|
||||
- Add song genres to search index
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
using System.Collections.Generic;
|
||||
using ErsatzTV.Core.Domain;
|
||||
using ErsatzTV.Core.Scheduling;
|
||||
using FluentAssertions;
|
||||
using LanguageExt;
|
||||
using LanguageExt.UnsafeValueAccess;
|
||||
using NUnit.Framework;
|
||||
|
||||
namespace ErsatzTV.Core.Tests.Scheduling
|
||||
{
|
||||
[TestFixture]
|
||||
public class ShuffledMediaCollectionEnumeratorTests
|
||||
{
|
||||
private readonly List<GroupedMediaItem> _mediaItems = new()
|
||||
{
|
||||
new GroupedMediaItem(new MediaItem { Id = 1 }, new List<MediaItem>()),
|
||||
new GroupedMediaItem(new MediaItem { Id = 2 }, new List<MediaItem>()),
|
||||
new GroupedMediaItem(new MediaItem { Id = 3 }, new List<MediaItem>())
|
||||
};
|
||||
|
||||
[Test]
|
||||
public void Peek_Zero_Should_Match_Current()
|
||||
{
|
||||
var state = new CollectionEnumeratorState { Index = 0, Seed = 0 };
|
||||
var enumerator = new ShuffledMediaCollectionEnumerator(_mediaItems, state);
|
||||
|
||||
Option<MediaItem> peek = enumerator.Peek(0);
|
||||
Option<MediaItem> current = enumerator.Current;
|
||||
|
||||
peek.IsSome.Should().BeTrue();
|
||||
current.IsSome.Should().BeTrue();
|
||||
peek.ValueUnsafe().Id.Should().Be(1);
|
||||
current.ValueUnsafe().Id.Should().Be(1);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Peek_One_Should_Match_Next()
|
||||
{
|
||||
var state = new CollectionEnumeratorState { Index = 0, Seed = 0 };
|
||||
var enumerator = new ShuffledMediaCollectionEnumerator(_mediaItems, state);
|
||||
|
||||
Option<MediaItem> peek = enumerator.Peek(1);
|
||||
|
||||
enumerator.MoveNext();
|
||||
Option<MediaItem> next = enumerator.Current;
|
||||
|
||||
peek.IsSome.Should().BeTrue();
|
||||
next.IsSome.Should().BeTrue();
|
||||
peek.ValueUnsafe().Id.Should().Be(2);
|
||||
next.ValueUnsafe().Id.Should().Be(2);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Peek_Two_Should_Match_NextNext()
|
||||
{
|
||||
var state = new CollectionEnumeratorState { Index = 0, Seed = 0 };
|
||||
var enumerator = new ShuffledMediaCollectionEnumerator(_mediaItems, state);
|
||||
|
||||
Option<MediaItem> peek = enumerator.Peek(2);
|
||||
|
||||
enumerator.MoveNext();
|
||||
enumerator.MoveNext();
|
||||
Option<MediaItem> next = enumerator.Current;
|
||||
|
||||
peek.IsSome.Should().BeTrue();
|
||||
next.IsSome.Should().BeTrue();
|
||||
peek.ValueUnsafe().Id.Should().Be(3);
|
||||
next.ValueUnsafe().Id.Should().Be(3);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Peek_Three_Should_Match_NextNextNext()
|
||||
{
|
||||
var state = new CollectionEnumeratorState { Index = 0, Seed = 0 };
|
||||
var enumerator = new ShuffledMediaCollectionEnumerator(_mediaItems, state);
|
||||
|
||||
Option<MediaItem> peek = enumerator.Peek(3);
|
||||
|
||||
enumerator.MoveNext();
|
||||
enumerator.MoveNext();
|
||||
enumerator.MoveNext();
|
||||
Option<MediaItem> next = enumerator.Current;
|
||||
|
||||
peek.IsSome.Should().BeTrue();
|
||||
next.IsSome.Should().BeTrue();
|
||||
peek.ValueUnsafe().Id.Should().Be(2);
|
||||
next.ValueUnsafe().Id.Should().Be(2);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
using System;
|
||||
|
||||
namespace ErsatzTV.Core.Scheduling
|
||||
{
|
||||
public class CloneableRandom
|
||||
{
|
||||
private readonly int _seed;
|
||||
private readonly Random _random;
|
||||
private int _count;
|
||||
|
||||
public CloneableRandom(int seed)
|
||||
{
|
||||
_seed = seed;
|
||||
_random = new Random(_seed);
|
||||
}
|
||||
|
||||
public CloneableRandom Clone()
|
||||
{
|
||||
var clone = new CloneableRandom(_seed);
|
||||
|
||||
for (var i = 0; i < _count; i++)
|
||||
{
|
||||
clone.Next();
|
||||
}
|
||||
|
||||
return clone;
|
||||
}
|
||||
|
||||
public int Next()
|
||||
{
|
||||
_count++;
|
||||
return _random.Next();
|
||||
}
|
||||
|
||||
public int Next(int maxValue)
|
||||
{
|
||||
_count++;
|
||||
return _random.Next(maxValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using ErsatzTV.Core.Domain;
|
||||
using ErsatzTV.Core.Interfaces.Scheduling;
|
||||
using LanguageExt;
|
||||
@@ -13,7 +12,7 @@ namespace ErsatzTV.Core.Scheduling
|
||||
{
|
||||
private readonly int _mediaItemCount;
|
||||
private readonly IList<GroupedMediaItem> _mediaItems;
|
||||
private Random _random;
|
||||
private CloneableRandom _random;
|
||||
private IList<MediaItem> _shuffled;
|
||||
|
||||
public ShuffledMediaCollectionEnumerator(
|
||||
@@ -29,7 +28,7 @@ namespace ErsatzTV.Core.Scheduling
|
||||
state.Seed = new Random(state.Seed).Next();
|
||||
}
|
||||
|
||||
_random = new Random(state.Seed);
|
||||
_random = new CloneableRandom(state.Seed);
|
||||
_shuffled = Shuffle(_mediaItems, _random);
|
||||
|
||||
State = new CollectionEnumeratorState { Seed = state.Seed };
|
||||
@@ -45,7 +44,7 @@ namespace ErsatzTV.Core.Scheduling
|
||||
|
||||
public void MoveNext()
|
||||
{
|
||||
if ((State.Index + 1) % _shuffled.Count == 0)
|
||||
if ((State.Index + 1) % _mediaItemCount == 0)
|
||||
{
|
||||
Option<MediaItem> tail = Current;
|
||||
|
||||
@@ -53,7 +52,7 @@ namespace ErsatzTV.Core.Scheduling
|
||||
do
|
||||
{
|
||||
State.Seed = _random.Next();
|
||||
_random = new Random(State.Seed);
|
||||
_random = new CloneableRandom(State.Seed);
|
||||
_shuffled = Shuffle(_mediaItems, _random);
|
||||
} while (_mediaItems.Count > 1 && Current == tail);
|
||||
}
|
||||
@@ -62,29 +61,28 @@ namespace ErsatzTV.Core.Scheduling
|
||||
State.Index++;
|
||||
}
|
||||
|
||||
State.Index %= _shuffled.Count;
|
||||
State.Index %= _mediaItemCount;
|
||||
}
|
||||
|
||||
public Option<MediaItem> Peek(int offset)
|
||||
{
|
||||
if (offset == 0)
|
||||
{
|
||||
return Current;
|
||||
}
|
||||
|
||||
if ((State.Index + offset) % _mediaItemCount == 0)
|
||||
{
|
||||
IList<MediaItem> shuffled;
|
||||
Option<MediaItem> tail = Current;
|
||||
|
||||
// clone the random
|
||||
var randomCopy = new Random();
|
||||
FieldInfo seedArrayInfo = typeof(Random).GetField(
|
||||
"_seedArray",
|
||||
BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
var seedArray = seedArrayInfo.GetValue(_random) as int[];
|
||||
int[] seedArrayCopy = seedArray.ToArray();
|
||||
seedArrayInfo.SetValue(randomCopy, seedArrayCopy);
|
||||
CloneableRandom randomCopy = _random.Clone();
|
||||
|
||||
do
|
||||
{
|
||||
int newSeed = randomCopy.Next();
|
||||
randomCopy = new Random(newSeed);
|
||||
randomCopy = new CloneableRandom(newSeed);
|
||||
shuffled = Shuffle(_mediaItems, randomCopy);
|
||||
} while (_mediaItems.Count > 1 && shuffled[0] == tail);
|
||||
|
||||
@@ -94,7 +92,7 @@ namespace ErsatzTV.Core.Scheduling
|
||||
return _shuffled.Any() ? _shuffled[(State.Index + offset) % _mediaItemCount] : None;
|
||||
}
|
||||
|
||||
private IList<MediaItem> Shuffle(IEnumerable<GroupedMediaItem> list, Random random)
|
||||
private IList<MediaItem> Shuffle(IEnumerable<GroupedMediaItem> list, CloneableRandom random)
|
||||
{
|
||||
GroupedMediaItem[] copy = list.ToArray();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user