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 split song genre tags
|
||||||
- Properly display all songs that have an identical album and title
|
- Properly display all songs that have an identical album and title
|
||||||
- Fix channel logo and watermark uploads
|
- 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
|
### Added
|
||||||
- Add song genres to search index
|
- 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;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Reflection;
|
|
||||||
using ErsatzTV.Core.Domain;
|
using ErsatzTV.Core.Domain;
|
||||||
using ErsatzTV.Core.Interfaces.Scheduling;
|
using ErsatzTV.Core.Interfaces.Scheduling;
|
||||||
using LanguageExt;
|
using LanguageExt;
|
||||||
@@ -13,7 +12,7 @@ namespace ErsatzTV.Core.Scheduling
|
|||||||
{
|
{
|
||||||
private readonly int _mediaItemCount;
|
private readonly int _mediaItemCount;
|
||||||
private readonly IList<GroupedMediaItem> _mediaItems;
|
private readonly IList<GroupedMediaItem> _mediaItems;
|
||||||
private Random _random;
|
private CloneableRandom _random;
|
||||||
private IList<MediaItem> _shuffled;
|
private IList<MediaItem> _shuffled;
|
||||||
|
|
||||||
public ShuffledMediaCollectionEnumerator(
|
public ShuffledMediaCollectionEnumerator(
|
||||||
@@ -29,7 +28,7 @@ namespace ErsatzTV.Core.Scheduling
|
|||||||
state.Seed = new Random(state.Seed).Next();
|
state.Seed = new Random(state.Seed).Next();
|
||||||
}
|
}
|
||||||
|
|
||||||
_random = new Random(state.Seed);
|
_random = new CloneableRandom(state.Seed);
|
||||||
_shuffled = Shuffle(_mediaItems, _random);
|
_shuffled = Shuffle(_mediaItems, _random);
|
||||||
|
|
||||||
State = new CollectionEnumeratorState { Seed = state.Seed };
|
State = new CollectionEnumeratorState { Seed = state.Seed };
|
||||||
@@ -45,7 +44,7 @@ namespace ErsatzTV.Core.Scheduling
|
|||||||
|
|
||||||
public void MoveNext()
|
public void MoveNext()
|
||||||
{
|
{
|
||||||
if ((State.Index + 1) % _shuffled.Count == 0)
|
if ((State.Index + 1) % _mediaItemCount == 0)
|
||||||
{
|
{
|
||||||
Option<MediaItem> tail = Current;
|
Option<MediaItem> tail = Current;
|
||||||
|
|
||||||
@@ -53,7 +52,7 @@ namespace ErsatzTV.Core.Scheduling
|
|||||||
do
|
do
|
||||||
{
|
{
|
||||||
State.Seed = _random.Next();
|
State.Seed = _random.Next();
|
||||||
_random = new Random(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 == tail);
|
||||||
}
|
}
|
||||||
@@ -62,29 +61,28 @@ namespace ErsatzTV.Core.Scheduling
|
|||||||
State.Index++;
|
State.Index++;
|
||||||
}
|
}
|
||||||
|
|
||||||
State.Index %= _shuffled.Count;
|
State.Index %= _mediaItemCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Option<MediaItem> Peek(int offset)
|
public Option<MediaItem> Peek(int offset)
|
||||||
{
|
{
|
||||||
|
if (offset == 0)
|
||||||
|
{
|
||||||
|
return Current;
|
||||||
|
}
|
||||||
|
|
||||||
if ((State.Index + offset) % _mediaItemCount == 0)
|
if ((State.Index + offset) % _mediaItemCount == 0)
|
||||||
{
|
{
|
||||||
IList<MediaItem> shuffled;
|
IList<MediaItem> shuffled;
|
||||||
Option<MediaItem> tail = Current;
|
Option<MediaItem> tail = Current;
|
||||||
|
|
||||||
// clone the random
|
// clone the random
|
||||||
var randomCopy = new Random();
|
CloneableRandom randomCopy = _random.Clone();
|
||||||
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);
|
|
||||||
|
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
int newSeed = randomCopy.Next();
|
int newSeed = randomCopy.Next();
|
||||||
randomCopy = new Random(newSeed);
|
randomCopy = new CloneableRandom(newSeed);
|
||||||
shuffled = Shuffle(_mediaItems, randomCopy);
|
shuffled = Shuffle(_mediaItems, randomCopy);
|
||||||
} while (_mediaItems.Count > 1 && shuffled[0] == tail);
|
} while (_mediaItems.Count > 1 && shuffled[0] == tail);
|
||||||
|
|
||||||
@@ -94,7 +92,7 @@ namespace ErsatzTV.Core.Scheduling
|
|||||||
return _shuffled.Any() ? _shuffled[(State.Index + offset) % _mediaItemCount] : None;
|
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();
|
GroupedMediaItem[] copy = list.ToArray();
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user