fix playout builds using duration or multiple (#115)
This commit is contained in:
@@ -610,6 +610,190 @@ namespace ErsatzTV.Core.Tests.Scheduling
|
|||||||
result.Items[5].MediaItemId.Should().Be(4);
|
result.Items[5].MediaItemId.Should().Be(4);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public async Task Alternating_MultipleContent_Should_Maintain_Counts()
|
||||||
|
{
|
||||||
|
var collectionOne = new Collection
|
||||||
|
{
|
||||||
|
Id = 1,
|
||||||
|
Name = "Multiple Items 1",
|
||||||
|
MediaItems = new List<MediaItem>
|
||||||
|
{
|
||||||
|
TestMovie(1, TimeSpan.FromHours(1), new DateTime(2020, 1, 1))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var collectionTwo = new Collection
|
||||||
|
{
|
||||||
|
Id = 2,
|
||||||
|
Name = "Multiple Items 2",
|
||||||
|
MediaItems = new List<MediaItem>
|
||||||
|
{
|
||||||
|
TestMovie(2, TimeSpan.FromHours(1), new DateTime(2020, 1, 1))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var fakeRepository = new FakeMediaCollectionRepository(
|
||||||
|
Map(
|
||||||
|
(collectionOne.Id, collectionOne.MediaItems.ToList()),
|
||||||
|
(collectionTwo.Id, collectionTwo.MediaItems.ToList())));
|
||||||
|
|
||||||
|
var items = new List<ProgramScheduleItem>
|
||||||
|
{
|
||||||
|
new ProgramScheduleItemMultiple
|
||||||
|
{
|
||||||
|
Id = 1,
|
||||||
|
Index = 1,
|
||||||
|
Collection = collectionOne,
|
||||||
|
CollectionId = collectionOne.Id,
|
||||||
|
StartTime = null,
|
||||||
|
Count = 3
|
||||||
|
},
|
||||||
|
new ProgramScheduleItemMultiple
|
||||||
|
{
|
||||||
|
Id = 2,
|
||||||
|
Index = 2,
|
||||||
|
Collection = collectionTwo,
|
||||||
|
CollectionId = collectionTwo.Id,
|
||||||
|
StartTime = null,
|
||||||
|
Count = 3
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var playout = new Playout
|
||||||
|
{
|
||||||
|
ProgramSchedule = new ProgramSchedule
|
||||||
|
{
|
||||||
|
Items = items,
|
||||||
|
MediaCollectionPlaybackOrder = PlaybackOrder.Chronological
|
||||||
|
},
|
||||||
|
Channel = new Channel(Guid.Empty) { Id = 1, Name = "Test Channel" },
|
||||||
|
Anchor = new PlayoutAnchor
|
||||||
|
{
|
||||||
|
NextStart = HoursAfterMidnight(1).UtcDateTime,
|
||||||
|
NextScheduleItem = items[0],
|
||||||
|
NextScheduleItemId = 1,
|
||||||
|
MultipleRemaining = 2
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var televisionRepo = new FakeTelevisionRepository();
|
||||||
|
var builder = new PlayoutBuilder(fakeRepository, televisionRepo, _logger);
|
||||||
|
|
||||||
|
DateTimeOffset start = HoursAfterMidnight(0);
|
||||||
|
DateTimeOffset finish = start + TimeSpan.FromHours(5);
|
||||||
|
|
||||||
|
Playout result = await builder.BuildPlayoutItems(playout, start, finish);
|
||||||
|
|
||||||
|
result.Items.Count.Should().Be(4);
|
||||||
|
|
||||||
|
result.Items[0].StartOffset.TimeOfDay.Should().Be(TimeSpan.FromHours(1));
|
||||||
|
result.Items[0].MediaItemId.Should().Be(1);
|
||||||
|
result.Items[1].StartOffset.TimeOfDay.Should().Be(TimeSpan.FromHours(2));
|
||||||
|
result.Items[1].MediaItemId.Should().Be(1);
|
||||||
|
|
||||||
|
result.Items[2].StartOffset.TimeOfDay.Should().Be(TimeSpan.FromHours(3));
|
||||||
|
result.Items[2].MediaItemId.Should().Be(2);
|
||||||
|
result.Items[3].StartOffset.TimeOfDay.Should().Be(TimeSpan.FromHours(4));
|
||||||
|
result.Items[3].MediaItemId.Should().Be(2);
|
||||||
|
|
||||||
|
result.Anchor.NextScheduleItem.Should().Be(items[1]);
|
||||||
|
result.Anchor.MultipleRemaining.Should().Be(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public async Task Alternating_Duration_Should_Maintain_Duration()
|
||||||
|
{
|
||||||
|
var collectionOne = new Collection
|
||||||
|
{
|
||||||
|
Id = 1,
|
||||||
|
Name = "Duration Items 1",
|
||||||
|
MediaItems = new List<MediaItem>
|
||||||
|
{
|
||||||
|
TestMovie(1, TimeSpan.FromHours(1), new DateTime(2020, 1, 1))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var collectionTwo = new Collection
|
||||||
|
{
|
||||||
|
Id = 2,
|
||||||
|
Name = "Duration Items 2",
|
||||||
|
MediaItems = new List<MediaItem>
|
||||||
|
{
|
||||||
|
TestMovie(2, TimeSpan.FromHours(1), new DateTime(2020, 1, 1))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var fakeRepository = new FakeMediaCollectionRepository(
|
||||||
|
Map(
|
||||||
|
(collectionOne.Id, collectionOne.MediaItems.ToList()),
|
||||||
|
(collectionTwo.Id, collectionTwo.MediaItems.ToList())));
|
||||||
|
|
||||||
|
var items = new List<ProgramScheduleItem>
|
||||||
|
{
|
||||||
|
new ProgramScheduleItemDuration
|
||||||
|
{
|
||||||
|
Id = 1,
|
||||||
|
Index = 1,
|
||||||
|
Collection = collectionOne,
|
||||||
|
CollectionId = collectionOne.Id,
|
||||||
|
StartTime = null,
|
||||||
|
PlayoutDuration = TimeSpan.FromHours(3),
|
||||||
|
OfflineTail = false
|
||||||
|
},
|
||||||
|
new ProgramScheduleItemDuration
|
||||||
|
{
|
||||||
|
Id = 2,
|
||||||
|
Index = 2,
|
||||||
|
Collection = collectionTwo,
|
||||||
|
CollectionId = collectionTwo.Id,
|
||||||
|
StartTime = null,
|
||||||
|
PlayoutDuration = TimeSpan.FromHours(3),
|
||||||
|
OfflineTail = false
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var playout = new Playout
|
||||||
|
{
|
||||||
|
ProgramSchedule = new ProgramSchedule
|
||||||
|
{
|
||||||
|
Items = items,
|
||||||
|
MediaCollectionPlaybackOrder = PlaybackOrder.Chronological
|
||||||
|
},
|
||||||
|
Channel = new Channel(Guid.Empty) { Id = 1, Name = "Test Channel" },
|
||||||
|
Anchor = new PlayoutAnchor
|
||||||
|
{
|
||||||
|
NextStart = HoursAfterMidnight(1).UtcDateTime,
|
||||||
|
NextScheduleItem = items[0],
|
||||||
|
NextScheduleItemId = 1,
|
||||||
|
DurationFinish = HoursAfterMidnight(3).UtcDateTime
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var televisionRepo = new FakeTelevisionRepository();
|
||||||
|
var builder = new PlayoutBuilder(fakeRepository, televisionRepo, _logger);
|
||||||
|
|
||||||
|
DateTimeOffset start = HoursAfterMidnight(0);
|
||||||
|
DateTimeOffset finish = start + TimeSpan.FromHours(5);
|
||||||
|
|
||||||
|
Playout result = await builder.BuildPlayoutItems(playout, start, finish);
|
||||||
|
|
||||||
|
result.Items.Count.Should().Be(4);
|
||||||
|
|
||||||
|
result.Items[0].StartOffset.TimeOfDay.Should().Be(TimeSpan.FromHours(1));
|
||||||
|
result.Items[0].MediaItemId.Should().Be(1);
|
||||||
|
result.Items[1].StartOffset.TimeOfDay.Should().Be(TimeSpan.FromHours(2));
|
||||||
|
result.Items[1].MediaItemId.Should().Be(1);
|
||||||
|
|
||||||
|
result.Items[2].StartOffset.TimeOfDay.Should().Be(TimeSpan.FromHours(3));
|
||||||
|
result.Items[2].MediaItemId.Should().Be(2);
|
||||||
|
result.Items[3].StartOffset.TimeOfDay.Should().Be(TimeSpan.FromHours(4));
|
||||||
|
result.Items[3].MediaItemId.Should().Be(2);
|
||||||
|
|
||||||
|
result.Anchor.NextScheduleItem.Should().Be(items[1]);
|
||||||
|
result.Anchor.DurationFinish.Should().Be(HoursAfterMidnight(6).UtcDateTime);
|
||||||
|
}
|
||||||
|
|
||||||
private static DateTimeOffset HoursAfterMidnight(int hours)
|
private static DateTimeOffset HoursAfterMidnight(int hours)
|
||||||
{
|
{
|
||||||
DateTimeOffset now = DateTimeOffset.Now;
|
DateTimeOffset now = DateTimeOffset.Now;
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using LanguageExt;
|
||||||
|
using static LanguageExt.Prelude;
|
||||||
|
|
||||||
namespace ErsatzTV.Core.Domain
|
namespace ErsatzTV.Core.Domain
|
||||||
{
|
{
|
||||||
@@ -9,7 +11,13 @@ namespace ErsatzTV.Core.Domain
|
|||||||
public ProgramScheduleItem NextScheduleItem { get; set; }
|
public ProgramScheduleItem NextScheduleItem { get; set; }
|
||||||
|
|
||||||
public DateTime NextStart { get; set; }
|
public DateTime NextStart { get; set; }
|
||||||
|
public int? MultipleRemaining { get; set; }
|
||||||
|
public DateTime? DurationFinish { get; set; }
|
||||||
|
|
||||||
public DateTimeOffset NextStartOffset => new DateTimeOffset(NextStart, TimeSpan.Zero).ToLocalTime();
|
public DateTimeOffset NextStartOffset => new DateTimeOffset(NextStart, TimeSpan.Zero).ToLocalTime();
|
||||||
|
|
||||||
|
public Option<DateTimeOffset> DurationFinishOffset =>
|
||||||
|
Optional(DurationFinish)
|
||||||
|
.Map(durationFinish => new DateTimeOffset(durationFinish, TimeSpan.Zero).ToLocalTime());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -113,7 +113,7 @@ namespace ErsatzTV.Core.Metadata
|
|||||||
existing.Plot = metadata.Plot;
|
existing.Plot = metadata.Plot;
|
||||||
existing.Tagline = metadata.Tagline;
|
existing.Tagline = metadata.Tagline;
|
||||||
existing.Title = metadata.Title;
|
existing.Title = metadata.Title;
|
||||||
|
|
||||||
if (existing.DateAdded == DateTime.MinValue)
|
if (existing.DateAdded == DateTime.MinValue)
|
||||||
{
|
{
|
||||||
existing.DateAdded = metadata.DateAdded;
|
existing.DateAdded = metadata.DateAdded;
|
||||||
@@ -149,17 +149,17 @@ namespace ErsatzTV.Core.Metadata
|
|||||||
async existing =>
|
async existing =>
|
||||||
{
|
{
|
||||||
var updated = false;
|
var updated = false;
|
||||||
|
|
||||||
existing.Outline = metadata.Outline;
|
existing.Outline = metadata.Outline;
|
||||||
existing.Plot = metadata.Plot;
|
existing.Plot = metadata.Plot;
|
||||||
existing.Tagline = metadata.Tagline;
|
existing.Tagline = metadata.Tagline;
|
||||||
existing.Title = metadata.Title;
|
existing.Title = metadata.Title;
|
||||||
|
|
||||||
if (existing.DateAdded == DateTime.MinValue)
|
if (existing.DateAdded == DateTime.MinValue)
|
||||||
{
|
{
|
||||||
existing.DateAdded = metadata.DateAdded;
|
existing.DateAdded = metadata.DateAdded;
|
||||||
}
|
}
|
||||||
|
|
||||||
existing.DateUpdated = metadata.DateUpdated;
|
existing.DateUpdated = metadata.DateUpdated;
|
||||||
existing.MetadataKind = metadata.MetadataKind;
|
existing.MetadataKind = metadata.MetadataKind;
|
||||||
existing.OriginalTitle = metadata.OriginalTitle;
|
existing.OriginalTitle = metadata.OriginalTitle;
|
||||||
@@ -254,7 +254,7 @@ namespace ErsatzTV.Core.Metadata
|
|||||||
existing.Plot = metadata.Plot;
|
existing.Plot = metadata.Plot;
|
||||||
existing.Tagline = metadata.Tagline;
|
existing.Tagline = metadata.Tagline;
|
||||||
existing.Title = metadata.Title;
|
existing.Title = metadata.Title;
|
||||||
|
|
||||||
if (existing.DateAdded == DateTime.MinValue)
|
if (existing.DateAdded == DateTime.MinValue)
|
||||||
{
|
{
|
||||||
existing.DateAdded = metadata.DateAdded;
|
existing.DateAdded = metadata.DateAdded;
|
||||||
|
|||||||
@@ -145,8 +145,10 @@ namespace ErsatzTV.Core.Scheduling
|
|||||||
// start with the previously-decided schedule item
|
// start with the previously-decided schedule item
|
||||||
int index = sortedScheduleItems.IndexOf(startAnchor.NextScheduleItem);
|
int index = sortedScheduleItems.IndexOf(startAnchor.NextScheduleItem);
|
||||||
|
|
||||||
Option<int> multipleRemaining = None;
|
// start with the previous multiple/duration states
|
||||||
Option<DateTimeOffset> durationFinish = None;
|
Option<int> multipleRemaining = Optional(startAnchor.MultipleRemaining);
|
||||||
|
Option<DateTimeOffset> durationFinish = startAnchor.DurationFinishOffset;
|
||||||
|
|
||||||
// loop until we're done filling the desired amount of time
|
// loop until we're done filling the desired amount of time
|
||||||
while (currentTime < playoutFinish)
|
while (currentTime < playoutFinish)
|
||||||
{
|
{
|
||||||
@@ -298,7 +300,9 @@ namespace ErsatzTV.Core.Scheduling
|
|||||||
{
|
{
|
||||||
NextScheduleItem = nextScheduleItem,
|
NextScheduleItem = nextScheduleItem,
|
||||||
NextScheduleItemId = nextScheduleItem.Id,
|
NextScheduleItemId = nextScheduleItem.Id,
|
||||||
NextStart = GetStartTimeAfter(nextScheduleItem, currentTime).UtcDateTime
|
NextStart = GetStartTimeAfter(nextScheduleItem, currentTime).UtcDateTime,
|
||||||
|
MultipleRemaining = multipleRemaining.IsSome ? multipleRemaining.ValueUnsafe() : null,
|
||||||
|
DurationFinish = durationFinish.IsSome ? durationFinish.ValueUnsafe().UtcDateTime : null
|
||||||
};
|
};
|
||||||
|
|
||||||
// build program schedule anchors
|
// build program schedule anchors
|
||||||
|
|||||||
Generated
+1820
File diff suppressed because it is too large
Load Diff
+34
@@ -0,0 +1,34 @@
|
|||||||
|
using System;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
namespace ErsatzTV.Infrastructure.Migrations
|
||||||
|
{
|
||||||
|
public partial class Add_PlayoutAnchor_DurationMultiple : Migration
|
||||||
|
{
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.AddColumn<DateTime>(
|
||||||
|
"Anchor_DurationFinish",
|
||||||
|
"Playout",
|
||||||
|
"TEXT",
|
||||||
|
nullable: true);
|
||||||
|
|
||||||
|
migrationBuilder.AddColumn<int>(
|
||||||
|
"Anchor_MultipleRemaining",
|
||||||
|
"Playout",
|
||||||
|
"INTEGER",
|
||||||
|
nullable: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
"Anchor_DurationFinish",
|
||||||
|
"Playout");
|
||||||
|
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
"Anchor_MultipleRemaining",
|
||||||
|
"Playout");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1412,6 +1412,12 @@ namespace ErsatzTV.Infrastructure.Migrations
|
|||||||
b1.Property<int>("PlayoutId")
|
b1.Property<int>("PlayoutId")
|
||||||
.HasColumnType("INTEGER");
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b1.Property<DateTime?>("DurationFinish")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b1.Property<int?>("MultipleRemaining")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
b1.Property<int>("NextScheduleItemId")
|
b1.Property<int>("NextScheduleItemId")
|
||||||
.HasColumnType("INTEGER");
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user