add "on demand" channel progress mode (#1790)
* update dependencies * add channel progress mode * implement on demand channel progress * update changelog
This commit is contained in:
@@ -9,6 +9,13 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
|||||||
- These libraries will now appear as ETV Other Video libraries
|
- These libraries will now appear as ETV Other Video libraries
|
||||||
- Items in these libraries will have tag metadata added from folders just like local Other Video libraries
|
- Items in these libraries will have tag metadata added from folders just like local Other Video libraries
|
||||||
- Thanks @raknam for adding this feature!
|
- Thanks @raknam for adding this feature!
|
||||||
|
- Add *experimental* support for `On Demand` channel progress
|
||||||
|
- With `On Demand` channel progress, the schedule will only advance when the channel is being streamed
|
||||||
|
- When the channel is idle, the schedule is unmodified and will be shifted forward as needed so no content is missed
|
||||||
|
- Setting a channel to `On Demand` progress will disable alternate schedules
|
||||||
|
- The `On Demand` setting will only be used for `Flood` playouts (NOT `Block` or `External JSON`)
|
||||||
|
- It is NOT recommended to use fixed start times with `On Demand` progress
|
||||||
|
- This will probably be disabled with a future update
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
- Add basic cache busting to XMLTV image URLs
|
- Add basic cache busting to XMLTV image URLs
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ public record ChannelViewModel(
|
|||||||
string Logo,
|
string Logo,
|
||||||
string PreferredAudioLanguageCode,
|
string PreferredAudioLanguageCode,
|
||||||
string PreferredAudioTitle,
|
string PreferredAudioTitle,
|
||||||
|
ChannelProgressMode ProgressMode,
|
||||||
StreamingMode StreamingMode,
|
StreamingMode StreamingMode,
|
||||||
int? WatermarkId,
|
int? WatermarkId,
|
||||||
int? FallbackFillerId,
|
int? FallbackFillerId,
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ public record CreateChannel(
|
|||||||
string Logo,
|
string Logo,
|
||||||
string PreferredAudioLanguageCode,
|
string PreferredAudioLanguageCode,
|
||||||
string PreferredAudioTitle,
|
string PreferredAudioTitle,
|
||||||
|
ChannelProgressMode ProgressMode,
|
||||||
StreamingMode StreamingMode,
|
StreamingMode StreamingMode,
|
||||||
int? WatermarkId,
|
int? WatermarkId,
|
||||||
int? FallbackFillerId,
|
int? FallbackFillerId,
|
||||||
|
|||||||
@@ -73,6 +73,7 @@ public class CreateChannelHandler(
|
|||||||
Group = request.Group,
|
Group = request.Group,
|
||||||
Categories = request.Categories,
|
Categories = request.Categories,
|
||||||
FFmpegProfileId = ffmpegProfileId,
|
FFmpegProfileId = ffmpegProfileId,
|
||||||
|
ProgressMode = request.ProgressMode,
|
||||||
StreamingMode = request.StreamingMode,
|
StreamingMode = request.StreamingMode,
|
||||||
Artwork = artwork,
|
Artwork = artwork,
|
||||||
PreferredAudioLanguageCode = preferredAudioLanguageCode,
|
PreferredAudioLanguageCode = preferredAudioLanguageCode,
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ public record UpdateChannel(
|
|||||||
string Logo,
|
string Logo,
|
||||||
string PreferredAudioLanguageCode,
|
string PreferredAudioLanguageCode,
|
||||||
string PreferredAudioTitle,
|
string PreferredAudioTitle,
|
||||||
|
ChannelProgressMode ProgressMode,
|
||||||
StreamingMode StreamingMode,
|
StreamingMode StreamingMode,
|
||||||
int? WatermarkId,
|
int? WatermarkId,
|
||||||
int? FallbackFillerId,
|
int? FallbackFillerId,
|
||||||
|
|||||||
@@ -67,6 +67,7 @@ public class UpdateChannelHandler(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
c.ProgressMode = update.ProgressMode;
|
||||||
c.StreamingMode = update.StreamingMode;
|
c.StreamingMode = update.StreamingMode;
|
||||||
c.WatermarkId = update.WatermarkId;
|
c.WatermarkId = update.WatermarkId;
|
||||||
c.FallbackFillerId = update.FallbackFillerId;
|
c.FallbackFillerId = update.FallbackFillerId;
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ internal static class Mapper
|
|||||||
GetLogo(channel),
|
GetLogo(channel),
|
||||||
channel.PreferredAudioLanguageCode,
|
channel.PreferredAudioLanguageCode,
|
||||||
channel.PreferredAudioTitle,
|
channel.PreferredAudioTitle,
|
||||||
|
channel.ProgressMode,
|
||||||
channel.StreamingMode,
|
channel.StreamingMode,
|
||||||
channel.WatermarkId,
|
channel.WatermarkId,
|
||||||
channel.FallbackFillerId,
|
channel.FallbackFillerId,
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
<PackageReference Include="Bugsnag" Version="3.1.0" />
|
<PackageReference Include="Bugsnag" Version="3.1.0" />
|
||||||
<PackageReference Include="CliWrap" Version="3.6.6" />
|
<PackageReference Include="CliWrap" Version="3.6.6" />
|
||||||
<PackageReference Include="Humanizer.Core" Version="2.14.1" />
|
<PackageReference Include="Humanizer.Core" Version="2.14.1" />
|
||||||
<PackageReference Include="MediatR" Version="12.2.0" />
|
<PackageReference Include="MediatR" Version="12.3.0" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="8.0.0" />
|
<PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="8.0.0" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="8.0.0" />
|
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="8.0.0" />
|
||||||
<PackageReference Include="Microsoft.VisualStudio.Threading.Analyzers" Version="17.10.48">
|
<PackageReference Include="Microsoft.VisualStudio.Threading.Analyzers" Version="17.10.48">
|
||||||
@@ -20,8 +20,8 @@
|
|||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||||
<PackageReference Include="Serilog.Formatting.Compact.Reader" Version="3.0.0" />
|
<PackageReference Include="Serilog.Formatting.Compact.Reader" Version="4.0.0" />
|
||||||
<PackageReference Include="WebMarkupMin.Core" Version="2.16.0" />
|
<PackageReference Include="WebMarkupMin.Core" Version="2.16.2" />
|
||||||
<PackageReference Include="Winista.MimeDetect" Version="1.1.0" />
|
<PackageReference Include="Winista.MimeDetect" Version="1.1.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
|||||||
@@ -37,6 +37,10 @@ public class CreateFloodPlayoutHandler : IRequestHandler<CreateFloodPlayout, Eit
|
|||||||
await dbContext.Playouts.AddAsync(playout);
|
await dbContext.Playouts.AddAsync(playout);
|
||||||
await dbContext.SaveChangesAsync();
|
await dbContext.SaveChangesAsync();
|
||||||
await _channel.WriteAsync(new BuildPlayout(playout.Id, PlayoutBuildMode.Reset));
|
await _channel.WriteAsync(new BuildPlayout(playout.Id, PlayoutBuildMode.Reset));
|
||||||
|
if (playout.Channel.ProgressMode is ChannelProgressMode.OnDemand)
|
||||||
|
{
|
||||||
|
await _channel.WriteAsync(new TimeShiftOnDemandPlayout(playout.Channel.Number, DateTimeOffset.Now, false));
|
||||||
|
}
|
||||||
await _channel.WriteAsync(new RefreshChannelList());
|
await _channel.WriteAsync(new RefreshChannelList());
|
||||||
return new CreatePlayoutResponse(playout.Id);
|
return new CreatePlayoutResponse(playout.Id);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
using ErsatzTV.Core;
|
||||||
|
|
||||||
|
namespace ErsatzTV.Application.Playouts;
|
||||||
|
|
||||||
|
public record TimeShiftOnDemandPlayout(string ChannelNumber, DateTimeOffset Now, bool Force)
|
||||||
|
: IRequest<Option<BaseError>>, IBackgroundServiceRequest;
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
using ErsatzTV.Core;
|
||||||
|
using ErsatzTV.Core.Domain;
|
||||||
|
using ErsatzTV.Core.Interfaces.Scheduling;
|
||||||
|
using ErsatzTV.Infrastructure.Data;
|
||||||
|
using ErsatzTV.Infrastructure.Extensions;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
|
namespace ErsatzTV.Application.Playouts;
|
||||||
|
|
||||||
|
public class TimeShiftOnDemandPlayoutHandler(
|
||||||
|
IPlayoutTimeShifter playoutTimeShifter,
|
||||||
|
IDbContextFactory<TvContext> dbContextFactory)
|
||||||
|
: IRequestHandler<TimeShiftOnDemandPlayout, Option<BaseError>>
|
||||||
|
{
|
||||||
|
public async Task<Option<BaseError>> Handle(TimeShiftOnDemandPlayout request, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await using TvContext dbContext = await dbContextFactory.CreateDbContextAsync(cancellationToken);
|
||||||
|
|
||||||
|
Option<Playout> maybePlayout = await dbContext.Playouts
|
||||||
|
.Include(p => p.Channel)
|
||||||
|
.Include(p => p.Items)
|
||||||
|
.Include(p => p.Anchor)
|
||||||
|
.Include(p => p.ProgramScheduleAnchors)
|
||||||
|
.SelectOneAsync(p => p.Channel.Number, p => p.Channel.Number == request.ChannelNumber);
|
||||||
|
|
||||||
|
foreach (Playout playout in maybePlayout)
|
||||||
|
{
|
||||||
|
playoutTimeShifter.TimeShift(playout, request.Now, request.Force);
|
||||||
|
await dbContext.SaveChangesAsync(cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Option<BaseError>.None;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
return BaseError.New(ex.Message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -49,9 +49,10 @@ public class
|
|||||||
playout.ProgramSchedulePlayoutType,
|
playout.ProgramSchedulePlayoutType,
|
||||||
playout.Channel.Name,
|
playout.Channel.Name,
|
||||||
playout.Channel.Number,
|
playout.Channel.Number,
|
||||||
|
playout.Channel.ProgressMode,
|
||||||
playout.ProgramSchedule?.Name ?? string.Empty,
|
playout.ProgramSchedule?.Name ?? string.Empty,
|
||||||
playout.ExternalJsonFile,
|
playout.ExternalJsonFile,
|
||||||
Optional(playout.DailyRebuildTime));
|
playout.DailyRebuildTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Task<Validation<BaseError, Playout>> Validate(
|
private static Task<Validation<BaseError, Playout>> Validate(
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
namespace ErsatzTV.Application.Playouts;
|
||||||
|
|
||||||
|
public record UpdateOnDemandCheckpoint(string ChannelNumber, DateTimeOffset Checkpoint)
|
||||||
|
: IRequest;
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
using ErsatzTV.Core.Domain;
|
||||||
|
using ErsatzTV.Core.Interfaces.Repositories;
|
||||||
|
using ErsatzTV.Infrastructure.Data;
|
||||||
|
using ErsatzTV.Infrastructure.Extensions;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
|
namespace ErsatzTV.Application.Playouts;
|
||||||
|
|
||||||
|
public class UpdateOnDemandCheckpointHandler(
|
||||||
|
IDbContextFactory<TvContext> dbContextFactory,
|
||||||
|
IConfigElementRepository configElementRepository,
|
||||||
|
ILogger<UpdateOnDemandCheckpointHandler> logger)
|
||||||
|
: IRequestHandler<UpdateOnDemandCheckpoint>
|
||||||
|
{
|
||||||
|
public async Task Handle(UpdateOnDemandCheckpoint request, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
await using TvContext dbContext = await dbContextFactory.CreateDbContextAsync(cancellationToken);
|
||||||
|
|
||||||
|
Option<Playout> maybePlayout = await dbContext.Playouts
|
||||||
|
.Include(p => p.Channel)
|
||||||
|
.Include(p => p.Items)
|
||||||
|
.SelectOneAsync(p => p.Channel.Number, p => p.Channel.Number == request.ChannelNumber);
|
||||||
|
|
||||||
|
foreach (Playout playout in maybePlayout)
|
||||||
|
{
|
||||||
|
if (playout.Channel.ProgressMode is not ChannelProgressMode.OnDemand)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int timeout = await (await configElementRepository.GetValue<int>(ConfigElementKey.FFmpegSegmenterTimeout))
|
||||||
|
.IfNoneAsync(60);
|
||||||
|
|
||||||
|
// don't move checkpoint back in time
|
||||||
|
DateTimeOffset newCheckpoint = request.Checkpoint - TimeSpan.FromSeconds(timeout);
|
||||||
|
if (newCheckpoint > playout.OnDemandCheckpoint)
|
||||||
|
{
|
||||||
|
playout.OnDemandCheckpoint = newCheckpoint;
|
||||||
|
}
|
||||||
|
|
||||||
|
// don't checkpoint before the first item
|
||||||
|
// this could happen if you watch a new playout for less time than the segmenter timeout
|
||||||
|
if (playout.Items.Count > 0)
|
||||||
|
{
|
||||||
|
DateTimeOffset minStart = playout.Items.Min(p => p.StartOffset);
|
||||||
|
if (playout.OnDemandCheckpoint < minStart)
|
||||||
|
{
|
||||||
|
playout.OnDemandCheckpoint = minStart;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.LogDebug(
|
||||||
|
"Updating on demand checkpoint for channel {Number} - {Name} to {Checkpoint}",
|
||||||
|
playout.Channel.Number,
|
||||||
|
playout.Channel.Name,
|
||||||
|
playout.OnDemandCheckpoint);
|
||||||
|
|
||||||
|
await dbContext.SaveChangesAsync(cancellationToken);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -41,9 +41,10 @@ public class UpdatePlayoutHandler : IRequestHandler<UpdatePlayout, Either<BaseEr
|
|||||||
playout.ProgramSchedulePlayoutType,
|
playout.ProgramSchedulePlayoutType,
|
||||||
playout.Channel.Name,
|
playout.Channel.Name,
|
||||||
playout.Channel.Number,
|
playout.Channel.Number,
|
||||||
|
playout.Channel.ProgressMode,
|
||||||
playout.ProgramSchedule?.Name ?? string.Empty,
|
playout.ProgramSchedule?.Name ?? string.Empty,
|
||||||
playout.ExternalJsonFile,
|
playout.ExternalJsonFile,
|
||||||
Optional(playout.DailyRebuildTime));
|
playout.DailyRebuildTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Task<Validation<BaseError, Playout>> Validate(TvContext dbContext, UpdatePlayout request) =>
|
private static Task<Validation<BaseError, Playout>> Validate(TvContext dbContext, UpdatePlayout request) =>
|
||||||
|
|||||||
@@ -7,6 +7,10 @@ public record PlayoutNameViewModel(
|
|||||||
ProgramSchedulePlayoutType PlayoutType,
|
ProgramSchedulePlayoutType PlayoutType,
|
||||||
string ChannelName,
|
string ChannelName,
|
||||||
string ChannelNumber,
|
string ChannelNumber,
|
||||||
|
ChannelProgressMode ProgressMode,
|
||||||
string ScheduleName,
|
string ScheduleName,
|
||||||
string ExternalJsonFile,
|
string ExternalJsonFile,
|
||||||
Option<TimeSpan> DailyRebuildTime);
|
TimeSpan? DbDailyRebuildTime)
|
||||||
|
{
|
||||||
|
public Option<TimeSpan> DailyRebuildTime => Optional(DbDailyRebuildTime);
|
||||||
|
}
|
||||||
|
|||||||
@@ -25,9 +25,10 @@ public class GetAllPlayoutsHandler : IRequestHandler<GetAllPlayouts, List<Playou
|
|||||||
p.ProgramSchedulePlayoutType,
|
p.ProgramSchedulePlayoutType,
|
||||||
p.Channel.Name,
|
p.Channel.Name,
|
||||||
p.Channel.Number,
|
p.Channel.Number,
|
||||||
|
p.Channel.ProgressMode,
|
||||||
p.ProgramScheduleId == null ? string.Empty : p.ProgramSchedule.Name,
|
p.ProgramScheduleId == null ? string.Empty : p.ProgramSchedule.Name,
|
||||||
p.ExternalJsonFile,
|
p.ExternalJsonFile,
|
||||||
Optional(p.DailyRebuildTime)))
|
p.DailyRebuildTime))
|
||||||
.ToListAsync(cancellationToken);
|
.ToListAsync(cancellationToken);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ using System.Timers;
|
|||||||
using Bugsnag;
|
using Bugsnag;
|
||||||
using CliWrap;
|
using CliWrap;
|
||||||
using CliWrap.Buffered;
|
using CliWrap.Buffered;
|
||||||
|
using ErsatzTV.Application.Playouts;
|
||||||
using ErsatzTV.Core;
|
using ErsatzTV.Core;
|
||||||
using ErsatzTV.Core.Domain;
|
using ErsatzTV.Core.Domain;
|
||||||
using ErsatzTV.Core.FFmpeg;
|
using ErsatzTV.Core.FFmpeg;
|
||||||
@@ -172,6 +173,9 @@ public class HlsSessionWorker : IHlsSessionWorker
|
|||||||
_transcodedUntil = DateTimeOffset.Now;
|
_transcodedUntil = DateTimeOffset.Now;
|
||||||
PlaylistStart = _transcodedUntil;
|
PlaylistStart = _transcodedUntil;
|
||||||
|
|
||||||
|
// time shift on-demand playout if needed
|
||||||
|
await _mediator.Send(new TimeShiftOnDemandPlayout(_channelNumber, _transcodedUntil, true), cancellationToken);
|
||||||
|
|
||||||
bool initialWorkAhead = Volatile.Read(ref _workAheadCount) < await GetWorkAheadLimit();
|
bool initialWorkAhead = Volatile.Read(ref _workAheadCount) < await GetWorkAheadLimit();
|
||||||
_state = initialWorkAhead ? HlsSessionState.SeekAndWorkAhead : HlsSessionState.SeekAndRealtime;
|
_state = initialWorkAhead ? HlsSessionState.SeekAndWorkAhead : HlsSessionState.SeekAndRealtime;
|
||||||
|
|
||||||
@@ -236,7 +240,7 @@ public class HlsSessionWorker : IHlsSessionWorker
|
|||||||
}
|
}
|
||||||
catch (Exception)
|
catch (Exception)
|
||||||
{
|
{
|
||||||
// do nothing
|
// do nothing
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -524,6 +528,17 @@ public class HlsSessionWorker : IHlsSessionWorker
|
|||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await _mediator.Send(
|
||||||
|
new UpdateOnDemandCheckpoint(_channelNumber, DateTimeOffset.Now),
|
||||||
|
CancellationToken.None);
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
// do nothing
|
||||||
|
}
|
||||||
|
|
||||||
if (!realtime)
|
if (!realtime)
|
||||||
{
|
{
|
||||||
Interlocked.Decrement(ref _workAheadCount);
|
Interlocked.Decrement(ref _workAheadCount);
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ using System.Text;
|
|||||||
using System.Timers;
|
using System.Timers;
|
||||||
using CliWrap;
|
using CliWrap;
|
||||||
using CliWrap.Buffered;
|
using CliWrap.Buffered;
|
||||||
|
using ErsatzTV.Application.Playouts;
|
||||||
using ErsatzTV.Core;
|
using ErsatzTV.Core;
|
||||||
using ErsatzTV.Core.FFmpeg;
|
using ErsatzTV.Core.FFmpeg;
|
||||||
using ErsatzTV.Core.Interfaces.FFmpeg;
|
using ErsatzTV.Core.Interfaces.FFmpeg;
|
||||||
@@ -124,6 +125,9 @@ public class HlsSessionWorkerV2 : IHlsSessionWorker
|
|||||||
_transcodedUntil = DateTimeOffset.Now;
|
_transcodedUntil = DateTimeOffset.Now;
|
||||||
PlaylistStart = _transcodedUntil;
|
PlaylistStart = _transcodedUntil;
|
||||||
|
|
||||||
|
// time shift on-demand playout if needed
|
||||||
|
await _mediator.Send(new TimeShiftOnDemandPlayout(_channelNumber, _transcodedUntil, true), cancellationToken);
|
||||||
|
|
||||||
// start concat/segmenter process
|
// start concat/segmenter process
|
||||||
// other transcode processes will be started by incoming requests from concat/segmenter process
|
// other transcode processes will be started by incoming requests from concat/segmenter process
|
||||||
|
|
||||||
@@ -171,6 +175,17 @@ public class HlsSessionWorkerV2 : IHlsSessionWorker
|
|||||||
_timer.Elapsed -= CancelRun;
|
_timer.Elapsed -= CancelRun;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await _mediator.Send(
|
||||||
|
new UpdateOnDemandCheckpoint(_channelNumber, DateTimeOffset.Now),
|
||||||
|
CancellationToken.None);
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
// do nothing
|
||||||
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_localFileSystem.EmptyFolder(Path.Combine(FileSystemLayout.TranscodeFolder, _channelNumber));
|
_localFileSystem.EmptyFolder(Path.Combine(FileSystemLayout.TranscodeFolder, _channelNumber));
|
||||||
@@ -192,7 +207,7 @@ public class HlsSessionWorkerV2 : IHlsSessionWorker
|
|||||||
}
|
}
|
||||||
catch (Exception)
|
catch (Exception)
|
||||||
{
|
{
|
||||||
// do nothing
|
// do nothing
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
<PackageReference Include="Bugsnag" Version="3.1.0" />
|
<PackageReference Include="Bugsnag" Version="3.1.0" />
|
||||||
<PackageReference Include="CliWrap" Version="3.6.6" />
|
<PackageReference Include="CliWrap" Version="3.6.6" />
|
||||||
<PackageReference Include="FluentAssertions" Version="6.12.0" />
|
<PackageReference Include="FluentAssertions" Version="6.12.0" />
|
||||||
<PackageReference Include="LanguageExt.Core" Version="4.4.8" />
|
<PackageReference Include="LanguageExt.Core" Version="4.4.9" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="8.0.0" />
|
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="8.0.0" />
|
||||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0" />
|
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Logging" Version="8.0.0" />
|
<PackageReference Include="Microsoft.Extensions.Logging" Version="8.0.0" />
|
||||||
@@ -24,9 +24,9 @@
|
|||||||
<PackageReference Include="NSubstitute" Version="5.1.0" />
|
<PackageReference Include="NSubstitute" Version="5.1.0" />
|
||||||
<PackageReference Include="NUnit" Version="4.1.0" />
|
<PackageReference Include="NUnit" Version="4.1.0" />
|
||||||
<PackageReference Include="NUnit3TestAdapter" Version="4.5.0" />
|
<PackageReference Include="NUnit3TestAdapter" Version="4.5.0" />
|
||||||
<PackageReference Include="Serilog" Version="3.1.1" />
|
<PackageReference Include="Serilog" Version="4.0.0" />
|
||||||
<PackageReference Include="Serilog.Extensions.Logging" Version="8.0.0" />
|
<PackageReference Include="Serilog.Extensions.Logging" Version="8.0.0" />
|
||||||
<PackageReference Include="Serilog.Sinks.Debug" Version="2.0.0" />
|
<PackageReference Include="Serilog.Sinks.Debug" Version="3.0.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
@@ -558,6 +558,7 @@ public class PlayoutBuilderTests
|
|||||||
Substitute.For<IMultiEpisodeShuffleCollectionEnumeratorFactory>();
|
Substitute.For<IMultiEpisodeShuffleCollectionEnumeratorFactory>();
|
||||||
ILocalFileSystem localFileSystem = Substitute.For<ILocalFileSystem>();
|
ILocalFileSystem localFileSystem = Substitute.For<ILocalFileSystem>();
|
||||||
var builder = new PlayoutBuilder(
|
var builder = new PlayoutBuilder(
|
||||||
|
Substitute.For<IPlayoutTimeShifter>(),
|
||||||
configRepo,
|
configRepo,
|
||||||
fakeRepository,
|
fakeRepository,
|
||||||
televisionRepo,
|
televisionRepo,
|
||||||
@@ -657,6 +658,7 @@ public class PlayoutBuilderTests
|
|||||||
Substitute.For<IMultiEpisodeShuffleCollectionEnumeratorFactory>();
|
Substitute.For<IMultiEpisodeShuffleCollectionEnumeratorFactory>();
|
||||||
ILocalFileSystem localFileSystem = Substitute.For<ILocalFileSystem>();
|
ILocalFileSystem localFileSystem = Substitute.For<ILocalFileSystem>();
|
||||||
var builder = new PlayoutBuilder(
|
var builder = new PlayoutBuilder(
|
||||||
|
Substitute.For<IPlayoutTimeShifter>(),
|
||||||
configRepo,
|
configRepo,
|
||||||
fakeRepository,
|
fakeRepository,
|
||||||
televisionRepo,
|
televisionRepo,
|
||||||
@@ -804,6 +806,7 @@ public class PlayoutBuilderTests
|
|||||||
Substitute.For<IMultiEpisodeShuffleCollectionEnumeratorFactory>();
|
Substitute.For<IMultiEpisodeShuffleCollectionEnumeratorFactory>();
|
||||||
ILocalFileSystem localFileSystem = Substitute.For<ILocalFileSystem>();
|
ILocalFileSystem localFileSystem = Substitute.For<ILocalFileSystem>();
|
||||||
var builder = new PlayoutBuilder(
|
var builder = new PlayoutBuilder(
|
||||||
|
Substitute.For<IPlayoutTimeShifter>(),
|
||||||
configRepo,
|
configRepo,
|
||||||
fakeRepository,
|
fakeRepository,
|
||||||
televisionRepo,
|
televisionRepo,
|
||||||
@@ -909,6 +912,7 @@ public class PlayoutBuilderTests
|
|||||||
Substitute.For<IMultiEpisodeShuffleCollectionEnumeratorFactory>();
|
Substitute.For<IMultiEpisodeShuffleCollectionEnumeratorFactory>();
|
||||||
ILocalFileSystem localFileSystem = Substitute.For<ILocalFileSystem>();
|
ILocalFileSystem localFileSystem = Substitute.For<ILocalFileSystem>();
|
||||||
var builder = new PlayoutBuilder(
|
var builder = new PlayoutBuilder(
|
||||||
|
Substitute.For<IPlayoutTimeShifter>(),
|
||||||
configRepo,
|
configRepo,
|
||||||
fakeRepository,
|
fakeRepository,
|
||||||
televisionRepo,
|
televisionRepo,
|
||||||
@@ -1023,6 +1027,7 @@ public class PlayoutBuilderTests
|
|||||||
Substitute.For<IMultiEpisodeShuffleCollectionEnumeratorFactory>();
|
Substitute.For<IMultiEpisodeShuffleCollectionEnumeratorFactory>();
|
||||||
ILocalFileSystem localFileSystem = Substitute.For<ILocalFileSystem>();
|
ILocalFileSystem localFileSystem = Substitute.For<ILocalFileSystem>();
|
||||||
var builder = new PlayoutBuilder(
|
var builder = new PlayoutBuilder(
|
||||||
|
Substitute.For<IPlayoutTimeShifter>(),
|
||||||
configRepo,
|
configRepo,
|
||||||
fakeRepository,
|
fakeRepository,
|
||||||
televisionRepo,
|
televisionRepo,
|
||||||
@@ -1130,6 +1135,7 @@ public class PlayoutBuilderTests
|
|||||||
Substitute.For<IMultiEpisodeShuffleCollectionEnumeratorFactory>();
|
Substitute.For<IMultiEpisodeShuffleCollectionEnumeratorFactory>();
|
||||||
ILocalFileSystem localFileSystem = Substitute.For<ILocalFileSystem>();
|
ILocalFileSystem localFileSystem = Substitute.For<ILocalFileSystem>();
|
||||||
var builder = new PlayoutBuilder(
|
var builder = new PlayoutBuilder(
|
||||||
|
Substitute.For<IPlayoutTimeShifter>(),
|
||||||
configRepo,
|
configRepo,
|
||||||
fakeRepository,
|
fakeRepository,
|
||||||
televisionRepo,
|
televisionRepo,
|
||||||
@@ -1241,6 +1247,7 @@ public class PlayoutBuilderTests
|
|||||||
Substitute.For<IMultiEpisodeShuffleCollectionEnumeratorFactory>();
|
Substitute.For<IMultiEpisodeShuffleCollectionEnumeratorFactory>();
|
||||||
ILocalFileSystem localFileSystem = Substitute.For<ILocalFileSystem>();
|
ILocalFileSystem localFileSystem = Substitute.For<ILocalFileSystem>();
|
||||||
var builder = new PlayoutBuilder(
|
var builder = new PlayoutBuilder(
|
||||||
|
Substitute.For<IPlayoutTimeShifter>(),
|
||||||
configRepo,
|
configRepo,
|
||||||
fakeRepository,
|
fakeRepository,
|
||||||
televisionRepo,
|
televisionRepo,
|
||||||
@@ -1357,6 +1364,7 @@ public class PlayoutBuilderTests
|
|||||||
Substitute.For<IMultiEpisodeShuffleCollectionEnumeratorFactory>();
|
Substitute.For<IMultiEpisodeShuffleCollectionEnumeratorFactory>();
|
||||||
ILocalFileSystem localFileSystem = Substitute.For<ILocalFileSystem>();
|
ILocalFileSystem localFileSystem = Substitute.For<ILocalFileSystem>();
|
||||||
var builder = new PlayoutBuilder(
|
var builder = new PlayoutBuilder(
|
||||||
|
Substitute.For<IPlayoutTimeShifter>(),
|
||||||
configRepo,
|
configRepo,
|
||||||
fakeRepository,
|
fakeRepository,
|
||||||
televisionRepo,
|
televisionRepo,
|
||||||
@@ -1462,6 +1470,7 @@ public class PlayoutBuilderTests
|
|||||||
Substitute.For<IMultiEpisodeShuffleCollectionEnumeratorFactory>();
|
Substitute.For<IMultiEpisodeShuffleCollectionEnumeratorFactory>();
|
||||||
ILocalFileSystem localFileSystem = Substitute.For<ILocalFileSystem>();
|
ILocalFileSystem localFileSystem = Substitute.For<ILocalFileSystem>();
|
||||||
var builder = new PlayoutBuilder(
|
var builder = new PlayoutBuilder(
|
||||||
|
Substitute.For<IPlayoutTimeShifter>(),
|
||||||
configRepo,
|
configRepo,
|
||||||
fakeRepository,
|
fakeRepository,
|
||||||
televisionRepo,
|
televisionRepo,
|
||||||
@@ -1578,6 +1587,7 @@ public class PlayoutBuilderTests
|
|||||||
Substitute.For<IMultiEpisodeShuffleCollectionEnumeratorFactory>();
|
Substitute.For<IMultiEpisodeShuffleCollectionEnumeratorFactory>();
|
||||||
ILocalFileSystem localFileSystem = Substitute.For<ILocalFileSystem>();
|
ILocalFileSystem localFileSystem = Substitute.For<ILocalFileSystem>();
|
||||||
var builder = new PlayoutBuilder(
|
var builder = new PlayoutBuilder(
|
||||||
|
Substitute.For<IPlayoutTimeShifter>(),
|
||||||
configRepo,
|
configRepo,
|
||||||
fakeRepository,
|
fakeRepository,
|
||||||
televisionRepo,
|
televisionRepo,
|
||||||
@@ -1705,6 +1715,7 @@ public class PlayoutBuilderTests
|
|||||||
Substitute.For<IMultiEpisodeShuffleCollectionEnumeratorFactory>();
|
Substitute.For<IMultiEpisodeShuffleCollectionEnumeratorFactory>();
|
||||||
ILocalFileSystem localFileSystem = Substitute.For<ILocalFileSystem>();
|
ILocalFileSystem localFileSystem = Substitute.For<ILocalFileSystem>();
|
||||||
var builder = new PlayoutBuilder(
|
var builder = new PlayoutBuilder(
|
||||||
|
Substitute.For<IPlayoutTimeShifter>(),
|
||||||
configRepo,
|
configRepo,
|
||||||
fakeRepository,
|
fakeRepository,
|
||||||
televisionRepo,
|
televisionRepo,
|
||||||
@@ -1824,6 +1835,7 @@ public class PlayoutBuilderTests
|
|||||||
Substitute.For<IMultiEpisodeShuffleCollectionEnumeratorFactory>();
|
Substitute.For<IMultiEpisodeShuffleCollectionEnumeratorFactory>();
|
||||||
ILocalFileSystem localFileSystem = Substitute.For<ILocalFileSystem>();
|
ILocalFileSystem localFileSystem = Substitute.For<ILocalFileSystem>();
|
||||||
var builder = new PlayoutBuilder(
|
var builder = new PlayoutBuilder(
|
||||||
|
Substitute.For<IPlayoutTimeShifter>(),
|
||||||
configRepo,
|
configRepo,
|
||||||
fakeRepository,
|
fakeRepository,
|
||||||
televisionRepo,
|
televisionRepo,
|
||||||
@@ -1903,6 +1915,7 @@ public class PlayoutBuilderTests
|
|||||||
Substitute.For<IMultiEpisodeShuffleCollectionEnumeratorFactory>();
|
Substitute.For<IMultiEpisodeShuffleCollectionEnumeratorFactory>();
|
||||||
ILocalFileSystem localFileSystem = Substitute.For<ILocalFileSystem>();
|
ILocalFileSystem localFileSystem = Substitute.For<ILocalFileSystem>();
|
||||||
var builder = new PlayoutBuilder(
|
var builder = new PlayoutBuilder(
|
||||||
|
Substitute.For<IPlayoutTimeShifter>(),
|
||||||
configRepo,
|
configRepo,
|
||||||
fakeRepository,
|
fakeRepository,
|
||||||
televisionRepo,
|
televisionRepo,
|
||||||
@@ -2118,6 +2131,7 @@ public class PlayoutBuilderTests
|
|||||||
Substitute.For<IMultiEpisodeShuffleCollectionEnumeratorFactory>();
|
Substitute.For<IMultiEpisodeShuffleCollectionEnumeratorFactory>();
|
||||||
ILocalFileSystem localFileSystem = Substitute.For<ILocalFileSystem>();
|
ILocalFileSystem localFileSystem = Substitute.For<ILocalFileSystem>();
|
||||||
var builder = new PlayoutBuilder(
|
var builder = new PlayoutBuilder(
|
||||||
|
Substitute.For<IPlayoutTimeShifter>(),
|
||||||
configRepo,
|
configRepo,
|
||||||
fakeRepository,
|
fakeRepository,
|
||||||
televisionRepo,
|
televisionRepo,
|
||||||
@@ -2609,6 +2623,7 @@ public class PlayoutBuilderTests
|
|||||||
Substitute.For<IMultiEpisodeShuffleCollectionEnumeratorFactory>();
|
Substitute.For<IMultiEpisodeShuffleCollectionEnumeratorFactory>();
|
||||||
ILocalFileSystem localFileSystem = Substitute.For<ILocalFileSystem>();
|
ILocalFileSystem localFileSystem = Substitute.For<ILocalFileSystem>();
|
||||||
var builder = new PlayoutBuilder(
|
var builder = new PlayoutBuilder(
|
||||||
|
Substitute.For<IPlayoutTimeShifter>(),
|
||||||
configRepo,
|
configRepo,
|
||||||
fakeRepository,
|
fakeRepository,
|
||||||
televisionRepo,
|
televisionRepo,
|
||||||
@@ -2723,6 +2738,7 @@ public class PlayoutBuilderTests
|
|||||||
Substitute.For<IMultiEpisodeShuffleCollectionEnumeratorFactory>();
|
Substitute.For<IMultiEpisodeShuffleCollectionEnumeratorFactory>();
|
||||||
ILocalFileSystem localFileSystem = Substitute.For<ILocalFileSystem>();
|
ILocalFileSystem localFileSystem = Substitute.For<ILocalFileSystem>();
|
||||||
var builder = new PlayoutBuilder(
|
var builder = new PlayoutBuilder(
|
||||||
|
Substitute.For<IPlayoutTimeShifter>(),
|
||||||
configRepo,
|
configRepo,
|
||||||
fakeRepository,
|
fakeRepository,
|
||||||
televisionRepo,
|
televisionRepo,
|
||||||
@@ -2837,6 +2853,7 @@ public class PlayoutBuilderTests
|
|||||||
Substitute.For<IMultiEpisodeShuffleCollectionEnumeratorFactory>();
|
Substitute.For<IMultiEpisodeShuffleCollectionEnumeratorFactory>();
|
||||||
ILocalFileSystem localFileSystem = Substitute.For<ILocalFileSystem>();
|
ILocalFileSystem localFileSystem = Substitute.For<ILocalFileSystem>();
|
||||||
var builder = new PlayoutBuilder(
|
var builder = new PlayoutBuilder(
|
||||||
|
Substitute.For<IPlayoutTimeShifter>(),
|
||||||
configRepo,
|
configRepo,
|
||||||
fakeRepository,
|
fakeRepository,
|
||||||
televisionRepo,
|
televisionRepo,
|
||||||
@@ -2946,6 +2963,7 @@ public class PlayoutBuilderTests
|
|||||||
Substitute.For<IMultiEpisodeShuffleCollectionEnumeratorFactory>();
|
Substitute.For<IMultiEpisodeShuffleCollectionEnumeratorFactory>();
|
||||||
ILocalFileSystem localFileSystem = Substitute.For<ILocalFileSystem>();
|
ILocalFileSystem localFileSystem = Substitute.For<ILocalFileSystem>();
|
||||||
var builder = new PlayoutBuilder(
|
var builder = new PlayoutBuilder(
|
||||||
|
Substitute.For<IPlayoutTimeShifter>(),
|
||||||
configRepo,
|
configRepo,
|
||||||
collectionRepo,
|
collectionRepo,
|
||||||
televisionRepo,
|
televisionRepo,
|
||||||
@@ -3001,6 +3019,7 @@ public class PlayoutBuilderTests
|
|||||||
Substitute.For<IMultiEpisodeShuffleCollectionEnumeratorFactory>();
|
Substitute.For<IMultiEpisodeShuffleCollectionEnumeratorFactory>();
|
||||||
ILocalFileSystem localFileSystem = Substitute.For<ILocalFileSystem>();
|
ILocalFileSystem localFileSystem = Substitute.For<ILocalFileSystem>();
|
||||||
var builder = new PlayoutBuilder(
|
var builder = new PlayoutBuilder(
|
||||||
|
Substitute.For<IPlayoutTimeShifter>(),
|
||||||
configRepo,
|
configRepo,
|
||||||
collectionRepo,
|
collectionRepo,
|
||||||
televisionRepo,
|
televisionRepo,
|
||||||
|
|||||||
@@ -117,6 +117,7 @@ public class ScheduleIntegrationTests
|
|||||||
provider.GetRequiredService<IFallbackMetadataProvider>());
|
provider.GetRequiredService<IFallbackMetadataProvider>());
|
||||||
|
|
||||||
var builder = new PlayoutBuilder(
|
var builder = new PlayoutBuilder(
|
||||||
|
Substitute.For<IPlayoutTimeShifter>(),
|
||||||
new ConfigElementRepository(factory),
|
new ConfigElementRepository(factory),
|
||||||
new MediaCollectionRepository(Substitute.For<IClient>(), searchIndex, factory),
|
new MediaCollectionRepository(Substitute.For<IClient>(), searchIndex, factory),
|
||||||
new TelevisionRepository(factory, provider.GetRequiredService<ILogger<TelevisionRepository>>()),
|
new TelevisionRepository(factory, provider.GetRequiredService<ILogger<TelevisionRepository>>()),
|
||||||
@@ -287,6 +288,7 @@ public class ScheduleIntegrationTests
|
|||||||
DateTimeOffset finish = start.AddDays(2);
|
DateTimeOffset finish = start.AddDays(2);
|
||||||
|
|
||||||
var builder = new PlayoutBuilder(
|
var builder = new PlayoutBuilder(
|
||||||
|
Substitute.For<IPlayoutTimeShifter>(),
|
||||||
new ConfigElementRepository(factory),
|
new ConfigElementRepository(factory),
|
||||||
new MediaCollectionRepository(Substitute.For<IClient>(), Substitute.For<ISearchIndex>(), factory),
|
new MediaCollectionRepository(Substitute.For<IClient>(), Substitute.For<ISearchIndex>(), factory),
|
||||||
new TelevisionRepository(factory, provider.GetRequiredService<ILogger<TelevisionRepository>>()),
|
new TelevisionRepository(factory, provider.GetRequiredService<ILogger<TelevisionRepository>>()),
|
||||||
|
|||||||
@@ -28,4 +28,5 @@ public class Channel
|
|||||||
public ChannelSubtitleMode SubtitleMode { get; set; }
|
public ChannelSubtitleMode SubtitleMode { get; set; }
|
||||||
public ChannelMusicVideoCreditsMode MusicVideoCreditsMode { get; set; }
|
public ChannelMusicVideoCreditsMode MusicVideoCreditsMode { get; set; }
|
||||||
public string MusicVideoCreditsTemplate { get; set; }
|
public string MusicVideoCreditsTemplate { get; set; }
|
||||||
|
public ChannelProgressMode ProgressMode { get; set; }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
namespace ErsatzTV.Core.Domain;
|
||||||
|
|
||||||
|
public enum ChannelProgressMode
|
||||||
|
{
|
||||||
|
Always = 0,
|
||||||
|
OnDemand = 1
|
||||||
|
}
|
||||||
@@ -22,4 +22,5 @@ public class Playout
|
|||||||
public TimeSpan? DailyRebuildTime { get; set; }
|
public TimeSpan? DailyRebuildTime { get; set; }
|
||||||
public int? DecoId { get; set; }
|
public int? DecoId { get; set; }
|
||||||
public Deco Deco { get; set; }
|
public Deco Deco { get; set; }
|
||||||
|
public DateTimeOffset? OnDemandCheckpoint { get; set; }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,21 +12,21 @@
|
|||||||
<PackageReference Include="Bugsnag" Version="3.1.0" />
|
<PackageReference Include="Bugsnag" Version="3.1.0" />
|
||||||
<PackageReference Include="Destructurama.Attributed" Version="4.0.0" />
|
<PackageReference Include="Destructurama.Attributed" Version="4.0.0" />
|
||||||
<PackageReference Include="Flurl" Version="4.0.0" />
|
<PackageReference Include="Flurl" Version="4.0.0" />
|
||||||
<PackageReference Include="LanguageExt.Core" Version="4.4.8" />
|
<PackageReference Include="LanguageExt.Core" Version="4.4.9" />
|
||||||
<PackageReference Include="LanguageExt.Transformers" Version="4.4.8" />
|
<PackageReference Include="LanguageExt.Transformers" Version="4.4.8" />
|
||||||
<PackageReference Include="MediatR" Version="12.2.0" />
|
<PackageReference Include="MediatR" Version="12.3.0" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="8.0.0" />
|
<PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="8.0.0" />
|
||||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="8.0.1" />
|
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="8.0.1" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Http" Version="8.0.0" />
|
<PackageReference Include="Microsoft.Extensions.Http" Version="8.0.0" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="8.0.1" />
|
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="8.0.1" />
|
||||||
<PackageReference Include="Microsoft.IO.RecyclableMemoryStream" Version="3.0.0" />
|
<PackageReference Include="Microsoft.IO.RecyclableMemoryStream" Version="3.0.1" />
|
||||||
<PackageReference Include="Microsoft.VisualStudio.Threading.Analyzers" Version="17.10.48">
|
<PackageReference Include="Microsoft.VisualStudio.Threading.Analyzers" Version="17.10.48">
|
||||||
<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="Newtonsoft.Json" Version="13.0.3" />
|
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||||
<PackageReference Include="Serilog" Version="3.1.1" />
|
<PackageReference Include="Serilog" Version="4.0.0" />
|
||||||
<PackageReference Include="Serilog.Sinks.Console" Version="5.0.1" />
|
<PackageReference Include="Serilog.Sinks.Console" Version="6.0.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
using ErsatzTV.Core.Domain;
|
||||||
|
|
||||||
|
namespace ErsatzTV.Core.Interfaces.Scheduling;
|
||||||
|
|
||||||
|
public interface IPlayoutTimeShifter
|
||||||
|
{
|
||||||
|
public void TimeShift(Playout playout, DateTimeOffset now, bool force);
|
||||||
|
}
|
||||||
@@ -17,6 +17,7 @@ public class PlayoutBuilder : IPlayoutBuilder
|
|||||||
{
|
{
|
||||||
private static readonly Random Random = new();
|
private static readonly Random Random = new();
|
||||||
private readonly IArtistRepository _artistRepository;
|
private readonly IArtistRepository _artistRepository;
|
||||||
|
private readonly IPlayoutTimeShifter _playoutTimeShifter;
|
||||||
private readonly IConfigElementRepository _configElementRepository;
|
private readonly IConfigElementRepository _configElementRepository;
|
||||||
private readonly ILocalFileSystem _localFileSystem;
|
private readonly ILocalFileSystem _localFileSystem;
|
||||||
private readonly IMediaCollectionRepository _mediaCollectionRepository;
|
private readonly IMediaCollectionRepository _mediaCollectionRepository;
|
||||||
@@ -26,6 +27,7 @@ public class PlayoutBuilder : IPlayoutBuilder
|
|||||||
private ILogger<PlayoutBuilder> _logger;
|
private ILogger<PlayoutBuilder> _logger;
|
||||||
|
|
||||||
public PlayoutBuilder(
|
public PlayoutBuilder(
|
||||||
|
IPlayoutTimeShifter playoutTimeShifter,
|
||||||
IConfigElementRepository configElementRepository,
|
IConfigElementRepository configElementRepository,
|
||||||
IMediaCollectionRepository mediaCollectionRepository,
|
IMediaCollectionRepository mediaCollectionRepository,
|
||||||
ITelevisionRepository televisionRepository,
|
ITelevisionRepository televisionRepository,
|
||||||
@@ -34,6 +36,7 @@ public class PlayoutBuilder : IPlayoutBuilder
|
|||||||
ILocalFileSystem localFileSystem,
|
ILocalFileSystem localFileSystem,
|
||||||
ILogger<PlayoutBuilder> logger)
|
ILogger<PlayoutBuilder> logger)
|
||||||
{
|
{
|
||||||
|
_playoutTimeShifter = playoutTimeShifter;
|
||||||
_configElementRepository = configElementRepository;
|
_configElementRepository = configElementRepository;
|
||||||
_mediaCollectionRepository = mediaCollectionRepository;
|
_mediaCollectionRepository = mediaCollectionRepository;
|
||||||
_televisionRepository = televisionRepository;
|
_televisionRepository = televisionRepository;
|
||||||
@@ -79,6 +82,12 @@ public class PlayoutBuilder : IPlayoutBuilder
|
|||||||
// return await Build(playout, mode, parameters with { Start = parameters.Start.AddDays(-2) });
|
// return await Build(playout, mode, parameters with { Start = parameters.Start.AddDays(-2) });
|
||||||
// }
|
// }
|
||||||
|
|
||||||
|
// time shift on demand channel if needed
|
||||||
|
if (playout.Channel.ProgressMode is ChannelProgressMode.OnDemand && mode is not PlayoutBuildMode.Reset)
|
||||||
|
{
|
||||||
|
_playoutTimeShifter.TimeShift(playout, parameters.Start, false);
|
||||||
|
}
|
||||||
|
|
||||||
return await Build(playout, mode, parameters, cancellationToken);
|
return await Build(playout, mode, parameters, cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -238,6 +247,13 @@ public class PlayoutBuilder : IPlayoutBuilder
|
|||||||
playout.Items.Clear();
|
playout.Items.Clear();
|
||||||
playout.Anchor = null;
|
playout.Anchor = null;
|
||||||
playout.ProgramScheduleAnchors.Clear();
|
playout.ProgramScheduleAnchors.Clear();
|
||||||
|
playout.OnDemandCheckpoint = null;
|
||||||
|
|
||||||
|
// don't trim start for on demand channels, we want to time shift it all forward
|
||||||
|
if (playout.Channel.ProgressMode is ChannelProgressMode.OnDemand)
|
||||||
|
{
|
||||||
|
TrimStart = false;
|
||||||
|
}
|
||||||
|
|
||||||
await BuildPlayoutItems(
|
await BuildPlayoutItems(
|
||||||
playout,
|
playout,
|
||||||
@@ -247,6 +263,12 @@ public class PlayoutBuilder : IPlayoutBuilder
|
|||||||
true,
|
true,
|
||||||
cancellationToken);
|
cancellationToken);
|
||||||
|
|
||||||
|
// time shift on demand channel if needed
|
||||||
|
if (playout.Channel.ProgressMode is ChannelProgressMode.OnDemand)
|
||||||
|
{
|
||||||
|
_playoutTimeShifter.TimeShift(playout, parameters.Start, false);
|
||||||
|
}
|
||||||
|
|
||||||
return playout;
|
return playout;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -391,20 +413,24 @@ public class PlayoutBuilder : IPlayoutBuilder
|
|||||||
playout.Items.RemoveAll(old => old.FinishOffset < trimBefore);
|
playout.Items.RemoveAll(old => old.FinishOffset < trimBefore);
|
||||||
}
|
}
|
||||||
|
|
||||||
// check for future items that aren't grouped inside range
|
// on demand channels end up with slightly more than expected due to time shifting from midnight to first build
|
||||||
var futureItems = playout.Items.Filter(i => i.StartOffset > trimAfter).ToList();
|
if (playout.Channel.ProgressMode is not ChannelProgressMode.OnDemand)
|
||||||
foreach (PlayoutItem futureItem in futureItems)
|
|
||||||
{
|
{
|
||||||
if (playout.Items.All(i => i == futureItem || i.GuideGroup != futureItem.GuideGroup))
|
// check for future items that aren't grouped inside range
|
||||||
|
var futureItems = playout.Items.Filter(i => i.StartOffset > trimAfter).ToList();
|
||||||
|
foreach (PlayoutItem futureItem in futureItems)
|
||||||
{
|
{
|
||||||
_logger.LogError(
|
if (playout.Items.All(i => i == futureItem || i.GuideGroup != futureItem.GuideGroup))
|
||||||
"Playout item scheduled for {Time} after hard stop of {HardStop}",
|
{
|
||||||
futureItem.StartOffset,
|
_logger.LogError(
|
||||||
trimAfter);
|
"Playout item scheduled for {Time} after hard stop of {HardStop}",
|
||||||
|
futureItem.StartOffset,
|
||||||
|
trimAfter);
|
||||||
|
|
||||||
// it feels hacky to have to clean up a playlist like this,
|
// it feels hacky to have to clean up a playlist like this,
|
||||||
// so only log the error, and leave the bad data to fail tests
|
// so only log the error, and leave the bad data to fail tests
|
||||||
// playout.Items.Remove(futureItem);
|
// playout.Items.Remove(futureItem);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -425,6 +451,12 @@ public class PlayoutBuilder : IPlayoutBuilder
|
|||||||
playout.ProgramScheduleAlternates,
|
playout.ProgramScheduleAlternates,
|
||||||
playoutStart);
|
playoutStart);
|
||||||
|
|
||||||
|
// on demand channels do NOT use alternate schedules
|
||||||
|
if (playout.Channel.ProgressMode is ChannelProgressMode.OnDemand)
|
||||||
|
{
|
||||||
|
activeSchedule = playout.ProgramSchedule;
|
||||||
|
}
|
||||||
|
|
||||||
// _logger.LogDebug("Active schedule is: {Schedule}", activeSchedule.Name);
|
// _logger.LogDebug("Active schedule is: {Schedule}", activeSchedule.Name);
|
||||||
|
|
||||||
// random start points are disabled in some scenarios, so ensure it's enabled and active
|
// random start points are disabled in some scenarios, so ensure it's enabled and active
|
||||||
|
|||||||
@@ -0,0 +1,90 @@
|
|||||||
|
using ErsatzTV.Core.Domain;
|
||||||
|
using ErsatzTV.Core.Interfaces.FFmpeg;
|
||||||
|
using ErsatzTV.Core.Interfaces.Scheduling;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
|
namespace ErsatzTV.Core.Scheduling;
|
||||||
|
|
||||||
|
public class PlayoutTimeShifter(IFFmpegSegmenterService segmenterService, ILogger<PlayoutTimeShifter> logger)
|
||||||
|
: IPlayoutTimeShifter
|
||||||
|
{
|
||||||
|
public void TimeShift(Playout playout, DateTimeOffset now, bool force)
|
||||||
|
{
|
||||||
|
if (playout.Channel.ProgressMode is not ChannelProgressMode.OnDemand)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!force && segmenterService.IsActive(playout.Channel.Number))
|
||||||
|
{
|
||||||
|
logger.LogDebug(
|
||||||
|
"Will not time shift on demand playout that is active for channel {Number} - {Name}",
|
||||||
|
playout.Channel.Number,
|
||||||
|
playout.Channel.Name);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (playout.Items.Count == 0)
|
||||||
|
{
|
||||||
|
logger.LogDebug(
|
||||||
|
"Unable to time shift empty playout for channel {Number} - {Name}",
|
||||||
|
playout.Channel.Number,
|
||||||
|
playout.Channel.Name);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (playout.OnDemandCheckpoint is null)
|
||||||
|
{
|
||||||
|
logger.LogDebug(
|
||||||
|
"Time shifting unwatched playout for channel {Number} - {Name}",
|
||||||
|
playout.Channel.Number,
|
||||||
|
playout.Channel.Name);
|
||||||
|
|
||||||
|
playout.OnDemandCheckpoint = playout.Items.Min(p => p.StartOffset);
|
||||||
|
}
|
||||||
|
|
||||||
|
TimeSpan toOffset = now - playout.OnDemandCheckpoint.IfNone(now);
|
||||||
|
|
||||||
|
logger.LogDebug(
|
||||||
|
"Time shifting playout for channel {Number} - {Name} forward by {Time}",
|
||||||
|
playout.Channel.Number,
|
||||||
|
playout.Channel.Name,
|
||||||
|
toOffset);
|
||||||
|
|
||||||
|
// time shift items
|
||||||
|
foreach (PlayoutItem playoutItem in playout.Items)
|
||||||
|
{
|
||||||
|
playoutItem.Start += toOffset;
|
||||||
|
playoutItem.Finish += toOffset;
|
||||||
|
|
||||||
|
if (playoutItem.GuideStart.HasValue)
|
||||||
|
{
|
||||||
|
playoutItem.GuideStart += toOffset;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (playoutItem.GuideFinish.HasValue)
|
||||||
|
{
|
||||||
|
playoutItem.GuideFinish += toOffset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// time shift anchors
|
||||||
|
foreach (PlayoutProgramScheduleAnchor anchor in playout.ProgramScheduleAnchors)
|
||||||
|
{
|
||||||
|
if (anchor.AnchorDate.HasValue)
|
||||||
|
{
|
||||||
|
anchor.AnchorDate += toOffset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// time shift anchor
|
||||||
|
if (playout.Anchor is not null)
|
||||||
|
{
|
||||||
|
playout.Anchor.NextStart += toOffset;
|
||||||
|
}
|
||||||
|
|
||||||
|
playout.OnDemandCheckpoint = now;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,7 +10,7 @@
|
|||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="CliWrap" Version="3.6.6" />
|
<PackageReference Include="CliWrap" Version="3.6.6" />
|
||||||
<PackageReference Include="LanguageExt.Core" Version="4.4.8" />
|
<PackageReference Include="LanguageExt.Core" Version="4.4.9" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="8.0.0" />
|
<PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="8.0.0" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="8.0.1" />
|
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="8.0.1" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="8.0.5" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="8.0.7" />
|
||||||
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="8.0.2" />
|
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="8.0.2" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
|||||||
Generated
+5802
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,29 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace ErsatzTV.Infrastructure.MySql.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class Add_Channel_ProgressMode : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.AddColumn<int>(
|
||||||
|
name: "ProgressMode",
|
||||||
|
table: "Channel",
|
||||||
|
type: "int",
|
||||||
|
nullable: false,
|
||||||
|
defaultValue: 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "ProgressMode",
|
||||||
|
table: "Channel");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Generated
+5805
File diff suppressed because it is too large
Load Diff
+29
@@ -0,0 +1,29 @@
|
|||||||
|
using System;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace ErsatzTV.Infrastructure.MySql.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class Add_Playout_OnDemandCheckpoint : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.AddColumn<DateTimeOffset>(
|
||||||
|
name: "OnDemandCheckpoint",
|
||||||
|
table: "Playout",
|
||||||
|
type: "datetime(6)",
|
||||||
|
nullable: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "OnDemandCheckpoint",
|
||||||
|
table: "Playout");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -17,7 +17,7 @@ namespace ErsatzTV.Infrastructure.MySql.Migrations
|
|||||||
{
|
{
|
||||||
#pragma warning disable 612, 618
|
#pragma warning disable 612, 618
|
||||||
modelBuilder
|
modelBuilder
|
||||||
.HasAnnotation("ProductVersion", "8.0.5")
|
.HasAnnotation("ProductVersion", "8.0.7")
|
||||||
.HasAnnotation("Relational:MaxIdentifierLength", 64);
|
.HasAnnotation("Relational:MaxIdentifierLength", 64);
|
||||||
|
|
||||||
MySqlModelBuilderExtensions.AutoIncrementColumns(modelBuilder);
|
MySqlModelBuilderExtensions.AutoIncrementColumns(modelBuilder);
|
||||||
@@ -277,6 +277,9 @@ namespace ErsatzTV.Infrastructure.MySql.Migrations
|
|||||||
b.Property<string>("PreferredSubtitleLanguageCode")
|
b.Property<string>("PreferredSubtitleLanguageCode")
|
||||||
.HasColumnType("longtext");
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b.Property<int>("ProgressMode")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
b.Property<int>("StreamingMode")
|
b.Property<int>("StreamingMode")
|
||||||
.HasColumnType("int");
|
.HasColumnType("int");
|
||||||
|
|
||||||
@@ -1699,6 +1702,9 @@ namespace ErsatzTV.Infrastructure.MySql.Migrations
|
|||||||
b.Property<string>("ExternalJsonFile")
|
b.Property<string>("ExternalJsonFile")
|
||||||
.HasColumnType("longtext");
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b.Property<DateTimeOffset?>("OnDemandCheckpoint")
|
||||||
|
.HasColumnType("datetime(6)");
|
||||||
|
|
||||||
b.Property<int?>("ProgramScheduleId")
|
b.Property<int?>("ProgramScheduleId")
|
||||||
.HasColumnType("int");
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
|||||||
@@ -13,8 +13,8 @@
|
|||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Dapper" Version="2.1.35" />
|
<PackageReference Include="Dapper" Version="2.1.35" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="8.0.5" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="8.0.7" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.5" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.7" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Generated
+5641
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,29 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace ErsatzTV.Infrastructure.Sqlite.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class Add_Channel_ProgressMode : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.AddColumn<int>(
|
||||||
|
name: "ProgressMode",
|
||||||
|
table: "Channel",
|
||||||
|
type: "INTEGER",
|
||||||
|
nullable: false,
|
||||||
|
defaultValue: 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "ProgressMode",
|
||||||
|
table: "Channel");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Generated
+5644
File diff suppressed because it is too large
Load Diff
+29
@@ -0,0 +1,29 @@
|
|||||||
|
using System;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace ErsatzTV.Infrastructure.Sqlite.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class Add_Playout_OnDemandCheckpoint : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.AddColumn<DateTimeOffset>(
|
||||||
|
name: "OnDemandCheckpoint",
|
||||||
|
table: "Playout",
|
||||||
|
type: "TEXT",
|
||||||
|
nullable: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "OnDemandCheckpoint",
|
||||||
|
table: "Playout");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -15,7 +15,7 @@ namespace ErsatzTV.Infrastructure.Sqlite.Migrations
|
|||||||
protected override void BuildModel(ModelBuilder modelBuilder)
|
protected override void BuildModel(ModelBuilder modelBuilder)
|
||||||
{
|
{
|
||||||
#pragma warning disable 612, 618
|
#pragma warning disable 612, 618
|
||||||
modelBuilder.HasAnnotation("ProductVersion", "8.0.5");
|
modelBuilder.HasAnnotation("ProductVersion", "8.0.7");
|
||||||
|
|
||||||
modelBuilder.Entity("ErsatzTV.Core.Domain.Actor", b =>
|
modelBuilder.Entity("ErsatzTV.Core.Domain.Actor", b =>
|
||||||
{
|
{
|
||||||
@@ -264,6 +264,9 @@ namespace ErsatzTV.Infrastructure.Sqlite.Migrations
|
|||||||
b.Property<string>("PreferredSubtitleLanguageCode")
|
b.Property<string>("PreferredSubtitleLanguageCode")
|
||||||
.HasColumnType("TEXT");
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<int>("ProgressMode")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
b.Property<int>("StreamingMode")
|
b.Property<int>("StreamingMode")
|
||||||
.HasColumnType("INTEGER");
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
@@ -1612,6 +1615,9 @@ namespace ErsatzTV.Infrastructure.Sqlite.Migrations
|
|||||||
b.Property<string>("ExternalJsonFile")
|
b.Property<string>("ExternalJsonFile")
|
||||||
.HasColumnType("TEXT");
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<DateTimeOffset?>("OnDemandCheckpoint")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
b.Property<int?>("ProgramScheduleId")
|
b.Property<int?>("ProgramScheduleId")
|
||||||
.HasColumnType("INTEGER");
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
|||||||
@@ -13,24 +13,24 @@
|
|||||||
<PackageReference Include="Blurhash.ImageSharp" Version="3.0.0" />
|
<PackageReference Include="Blurhash.ImageSharp" Version="3.0.0" />
|
||||||
<PackageReference Include="CliWrap" Version="3.6.6" />
|
<PackageReference Include="CliWrap" Version="3.6.6" />
|
||||||
<PackageReference Include="Dapper" Version="2.1.35" />
|
<PackageReference Include="Dapper" Version="2.1.35" />
|
||||||
<PackageReference Include="Elastic.Clients.Elasticsearch" Version="8.13.12" />
|
<PackageReference Include="Elastic.Clients.Elasticsearch" Version="8.14.6" />
|
||||||
<PackageReference Include="Jint" Version="3.1.1" />
|
<PackageReference Include="Jint" Version="3.1.5" />
|
||||||
<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="8.0.5" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.7" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.5">
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.7">
|
||||||
<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.Relational" Version="8.0.5" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="8.0.7" />
|
||||||
<PackageReference Include="Microsoft.VisualStudio.Threading.Analyzers" Version="17.10.48">
|
<PackageReference Include="Microsoft.VisualStudio.Threading.Analyzers" Version="17.10.48">
|
||||||
<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="Refit" Version="7.0.0" />
|
<PackageReference Include="Refit" Version="7.1.2" />
|
||||||
<PackageReference Include="Refit.Newtonsoft.Json" Version="7.0.0" />
|
<PackageReference Include="Refit.Newtonsoft.Json" Version="7.1.2" />
|
||||||
<PackageReference Include="Refit.Xml" Version="7.0.0" />
|
<PackageReference Include="Refit.Xml" Version="7.1.2" />
|
||||||
<PackageReference Include="Scriban.Signed" Version="5.10.0" />
|
<PackageReference Include="Scriban.Signed" Version="5.10.0" />
|
||||||
<PackageReference Include="TagLibSharp" Version="2.3.0" />
|
<PackageReference Include="TagLibSharp" Version="2.3.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="FluentAssertions" Version="6.12.0" />
|
<PackageReference Include="FluentAssertions" Version="6.12.0" />
|
||||||
<PackageReference Include="LanguageExt.Core" Version="4.4.8" />
|
<PackageReference Include="LanguageExt.Core" Version="4.4.9" />
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.10.0" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.10.0" />
|
||||||
<PackageReference Include="NSubstitute" Version="5.1.0" />
|
<PackageReference Include="NSubstitute" Version="5.1.0" />
|
||||||
<PackageReference Include="NUnit" Version="4.1.0" />
|
<PackageReference Include="NUnit" Version="4.1.0" />
|
||||||
|
|||||||
@@ -22,17 +22,17 @@
|
|||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="CliWrap" Version="3.6.6" />
|
<PackageReference Include="CliWrap" Version="3.6.6" />
|
||||||
<PackageReference Include="Humanizer.Core" Version="2.14.1" />
|
<PackageReference Include="Humanizer.Core" Version="2.14.1" />
|
||||||
<PackageReference Include="LanguageExt.Core" Version="4.4.8" />
|
<PackageReference Include="LanguageExt.Core" Version="4.4.9" />
|
||||||
<PackageReference Include="MediatR" Version="12.2.0" />
|
<PackageReference Include="MediatR" Version="12.3.0" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="8.0.0" />
|
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="8.0.0" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Configuration" Version="8.0.0" />
|
<PackageReference Include="Microsoft.Extensions.Configuration" Version="8.0.0" />
|
||||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="8.0.1" />
|
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="8.0.1" />
|
||||||
<PackageReference Include="Serilog" Version="3.1.1" />
|
<PackageReference Include="Serilog" Version="4.0.0" />
|
||||||
<PackageReference Include="Serilog.AspNetCore" Version="8.0.1" />
|
<PackageReference Include="Serilog.AspNetCore" Version="8.0.1" />
|
||||||
<PackageReference Include="Serilog.Extensions.Hosting" Version="8.0.0" />
|
<PackageReference Include="Serilog.Extensions.Hosting" Version="8.0.0" />
|
||||||
<PackageReference Include="Serilog.Formatting.Compact" Version="2.0.0" />
|
<PackageReference Include="Serilog.Formatting.Compact" Version="3.0.0" />
|
||||||
<PackageReference Include="Serilog.Sinks.Console" Version="5.0.1" />
|
<PackageReference Include="Serilog.Sinks.Console" Version="6.0.0" />
|
||||||
<PackageReference Include="Serilog.Sinks.File" Version="5.0.0" />
|
<PackageReference Include="Serilog.Sinks.File" Version="6.0.0" />
|
||||||
<PackageReference Include="System.CommandLine" Version="2.0.0-beta4.22272.1" />
|
<PackageReference Include="System.CommandLine" Version="2.0.0-beta4.22272.1" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
|||||||
+12
-12
@@ -16,20 +16,20 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Blazored.FluentValidation" Version="2.1.0" />
|
<PackageReference Include="Blazored.FluentValidation" Version="2.2.0" />
|
||||||
<PackageReference Include="Bugsnag.AspNet.Core" Version="3.1.0" />
|
<PackageReference Include="Bugsnag.AspNet.Core" Version="3.1.0" />
|
||||||
<PackageReference Include="FluentValidation" Version="11.9.1" />
|
<PackageReference Include="FluentValidation" Version="11.9.2" />
|
||||||
<PackageReference Include="FluentValidation.AspNetCore" Version="11.3.0" />
|
<PackageReference Include="FluentValidation.AspNetCore" Version="11.3.0" />
|
||||||
<PackageReference Include="Heron.MudCalendar" Version="1.1.2" />
|
<PackageReference Include="Heron.MudCalendar" Version="1.1.2" />
|
||||||
<PackageReference Include="HtmlSanitizer" Version="8.0.865" />
|
<PackageReference Include="HtmlSanitizer" Version="8.0.865" />
|
||||||
<PackageReference Include="LanguageExt.Core" Version="4.4.8" />
|
<PackageReference Include="LanguageExt.Core" Version="4.4.9" />
|
||||||
<PackageReference Include="Markdig" Version="0.37.0" />
|
<PackageReference Include="Markdig" Version="0.37.0" />
|
||||||
<PackageReference Include="MediatR.Courier.DependencyInjection" Version="5.0.0" />
|
<PackageReference Include="MediatR.Courier.DependencyInjection" Version="5.0.0" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.5" />
|
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.7" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.OpenIdConnect" Version="8.0.5" />
|
<PackageReference Include="Microsoft.AspNetCore.Authentication.OpenIdConnect" Version="8.0.7" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="8.0.5" />
|
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="8.0.7" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.SpaServices.Extensions" Version="8.0.5" />
|
<PackageReference Include="Microsoft.AspNetCore.SpaServices.Extensions" Version="8.0.7" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.5">
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.7">
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
@@ -37,12 +37,12 @@
|
|||||||
<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="MudBlazor" Version="6.19.1" />
|
<PackageReference Include="MudBlazor" Version="6.21.0" />
|
||||||
<PackageReference Include="NaturalSort.Extension" Version="4.3.0" />
|
<PackageReference Include="NaturalSort.Extension" Version="4.3.0" />
|
||||||
<PackageReference Include="Refit.HttpClientFactory" Version="7.0.0" />
|
<PackageReference Include="Refit.HttpClientFactory" Version="7.1.2" />
|
||||||
<PackageReference Include="Serilog" Version="3.1.1" />
|
<PackageReference Include="Serilog" Version="4.0.0" />
|
||||||
<PackageReference Include="Serilog.AspNetCore" Version="8.0.1" />
|
<PackageReference Include="Serilog.AspNetCore" Version="8.0.1" />
|
||||||
<PackageReference Include="Serilog.Settings.Configuration" Version="8.0.0" />
|
<PackageReference Include="Serilog.Settings.Configuration" Version="8.0.2" />
|
||||||
<PackageReference Include="System.IO.FileSystem.Primitives" Version="4.3.0" />
|
<PackageReference Include="System.IO.FileSystem.Primitives" Version="4.3.0" />
|
||||||
<PackageReference Include="System.Text.Encoding.Extensions" Version="4.3.0" />
|
<PackageReference Include="System.Text.Encoding.Extensions" Version="4.3.0" />
|
||||||
<PackageReference Include="System.Runtime.Handles" Version="4.3.0" />
|
<PackageReference Include="System.Runtime.Handles" Version="4.3.0" />
|
||||||
|
|||||||
@@ -30,6 +30,10 @@
|
|||||||
<MudTextField Class="mt-3" Label="Name" @bind-Value="_model.Name" For="@(() => _model.Name)"/>
|
<MudTextField Class="mt-3" Label="Name" @bind-Value="_model.Name" For="@(() => _model.Name)"/>
|
||||||
<MudTextField Class="mt-3" Label="Group" @bind-Value="_model.Group" For="@(() => _model.Group)"/>
|
<MudTextField Class="mt-3" Label="Group" @bind-Value="_model.Group" For="@(() => _model.Group)"/>
|
||||||
<MudTextField Class="mt-3" Label="Categories" @bind-Value="_model.Categories" For="@(() => _model.Categories)" Placeholder="Comma-separated list of categories"/>
|
<MudTextField Class="mt-3" Label="Categories" @bind-Value="_model.Categories" For="@(() => _model.Categories)" Placeholder="Comma-separated list of categories"/>
|
||||||
|
<MudSelect Class="mt-3" Label="Progress Mode" @bind-Value="_model.ProgressMode" For="@(() => _model.ProgressMode)">
|
||||||
|
<MudSelectItem Value="@(ChannelProgressMode.Always)">Always</MudSelectItem>
|
||||||
|
<MudSelectItem Value="@(ChannelProgressMode.OnDemand)">On Demand</MudSelectItem>
|
||||||
|
</MudSelect>
|
||||||
<MudSelect Class="mt-3" Label="Streaming Mode" @bind-Value="_model.StreamingMode" For="@(() => _model.StreamingMode)">
|
<MudSelect Class="mt-3" Label="Streaming Mode" @bind-Value="_model.StreamingMode" For="@(() => _model.StreamingMode)">
|
||||||
<MudSelectItem Value="@(StreamingMode.TransportStreamHybrid)">MPEG-TS</MudSelectItem>
|
<MudSelectItem Value="@(StreamingMode.TransportStreamHybrid)">MPEG-TS</MudSelectItem>
|
||||||
<MudSelectItem Value="@(StreamingMode.TransportStream)">MPEG-TS (Legacy)</MudSelectItem>
|
<MudSelectItem Value="@(StreamingMode.TransportStream)">MPEG-TS (Legacy)</MudSelectItem>
|
||||||
@@ -180,6 +184,7 @@
|
|||||||
_model.Number = channelViewModel.Number;
|
_model.Number = channelViewModel.Number;
|
||||||
_model.FFmpegProfileId = channelViewModel.FFmpegProfileId;
|
_model.FFmpegProfileId = channelViewModel.FFmpegProfileId;
|
||||||
_model.Logo = channelViewModel.Logo;
|
_model.Logo = channelViewModel.Logo;
|
||||||
|
_model.ProgressMode = channelViewModel.ProgressMode;
|
||||||
_model.StreamingMode = channelViewModel.StreamingMode;
|
_model.StreamingMode = channelViewModel.StreamingMode;
|
||||||
_model.PreferredAudioLanguageCode = channelViewModel.PreferredAudioLanguageCode;
|
_model.PreferredAudioLanguageCode = channelViewModel.PreferredAudioLanguageCode;
|
||||||
_model.PreferredAudioTitle = channelViewModel.PreferredAudioTitle;
|
_model.PreferredAudioTitle = channelViewModel.PreferredAudioTitle;
|
||||||
|
|||||||
@@ -81,12 +81,24 @@
|
|||||||
</div>
|
</div>
|
||||||
@if (context.PlayoutType == ProgramSchedulePlayoutType.Flood)
|
@if (context.PlayoutType == ProgramSchedulePlayoutType.Flood)
|
||||||
{
|
{
|
||||||
<MudTooltip Text="Edit Alternate Schedules">
|
if (context.ProgressMode is ChannelProgressMode.OnDemand)
|
||||||
<MudIconButton Icon="@Icons.Material.Filled.EditCalendar"
|
{
|
||||||
Disabled="@EntityLocker.IsPlayoutLocked(context.PlayoutId)"
|
<MudTooltip Text="Alternate Schedules are not supported with On Demand progress">
|
||||||
Href="@($"playouts/{context.PlayoutId}/alternate-schedules")">
|
<MudIconButton Icon="@Icons.Material.Filled.EditCalendar"
|
||||||
</MudIconButton>
|
Disabled="true">
|
||||||
</MudTooltip>
|
</MudIconButton>
|
||||||
|
</MudTooltip>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<MudTooltip Text="Edit Alternate Schedules">
|
||||||
|
<MudIconButton Icon="@Icons.Material.Filled.EditCalendar"
|
||||||
|
Disabled="@EntityLocker.IsPlayoutLocked(context.PlayoutId)"
|
||||||
|
Href="@($"playouts/{context.PlayoutId}/alternate-schedules")">
|
||||||
|
</MudIconButton>
|
||||||
|
</MudTooltip>
|
||||||
|
}
|
||||||
|
|
||||||
<MudTooltip Text="Reset Playout">
|
<MudTooltip Text="Reset Playout">
|
||||||
<MudIconButton Icon="@Icons.Material.Filled.Refresh"
|
<MudIconButton Icon="@Icons.Material.Filled.Refresh"
|
||||||
Disabled="@EntityLocker.IsPlayoutLocked(context.PlayoutId)"
|
Disabled="@EntityLocker.IsPlayoutLocked(context.PlayoutId)"
|
||||||
|
|||||||
@@ -71,6 +71,9 @@ public class WorkerService : BackgroundService
|
|||||||
buildPlayout.PlayoutId,
|
buildPlayout.PlayoutId,
|
||||||
error.Value));
|
error.Value));
|
||||||
break;
|
break;
|
||||||
|
case TimeShiftOnDemandPlayout timeShiftOnDemandPlayout:
|
||||||
|
await mediator.Send(timeShiftOnDemandPlayout, stoppingToken);
|
||||||
|
break;
|
||||||
case DeleteOrphanedArtwork deleteOrphanedArtwork:
|
case DeleteOrphanedArtwork deleteOrphanedArtwork:
|
||||||
await mediator.Send(deleteOrphanedArtwork, stoppingToken);
|
await mediator.Send(deleteOrphanedArtwork, stoppingToken);
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
|
using System.Net.Security;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
@@ -654,6 +655,7 @@ public class Startup
|
|||||||
services.AddScoped<IBlockPlayoutBuilder, BlockPlayoutBuilder>();
|
services.AddScoped<IBlockPlayoutBuilder, BlockPlayoutBuilder>();
|
||||||
services.AddScoped<IBlockPlayoutPreviewBuilder, BlockPlayoutPreviewBuilder>();
|
services.AddScoped<IBlockPlayoutPreviewBuilder, BlockPlayoutPreviewBuilder>();
|
||||||
services.AddScoped<IExternalJsonPlayoutBuilder, ExternalJsonPlayoutBuilder>();
|
services.AddScoped<IExternalJsonPlayoutBuilder, ExternalJsonPlayoutBuilder>();
|
||||||
|
services.AddScoped<IPlayoutTimeShifter, PlayoutTimeShifter>();
|
||||||
services.AddScoped<IImageCache, ImageCache>();
|
services.AddScoped<IImageCache, ImageCache>();
|
||||||
services.AddScoped<ILocalFileSystem, LocalFileSystem>();
|
services.AddScoped<ILocalFileSystem, LocalFileSystem>();
|
||||||
services.AddScoped<IPlexServerApiClient, PlexServerApiClient>();
|
services.AddScoped<IPlexServerApiClient, PlexServerApiClient>();
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ public class ChannelEditViewModel
|
|||||||
public string PreferredAudioLanguageCode { get; set; }
|
public string PreferredAudioLanguageCode { get; set; }
|
||||||
public string PreferredAudioTitle { get; set; }
|
public string PreferredAudioTitle { get; set; }
|
||||||
public string Logo { get; set; }
|
public string Logo { get; set; }
|
||||||
|
public ChannelProgressMode ProgressMode { get; set; }
|
||||||
public StreamingMode StreamingMode { get; set; }
|
public StreamingMode StreamingMode { get; set; }
|
||||||
public int? WatermarkId { get; set; }
|
public int? WatermarkId { get; set; }
|
||||||
public int? FallbackFillerId { get; set; }
|
public int? FallbackFillerId { get; set; }
|
||||||
@@ -41,6 +42,7 @@ public class ChannelEditViewModel
|
|||||||
Logo,
|
Logo,
|
||||||
PreferredAudioLanguageCode,
|
PreferredAudioLanguageCode,
|
||||||
PreferredAudioTitle,
|
PreferredAudioTitle,
|
||||||
|
ProgressMode,
|
||||||
StreamingMode,
|
StreamingMode,
|
||||||
WatermarkId,
|
WatermarkId,
|
||||||
FallbackFillerId,
|
FallbackFillerId,
|
||||||
@@ -59,6 +61,7 @@ public class ChannelEditViewModel
|
|||||||
Logo,
|
Logo,
|
||||||
PreferredAudioLanguageCode,
|
PreferredAudioLanguageCode,
|
||||||
PreferredAudioTitle,
|
PreferredAudioTitle,
|
||||||
|
ProgressMode,
|
||||||
StreamingMode,
|
StreamingMode,
|
||||||
WatermarkId,
|
WatermarkId,
|
||||||
FallbackFillerId,
|
FallbackFillerId,
|
||||||
|
|||||||
Reference in New Issue
Block a user