Browse Source

add mirror playout offset (#2391)

pull/2394/head
Jason Dove 11 months ago committed by GitHub
parent
commit
e96ac0202b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 2
      CHANGELOG.md
  2. 1
      ErsatzTV.Application/Channels/ChannelViewModel.cs
  3. 1
      ErsatzTV.Application/Channels/Commands/CreateChannel.cs
  4. 2
      ErsatzTV.Application/Channels/Commands/CreateChannelHandler.cs
  5. 39
      ErsatzTV.Application/Channels/Commands/RefreshChannelDataHandler.cs
  6. 2
      ErsatzTV.Application/Channels/Commands/RefreshChannelListHandler.cs
  7. 1
      ErsatzTV.Application/Channels/Commands/UpdateChannel.cs
  8. 3
      ErsatzTV.Application/Channels/Commands/UpdateChannelHandler.cs
  9. 1
      ErsatzTV.Application/Channels/Mapper.cs
  10. 3
      ErsatzTV.Application/MediaCollections/Commands/PreviewPlaylistPlayoutHandler.cs
  11. 30
      ErsatzTV.Application/Playouts/Commands/BuildPlayoutHandler.cs
  12. 3
      ErsatzTV.Application/Scheduling/Commands/PreviewBlockPlayoutHandler.cs
  13. 8
      ErsatzTV.Application/Streaming/Queries/FFmpegProcessHandler.cs
  14. 16
      ErsatzTV.Core.Tests/Scheduling/ClassicScheduling/ContinuePlayoutTests.cs
  15. 110
      ErsatzTV.Core.Tests/Scheduling/ClassicScheduling/NewPlayoutTests.cs
  16. 6
      ErsatzTV.Core.Tests/Scheduling/ClassicScheduling/PlayoutBuilderTestBase.cs
  17. 3
      ErsatzTV.Core.Tests/Scheduling/ClassicScheduling/RefreshPlayoutTests.cs
  18. 3
      ErsatzTV.Core.Tests/Scheduling/ScheduleIntegrationTests.cs
  19. 3
      ErsatzTV.Core/Scheduling/PlayoutReferenceData.cs
  20. 10
      ErsatzTV/Pages/ChannelEditor.razor
  21. 10
      ErsatzTV/ViewModels/ChannelEditViewModel.cs

2
CHANGELOG.md

@ -10,6 +10,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). @@ -10,6 +10,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- `Generated`: default/existing behavior where channel must have its own playout
- `Mirror`: channel will play content from the specified `Mirror Source Channel`'s playout
- This allows the exact same content on different channels with different channel settings
- `Playout Offset` can be used to offset the times of scheduled playout items from the mirror source channel
- e.g. -2 hours will cause the mirror channel to play content 2 hours before the mirror source channel
### Fixed
- Fix transcoding content with bt709/pc color metadata

1
ErsatzTV.Application/Channels/ChannelViewModel.cs

@ -19,6 +19,7 @@ public record ChannelViewModel( @@ -19,6 +19,7 @@ public record ChannelViewModel(
ChannelPlayoutSource PlayoutSource,
ChannelPlayoutMode PlayoutMode,
int? MirrorSourceChannelId,
TimeSpan? PlayoutOffset,
StreamingMode StreamingMode,
int? WatermarkId,
int? FallbackFillerId,

1
ErsatzTV.Application/Channels/Commands/CreateChannel.cs

@ -18,6 +18,7 @@ public record CreateChannel( @@ -18,6 +18,7 @@ public record CreateChannel(
ChannelPlayoutSource PlayoutSource,
ChannelPlayoutMode PlayoutMode,
int? MirrorSourceChannelId,
TimeSpan? PlayoutOffset,
StreamingMode StreamingMode,
int? WatermarkId,
int? FallbackFillerId,

2
ErsatzTV.Application/Channels/Commands/CreateChannelHandler.cs

@ -79,6 +79,7 @@ public class CreateChannelHandler( @@ -79,6 +79,7 @@ public class CreateChannelHandler(
PlayoutSource = request.PlayoutSource,
PlayoutMode = request.PlayoutMode,
MirrorSourceChannelId = request.MirrorSourceChannelId,
PlayoutOffset = request.PlayoutOffset,
StreamingMode = request.StreamingMode,
Artwork = artwork,
StreamSelectorMode = request.StreamSelectorMode,
@ -103,6 +104,7 @@ public class CreateChannelHandler( @@ -103,6 +104,7 @@ public class CreateChannelHandler(
else
{
channel.MirrorSourceChannelId = null;
channel.PlayoutOffset = null;
}
foreach (int id in watermarkId)

39
ErsatzTV.Application/Channels/Commands/RefreshChannelDataHandler.cs

@ -11,6 +11,7 @@ using ErsatzTV.Core.Iptv; @@ -11,6 +11,7 @@ using ErsatzTV.Core.Iptv;
using ErsatzTV.Core.Jellyfin;
using ErsatzTV.Core.Streaming;
using ErsatzTV.Infrastructure.Data;
using ErsatzTV.Infrastructure.Extensions;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using Microsoft.IO;
@ -52,10 +53,8 @@ public class RefreshChannelDataHandler : IRequestHandler<RefreshChannelData> @@ -52,10 +53,8 @@ public class RefreshChannelDataHandler : IRequestHandler<RefreshChannelData>
string targetFile = Path.Combine(FileSystemLayout.ChannelGuideCacheFolder, $"{request.ChannelNumber}.xml");
await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(cancellationToken);
string channelNumber = request.ChannelNumber;
int hiddenCount = await dbContext.Channels
.Where(c => c.Number == channelNumber && c.ShowInEpg == false)
.Where(c => c.Number == request.ChannelNumber && c.ShowInEpg == false)
.CountAsync(cancellationToken);
if (hiddenCount > 0)
{
@ -99,20 +98,22 @@ public class RefreshChannelDataHandler : IRequestHandler<RefreshChannelData> @@ -99,20 +98,22 @@ public class RefreshChannelDataHandler : IRequestHandler<RefreshChannelData>
string otherVideoText = await File.ReadAllTextAsync(otherVideoTemplateFileName, cancellationToken);
var otherVideoTemplate = Template.Parse(otherVideoText, otherVideoTemplateFileName);
Option<string> maybeMirrorNumber = await dbContext.Channels
.Filter(c => c.Number == channelNumber)
TimeSpan playoutOffset = TimeSpan.Zero;
string mirrorChannelNumber = null;
Option<Channel> maybeChannel = await dbContext.Channels
.AsNoTracking()
.Include(c => c.MirrorSourceChannel)
.Filter(c => c.PlayoutSource == ChannelPlayoutSource.Mirror && c.MirrorSourceChannelId != null)
.Map(c => c.MirrorSourceChannel.Number)
.ToListAsync(cancellationToken)
.Map(list => list.HeadOrNone());
foreach (string mirrorNumber in maybeMirrorNumber)
.SelectOneAsync(c => c.Number == request.ChannelNumber, c => c.Number == request.ChannelNumber, cancellationToken);
foreach (Channel channel in maybeChannel)
{
request = new RefreshChannelData(ChannelNumber: mirrorNumber);
mirrorChannelNumber = channel.MirrorSourceChannel.Number;
playoutOffset = channel.PlayoutOffset ?? TimeSpan.Zero;
}
List<Playout> playouts = await dbContext.Playouts
.AsNoTracking()
.Filter(pi => pi.Channel.Number == request.ChannelNumber)
.Filter(pi => pi.Channel.Number == (mirrorChannelNumber ?? request.ChannelNumber))
.Include(p => p.Items)
.ThenInclude(i => i.MediaItem)
.ThenInclude(i => (i as Episode).EpisodeMetadata)
@ -213,6 +214,11 @@ public class RefreshChannelDataHandler : IRequestHandler<RefreshChannelData> @@ -213,6 +214,11 @@ public class RefreshChannelDataHandler : IRequestHandler<RefreshChannelData>
.OrderBy(pi => pi.Start)
.Filter(pi => pi.StartOffset <= finish)
.ToList();
foreach (var item in floodSorted)
{
item.Start += playoutOffset;
item.Finish += playoutOffset;
}
await WritePlayoutXml(
request,
floodSorted,
@ -232,6 +238,11 @@ public class RefreshChannelDataHandler : IRequestHandler<RefreshChannelData> @@ -232,6 +238,11 @@ public class RefreshChannelDataHandler : IRequestHandler<RefreshChannelData>
.OrderBy(pi => pi.Start)
.Filter(pi => pi.StartOffset <= finish)
.ToList();
foreach (var item in blockSorted)
{
item.Start += playoutOffset;
item.Finish += playoutOffset;
}
await WriteBlockPlayoutXml(
request,
blockSorted,
@ -249,7 +260,11 @@ public class RefreshChannelDataHandler : IRequestHandler<RefreshChannelData> @@ -249,7 +260,11 @@ public class RefreshChannelDataHandler : IRequestHandler<RefreshChannelData>
var externalJsonSorted = (await CollectExternalJsonItems(playout.ScheduleFile))
.Filter(pi => pi.StartOffset <= finish)
.ToList();
foreach (var item in externalJsonSorted)
{
item.Start += playoutOffset;
item.Finish += playoutOffset;
}
await WritePlayoutXml(
request,
externalJsonSorted,

2
ErsatzTV.Application/Channels/Commands/RefreshChannelListHandler.cs

@ -118,7 +118,7 @@ public class RefreshChannelListHandler : IRequestHandler<RefreshChannelList> @@ -118,7 +118,7 @@ public class RefreshChannelListHandler : IRequestHandler<RefreshChannelList>
const string QUERY = @"select C.Number, C.Name, C.Categories, A.Path as ArtworkPath
from Channel C
left outer join Artwork A on C.Id = A.ChannelId and A.ArtworkKind = 2
where C.Id in (select ChannelId from Playout) and C.IsEnabled = 1 and C.ShowInEPG = 1
where (C.Id in (select ChannelId from Playout) or C.MirrorSourceChannelId in (select ChannelId from Playout)) and C.IsEnabled = 1 and C.ShowInEPG = 1
order by CAST(C.Number as double)";
// TODO: this needs to be fixed for sqlite/mariadb

1
ErsatzTV.Application/Channels/Commands/UpdateChannel.cs

@ -19,6 +19,7 @@ public record UpdateChannel( @@ -19,6 +19,7 @@ public record UpdateChannel(
ChannelPlayoutSource PlayoutSource,
ChannelPlayoutMode PlayoutMode,
int? MirrorSourceChannelId,
TimeSpan? PlayoutOffset,
StreamingMode StreamingMode,
int? WatermarkId,
int? FallbackFillerId,

3
ErsatzTV.Application/Channels/Commands/UpdateChannelHandler.cs

@ -108,13 +108,16 @@ public class UpdateChannelHandler( @@ -108,13 +108,16 @@ public class UpdateChannelHandler(
{
c.PlayoutMode = ChannelPlayoutMode.Continuous;
hasEpgChange |= c.MirrorSourceChannelId != update.MirrorSourceChannelId;
hasEpgChange |= c.PlayoutOffset != update.PlayoutOffset;
}
else
{
c.MirrorSourceChannelId = null;
c.PlayoutOffset = null;
}
c.MirrorSourceChannelId = update.MirrorSourceChannelId;
c.PlayoutOffset = update.PlayoutOffset;
c.StreamingMode = update.StreamingMode;
c.WatermarkId = update.WatermarkId;
c.FallbackFillerId = update.FallbackFillerId;

1
ErsatzTV.Application/Channels/Mapper.cs

@ -22,6 +22,7 @@ internal static class Mapper @@ -22,6 +22,7 @@ internal static class Mapper
channel.PlayoutSource,
channel.PlayoutMode,
channel.MirrorSourceChannelId,
channel.PlayoutOffset,
channel.StreamingMode,
channel.WatermarkId,
channel.FallbackFillerId,

3
ErsatzTV.Application/MediaCollections/Commands/PreviewPlaylistPlayoutHandler.cs

@ -50,7 +50,8 @@ public class PreviewPlaylistPlayoutHandler( @@ -50,7 +50,8 @@ public class PreviewPlaylistPlayoutHandler(
playout.Templates.ToList(),
new ProgramSchedule(),
playout.ProgramScheduleAlternates.ToList(),
playout.PlayoutHistory.ToList());
playout.PlayoutHistory.ToList(),
TimeSpan.Zero);
// TODO: make an explicit method to preview, this is ugly
playoutBuilder.TrimStart = false;

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

@ -171,7 +171,7 @@ public class BuildPlayoutHandler : IRequestHandler<BuildPlayout, Either<BaseErro @@ -171,7 +171,7 @@ public class BuildPlayoutHandler : IRequestHandler<BuildPlayout, Either<BaseErro
{
changeCount += await dbContext.PlayoutItems
.Where(pi => pi.PlayoutId == playout.Id)
.Where(pi => pi.Finish < removeBefore.UtcDateTime)
.Where(pi => pi.Finish < removeBefore.UtcDateTime + referenceData.MinPlayoutOffset)
.ExecuteDeleteAsync(cancellationToken);
}
@ -242,6 +242,17 @@ public class BuildPlayoutHandler : IRequestHandler<BuildPlayout, Either<BaseErro @@ -242,6 +242,17 @@ public class BuildPlayoutHandler : IRequestHandler<BuildPlayout, Either<BaseErro
playout.ScheduleKind is PlayoutScheduleKind.ExternalJson)
{
await _workerChannel.WriteAsync(new RefreshChannelData(channelNumber), cancellationToken);
// refresh guide data for all mirror channels, too
List<string> maybeMirrors = await dbContext.Channels
.AsNoTracking()
.Filter(c => c.MirrorSourceChannelId == referenceData.Channel.Id)
.Map(c => c.Number)
.ToListAsync(cancellationToken);
foreach (string mirror in maybeMirrors)
{
await _workerChannel.WriteAsync(new RefreshChannelData(mirror), cancellationToken);
}
}
await _workerChannel.WriteAsync(new ExtractEmbeddedSubtitles(playout.Id), cancellationToken);
@ -338,6 +349,20 @@ public class BuildPlayoutHandler : IRequestHandler<BuildPlayout, Either<BaseErro @@ -338,6 +349,20 @@ public class BuildPlayoutHandler : IRequestHandler<BuildPlayout, Either<BaseErro
.Where(c => c.Playouts.Any(p => p.Id == playoutId))
.FirstOrDefaultAsync();
TimeSpan minPlayoutOffset = TimeSpan.Zero;
List<Channel> mirrorChannels = await dbContext.Channels
.AsNoTracking()
.Where(c => c.MirrorSourceChannelId == channel.Id)
.ToListAsync();
foreach (var mirrorChannel in mirrorChannels)
{
var offset = mirrorChannel.PlayoutOffset ?? TimeSpan.Zero;
if (offset < minPlayoutOffset)
{
minPlayoutOffset = offset;
}
}
Option<Deco> deco = Option<Deco>.None;
List<PlayoutItem> existingItems = [];
List<PlayoutTemplate> playoutTemplates = [];
@ -439,6 +464,7 @@ public class BuildPlayoutHandler : IRequestHandler<BuildPlayout, Either<BaseErro @@ -439,6 +464,7 @@ public class BuildPlayoutHandler : IRequestHandler<BuildPlayout, Either<BaseErro
playoutTemplates,
programSchedule,
programScheduleAlternates,
playoutHistory);
playoutHistory,
minPlayoutOffset);
}
}

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

@ -65,7 +65,8 @@ public class PreviewBlockPlayoutHandler( @@ -65,7 +65,8 @@ public class PreviewBlockPlayoutHandler(
playout.Templates.ToList(),
playout.ProgramSchedule,
playout.ProgramScheduleAlternates,
playout.PlayoutHistory.ToList());
playout.PlayoutHistory.ToList(),
TimeSpan.Zero);
PlayoutBuildResult result =
await blockPlayoutBuilder.Build(

8
ErsatzTV.Application/Streaming/Queries/FFmpegProcessHandler.cs

@ -22,7 +22,13 @@ public abstract class FFmpegProcessHandler<T> : IRequestHandler<T, Either<BaseEr @@ -22,7 +22,13 @@ public abstract class FFmpegProcessHandler<T> : IRequestHandler<T, Either<BaseEr
request,
cancellationToken);
return await validation.Match(
tuple => GetProcess(dbContext, request, tuple.Item1, tuple.Item2, tuple.Item3, cancellationToken),
tuple => GetProcess(
dbContext,
request with { Now = request.Now - (tuple.Item1.PlayoutOffset ?? TimeSpan.Zero) },
tuple.Item1,
tuple.Item2,
tuple.Item3,
cancellationToken),
error => Task.FromResult<Either<BaseError, PlayoutItemProcessModel>>(error.Join()));
}

16
ErsatzTV.Core.Tests/Scheduling/ClassicScheduling/ContinuePlayoutTests.cs

@ -525,7 +525,15 @@ public class ContinuePlayoutTests : PlayoutBuilderTestBase @@ -525,7 +525,15 @@ public class ContinuePlayoutTests : PlayoutBuilderTestBase
};
var referenceData =
new PlayoutReferenceData(playout.Channel, Option<Deco>.None, [], [], playout.ProgramSchedule, [], []);
new PlayoutReferenceData(
playout.Channel,
Option<Deco>.None,
[],
[],
playout.ProgramSchedule,
[],
[],
TimeSpan.Zero);
IConfigElementRepository configRepo = Substitute.For<IConfigElementRepository>();
var televisionRepo = new FakeTelevisionRepository();
@ -644,7 +652,8 @@ public class ContinuePlayoutTests : PlayoutBuilderTestBase @@ -644,7 +652,8 @@ public class ContinuePlayoutTests : PlayoutBuilderTestBase
};
var referenceData =
new PlayoutReferenceData(playout.Channel, Option<Deco>.None, [], [], playout.ProgramSchedule, [], []);
new PlayoutReferenceData(playout.Channel, Option<Deco>.None, [], [], playout.ProgramSchedule, [], [],
TimeSpan.Zero);
IConfigElementRepository configRepo = Substitute.For<IConfigElementRepository>();
var televisionRepo = new FakeTelevisionRepository();
@ -765,7 +774,8 @@ public class ContinuePlayoutTests : PlayoutBuilderTestBase @@ -765,7 +774,8 @@ public class ContinuePlayoutTests : PlayoutBuilderTestBase
};
var referenceData =
new PlayoutReferenceData(playout.Channel, Option<Deco>.None, [], [], playout.ProgramSchedule, [], []);
new PlayoutReferenceData(playout.Channel, Option<Deco>.None, [], [], playout.ProgramSchedule, [], [],
TimeSpan.Zero);
IConfigElementRepository configRepo = Substitute.For<IConfigElementRepository>();
var televisionRepo = new FakeTelevisionRepository();

110
ErsatzTV.Core.Tests/Scheduling/ClassicScheduling/NewPlayoutTests.cs

@ -534,7 +534,15 @@ public class NewPlayoutTests : PlayoutBuilderTestBase @@ -534,7 +534,15 @@ public class NewPlayoutTests : PlayoutBuilderTestBase
};
var referenceData =
new PlayoutReferenceData(playout.Channel, Option<Deco>.None, [], [], playout.ProgramSchedule, [], []);
new PlayoutReferenceData(
playout.Channel,
Option<Deco>.None,
[],
[],
playout.ProgramSchedule,
[],
[],
TimeSpan.Zero);
IConfigElementRepository configRepo = Substitute.For<IConfigElementRepository>();
var televisionRepo = new FakeTelevisionRepository();
@ -640,7 +648,15 @@ public class NewPlayoutTests : PlayoutBuilderTestBase @@ -640,7 +648,15 @@ public class NewPlayoutTests : PlayoutBuilderTestBase
};
var referenceData =
new PlayoutReferenceData(playout.Channel, Option<Deco>.None, [], [], playout.ProgramSchedule, [], []);
new PlayoutReferenceData(
playout.Channel,
Option<Deco>.None,
[],
[],
playout.ProgramSchedule,
[],
[],
TimeSpan.Zero);
IConfigElementRepository configRepo = Substitute.For<IConfigElementRepository>();
var televisionRepo = new FakeTelevisionRepository();
@ -797,7 +813,15 @@ public class NewPlayoutTests : PlayoutBuilderTestBase @@ -797,7 +813,15 @@ public class NewPlayoutTests : PlayoutBuilderTestBase
};
var referenceData =
new PlayoutReferenceData(playout.Channel, Option<Deco>.None, [], [], playout.ProgramSchedule, [], []);
new PlayoutReferenceData(
playout.Channel,
Option<Deco>.None,
[],
[],
playout.ProgramSchedule,
[],
[],
TimeSpan.Zero);
IConfigElementRepository configRepo = Substitute.For<IConfigElementRepository>();
var televisionRepo = new FakeTelevisionRepository();
@ -913,7 +937,15 @@ public class NewPlayoutTests : PlayoutBuilderTestBase @@ -913,7 +937,15 @@ public class NewPlayoutTests : PlayoutBuilderTestBase
};
var referenceData =
new PlayoutReferenceData(playout.Channel, Option<Deco>.None, [], [], playout.ProgramSchedule, [], []);
new PlayoutReferenceData(
playout.Channel,
Option<Deco>.None,
[],
[],
playout.ProgramSchedule,
[],
[],
TimeSpan.Zero);
IConfigElementRepository configRepo = Substitute.For<IConfigElementRepository>();
var televisionRepo = new FakeTelevisionRepository();
@ -1028,7 +1060,15 @@ public class NewPlayoutTests : PlayoutBuilderTestBase @@ -1028,7 +1060,15 @@ public class NewPlayoutTests : PlayoutBuilderTestBase
};
var referenceData =
new PlayoutReferenceData(playout.Channel, Option<Deco>.None, [], [], playout.ProgramSchedule, [], []);
new PlayoutReferenceData(
playout.Channel,
Option<Deco>.None,
[],
[],
playout.ProgramSchedule,
[],
[],
TimeSpan.Zero);
IConfigElementRepository configRepo = Substitute.For<IConfigElementRepository>();
var televisionRepo = new FakeTelevisionRepository();
@ -1144,7 +1184,15 @@ public class NewPlayoutTests : PlayoutBuilderTestBase @@ -1144,7 +1184,15 @@ public class NewPlayoutTests : PlayoutBuilderTestBase
};
var referenceData =
new PlayoutReferenceData(playout.Channel, Option<Deco>.None, [], [], playout.ProgramSchedule, [], []);
new PlayoutReferenceData(
playout.Channel,
Option<Deco>.None,
[],
[],
playout.ProgramSchedule,
[],
[],
TimeSpan.Zero);
IConfigElementRepository configRepo = Substitute.For<IConfigElementRepository>();
var televisionRepo = new FakeTelevisionRepository();
@ -1265,7 +1313,15 @@ public class NewPlayoutTests : PlayoutBuilderTestBase @@ -1265,7 +1313,15 @@ public class NewPlayoutTests : PlayoutBuilderTestBase
};
var referenceData =
new PlayoutReferenceData(playout.Channel, Option<Deco>.None, [], [], playout.ProgramSchedule, [], []);
new PlayoutReferenceData(
playout.Channel,
Option<Deco>.None,
[],
[],
playout.ProgramSchedule,
[],
[],
TimeSpan.Zero);
IConfigElementRepository configRepo = Substitute.For<IConfigElementRepository>();
var televisionRepo = new FakeTelevisionRepository();
@ -1386,7 +1442,15 @@ public class NewPlayoutTests : PlayoutBuilderTestBase @@ -1386,7 +1442,15 @@ public class NewPlayoutTests : PlayoutBuilderTestBase
};
var referenceData =
new PlayoutReferenceData(playout.Channel, Option<Deco>.None, [], [], playout.ProgramSchedule, [], []);
new PlayoutReferenceData(
playout.Channel,
Option<Deco>.None,
[],
[],
playout.ProgramSchedule,
[],
[],
TimeSpan.Zero);
IConfigElementRepository configRepo = Substitute.For<IConfigElementRepository>();
var televisionRepo = new FakeTelevisionRepository();
@ -1516,7 +1580,15 @@ public class NewPlayoutTests : PlayoutBuilderTestBase @@ -1516,7 +1580,15 @@ public class NewPlayoutTests : PlayoutBuilderTestBase
};
var referenceData =
new PlayoutReferenceData(playout.Channel, Option<Deco>.None, [], [], playout.ProgramSchedule, [], []);
new PlayoutReferenceData(
playout.Channel,
Option<Deco>.None,
[],
[],
playout.ProgramSchedule,
[],
[],
TimeSpan.Zero);
IConfigElementRepository configRepo = Substitute.For<IConfigElementRepository>();
var televisionRepo = new FakeTelevisionRepository();
@ -1639,7 +1711,15 @@ public class NewPlayoutTests : PlayoutBuilderTestBase @@ -1639,7 +1711,15 @@ public class NewPlayoutTests : PlayoutBuilderTestBase
};
var referenceData =
new PlayoutReferenceData(playout.Channel, Option<Deco>.None, [], [], playout.ProgramSchedule, [], []);
new PlayoutReferenceData(
playout.Channel,
Option<Deco>.None,
[],
[],
playout.ProgramSchedule,
[],
[],
TimeSpan.Zero);
IConfigElementRepository configRepo = Substitute.For<IConfigElementRepository>();
var televisionRepo = new FakeTelevisionRepository();
@ -1728,7 +1808,15 @@ public class NewPlayoutTests : PlayoutBuilderTestBase @@ -1728,7 +1808,15 @@ public class NewPlayoutTests : PlayoutBuilderTestBase
};
var referenceData =
new PlayoutReferenceData(playout.Channel, Option<Deco>.None, [], [], playout.ProgramSchedule, [], []);
new PlayoutReferenceData(
playout.Channel,
Option<Deco>.None,
[],
[],
playout.ProgramSchedule,
[],
[],
TimeSpan.Zero);
IConfigElementRepository configRepo = Substitute.For<IConfigElementRepository>();
var televisionRepo = new FakeTelevisionRepository();

6
ErsatzTV.Core.Tests/Scheduling/ClassicScheduling/PlayoutBuilderTestBase.cs

@ -95,7 +95,8 @@ public abstract class PlayoutBuilderTestBase @@ -95,7 +95,8 @@ public abstract class PlayoutBuilderTestBase
[],
playout.ProgramSchedule,
[],
[]);
[],
TimeSpan.Zero);
return new TestData(builder, playout, referenceData);
}
@ -208,7 +209,8 @@ public abstract class PlayoutBuilderTestBase @@ -208,7 +209,8 @@ public abstract class PlayoutBuilderTestBase
[],
playout.ProgramSchedule,
[],
[]);
[],
TimeSpan.Zero);
return new TestData(builder, playout, referenceData);
}

3
ErsatzTV.Core.Tests/Scheduling/ClassicScheduling/RefreshPlayoutTests.cs

@ -93,7 +93,8 @@ public class RefreshPlayoutTests : PlayoutBuilderTestBase @@ -93,7 +93,8 @@ public class RefreshPlayoutTests : PlayoutBuilderTestBase
[],
playout.ProgramSchedule,
[],
[]);
[],
TimeSpan.Zero);
IConfigElementRepository configRepo = Substitute.For<IConfigElementRepository>();
var televisionRepo = new FakeTelevisionRepository();

3
ErsatzTV.Core.Tests/Scheduling/ScheduleIntegrationTests.cs

@ -503,6 +503,7 @@ public class ScheduleIntegrationTests @@ -503,6 +503,7 @@ public class ScheduleIntegrationTests
playoutTemplates,
programSchedule,
programScheduleAlternates,
playoutHistory);
playoutHistory,
TimeSpan.Zero);
}
}

3
ErsatzTV.Core/Scheduling/PlayoutReferenceData.cs

@ -10,4 +10,5 @@ public record PlayoutReferenceData( @@ -10,4 +10,5 @@ public record PlayoutReferenceData(
List<PlayoutTemplate> PlayoutTemplates,
ProgramSchedule ProgramSchedule,
List<ProgramScheduleAlternate> ProgramScheduleAlternates,
List<PlayoutHistory> PlayoutHistory);
List<PlayoutHistory> PlayoutHistory,
TimeSpan MinPlayoutOffset);

10
ErsatzTV/Pages/ChannelEditor.razor

@ -92,6 +92,15 @@ @@ -92,6 +92,15 @@
}
</MudSelect>
</MudStack>
<MudStack Row="true" Breakpoint="Breakpoint.SmAndDown" Class="form-field-stack gap-md-8 mb-5">
<div class="d-flex">
<MudText>Playout Offset</MudText>
</div>
<MudTextField T="int"
@bind-Value="_model.PlayoutOffsetHours"
Adornment="Adornment.End"
AdornmentText="hours"/>
</MudStack>
}
else
{
@ -376,6 +385,7 @@ else @@ -376,6 +385,7 @@ else
_model.PlayoutSource = channelViewModel.PlayoutSource;
_model.PlayoutMode = channelViewModel.PlayoutMode;
_model.MirrorSourceChannelId = channelViewModel.MirrorSourceChannelId;
_model.PlayoutOffset = channelViewModel.PlayoutOffset;
_model.StreamingMode = channelViewModel.StreamingMode;
_model.StreamSelectorMode = channelViewModel.StreamSelectorMode;
_model.StreamSelector = channelViewModel.StreamSelector;

10
ErsatzTV/ViewModels/ChannelEditViewModel.cs

@ -22,6 +22,14 @@ public class ChannelEditViewModel @@ -22,6 +22,14 @@ public class ChannelEditViewModel
public ChannelPlayoutSource PlayoutSource { get; set; }
public ChannelPlayoutMode PlayoutMode { get; set; }
public int? MirrorSourceChannelId { get; set; }
public TimeSpan? PlayoutOffset { get; set; }
public int PlayoutOffsetHours
{
get => PlayoutOffset?.Hours ?? 0;
set => PlayoutOffset = new TimeSpan(hours: value, minutes: 0, seconds: 0);
}
public StreamingMode StreamingMode { get; set; }
public int? WatermarkId { get; set; }
public int? FallbackFillerId { get; set; }
@ -61,6 +69,7 @@ public class ChannelEditViewModel @@ -61,6 +69,7 @@ public class ChannelEditViewModel
PlayoutSource,
PlayoutMode,
MirrorSourceChannelId,
PlayoutOffset,
StreamingMode,
WatermarkId,
FallbackFillerId,
@ -91,6 +100,7 @@ public class ChannelEditViewModel @@ -91,6 +100,7 @@ public class ChannelEditViewModel
PlayoutSource,
PlayoutMode,
MirrorSourceChannelId,
PlayoutOffset,
StreamingMode,
WatermarkId,
FallbackFillerId,

Loading…
Cancel
Save