Browse Source

fix filler playout crash (#517)

pull/518/head
Jason Dove 4 years ago committed by GitHub
parent
commit
56f94f489a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      CHANGELOG.md
  2. 90
      ErsatzTV.Core.Tests/Scheduling/ShuffledMediaCollectionEnumeratorTests.cs
  3. 41
      ErsatzTV.Core/Scheduling/CloneableRandom.cs
  4. 30
      ErsatzTV.Core/Scheduling/ShuffledMediaCollectionEnumerator.cs

1
CHANGELOG.md

@ -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

90
ErsatzTV.Core.Tests/Scheduling/ShuffledMediaCollectionEnumeratorTests.cs

@ -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);
}
}
}

41
ErsatzTV.Core/Scheduling/CloneableRandom.cs

@ -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);
}
}
}

30
ErsatzTV.Core/Scheduling/ShuffledMediaCollectionEnumerator.cs

@ -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();

Loading…
Cancel
Save