enable graphics engine in playback troubleshooting (#2274)
* enable graphics engine in playback troubleshooting * fix text subtitles with graphics engine (watermarks)
This commit is contained in:
@@ -463,6 +463,8 @@ public class HlsSessionWorker : IHlsSessionWorker
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
using var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
|
||||||
|
|
||||||
var processWithPipe = process;
|
var processWithPipe = process;
|
||||||
foreach (var graphicsEngineContext in processModel.GraphicsEngineContext)
|
foreach (var graphicsEngineContext in processModel.GraphicsEngineContext)
|
||||||
{
|
{
|
||||||
@@ -474,13 +476,13 @@ public class HlsSessionWorker : IHlsSessionWorker
|
|||||||
_ = _graphicsEngine.Run(
|
_ = _graphicsEngine.Run(
|
||||||
graphicsEngineContext,
|
graphicsEngineContext,
|
||||||
pipe.Writer,
|
pipe.Writer,
|
||||||
cancellationToken);
|
linkedCts.Token);
|
||||||
}
|
}
|
||||||
|
|
||||||
CommandResult commandResult = await processWithPipe
|
CommandResult commandResult = await processWithPipe
|
||||||
.WithStandardErrorPipe(PipeTarget.ToStringBuilder(stdErrBuffer))
|
.WithStandardErrorPipe(PipeTarget.ToStringBuilder(stdErrBuffer))
|
||||||
.WithValidation(CommandResultValidation.None)
|
.WithValidation(CommandResultValidation.None)
|
||||||
.ExecuteAsync(cancellationToken);
|
.ExecuteAsync(linkedCts.Token);
|
||||||
|
|
||||||
if (commandResult.ExitCode == 0)
|
if (commandResult.ExitCode == 0)
|
||||||
{
|
{
|
||||||
@@ -493,6 +495,8 @@ public class HlsSessionWorker : IHlsSessionWorker
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
await linkedCts.CancelAsync();
|
||||||
|
|
||||||
// detect the non-zero exit code and transcode the ffmpeg error message instead
|
// detect the non-zero exit code and transcode the ffmpeg error message instead
|
||||||
string errorMessage = stdErrBuffer.ToString();
|
string errorMessage = stdErrBuffer.ToString();
|
||||||
if (string.IsNullOrWhiteSpace(errorMessage))
|
if (string.IsNullOrWhiteSpace(errorMessage))
|
||||||
@@ -515,6 +519,7 @@ public class HlsSessionWorker : IHlsSessionWorker
|
|||||||
processModel.MaybeDuration,
|
processModel.MaybeDuration,
|
||||||
processModel.Until,
|
processModel.Until,
|
||||||
errorMessage),
|
errorMessage),
|
||||||
|
// ReSharper disable once PossiblyMistakenUseOfCancellationToken
|
||||||
cancellationToken);
|
cancellationToken);
|
||||||
|
|
||||||
foreach (PlayoutItemProcessModel errorProcessModel in maybeOfflineProcess.RightAsEnumerable())
|
foreach (PlayoutItemProcessModel errorProcessModel in maybeOfflineProcess.RightAsEnumerable())
|
||||||
@@ -527,6 +532,7 @@ public class HlsSessionWorker : IHlsSessionWorker
|
|||||||
|
|
||||||
commandResult = await errorProcess
|
commandResult = await errorProcess
|
||||||
.WithValidation(CommandResultValidation.None)
|
.WithValidation(CommandResultValidation.None)
|
||||||
|
// ReSharper disable once PossiblyMistakenUseOfCancellationToken
|
||||||
.ExecuteBufferedAsync(Encoding.UTF8, cancellationToken);
|
.ExecuteBufferedAsync(Encoding.UTF8, cancellationToken);
|
||||||
|
|
||||||
if (commandResult.ExitCode == 0)
|
if (commandResult.ExitCode == 0)
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
using CliWrap;
|
|
||||||
using ErsatzTV.Core;
|
using ErsatzTV.Core;
|
||||||
|
using ErsatzTV.Core.Interfaces.FFmpeg;
|
||||||
|
|
||||||
namespace ErsatzTV.Application.Troubleshooting;
|
namespace ErsatzTV.Application.Troubleshooting;
|
||||||
|
|
||||||
@@ -9,4 +9,4 @@ public record PrepareTroubleshootingPlayback(
|
|||||||
int WatermarkId,
|
int WatermarkId,
|
||||||
int? SubtitleId,
|
int? SubtitleId,
|
||||||
bool StartFromBeginning)
|
bool StartFromBeginning)
|
||||||
: IRequest<Either<BaseError, Command>>;
|
: IRequest<Either<BaseError, PlayoutItemResult>>;
|
||||||
|
|||||||
+5
-7
@@ -29,9 +29,9 @@ public class PrepareTroubleshootingPlaybackHandler(
|
|||||||
ILocalFileSystem localFileSystem,
|
ILocalFileSystem localFileSystem,
|
||||||
IEntityLocker entityLocker,
|
IEntityLocker entityLocker,
|
||||||
ILogger<PrepareTroubleshootingPlaybackHandler> logger)
|
ILogger<PrepareTroubleshootingPlaybackHandler> logger)
|
||||||
: IRequestHandler<PrepareTroubleshootingPlayback, Either<BaseError, Command>>
|
: IRequestHandler<PrepareTroubleshootingPlayback, Either<BaseError, PlayoutItemResult>>
|
||||||
{
|
{
|
||||||
public async Task<Either<BaseError, Command>> Handle(PrepareTroubleshootingPlayback request, CancellationToken cancellationToken)
|
public async Task<Either<BaseError, PlayoutItemResult>> Handle(PrepareTroubleshootingPlayback request, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -39,7 +39,7 @@ public class PrepareTroubleshootingPlaybackHandler(
|
|||||||
Validation<BaseError, Tuple<MediaItem, string, string, FFmpegProfile>> validation = await Validate(dbContext, request);
|
Validation<BaseError, Tuple<MediaItem, string, string, FFmpegProfile>> validation = await Validate(dbContext, request);
|
||||||
return await validation.Match(
|
return await validation.Match(
|
||||||
tuple => GetProcess(dbContext, request, tuple.Item1, tuple.Item2, tuple.Item3, tuple.Item4),
|
tuple => GetProcess(dbContext, request, tuple.Item1, tuple.Item2, tuple.Item3, tuple.Item4),
|
||||||
error => Task.FromResult<Either<BaseError, Command>>(error.Join()));
|
error => Task.FromResult<Either<BaseError, PlayoutItemResult>>(error.Join()));
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -49,7 +49,7 @@ public class PrepareTroubleshootingPlaybackHandler(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<Either<BaseError, Command>> GetProcess(
|
private async Task<Either<BaseError, PlayoutItemResult>> GetProcess(
|
||||||
TvContext dbContext,
|
TvContext dbContext,
|
||||||
PrepareTroubleshootingPlayback request,
|
PrepareTroubleshootingPlayback request,
|
||||||
MediaItem mediaItem,
|
MediaItem mediaItem,
|
||||||
@@ -150,9 +150,7 @@ public class PrepareTroubleshootingPlaybackHandler(
|
|||||||
FileSystemLayout.TranscodeTroubleshootingFolder,
|
FileSystemLayout.TranscodeTroubleshootingFolder,
|
||||||
_ => { });
|
_ => { });
|
||||||
|
|
||||||
// TODO: graphics engine?
|
return playoutItemResult;
|
||||||
|
|
||||||
return playoutItemResult.Process;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static async Task<List<Subtitle>> GetSelectedSubtitle(MediaItem mediaItem, PrepareTroubleshootingPlayback request)
|
private static async Task<List<Subtitle>> GetSelectedSubtitle(MediaItem mediaItem, PrepareTroubleshootingPlayback request)
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
using CliWrap;
|
|
||||||
using ErsatzTV.Application.MediaItems;
|
using ErsatzTV.Application.MediaItems;
|
||||||
|
using ErsatzTV.Core.Interfaces.FFmpeg;
|
||||||
|
|
||||||
namespace ErsatzTV.Application.Troubleshooting;
|
namespace ErsatzTV.Application.Troubleshooting;
|
||||||
|
|
||||||
public record StartTroubleshootingPlayback(
|
public record StartTroubleshootingPlayback(
|
||||||
Command Command,
|
Guid SessionId,
|
||||||
|
PlayoutItemResult PlayoutItemResult,
|
||||||
MediaItemInfo MediaItemInfo,
|
MediaItemInfo MediaItemInfo,
|
||||||
TroubleshootingInfo TroubleshootingInfo) : IRequest, IFFmpegWorkerRequest;
|
TroubleshootingInfo TroubleshootingInfo) : IRequest, IFFmpegWorkerRequest;
|
||||||
|
|||||||
+52
-9
@@ -1,11 +1,13 @@
|
|||||||
|
using System.IO.Pipelines;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using System.Text.Json.Serialization;
|
using System.Text.Json.Serialization;
|
||||||
using CliWrap;
|
using CliWrap;
|
||||||
using CliWrap.Buffered;
|
|
||||||
using ErsatzTV.Core;
|
using ErsatzTV.Core;
|
||||||
using ErsatzTV.Core.Domain;
|
using ErsatzTV.Core.Domain;
|
||||||
using ErsatzTV.Core.Interfaces.Locking;
|
using ErsatzTV.Core.Interfaces.Locking;
|
||||||
|
using ErsatzTV.Core.Interfaces.Streaming;
|
||||||
|
using ErsatzTV.Core.Interfaces.Troubleshooting;
|
||||||
using ErsatzTV.Core.Notifications;
|
using ErsatzTV.Core.Notifications;
|
||||||
using ErsatzTV.FFmpeg.Runtime;
|
using ErsatzTV.FFmpeg.Runtime;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
@@ -13,9 +15,11 @@ using Microsoft.Extensions.Logging;
|
|||||||
namespace ErsatzTV.Application.Troubleshooting;
|
namespace ErsatzTV.Application.Troubleshooting;
|
||||||
|
|
||||||
public class StartTroubleshootingPlaybackHandler(
|
public class StartTroubleshootingPlaybackHandler(
|
||||||
|
ITroubleshootingNotifier notifier,
|
||||||
IMediator mediator,
|
IMediator mediator,
|
||||||
IEntityLocker entityLocker,
|
IEntityLocker entityLocker,
|
||||||
IRuntimeInfo runtimeInfo,
|
IRuntimeInfo runtimeInfo,
|
||||||
|
IGraphicsEngine graphicsEngine,
|
||||||
ILogger<StartTroubleshootingPlaybackHandler> logger)
|
ILogger<StartTroubleshootingPlaybackHandler> logger)
|
||||||
: IRequestHandler<StartTroubleshootingPlayback>
|
: IRequestHandler<StartTroubleshootingPlayback>
|
||||||
{
|
{
|
||||||
@@ -83,17 +87,56 @@ public class StartTroubleshootingPlaybackHandler(
|
|||||||
cancellationToken);
|
cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.LogDebug("ffmpeg troubleshooting arguments {FFmpegArguments}", request.Command.Arguments);
|
logger.LogDebug("ffmpeg troubleshooting arguments {FFmpegArguments}", request.PlayoutItemResult.Process.Arguments);
|
||||||
|
|
||||||
BufferedCommandResult result = await request.Command
|
var maybePipe = Option<Pipe>.None;
|
||||||
.WithValidation(CommandResultValidation.None)
|
|
||||||
.ExecuteBufferedAsync(cancellationToken);
|
|
||||||
|
|
||||||
await mediator.Publish(
|
try
|
||||||
new PlaybackTroubleshootingCompletedNotification(result.ExitCode),
|
{
|
||||||
cancellationToken);
|
using var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
|
||||||
|
|
||||||
logger.LogDebug("Troubleshooting playback completed with exit code {ExitCode}", result.ExitCode);
|
var processWithPipe = request.PlayoutItemResult.Process;
|
||||||
|
foreach (var graphicsEngineContext in request.PlayoutItemResult.GraphicsEngineContext)
|
||||||
|
{
|
||||||
|
var pipe = new Pipe();
|
||||||
|
maybePipe = pipe;
|
||||||
|
processWithPipe = processWithPipe.WithStandardInputPipe(PipeSource.FromStream(pipe.Reader.AsStream()));
|
||||||
|
|
||||||
|
// fire and forget graphics engine task
|
||||||
|
_ = graphicsEngine.Run(
|
||||||
|
graphicsEngineContext,
|
||||||
|
pipe.Writer,
|
||||||
|
linkedCts.Token);
|
||||||
|
}
|
||||||
|
|
||||||
|
CommandResult commandResult = await processWithPipe
|
||||||
|
.WithStandardErrorPipe(PipeTarget.Null)
|
||||||
|
.WithValidation(CommandResultValidation.None)
|
||||||
|
.ExecuteAsync(linkedCts.Token);
|
||||||
|
|
||||||
|
await mediator.Publish(
|
||||||
|
new PlaybackTroubleshootingCompletedNotification(commandResult.ExitCode),
|
||||||
|
linkedCts.Token);
|
||||||
|
|
||||||
|
logger.LogDebug("Troubleshooting playback completed with exit code {ExitCode}", commandResult.ExitCode);
|
||||||
|
|
||||||
|
if (commandResult.ExitCode != 0)
|
||||||
|
{
|
||||||
|
await linkedCts.CancelAsync();
|
||||||
|
notifier.NotifyFailed(request.SessionId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Console.WriteLine(e);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
foreach (var pipe in maybePipe)
|
||||||
|
{
|
||||||
|
await pipe.Writer.CompleteAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
namespace ErsatzTV.Core.Interfaces.Troubleshooting;
|
||||||
|
|
||||||
|
public interface ITroubleshootingNotifier
|
||||||
|
{
|
||||||
|
bool IsFailed(Guid sessionId);
|
||||||
|
|
||||||
|
void NotifyFailed(Guid sessionId);
|
||||||
|
|
||||||
|
void RemoveSession(Guid sessionId);
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
using System.Collections.Concurrent;
|
||||||
|
using ErsatzTV.Core.Interfaces.Troubleshooting;
|
||||||
|
|
||||||
|
namespace ErsatzTV.Core.Troubleshooting;
|
||||||
|
|
||||||
|
public class TroubleshootingNotifier : ITroubleshootingNotifier
|
||||||
|
{
|
||||||
|
private readonly ConcurrentDictionary<Guid, bool> _failedSessions = new();
|
||||||
|
|
||||||
|
public bool IsFailed(Guid sessionId)
|
||||||
|
{
|
||||||
|
return _failedSessions.TryGetValue(sessionId, out _);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void NotifyFailed(Guid sessionId)
|
||||||
|
{
|
||||||
|
_failedSessions[sessionId] = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RemoveSession(Guid sessionId)
|
||||||
|
{
|
||||||
|
_failedSessions.TryRemove(sessionId, out _);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -91,7 +91,7 @@ public class ComplexFilter : IPipelineStep
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach ((string path, _) in _maybeSubtitleInputFile)
|
foreach ((string path, _) in _maybeSubtitleInputFile.Filter(s => s.IsImageBased))
|
||||||
{
|
{
|
||||||
if (!distinctPaths.Contains(path))
|
if (!distinctPaths.Contains(path))
|
||||||
{
|
{
|
||||||
@@ -149,7 +149,7 @@ public class ComplexFilter : IPipelineStep
|
|||||||
}
|
}
|
||||||
|
|
||||||
foreach (SubtitleInputFile subtitleInputFile in _maybeSubtitleInputFile.Filter(s =>
|
foreach (SubtitleInputFile subtitleInputFile in _maybeSubtitleInputFile.Filter(s =>
|
||||||
s.Method == SubtitleMethod.Burn))
|
s is { IsImageBased: true, Method: SubtitleMethod.Burn }))
|
||||||
{
|
{
|
||||||
int inputIndex = distinctPaths.IndexOf(subtitleInputFile.Path);
|
int inputIndex = distinctPaths.IndexOf(subtitleInputFile.Path);
|
||||||
foreach ((int index, _, _) in subtitleInputFile.Streams)
|
foreach ((int index, _, _) in subtitleInputFile.Streams)
|
||||||
|
|||||||
@@ -87,6 +87,10 @@ public class GraphicsEngine(ILogger<GraphicsEngine> logger) : IGraphicsEngine
|
|||||||
frameCount++;
|
frameCount++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
// do nothing; don't want to throw on a background task
|
||||||
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
await pipeWriter.CompleteAsync();
|
await pipeWriter.CompleteAsync();
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
using System.Threading.Channels;
|
using System.Threading.Channels;
|
||||||
using CliWrap;
|
|
||||||
using ErsatzTV.Application;
|
using ErsatzTV.Application;
|
||||||
using ErsatzTV.Application.MediaItems;
|
using ErsatzTV.Application.MediaItems;
|
||||||
using ErsatzTV.Application.Troubleshooting;
|
using ErsatzTV.Application.Troubleshooting;
|
||||||
using ErsatzTV.Application.Troubleshooting.Queries;
|
using ErsatzTV.Application.Troubleshooting.Queries;
|
||||||
using ErsatzTV.Core;
|
using ErsatzTV.Core;
|
||||||
|
using ErsatzTV.Core.Interfaces.FFmpeg;
|
||||||
using ErsatzTV.Core.Interfaces.Metadata;
|
using ErsatzTV.Core.Interfaces.Metadata;
|
||||||
|
using ErsatzTV.Core.Interfaces.Troubleshooting;
|
||||||
using MediatR;
|
using MediatR;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
|
||||||
@@ -15,6 +16,7 @@ namespace ErsatzTV.Controllers.Api;
|
|||||||
public class TroubleshootController(
|
public class TroubleshootController(
|
||||||
ChannelWriter<IFFmpegWorkerRequest> channelWriter,
|
ChannelWriter<IFFmpegWorkerRequest> channelWriter,
|
||||||
ILocalFileSystem localFileSystem,
|
ILocalFileSystem localFileSystem,
|
||||||
|
ITroubleshootingNotifier notifier,
|
||||||
IMediator mediator) : ControllerBase
|
IMediator mediator) : ControllerBase
|
||||||
{
|
{
|
||||||
[HttpHead("api/troubleshoot/playback.m3u8")]
|
[HttpHead("api/troubleshoot/playback.m3u8")]
|
||||||
@@ -32,50 +34,75 @@ public class TroubleshootController(
|
|||||||
bool startFromBeginning,
|
bool startFromBeginning,
|
||||||
CancellationToken cancellationToken)
|
CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
Either<BaseError, Command> result = await mediator.Send(
|
try
|
||||||
new PrepareTroubleshootingPlayback(mediaItem, ffmpegProfile, watermark, subtitleId, startFromBeginning),
|
{
|
||||||
cancellationToken);
|
Either<BaseError, PlayoutItemResult> result = await mediator.Send(
|
||||||
|
new PrepareTroubleshootingPlayback(mediaItem, ffmpegProfile, watermark, subtitleId, startFromBeginning),
|
||||||
|
cancellationToken);
|
||||||
|
|
||||||
return await result.MatchAsync<IActionResult>(
|
if (result.IsLeft)
|
||||||
async command =>
|
|
||||||
{
|
{
|
||||||
Either<BaseError, MediaItemInfo> maybeMediaInfo = await mediator.Send(new GetMediaItemInfo(mediaItem), cancellationToken);
|
return NotFound();
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var playoutItemResult in result.RightToSeq())
|
||||||
|
{
|
||||||
|
Either<BaseError, MediaItemInfo> maybeMediaInfo =
|
||||||
|
await mediator.Send(new GetMediaItemInfo(mediaItem), cancellationToken);
|
||||||
foreach (MediaItemInfo mediaInfo in maybeMediaInfo.RightToSeq())
|
foreach (MediaItemInfo mediaInfo in maybeMediaInfo.RightToSeq())
|
||||||
{
|
{
|
||||||
TroubleshootingInfo troubleshootingInfo = await mediator.Send(
|
var sessionId = Guid.NewGuid();
|
||||||
new GetTroubleshootingInfo(),
|
|
||||||
cancellationToken);
|
|
||||||
|
|
||||||
// filter ffmpeg profiles
|
try
|
||||||
troubleshootingInfo.FFmpegProfiles.RemoveAll(p => p.Id != ffmpegProfile);
|
|
||||||
|
|
||||||
// filter watermarks
|
|
||||||
troubleshootingInfo.Watermarks.RemoveAll(p => p.Id != watermark);
|
|
||||||
|
|
||||||
await channelWriter.WriteAsync(
|
|
||||||
new StartTroubleshootingPlayback(command, mediaInfo, troubleshootingInfo),
|
|
||||||
cancellationToken);
|
|
||||||
|
|
||||||
string playlistFile = Path.Combine(
|
|
||||||
FileSystemLayout.TranscodeFolder,
|
|
||||||
".troubleshooting",
|
|
||||||
"live.m3u8");
|
|
||||||
|
|
||||||
while (!localFileSystem.FileExists(playlistFile))
|
|
||||||
{
|
{
|
||||||
await Task.Delay(TimeSpan.FromMilliseconds(250), cancellationToken);
|
TroubleshootingInfo troubleshootingInfo = await mediator.Send(
|
||||||
if (cancellationToken.IsCancellationRequested)
|
new GetTroubleshootingInfo(),
|
||||||
|
cancellationToken);
|
||||||
|
|
||||||
|
// filter ffmpeg profiles
|
||||||
|
troubleshootingInfo.FFmpegProfiles.RemoveAll(p => p.Id != ffmpegProfile);
|
||||||
|
|
||||||
|
// filter watermarks
|
||||||
|
troubleshootingInfo.Watermarks.RemoveAll(p => p.Id != watermark);
|
||||||
|
|
||||||
|
await channelWriter.WriteAsync(
|
||||||
|
new StartTroubleshootingPlayback(sessionId, playoutItemResult, mediaInfo,
|
||||||
|
troubleshootingInfo),
|
||||||
|
cancellationToken);
|
||||||
|
|
||||||
|
string playlistFile = Path.Combine(
|
||||||
|
FileSystemLayout.TranscodeFolder,
|
||||||
|
".troubleshooting",
|
||||||
|
"live.m3u8");
|
||||||
|
|
||||||
|
while (!localFileSystem.FileExists(playlistFile))
|
||||||
{
|
{
|
||||||
break;
|
await Task.Delay(TimeSpan.FromMilliseconds(250), cancellationToken);
|
||||||
|
if (cancellationToken.IsCancellationRequested || notifier.IsFailed(sessionId))
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!notifier.IsFailed(sessionId))
|
||||||
|
{
|
||||||
|
return Redirect("~/iptv/session/.troubleshooting/live.m3u8");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
notifier.RemoveSession(sessionId);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Redirect("~/iptv/session/.troubleshooting/live.m3u8");
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
// do nothing
|
||||||
|
}
|
||||||
|
|
||||||
return NotFound();
|
return NotFound();
|
||||||
},
|
|
||||||
_ => NotFound());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpHead("api/troubleshoot/playback/archive")]
|
[HttpHead("api/troubleshoot/playback/archive")]
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ using ErsatzTV.Core.Interfaces.Scripting;
|
|||||||
using ErsatzTV.Core.Interfaces.Search;
|
using ErsatzTV.Core.Interfaces.Search;
|
||||||
using ErsatzTV.Core.Interfaces.Streaming;
|
using ErsatzTV.Core.Interfaces.Streaming;
|
||||||
using ErsatzTV.Core.Interfaces.Trakt;
|
using ErsatzTV.Core.Interfaces.Trakt;
|
||||||
|
using ErsatzTV.Core.Interfaces.Troubleshooting;
|
||||||
using ErsatzTV.Core.Jellyfin;
|
using ErsatzTV.Core.Jellyfin;
|
||||||
using ErsatzTV.Core.Metadata;
|
using ErsatzTV.Core.Metadata;
|
||||||
using ErsatzTV.Core.Plex;
|
using ErsatzTV.Core.Plex;
|
||||||
@@ -39,6 +40,7 @@ using ErsatzTV.Core.Scheduling.BlockScheduling;
|
|||||||
using ErsatzTV.Core.Scheduling.YamlScheduling;
|
using ErsatzTV.Core.Scheduling.YamlScheduling;
|
||||||
using ErsatzTV.Core.Search;
|
using ErsatzTV.Core.Search;
|
||||||
using ErsatzTV.Core.Trakt;
|
using ErsatzTV.Core.Trakt;
|
||||||
|
using ErsatzTV.Core.Troubleshooting;
|
||||||
using ErsatzTV.FFmpeg.Capabilities;
|
using ErsatzTV.FFmpeg.Capabilities;
|
||||||
using ErsatzTV.FFmpeg.Pipeline;
|
using ErsatzTV.FFmpeg.Pipeline;
|
||||||
using ErsatzTV.FFmpeg.Runtime;
|
using ErsatzTV.FFmpeg.Runtime;
|
||||||
@@ -614,6 +616,7 @@ public class Startup
|
|||||||
services.AddSingleton<ISearchTargets, SearchTargets>();
|
services.AddSingleton<ISearchTargets, SearchTargets>();
|
||||||
services.AddSingleton<ISmartCollectionCache, SmartCollectionCache>();
|
services.AddSingleton<ISmartCollectionCache, SmartCollectionCache>();
|
||||||
services.AddSingleton<SearchQueryParser>();
|
services.AddSingleton<SearchQueryParser>();
|
||||||
|
services.AddSingleton<ITroubleshootingNotifier, TroubleshootingNotifier>();
|
||||||
|
|
||||||
if (SearchHelper.IsElasticSearchEnabled)
|
if (SearchHelper.IsElasticSearchEnabled)
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user