Browse Source

fix overlapping block playout items (#2302)

* check for overlapping playout items

* tweak block filler builder

* fix overlapping block playout items

* update changelog

* minor cleanup
pull/2303/head
Jason Dove 5 months ago committed by GitHub
parent
commit
771bfba01c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 3
      CHANGELOG.md
  2. 4
      ErsatzTV.Application/Playouts/Commands/BuildPlayoutHandler.cs
  3. 3
      ErsatzTV.Application/Playouts/Queries/CheckForOverlappingPlayoutItems.cs
  4. 41
      ErsatzTV.Application/Playouts/Queries/CheckForOverlappingPlayoutItemsHandler.cs
  5. 2
      ErsatzTV.Application/Scheduling/Commands/PreviewBlockPlayoutHandler.cs
  6. 89
      ErsatzTV.Core/Scheduling/BlockScheduling/BlockPlayoutBuilder.cs
  7. 9
      ErsatzTV.Core/Scheduling/BlockScheduling/BlockPlayoutChangeDetection.cs
  8. 31
      ErsatzTV.Core/Scheduling/BlockScheduling/BlockPlayoutFillerBuilder.cs
  9. 4
      ErsatzTV.Core/Scheduling/PlayoutBuildResult.cs
  10. 6335
      ErsatzTV.Infrastructure.MySql/Migrations/20250812005232_Add_PlayoutItemStartFinishIndex.Designer.cs
  11. 27
      ErsatzTV.Infrastructure.MySql/Migrations/20250812005232_Add_PlayoutItemStartFinishIndex.cs
  12. 5
      ErsatzTV.Infrastructure.MySql/Migrations/TvContextModelSnapshot.cs
  13. 6170
      ErsatzTV.Infrastructure.Sqlite/Migrations/20250812005134_Add_PlayoutItemStartFinishIndex.Designer.cs
  14. 27
      ErsatzTV.Infrastructure.Sqlite/Migrations/20250812005134_Add_PlayoutItemStartFinishIndex.cs
  15. 5
      ErsatzTV.Infrastructure.Sqlite/Migrations/TvContextModelSnapshot.cs
  16. 3
      ErsatzTV.Infrastructure/Data/Configurations/PlayoutItemConfiguration.cs
  17. 12
      ErsatzTV/Pages/BlockEditor.razor
  18. 11
      ErsatzTV/Services/SchedulerService.cs
  19. 3
      ErsatzTV/Services/WorkerService.cs

3
CHANGELOG.md

@ -44,6 +44,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). @@ -44,6 +44,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Fix green output when padding with VAAPI accel and i965 driver
- Fix watermark custom image validation
- Fix playback when using any watermarks that were saved with invalid state (no image)
- Fix overlapping block playout items caused by `Stop scheduling block items` value `After Duration End`
- Existing overlapping items will not be removed, but no new overlapping items will be created
- Until these existing items age out, there will be warnings logged after each playout build/extension
### Changed
- Allow multiple watermarks in playback troubleshooting

4
ErsatzTV.Application/Playouts/Commands/BuildPlayoutHandler.cs

@ -228,6 +228,10 @@ public class BuildPlayoutHandler : IRequestHandler<BuildPlayout, Either<BaseErro @@ -228,6 +228,10 @@ public class BuildPlayoutHandler : IRequestHandler<BuildPlayout, Either<BaseErro
_ffmpegSegmenterService.PlayoutUpdated(referenceData.Channel.Number);
}
await _workerChannel.WriteAsync(
new CheckForOverlappingPlayoutItems(request.PlayoutId),
cancellationToken);
string fileName = Path.Combine(FileSystemLayout.ChannelGuideCacheFolder, $"{channelNumber}.xml");
if (hasChanges || !File.Exists(fileName) ||
playout.ProgramSchedulePlayoutType is ProgramSchedulePlayoutType.ExternalJson)

3
ErsatzTV.Application/Playouts/Queries/CheckForOverlappingPlayoutItems.cs

@ -0,0 +1,3 @@ @@ -0,0 +1,3 @@
namespace ErsatzTV.Application.Playouts;
public record CheckForOverlappingPlayoutItems(int PlayoutId) : IRequest, IBackgroundServiceRequest;

41
ErsatzTV.Application/Playouts/Queries/CheckForOverlappingPlayoutItemsHandler.cs

@ -0,0 +1,41 @@ @@ -0,0 +1,41 @@
using ErsatzTV.Infrastructure.Data;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
namespace ErsatzTV.Application.Playouts;
public class CheckForOverlappingPlayoutItemsHandler(
IDbContextFactory<TvContext> dbContextFactory,
ILogger<CheckForOverlappingPlayoutItemsHandler> logger)
: IRequestHandler<CheckForOverlappingPlayoutItems>
{
public async Task Handle(CheckForOverlappingPlayoutItems request, CancellationToken cancellationToken)
{
await using TvContext dbContext = await dbContextFactory.CreateDbContextAsync(cancellationToken);
bool hasConflict = await dbContext.PlayoutItems
.Where(pi => pi.PlayoutId == request.PlayoutId)
.AnyAsync(a => dbContext.PlayoutItems
.Any(b =>
a.Id < b.Id &&
a.Start < b.Finish &&
a.Finish > b.Start),
cancellationToken);
if (hasConflict)
{
var maybeChannel = await dbContext.Channels
.AsNoTracking()
.Where(c => c.Playouts.Any(p => p.Id == request.PlayoutId))
.FirstOrDefaultAsync(cancellationToken)
.Map(Optional);
foreach (var channel in maybeChannel)
{
logger.LogWarning(
"Playout for channel {ChannelName} has overlapping playout items; this may be a bug.",
channel.Name);
}
}
}
}

2
ErsatzTV.Application/Scheduling/Commands/PreviewBlockPlayoutHandler.cs

@ -103,7 +103,7 @@ public class PreviewBlockPlayoutHandler( @@ -103,7 +103,7 @@ public class PreviewBlockPlayoutHandler(
}
}
return playout.Items.Map(Mapper.ProjectToViewModel).ToList();
return result.AddedItems.Map(Mapper.ProjectToViewModel).ToList();
}
private static Block MapToBlock(ReplaceBlockItems request) =>

89
ErsatzTV.Core/Scheduling/BlockScheduling/BlockPlayoutBuilder.cs

@ -35,7 +35,7 @@ public class BlockPlayoutBuilder( @@ -35,7 +35,7 @@ public class BlockPlayoutBuilder(
{
var result = PlayoutBuildResult.Empty;
Logger.LogDebug(
logger.LogDebug(
"Building block playout {PlayoutId} for channel {ChannelNumber} - {ChannelName}",
playout.Id,
referenceData.Channel.Number,
@ -90,9 +90,12 @@ public class BlockPlayoutBuilder( @@ -90,9 +90,12 @@ public class BlockPlayoutBuilder(
foreach (PlayoutItem playoutItem in playoutItemsToRemove)
{
result = BlockPlayoutChangeDetection.RemoveItemAndHistory(playout, playoutItem, result);
BlockPlayoutChangeDetection.RemoveItemAndHistory(referenceData, playoutItem, result);
}
var playoutItemsToRemoveIds = playoutItemsToRemove.Select(i => i.Id).ToHashSet();
var baseItems = referenceData.ExistingItems.Where(i => !playoutItemsToRemoveIds.Contains(i.Id)).ToList();
DateTimeOffset currentTime = start;
if (updatedEffectiveBlocks.Count > 0)
{
@ -101,23 +104,37 @@ public class BlockPlayoutBuilder( @@ -101,23 +104,37 @@ public class BlockPlayoutBuilder(
foreach (EffectiveBlock effectiveBlock in updatedEffectiveBlocks)
{
DateTimeOffset maxExistingFinish = baseItems
.Where(i => i.Start < effectiveBlock.Start.UtcDateTime)
.Select(i => i.FinishOffset)
.DefaultIfEmpty(DateTimeOffset.MinValue)
.Max();
if (currentTime < effectiveBlock.Start)
{
currentTime = effectiveBlock.Start;
}
Logger.LogDebug(
"Will schedule block {Block} at {Start}",
effectiveBlock.Block.Name,
effectiveBlock.Start);
if (currentTime < maxExistingFinish)
{
currentTime = maxExistingFinish;
}
else
if (currentTime > effectiveBlock.Start)
{
Logger.LogDebug(
logger.LogDebug(
"Will schedule block {Block} with start {Start} at {ActualStart}",
effectiveBlock.Block.Name,
effectiveBlock.Start,
currentTime);
}
else
{
logger.LogDebug(
"Will schedule block {Block} at {Start}",
effectiveBlock.Block.Name,
effectiveBlock.Start);
}
DateTimeOffset blockFinish = effectiveBlock.Start.AddMinutes(effectiveBlock.Block.Minutes);
@ -131,7 +148,7 @@ public class BlockPlayoutBuilder( @@ -131,7 +148,7 @@ public class BlockPlayoutBuilder(
if (currentTime >= blockFinish)
{
Logger.LogDebug(
logger.LogDebug(
"Current time {Time} for block {Block} is beyond block finish {Finish}; will stop with this block's items",
currentTime,
effectiveBlock.Block.Name,
@ -157,7 +174,7 @@ public class BlockPlayoutBuilder( @@ -157,7 +174,7 @@ public class BlockPlayoutBuilder(
foreach (MediaItem mediaItem in enumerator.Current)
{
Logger.LogDebug(
logger.LogDebug(
"current item: {Id} / {Title}",
mediaItem.Id,
mediaItem is Episode e ? GetTitle(e) : string.Empty);
@ -194,7 +211,7 @@ public class BlockPlayoutBuilder( @@ -194,7 +211,7 @@ public class BlockPlayoutBuilder(
if (effectiveBlock.Block.StopScheduling is BlockStopScheduling.BeforeDurationEnd
&& playoutItem.FinishOffset > blockFinish)
{
Logger.LogDebug(
logger.LogDebug(
"Current time {Time} for block {Block} would go beyond block finish {Finish}; will not schedule more items",
currentTime,
effectiveBlock.Block.Name,
@ -263,14 +280,14 @@ public class BlockPlayoutBuilder( @@ -263,14 +280,14 @@ public class BlockPlayoutBuilder(
referenceData.PlayoutHistory.Append(result.AddedHistory).ToList(),
blockItem,
historyKey,
Logger),
logger),
PlaybackOrder.SeasonEpisode => BlockPlayoutEnumerator.SeasonEpisode(
collectionItems,
currentTime,
referenceData.PlayoutHistory.Append(result.AddedHistory).ToList(),
blockItem,
historyKey,
Logger),
logger),
PlaybackOrder.Shuffle => BlockPlayoutEnumerator.Shuffle(
collectionItems,
currentTime,
@ -312,39 +329,31 @@ public class BlockPlayoutBuilder( @@ -312,39 +329,31 @@ public class BlockPlayoutBuilder(
private static PlayoutBuildResult CleanUpHistory(PlayoutReferenceData referenceData, DateTimeOffset start, PlayoutBuildResult result)
{
var groups = new Dictionary<string, List<PlayoutHistory>>();
foreach (PlayoutHistory history in referenceData.PlayoutHistory.Append(result.AddedHistory))
var allItemsToDelete = referenceData.PlayoutHistory
.Append(result.AddedHistory)
.GroupBy(h => (h.BlockId, h.Key))
.SelectMany(group => group
.Filter(h => h.When < start.UtcDateTime)
.OrderByDescending(h => h.When)
.Tail());
var addedToRemove = new System.Collections.Generic.HashSet<PlayoutHistory>();
foreach (PlayoutHistory delete in allItemsToDelete)
{
var key = $"{history.BlockId}-{history.Key}";
if (!groups.TryGetValue(key, out List<PlayoutHistory> group))
if (delete.Id > 0)
{
group = [];
groups[key] = group;
result.HistoryToRemove.Add(delete.Id);
}
else
{
addedToRemove.Add(delete);
}
group.Add(history);
}
foreach ((string _, List<PlayoutHistory> group) in groups)
if (addedToRemove.Count > 0)
{
//logger.LogDebug("History key {Key} has {Count} items in group", key, group.Count);
IEnumerable<PlayoutHistory> toDelete = group
.Filter(h => h.When < start.UtcDateTime)
.OrderByDescending(h => h.When)
.Tail();
foreach (PlayoutHistory delete in toDelete)
{
if (delete.Id > 0)
{
result.HistoryToRemove.Add(delete.Id);
}
else
{
result.AddedHistory.Remove(delete);
}
}
result.AddedHistory.RemoveAll(addedToRemove.Contains);
}
return result;

9
ErsatzTV.Core/Scheduling/BlockScheduling/BlockPlayoutChangeDetection.cs

@ -146,18 +146,19 @@ internal static class BlockPlayoutChangeDetection @@ -146,18 +146,19 @@ internal static class BlockPlayoutChangeDetection
return Tuple(updatedBlocks.ToList(), playoutItems.Filter(i => updatedItemIds.Contains(i.Id)).ToList());
}
public static PlayoutBuildResult RemoveItemAndHistory(Playout playout, PlayoutItem playoutItem, PlayoutBuildResult result)
public static void RemoveItemAndHistory(
PlayoutReferenceData referenceData,
PlayoutItem playoutItem,
PlayoutBuildResult result)
{
result.ItemsToRemove.Add(playoutItem.Id);
Option<PlayoutHistory> historyToRemove = playout.PlayoutHistory
Option<PlayoutHistory> historyToRemove = referenceData.PlayoutHistory
.Find(h => h.When == playoutItem.Start);
foreach (PlayoutHistory history in historyToRemove)
{
result.HistoryToRemove.Add(history.Id);
}
return result;
}
}

31
ErsatzTV.Core/Scheduling/BlockScheduling/BlockPlayoutFillerBuilder.cs

@ -27,25 +27,40 @@ public class BlockPlayoutFillerBuilder( @@ -27,25 +27,40 @@ public class BlockPlayoutFillerBuilder(
PlayoutBuildMode mode,
CancellationToken cancellationToken)
{
var allItems = referenceData.ExistingItems.Append(result.AddedItems).ToList();
var filteredExistingItems = referenceData.ExistingItems
.Where(i => !result.ItemsToRemove.Contains(i.Id))
.ToList();
var filteredExistingHistory = referenceData.PlayoutHistory
.Where(h => !result.HistoryToRemove.Contains(h.Id))
.ToList();
var allItems = result.AddedItems.ToList();
if (mode is PlayoutBuildMode.Reset)
{
// remove all playout items with type filler
// except block items that are hidden from the guide (guide mode)
var toRemove = allItems
.Where(pi => pi.FillerKind is not FillerKind.None and not FillerKind.GuideMode)
.ToList();
foreach (PlayoutItem playoutItem in toRemove)
foreach (var item in filteredExistingItems)
{
result = BlockPlayoutChangeDetection.RemoveItemAndHistory(playout, playoutItem, result);
if (item.FillerKind is FillerKind.None or FillerKind.GuideMode)
{
allItems.Add(item);
continue;
}
BlockPlayoutChangeDetection.RemoveItemAndHistory(referenceData, item, result);
}
}
else
{
allItems.AddRange(filteredExistingItems);
}
var collectionEnumerators = new Dictionary<CollectionKey, IMediaCollectionEnumerator>();
// find all unscheduled periods
var queue = new Queue<PlayoutItem>(allItems);
var queue = new Queue<PlayoutItem>(allItems.OrderBy(i => i.Start));
while (queue.Count > 1)
{
PlayoutItem one = queue.Dequeue();
@ -83,7 +98,7 @@ public class BlockPlayoutFillerBuilder( @@ -83,7 +98,7 @@ public class BlockPlayoutFillerBuilder(
collectionItems,
start,
playout.Seed,
referenceData.PlayoutHistory.Append(result.AddedHistory).ToList(),
filteredExistingHistory.Append(result.AddedHistory).ToList(),
deco,
historyKey);

4
ErsatzTV.Core/Scheduling/PlayoutBuildResult.cs

@ -8,9 +8,9 @@ public record PlayoutBuildResult( @@ -8,9 +8,9 @@ public record PlayoutBuildResult(
Option<DateTimeOffset> RemoveBefore,
Option<DateTimeOffset> RemoveAfter,
List<PlayoutItem> AddedItems,
List<int> ItemsToRemove,
System.Collections.Generic.HashSet<int> ItemsToRemove,
List<PlayoutHistory> AddedHistory,
List<int> HistoryToRemove,
System.Collections.Generic.HashSet<int> HistoryToRemove,
Option<DateTimeOffset> TimeShiftTo)
{
public static PlayoutBuildResult Empty =>

6335
ErsatzTV.Infrastructure.MySql/Migrations/20250812005232_Add_PlayoutItemStartFinishIndex.Designer.cs generated

File diff suppressed because it is too large Load Diff

27
ErsatzTV.Infrastructure.MySql/Migrations/20250812005232_Add_PlayoutItemStartFinishIndex.cs

@ -0,0 +1,27 @@ @@ -0,0 +1,27 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace ErsatzTV.Infrastructure.MySql.Migrations
{
/// <inheritdoc />
public partial class Add_PlayoutItemStartFinishIndex : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateIndex(
name: "IX_PlayoutItem_Start_Finish",
table: "PlayoutItem",
columns: new[] { "Start", "Finish" });
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropIndex(
name: "IX_PlayoutItem_Start_Finish",
table: "PlayoutItem");
}
}
}

5
ErsatzTV.Infrastructure.MySql/Migrations/TvContextModelSnapshot.cs

@ -17,7 +17,7 @@ namespace ErsatzTV.Infrastructure.MySql.Migrations @@ -17,7 +17,7 @@ namespace ErsatzTV.Infrastructure.MySql.Migrations
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "9.0.7")
.HasAnnotation("ProductVersion", "9.0.8")
.HasAnnotation("Relational:MaxIdentifierLength", 64);
MySqlModelBuilderExtensions.AutoIncrementColumns(modelBuilder);
@ -1925,6 +1925,9 @@ namespace ErsatzTV.Infrastructure.MySql.Migrations @@ -1925,6 +1925,9 @@ namespace ErsatzTV.Infrastructure.MySql.Migrations
b.HasIndex("PlayoutId");
b.HasIndex("Start", "Finish")
.HasDatabaseName("IX_PlayoutItem_Start_Finish");
b.ToTable("PlayoutItem", (string)null);
});

6170
ErsatzTV.Infrastructure.Sqlite/Migrations/20250812005134_Add_PlayoutItemStartFinishIndex.Designer.cs generated

File diff suppressed because it is too large Load Diff

27
ErsatzTV.Infrastructure.Sqlite/Migrations/20250812005134_Add_PlayoutItemStartFinishIndex.cs

@ -0,0 +1,27 @@ @@ -0,0 +1,27 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace ErsatzTV.Infrastructure.Sqlite.Migrations
{
/// <inheritdoc />
public partial class Add_PlayoutItemStartFinishIndex : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateIndex(
name: "IX_PlayoutItem_Start_Finish",
table: "PlayoutItem",
columns: new[] { "Start", "Finish" });
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropIndex(
name: "IX_PlayoutItem_Start_Finish",
table: "PlayoutItem");
}
}
}

5
ErsatzTV.Infrastructure.Sqlite/Migrations/TvContextModelSnapshot.cs

@ -15,7 +15,7 @@ namespace ErsatzTV.Infrastructure.Sqlite.Migrations @@ -15,7 +15,7 @@ namespace ErsatzTV.Infrastructure.Sqlite.Migrations
protected override void BuildModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder.HasAnnotation("ProductVersion", "9.0.7");
modelBuilder.HasAnnotation("ProductVersion", "9.0.8");
modelBuilder.Entity("ErsatzTV.Core.Domain.Actor", b =>
{
@ -1834,6 +1834,9 @@ namespace ErsatzTV.Infrastructure.Sqlite.Migrations @@ -1834,6 +1834,9 @@ namespace ErsatzTV.Infrastructure.Sqlite.Migrations
b.HasIndex("PlayoutId");
b.HasIndex("Start", "Finish")
.HasDatabaseName("IX_PlayoutItem_Start_Finish");
b.ToTable("PlayoutItem", (string)null);
});

3
ErsatzTV.Infrastructure/Data/Configurations/PlayoutItemConfiguration.cs

@ -11,6 +11,9 @@ public class PlayoutItemConfiguration : IEntityTypeConfiguration<PlayoutItem> @@ -11,6 +11,9 @@ public class PlayoutItemConfiguration : IEntityTypeConfiguration<PlayoutItem>
{
builder.ToTable("PlayoutItem");
builder.HasIndex(p => new { p.Start, p.Finish })
.HasDatabaseName("IX_PlayoutItem_Start_Finish");
builder.HasOne(pi => pi.MediaItem)
.WithMany()
.HasForeignKey(pi => pi.MediaItemId)

12
ErsatzTV/Pages/BlockEditor.razor

@ -14,22 +14,22 @@ @@ -14,22 +14,22 @@
<MudPaper Square="true" Style="display: flex; height: 64px; min-height: 64px; width: 100%; z-index: 100; align-items: center">
<div style="display: flex; flex-direction: row; margin-bottom: auto; margin-top: auto; width: 100%; align-items: center" class="ml-6 mr-6">
<div class="d-none d-md-flex">
<MudButton Variant="Variant.Filled" Color="Color.Primary" OnClick="SaveChanges" StartIcon="@Icons.Material.Filled.Save">
<MudButton Variant="Variant.Filled" Color="Color.Primary" OnClick="@SaveChanges" StartIcon="@Icons.Material.Filled.Save">
Save Block
</MudButton>
<MudButton Class="ml-3" Variant="Variant.Filled" Color="Color.Default" OnClick="AddBlockItem" StartIcon="@Icons.Material.Filled.PlaylistAdd">
<MudButton Class="ml-3" Variant="Variant.Filled" Color="Color.Default" OnClick="@AddBlockItem" StartIcon="@Icons.Material.Filled.PlaylistAdd">
Add Block Item
</MudButton>
<MudButton Class="ml-3" Variant="Variant.Filled" Color="Color.Secondary" OnClick="PreviewPlayout" StartIcon="@Icons.Material.Filled.Preview">
<MudButton Class="ml-3" Variant="Variant.Filled" Color="Color.Secondary" OnClick="@PreviewPlayout" StartIcon="@Icons.Material.Filled.Preview">
Preview Block Playout
</MudButton>
</div>
<div style="align-items: center; display: flex; margin-left: auto;" class="d-md-none">
<div class="flex-grow-1"></div>
<MudMenu Icon="@Icons.Material.Filled.MoreVert">
<MudMenuItem Icon="@Icons.Material.Filled.Save" Label="Save Block" OnClick="SaveChanges"/>
<MudMenuItem Icon="@Icons.Material.Filled.PlaylistAdd" Label="Add Block Item" OnClick="AddBlockItem"/>
<MudMenuItem Icon="@Icons.Material.Filled.Preview" Label="Preview Block Playout" OnClick="PreviewPlayout"/>
<MudMenuItem Icon="@Icons.Material.Filled.Save" Label="Save Block" OnClick="@SaveChanges"/>
<MudMenuItem Icon="@Icons.Material.Filled.PlaylistAdd" Label="Add Block Item" OnClick="@AddBlockItem"/>
<MudMenuItem Icon="@Icons.Material.Filled.Preview" Label="Preview Block Playout" OnClick="@PreviewPlayout"/>
</MudMenu>
</div>
</div>

11
ErsatzTV/Services/SchedulerService.cs

@ -164,6 +164,7 @@ public class SchedulerService : BackgroundService @@ -164,6 +164,7 @@ public class SchedulerService : BackgroundService
TvContext dbContext = scope.ServiceProvider.GetRequiredService<TvContext>();
List<Playout> playouts = await dbContext.Playouts
.AsNoTracking()
.Filter(p => p.DailyRebuildTime != null)
.Include(p => p.Channel)
.ToListAsync(cancellationToken);
@ -208,8 +209,10 @@ public class SchedulerService : BackgroundService @@ -208,8 +209,10 @@ public class SchedulerService : BackgroundService
TvContext dbContext = scope.ServiceProvider.GetRequiredService<TvContext>();
List<Playout> playouts = await dbContext.Playouts
.AsNoTracking()
.Include(p => p.Channel)
.ToListAsync(cancellationToken);
foreach (int playoutId in playouts.OrderBy(p => decimal.Parse(p.Channel.Number, CultureInfo.InvariantCulture))
.Map(p => p.Id))
{
@ -243,7 +246,7 @@ public class SchedulerService : BackgroundService @@ -243,7 +246,7 @@ public class SchedulerService : BackgroundService
var mediaSourceIds = new System.Collections.Generic.HashSet<int>();
foreach (PlexLibrary library in dbContext.PlexLibraries.Filter(l => l.ShouldSyncItems))
foreach (PlexLibrary library in dbContext.PlexLibraries.AsNoTracking().Filter(l => l.ShouldSyncItems))
{
mediaSourceIds.Add(library.MediaSourceId);
@ -277,7 +280,7 @@ public class SchedulerService : BackgroundService @@ -277,7 +280,7 @@ public class SchedulerService : BackgroundService
var mediaSourceIds = new System.Collections.Generic.HashSet<int>();
foreach (JellyfinLibrary library in dbContext.JellyfinLibraries.Filter(l => l.ShouldSyncItems))
foreach (JellyfinLibrary library in dbContext.JellyfinLibraries.AsNoTracking().Filter(l => l.ShouldSyncItems))
{
mediaSourceIds.Add(library.MediaSourceId);
@ -304,7 +307,7 @@ public class SchedulerService : BackgroundService @@ -304,7 +307,7 @@ public class SchedulerService : BackgroundService
var mediaSourceIds = new System.Collections.Generic.HashSet<int>();
foreach (EmbyLibrary library in dbContext.EmbyLibraries.Filter(l => l.ShouldSyncItems))
foreach (EmbyLibrary library in dbContext.EmbyLibraries.AsNoTracking().Filter(l => l.ShouldSyncItems))
{
mediaSourceIds.Add(library.MediaSourceId);
@ -332,6 +335,7 @@ public class SchedulerService : BackgroundService @@ -332,6 +335,7 @@ public class SchedulerService : BackgroundService
DateTime target = DateTime.UtcNow.AddDays(-1);
List<TraktList> traktLists = await dbContext.TraktLists
.AsNoTracking()
.Filter(tl => tl.AutoRefresh && (tl.LastUpdate == null || tl.LastUpdate <= target))
.ToListAsync(cancellationToken);
@ -355,6 +359,7 @@ public class SchedulerService : BackgroundService @@ -355,6 +359,7 @@ public class SchedulerService : BackgroundService
DateTime target = DateTime.UtcNow.AddHours(-1);
List<TraktList> traktLists = await dbContext.TraktLists
.AsNoTracking()
.Filter(tl => tl.LastMatch == null || tl.LastMatch <= target)
.ToListAsync(cancellationToken);

3
ErsatzTV/Services/WorkerService.cs

@ -76,7 +76,10 @@ public class WorkerService : BackgroundService @@ -76,7 +76,10 @@ public class WorkerService : BackgroundService
"Unable to build playout {PlayoutId}: {Error}",
buildPlayout.PlayoutId,
error.Value));
break;
}
case CheckForOverlappingPlayoutItems checkForOverlappingPlayoutItems:
await mediator.Send(checkForOverlappingPlayoutItems, stoppingToken);
break;
case TimeShiftOnDemandPlayout timeShiftOnDemandPlayout:
await mediator.Send(timeShiftOnDemandPlayout, stoppingToken);

Loading…
Cancel
Save