add start from beginning option to playback troubleshooting (#2182)
This commit is contained in:
+4
-4
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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>>;
|
||||||
|
|||||||
@@ -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>>;
|
||||||
|
|||||||
+11
-2
@@ -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,
|
||||||
|
|||||||
@@ -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"),
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
Reference in New Issue
Block a user