Browse Source

add start from beginning option to playback troubleshooting (#2182)

pull/2183/head
Jason Dove 1 year ago committed by GitHub
parent
commit
626af6876b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 8
      CHANGELOG.md
  2. 6
      ErsatzTV.Application/Troubleshooting/Commands/ArchiveTroubleshootingResults.cs
  3. 6
      ErsatzTV.Application/Troubleshooting/Commands/PrepareTroubleshootingPlayback.cs
  4. 13
      ErsatzTV.Application/Troubleshooting/Commands/PrepareTroubleshootingPlaybackHandler.cs
  5. 12
      ErsatzTV.Scanner.Tests/Core/FFmpeg/TranscodingTests.cs
  6. 8
      ErsatzTV/Controllers/Api/TroubleshootController.cs
  7. 14
      ErsatzTV/Pages/PlaybackTroubleshooting.razor

8
CHANGELOG.md

@ -107,11 +107,11 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Remote Stream library items consist of YAML (`.yml`) files with the following fields - Remote Stream library items consist of YAML (`.yml`) files with the following fields
- `url`: the URL of the content that can be played directly by ffmpeg - `url`: the URL of the content that can be played directly by ffmpeg
- `script`: the process name and arguments for a command that will output content to stdout - `script`: the process name and arguments for a command that will output content to stdout
- `duration`: when the content is "live" and does not have duration metadata, this must be provided to allow scheduling
- `is_live`: *required* property that indicates whether the remote stream contains live content - `is_live`: *required* property that indicates whether the remote stream contains live content
- When this is set to `true`, ETV cannot work ahead on transcoding this item, which is a necessary tradeoff for supporting live content - When this is set to `true`, ETV cannot work ahead on transcoding this item, which is a necessary tradeoff for supporting live content
- When this is set to `false`, ETV will treat the stream as VOD and attempt to work ahead on transcoding like any other local item - When this is set to `false`, ETV will treat the stream as VOD and attempt to work ahead on transcoding like any other local item
- This *will* cause errors when the content is actually live, so it's important to configure this correctly - This *will* cause errors when the content is actually live, so it's important to configure this correctly
- `duration`: when the content is live and does not have duration metadata, this must be provided to allow scheduling
- The remote stream definition (YAML file) may provide either a `url` or a `script` - The remote stream definition (YAML file) may provide either a `url` or a `script`
- If both are provided, `url` will be used - If both are provided, `url` will be used

6
ErsatzTV.Application/Troubleshooting/Commands/ArchiveTroubleshootingResults.cs

@ -1,4 +1,8 @@
namespace ErsatzTV.Application.Troubleshooting; namespace ErsatzTV.Application.Troubleshooting;
public record ArchiveTroubleshootingResults(int MediaItemId, int FFmpegProfileId, int WatermarkId) public record ArchiveTroubleshootingResults(
int MediaItemId,
int FFmpegProfileId,
int WatermarkId,
bool StartFromBeginning)
: IRequest<Option<string>>; : IRequest<Option<string>>;

6
ErsatzTV.Application/Troubleshooting/Commands/PrepareTroubleshootingPlayback.cs

@ -3,5 +3,9 @@ using ErsatzTV.Core;
namespace ErsatzTV.Application.Troubleshooting; namespace ErsatzTV.Application.Troubleshooting;
public record PrepareTroubleshootingPlayback(int MediaItemId, int FFmpegProfileId, int WatermarkId) public record PrepareTroubleshootingPlayback(
int MediaItemId,
int FFmpegProfileId,
int WatermarkId,
bool StartFromBeginning)
: IRequest<Either<BaseError, Command>>; : IRequest<Either<BaseError, Command>>;

13
ErsatzTV.Application/Troubleshooting/Commands/PrepareTroubleshootingPlaybackHandler.cs

@ -96,6 +96,15 @@ public class PrepareTroubleshootingPlaybackHandler(
// we cannot burst live input // we cannot burst live input
bool hlsRealtime = mediaItem is RemoteStream { IsLive: true }; bool hlsRealtime = mediaItem is RemoteStream { IsLive: true };
TimeSpan inPoint = TimeSpan.Zero;
TimeSpan outPoint = duration;
if (!hlsRealtime && !request.StartFromBeginning)
{
inPoint = TimeSpan.FromSeconds(version.Duration.TotalSeconds / 2.0);
duration = TimeSpan.FromSeconds(duration.TotalSeconds / 2.0);
outPoint = inPoint + duration;
}
Command process = await ffmpegProcessService.ForPlayoutItem( Command process = await ffmpegProcessService.ForPlayoutItem(
ffmpegPath, ffmpegPath,
ffprobePath, ffprobePath,
@ -127,8 +136,8 @@ public class PrepareTroubleshootingPlaybackHandler(
Option<int>.None, Option<int>.None,
hlsRealtime, hlsRealtime,
FillerKind.None, FillerKind.None,
TimeSpan.Zero, inPoint,
duration, outPoint,
0, 0,
None, None,
false, false,

12
ErsatzTV.Scanner.Tests/Core/FFmpeg/TranscodingTests.cs

@ -145,23 +145,23 @@ public class TranscodingTests
public static InputFormat[] InputFormats = public static InputFormat[] InputFormats =
[ [
// // // example format that requires colorspace filter // // // example format that requires colorspace filter
// new("libx264", "yuv420p", "tv", "smpte170m", "bt709", "smpte170m"), new("libx264", "yuv420p", "tv", "smpte170m", "bt709", "smpte170m"),
// // // // // //
// // // // example format that requires setparams filter // // // // example format that requires setparams filter
// new("libx264", "yuv420p", string.Empty, string.Empty, string.Empty, string.Empty), new("libx264", "yuv420p", string.Empty, string.Empty, string.Empty, string.Empty),
// // // // // //
// // // // new("libx264", "yuvj420p"), // // // // new("libx264", "yuvj420p"),
// new("libx264", "yuv420p10le"), new("libx264", "yuv420p10le"),
// // // // new("libx264", "yuv444p10le"), // // // // new("libx264", "yuv444p10le"),
// // // // // //
// // // // new("mpeg1video", "yuv420p"), // // // // new("mpeg1video", "yuv420p"),
// // // // // // // //
// new("mpeg2video", "yuv420p"), new("mpeg2video", "yuv420p"),
// // // //
//new InputFormat("libx265", "yuv420p"), //new InputFormat("libx265", "yuv420p"),
new("libx265", "yuv420p10le") new("libx265", "yuv420p10le"),
// //
// new("mpeg4", "yuv420p"), new("mpeg4", "yuv420p"),
// //
// new("libvpx-vp9", "yuv420p"), // new("libvpx-vp9", "yuv420p"),
// new("libvpx-vp9", "yuv420p10le"), // new("libvpx-vp9", "yuv420p10le"),

8
ErsatzTV/Controllers/Api/TroubleshootController.cs

@ -26,10 +26,12 @@ public class TroubleshootController(
int ffmpegProfile, int ffmpegProfile,
[FromQuery] [FromQuery]
int watermark, int watermark,
[FromQuery]
bool startFromBeginning,
CancellationToken cancellationToken) CancellationToken cancellationToken)
{ {
Either<BaseError, Command> result = await mediator.Send( Either<BaseError, Command> result = await mediator.Send(
new PrepareTroubleshootingPlayback(mediaItem, ffmpegProfile, watermark), new PrepareTroubleshootingPlayback(mediaItem, ffmpegProfile, watermark, startFromBeginning),
cancellationToken); cancellationToken);
return await result.MatchAsync<IActionResult>( return await result.MatchAsync<IActionResult>(
@ -83,10 +85,12 @@ public class TroubleshootController(
int ffmpegProfile, int ffmpegProfile,
[FromQuery] [FromQuery]
int watermark, int watermark,
[FromQuery]
bool startFromBeginning,
CancellationToken cancellationToken) CancellationToken cancellationToken)
{ {
Option<string> maybeArchivePath = await mediator.Send( Option<string> maybeArchivePath = await mediator.Send(
new ArchiveTroubleshootingResults(mediaItem, ffmpegProfile, watermark), new ArchiveTroubleshootingResults(mediaItem, ffmpegProfile, watermark, startFromBeginning),
cancellationToken); cancellationToken);
foreach (string archivePath in maybeArchivePath) foreach (string archivePath in maybeArchivePath)

14
ErsatzTV/Pages/PlaybackTroubleshooting.razor

@ -64,6 +64,12 @@
} }
</MudSelect> </MudSelect>
</MudStack> </MudStack>
<MudStack Row="true" Breakpoint="Breakpoint.SmAndDown" Class="form-field-stack gap-md-8 mb-5">
<div class="d-flex">
<MudText>Start From Beginning</MudText>
</div>
<MudCheckBox @bind-Value="_startFromBeginning" Dense="true" Disabled="@(string.Equals(_info?.Kind, "RemoteStream", StringComparison.OrdinalIgnoreCase))" />
</MudStack>
<MudText Typo="Typo.h5" Class="mt-10 mb-2">Preview</MudText> <MudText Typo="Typo.h5" Class="mt-10 mb-2">Preview</MudText>
<MudDivider Class="mb-6"/> <MudDivider Class="mb-6"/>
<MudStack Row="true" Breakpoint="Breakpoint.SmAndDown" Class="form-field-stack gap-md-8 mb-5"> <MudStack Row="true" Breakpoint="Breakpoint.SmAndDown" Class="form-field-stack gap-md-8 mb-5">
@ -72,7 +78,7 @@
Color="Color.Primary" Color="Color.Primary"
StartIcon="@Icons.Material.Filled.PlayCircle" StartIcon="@Icons.Material.Filled.PlayCircle"
Disabled="@(Locker.IsTroubleshootingPlaybackLocked() || _mediaItemId is null)" Disabled="@(Locker.IsTroubleshootingPlaybackLocked() || _mediaItemId is null)"
OnClick="PreviewChannel"> OnClick="@PreviewChannel">
Play Play
</MudButton> </MudButton>
</MudStack> </MudStack>
@ -105,6 +111,7 @@
private MediaItemInfo _info; private MediaItemInfo _info;
private int _ffmpegProfileId; private int _ffmpegProfileId;
private int? _watermarkId; private int? _watermarkId;
private bool _startFromBeginning;
private bool _hasPlayed; private bool _hasPlayed;
public void Dispose() public void Dispose()
@ -136,7 +143,7 @@
{ {
var uri = new UriBuilder(NavigationManager.ToAbsoluteUri(NavigationManager.Uri)); var uri = new UriBuilder(NavigationManager.ToAbsoluteUri(NavigationManager.Uri));
uri.Path = uri.Path.Replace("/system/troubleshooting/playback", "/api/troubleshoot/playback.m3u8"); uri.Path = uri.Path.Replace("/system/troubleshooting/playback", "/api/troubleshoot/playback.m3u8");
uri.Query = $"?mediaItem={_mediaItemId}&ffmpegProfile={_ffmpegProfileId}&watermark={_watermarkId ?? 0}"; uri.Query = $"?mediaItem={_mediaItemId}&ffmpegProfile={_ffmpegProfileId}&watermark={_watermarkId ?? 0}&startFromBeginning={_startFromBeginning}";
await JsRuntime.InvokeVoidAsync("previewChannel", uri.ToString()); await JsRuntime.InvokeVoidAsync("previewChannel", uri.ToString());
await Task.Delay(TimeSpan.FromSeconds(1)); await Task.Delay(TimeSpan.FromSeconds(1));
@ -155,6 +162,7 @@
foreach (MediaItemInfo info in maybeInfo.RightToSeq()) foreach (MediaItemInfo info in maybeInfo.RightToSeq())
{ {
_info = info; _info = info;
_startFromBeginning = string.Equals(info.Kind, "RemoteStream", StringComparison.OrdinalIgnoreCase);
} }
if (maybeInfo.IsLeft) if (maybeInfo.IsLeft)
@ -168,7 +176,7 @@
private async Task DownloadResults() private async Task DownloadResults()
{ {
await JsRuntime.InvokeVoidAsync("window.open", $"api/troubleshoot/playback/archive?mediaItem={_mediaItemId ?? 0}&ffmpegProfile={_ffmpegProfileId}&watermark={_watermarkId ?? 0}"); await JsRuntime.InvokeVoidAsync("window.open", $"api/troubleshoot/playback/archive?mediaItem={_mediaItemId ?? 0}&ffmpegProfile={_ffmpegProfileId}&watermark={_watermarkId ?? 0}&startFromBeginning={_startFromBeginning}");
} }
private void HandleTroubleshootingCompleted(PlaybackTroubleshootingCompletedNotification result) private void HandleTroubleshootingCompleted(PlaybackTroubleshootingCompletedNotification result)

Loading…
Cancel
Save