playback troubleshooting improvements (#2157)

This commit is contained in:
Jason Dove
2025-07-17 14:53:11 +00:00
committed by GitHub
parent 578cdb1e14
commit 223bdff8d6
12 changed files with 165 additions and 89 deletions
@@ -1,9 +1,10 @@
using System.Threading.Channels;
using CliWrap;
using ErsatzTV.Application;
using ErsatzTV.Application.MediaItems;
using ErsatzTV.Application.Troubleshooting;
using ErsatzTV.Application.Troubleshooting.Queries;
using ErsatzTV.Core;
using ErsatzTV.Core.Interfaces.Locking;
using ErsatzTV.Core.Interfaces.Metadata;
using MediatR;
using Microsoft.AspNetCore.Mvc;
@@ -12,7 +13,6 @@ namespace ErsatzTV.Controllers.Api;
[ApiController]
public class TroubleshootController(
IEntityLocker entityLocker,
ChannelWriter<IFFmpegWorkerRequest> channelWriter,
ILocalFileSystem localFileSystem,
IMediator mediator) : ControllerBase
@@ -28,8 +28,6 @@ public class TroubleshootController(
int watermark,
CancellationToken cancellationToken)
{
entityLocker.LockTroubleshootingPlayback();
Either<BaseError, Command> result = await mediator.Send(
new PrepareTroubleshootingPlayback(mediaItem, ffmpegProfile, watermark),
cancellationToken);
@@ -37,21 +35,41 @@ public class TroubleshootController(
return await result.MatchAsync<IActionResult>(
async command =>
{
await channelWriter.WriteAsync(new StartTroubleshootingPlayback(command), CancellationToken.None);
string playlistFile = Path.Combine(FileSystemLayout.TranscodeFolder, ".troubleshooting", "live.m3u8");
DateTimeOffset start = DateTimeOffset.Now;
while (!localFileSystem.FileExists(playlistFile) &&
DateTimeOffset.Now - start < TimeSpan.FromSeconds(15))
Either<BaseError, MediaItemInfo> maybeMediaInfo = await mediator.Send(new GetMediaItemInfo(mediaItem), cancellationToken);
foreach (MediaItemInfo mediaInfo in maybeMediaInfo.RightToSeq())
{
await Task.Delay(TimeSpan.FromMilliseconds(250), cancellationToken);
if (cancellationToken.IsCancellationRequested)
TroubleshootingInfo troubleshootingInfo = await mediator.Send(
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(command, 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)
{
break;
}
}
return Redirect("~/iptv/session/.troubleshooting/live.m3u8");
}
return Redirect("~/iptv/session/.troubleshooting/live.m3u8");
return NotFound();
},
_ => NotFound());
}
+25 -4
View File
@@ -2,11 +2,15 @@
@using ErsatzTV.Application.FFmpegProfiles
@using ErsatzTV.Application.MediaItems
@using ErsatzTV.Application.Watermarks
@using ErsatzTV.Core.Notifications
@using MediatR.Courier
@implements IDisposable
@inject IMediator Mediator
@inject NavigationManager NavigationManager
@inject IJSRuntime JsRuntime
@inject IEntityLocker Locker
@inject ICourier Courier;
@inject ISnackbar Snackbar;
<MudForm Style="max-height: 100%">
<MudPaper Square="true" Style="display: flex; height: 64px; min-height: 64px; width: 100%; z-index: 100; align-items: center">
@@ -109,7 +113,11 @@
_cts.Dispose();
}
protected override void OnInitialized() => Locker.OnTroubleshootingPlaybackChanged += LockChanged;
protected override void OnInitialized()
{
Locker.OnTroubleshootingPlaybackChanged += LockChanged;
Courier.Subscribe<PlaybackTroubleshootingCompletedNotification>(HandleTroubleshootingCompleted);
}
protected override async Task OnParametersSetAsync()
{
@@ -126,13 +134,14 @@
private async Task PreviewChannel()
{
Locker.LockTroubleshootingPlayback();
_hasPlayed = true;
var uri = new UriBuilder(NavigationManager.ToAbsoluteUri(NavigationManager.Uri));
uri.Path = uri.Path.Replace("/system/troubleshooting/playback", "/api/troubleshoot/playback.m3u8");
uri.Query = $"?mediaItem={_mediaItemId}&ffmpegProfile={_ffmpegProfileId}&watermark={_watermarkId ?? 0}";
await JsRuntime.InvokeVoidAsync("previewChannel", uri.ToString());
await Task.Delay(TimeSpan.FromSeconds(1));
_hasPlayed = true;
}
private async Task OnMediaItemIdChanged(int? mediaItemId)
@@ -162,4 +171,16 @@
await JsRuntime.InvokeVoidAsync("window.open", $"api/troubleshoot/playback/archive?mediaItem={_mediaItemId ?? 0}&ffmpegProfile={_ffmpegProfileId}&watermark={_watermarkId ?? 0}");
}
private void HandleTroubleshootingCompleted(PlaybackTroubleshootingCompletedNotification result)
{
if (result.ExitCode == 0)
{
Snackbar.Add("FFmpeg troubleshooting process exited successfully", Severity.Success);
}
else
{
Snackbar.Add($"FFmpeg troubleshooting process exited with code {result.ExitCode}", Severity.Warning);
}
}
}
+8
View File
@@ -58,6 +58,14 @@
if (Hls.isSupported()) {
var hls = new Hls({
debug: true,
manifestLoadPolicy: {
default: {
maxTimeToFirstByteMs: Infinity,
maxLoadTimeMs: 60000,
timeoutRetry: null,
errorRetry: null
},
},
});
$('#video').data('hls', hls);
hls.loadSource(uri);