Browse Source

more scheduling fixes (#732)

* fix skipping days with fixed start times

* fix playouts getting "stuck" on the same items

* rebuild all playouts

* update dependencies
pull/733/head
Jason Dove 4 years ago committed by GitHub
parent
commit
af39d93442
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      CHANGELOG.md
  2. 30
      ErsatzTV.Core.Tests/Scheduling/PlayoutBuilderTests.cs
  3. 30
      ErsatzTV.Core.Tests/Scheduling/PlayoutModeSchedulerBaseTests.cs
  4. 29
      ErsatzTV.Core/Scheduling/PlayoutBuilder.cs
  5. 2
      ErsatzTV.Core/Scheduling/PlayoutModeSchedulerBase.cs
  6. 6
      ErsatzTV.Infrastructure/ErsatzTV.Infrastructure.csproj
  7. 3891
      ErsatzTV.Infrastructure/Migrations/20220412140722_Rebuild_AllPlayouts20220412.Designer.cs
  8. 20
      ErsatzTV.Infrastructure/Migrations/20220412140722_Rebuild_AllPlayouts20220412.cs
  9. 6
      ErsatzTV/ErsatzTV.csproj

2
CHANGELOG.md

@ -7,6 +7,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
### Fixed ### Fixed
- Fix `HLS Segmenter` bug where it would drift off of the schedule if a playout was changed while the segmenter was running - Fix `HLS Segmenter` bug where it would drift off of the schedule if a playout was changed while the segmenter was running
- Ensure clients that use HDHomeRun emulation (like Plex) always get an `MPEG-TS` stream, regardless of the configured streaming mode - Ensure clients that use HDHomeRun emulation (like Plex) always get an `MPEG-TS` stream, regardless of the configured streaming mode
- Fix scheduling bug that caused some days to be skipped when fixed start times were used
### Added ### Added
- Add `Preferred Subtitle Language` and `Subtitle Mode` to channel settings - Add `Preferred Subtitle Language` and `Subtitle Mode` to channel settings
@ -25,6 +26,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- This mode is used when a schedule is updated, or when collection modifications trigger a playout rebuild - This mode is used when a schedule is updated, or when collection modifications trigger a playout rebuild
- `Reset` - this mode will rebuild the entire playout and will NOT maintain progress - `Reset` - this mode will rebuild the entire playout and will NOT maintain progress
- This mode is only used when the `Reset Playout` button is clicked on the Playouts page - This mode is only used when the `Reset Playout` button is clicked on the Playouts page
- **This requires rebuilding all playouts, which will happen on startup after upgrading**
- Use ffmpeg to resize images; this should help reduce ErsatzTV's memory use - Use ffmpeg to resize images; this should help reduce ErsatzTV's memory use
- Use ffprobe to check for animated logos and watermarks; this should help reduce ErsatzTV's memory use - Use ffprobe to check for animated logos and watermarks; this should help reduce ErsatzTV's memory use
- Allow two decimals in channel numbers (e.g. `5.73`) - Allow two decimals in channel numbers (e.g. `5.73`)

30
ErsatzTV.Core.Tests/Scheduling/PlayoutBuilderTests.cs

@ -1,4 +1,5 @@
using ErsatzTV.Core.Domain; using Destructurama;
using ErsatzTV.Core.Domain;
using ErsatzTV.Core.Domain.Filler; using ErsatzTV.Core.Domain.Filler;
using ErsatzTV.Core.Interfaces.Repositories; using ErsatzTV.Core.Interfaces.Repositories;
using ErsatzTV.Core.Scheduling; using ErsatzTV.Core.Scheduling;
@ -21,6 +22,7 @@ namespace ErsatzTV.Core.Tests.Scheduling
Log.Logger = new LoggerConfiguration() Log.Logger = new LoggerConfiguration()
.MinimumLevel.Debug() .MinimumLevel.Debug()
.WriteTo.Console() .WriteTo.Console()
.Destructure.UsingAttributes()
.CreateLogger(); .CreateLogger();
ILoggerFactory loggerFactory = new LoggerFactory().AddSerilog(Log.Logger); ILoggerFactory loggerFactory = new LoggerFactory().AddSerilog(Log.Logger);
@ -1694,6 +1696,32 @@ namespace ErsatzTV.Core.Tests.Scheduling
playout.Anchor.NextStartOffset.Should().Be(DateTime.Today.AddDays(1)); playout.Anchor.NextStartOffset.Should().Be(DateTime.Today.AddDays(1));
playout.ProgramScheduleAnchors.Count.Should().Be(1); playout.ProgramScheduleAnchors.Count.Should().Be(1);
playout.ProgramScheduleAnchors.Head().EnumeratorState.Index.Should().Be(0); playout.ProgramScheduleAnchors.Head().EnumeratorState.Index.Should().Be(0);
PlayoutProgramScheduleAnchor headAnchor = playout.ProgramScheduleAnchors.Head();
// throw in a detractor anchor - playout builder should prioritize the "continue" anchor
playout.ProgramScheduleAnchors.Insert(
0,
new PlayoutProgramScheduleAnchor
{
Id = headAnchor.Id + 1,
Collection = headAnchor.Collection,
CollectionId = headAnchor.CollectionId,
Playout = playout,
PlayoutId = playout.Id,
AnchorDate = DateTime.Today.ToUniversalTime(),
CollectionType = headAnchor.CollectionType,
EnumeratorState = new CollectionEnumeratorState
{ Index = headAnchor.EnumeratorState.Index + 1, Seed = headAnchor.EnumeratorState.Seed },
MediaItem = headAnchor.MediaItem,
MediaItemId = headAnchor.MediaItemId,
MultiCollection = headAnchor.MultiCollection,
MultiCollectionId = headAnchor.MultiCollectionId,
ProgramSchedule = headAnchor.ProgramSchedule,
ProgramScheduleId = headAnchor.ProgramScheduleId,
SmartCollection = headAnchor.SmartCollection,
SmartCollectionId = headAnchor.SmartCollectionId
});
// continue 1h later // continue 1h later
DateTimeOffset start2 = HoursAfterMidnight(1); DateTimeOffset start2 = HoursAfterMidnight(1);

30
ErsatzTV.Core.Tests/Scheduling/PlayoutModeSchedulerBaseTests.cs

@ -3,7 +3,6 @@ using ErsatzTV.Core.Domain.Filler;
using ErsatzTV.Core.Interfaces.Scheduling; using ErsatzTV.Core.Interfaces.Scheduling;
using ErsatzTV.Core.Scheduling; using ErsatzTV.Core.Scheduling;
using FluentAssertions; using FluentAssertions;
using Microsoft.Extensions.Logging;
using Moq; using Moq;
using NUnit.Framework; using NUnit.Framework;
@ -286,6 +285,35 @@ public class PlayoutModeSchedulerBaseTests : SchedulerTestBase
} }
} }
[TestFixture]
public class GetStartTimeAfter
{
[Test]
public void Should_Compare_Time_As_Local_Time()
{
var enumerator = new Mock<IScheduleItemsEnumerator>();
var state = new PlayoutBuilderState(
enumerator.Object,
None,
None,
false,
false,
0,
DateTime.Today.AddHours(6).ToUniversalTime());
var scheduleItem = new ProgramScheduleItemOne
{
StartTime = TimeSpan.FromHours(6)
};
DateTimeOffset result =
PlayoutModeSchedulerBase<ProgramScheduleItem>.GetStartTimeAfter(state, scheduleItem);
result.Should().Be(DateTime.Today.AddHours(6));
}
}
private static Movie TestMovie(int id, TimeSpan duration, DateTime aired) => private static Movie TestMovie(int id, TimeSpan duration, DateTime aired) =>
new() new()
{ {

29
ErsatzTV.Core/Scheduling/PlayoutBuilder.cs

@ -205,6 +205,12 @@ public class PlayoutBuilder : IPlayoutBuilder
playout.Id, playout.Id,
playout.Channel.Number, playout.Channel.Number,
playout.Channel.Name); playout.Channel.Name);
// remove old checkpoints
playout.ProgramScheduleAnchors.RemoveAll(
a => a.AnchorDateOffset.IfNone(SystemTime.MaxValueUtc) < parameters.Start.Date);
// _logger.LogDebug("Remaining anchors: {@Anchors}", playout.ProgramScheduleAnchors);
await BuildPlayoutItems( await BuildPlayoutItems(
playout, playout,
@ -285,7 +291,7 @@ public class PlayoutBuilder : IPlayoutBuilder
// playoutFinish); // playoutFinish);
// build each day with "continue" anchors // build each day with "continue" anchors
while (finish <= playoutFinish) while (finish < playoutFinish)
{ {
_logger.LogDebug("Building playout from {Start} to {Finish}", start, finish); _logger.LogDebug("Building playout from {Start} to {Finish}", start, finish);
playout = await BuildPlayoutItems(playout, start, finish, collectionMediaItems, true); playout = await BuildPlayoutItems(playout, start, finish, collectionMediaItems, true);
@ -632,13 +638,20 @@ public class PlayoutBuilder : IPlayoutBuilder
List<MediaItem> mediaItems, List<MediaItem> mediaItems,
PlaybackOrder playbackOrder) PlaybackOrder playbackOrder)
{ {
Option<PlayoutProgramScheduleAnchor> maybeAnchor = playout.ProgramScheduleAnchors.FirstOrDefault( Option<PlayoutProgramScheduleAnchor> maybeAnchor = playout.ProgramScheduleAnchors
a => a.ProgramScheduleId == playout.ProgramScheduleId .OrderByDescending(a => a.AnchorDate is null)
&& a.CollectionType == collectionKey.CollectionType .FirstOrDefault(
&& a.CollectionId == collectionKey.CollectionId a => a.ProgramScheduleId == playout.ProgramScheduleId
&& a.MultiCollectionId == collectionKey.MultiCollectionId && a.CollectionType == collectionKey.CollectionType
&& a.SmartCollectionId == collectionKey.SmartCollectionId && a.CollectionId == collectionKey.CollectionId
&& a.MediaItemId == collectionKey.MediaItemId); && a.MultiCollectionId == collectionKey.MultiCollectionId
&& a.SmartCollectionId == collectionKey.SmartCollectionId
&& a.MediaItemId == collectionKey.MediaItemId);
foreach (PlayoutProgramScheduleAnchor anchor in maybeAnchor)
{
_logger.LogDebug("Selecting anchor {@Anchor}", anchor);
}
CollectionEnumeratorState state = maybeAnchor.Match( CollectionEnumeratorState state = maybeAnchor.Match(
anchor => anchor.EnumeratorState ?? anchor => anchor.EnumeratorState ??

2
ErsatzTV.Core/Scheduling/PlayoutModeSchedulerBase.cs

@ -20,7 +20,7 @@ public abstract class PlayoutModeSchedulerBase<T> : IPlayoutModeScheduler<T> whe
PlayoutBuilderState state, PlayoutBuilderState state,
ProgramScheduleItem scheduleItem) ProgramScheduleItem scheduleItem)
{ {
DateTimeOffset startTime = state.CurrentTime; DateTimeOffset startTime = state.CurrentTime.ToLocalTime();
bool isIncomplete = scheduleItem is ProgramScheduleItemMultiple && state.MultipleRemaining.IsSome || bool isIncomplete = scheduleItem is ProgramScheduleItemMultiple && state.MultipleRemaining.IsSome ||
scheduleItem is ProgramScheduleItemDuration && state.DurationFinish.IsSome || scheduleItem is ProgramScheduleItemDuration && state.DurationFinish.IsSome ||

6
ErsatzTV.Infrastructure/ErsatzTV.Infrastructure.csproj

@ -14,12 +14,12 @@
<PackageReference Include="Lucene.Net" Version="4.8.0-beta00016" /> <PackageReference Include="Lucene.Net" Version="4.8.0-beta00016" />
<PackageReference Include="Lucene.Net.Analysis.Common" Version="4.8.0-beta00016" /> <PackageReference Include="Lucene.Net.Analysis.Common" Version="4.8.0-beta00016" />
<PackageReference Include="Lucene.Net.QueryParser" Version="4.8.0-beta00016" /> <PackageReference Include="Lucene.Net.QueryParser" Version="4.8.0-beta00016" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="6.0.3" /> <PackageReference Include="Microsoft.EntityFrameworkCore" Version="6.0.4" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="6.0.3"> <PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="6.0.4">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="6.0.3" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="6.0.4" />
<PackageReference Include="Microsoft.VisualStudio.Threading.Analyzers" Version="17.1.46"> <PackageReference Include="Microsoft.VisualStudio.Threading.Analyzers" Version="17.1.46">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>

3891
ErsatzTV.Infrastructure/Migrations/20220412140722_Rebuild_AllPlayouts20220412.Designer.cs generated

File diff suppressed because it is too large Load Diff

20
ErsatzTV.Infrastructure/Migrations/20220412140722_Rebuild_AllPlayouts20220412.cs

@ -0,0 +1,20 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace ErsatzTV.Infrastructure.Migrations
{
public partial class Rebuild_AllPlayouts20220412 : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.Sql(@"DELETE FROM PlayoutItem");
migrationBuilder.Sql(@"DELETE FROM PlayoutProgramScheduleAnchor");
migrationBuilder.Sql(@"DELETE FROM PlayoutAnchor");
}
protected override void Down(MigrationBuilder migrationBuilder)
{
}
}
}

6
ErsatzTV/ErsatzTV.csproj

@ -62,9 +62,9 @@
<PackageReference Include="Markdig" Version="0.28.1" /> <PackageReference Include="Markdig" Version="0.28.1" />
<PackageReference Include="MediatR.Courier.DependencyInjection" Version="5.0.0" /> <PackageReference Include="MediatR.Courier.DependencyInjection" Version="5.0.0" />
<PackageReference Include="MediatR.Extensions.Microsoft.DependencyInjection" Version="10.0.1" /> <PackageReference Include="MediatR.Extensions.Microsoft.DependencyInjection" Version="10.0.1" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="6.0.3" /> <PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="6.0.4" />
<PackageReference Include="Microsoft.AspNetCore.SpaServices.Extensions" Version="6.0.3" /> <PackageReference Include="Microsoft.AspNetCore.SpaServices.Extensions" Version="6.0.4" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="6.0.3"> <PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="6.0.4">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>

Loading…
Cancel
Save