add stream selector to playback troubleshooting (#2474)
This commit is contained in:
@@ -62,6 +62,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
|||||||
- Preview button will be red when preview is disabled due to browser incompatibility
|
- Preview button will be red when preview is disabled due to browser incompatibility
|
||||||
- Add AV1 encoding support with NVIDIA, VAAPI and QSV acceleration
|
- Add AV1 encoding support with NVIDIA, VAAPI and QSV acceleration
|
||||||
- This also requires `HLS Segmenter (fmp4)`
|
- This also requires `HLS Segmenter (fmp4)`
|
||||||
|
- Add `Stream Selector` option to playback troubleshooting tool
|
||||||
|
- This can be helpful for validating stream selector behavior with specific content
|
||||||
|
- Manual subtitle selection will be disabled when using a stream selector
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
- Fix green output when libplacebo tonemapping is used with NVIDIA acceleration and 10-bit output in FFmpeg Profile
|
- Fix green output when libplacebo tonemapping is used with NVIDIA acceleration and 10-bit output in FFmpeg Profile
|
||||||
|
|||||||
@@ -22,14 +22,22 @@ public class ArchiveTroubleshootingResultsHandler(ILocalFileSystem localFileSyst
|
|||||||
{
|
{
|
||||||
hasReport = true;
|
hasReport = true;
|
||||||
zipArchive.CreateEntryFromFile(file, fileName);
|
zipArchive.CreateEntryFromFile(file, fileName);
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Path.GetExtension(file).Equals(".json", StringComparison.OrdinalIgnoreCase))
|
if (Path.GetExtension(file).Equals(".json", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
zipArchive.CreateEntryFromFile(file, fileName);
|
zipArchive.CreateEntryFromFile(file, fileName);
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (fileName.Contains("capabilities", StringComparison.OrdinalIgnoreCase))
|
if (fileName.Contains("capabilities", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
zipArchive.CreateEntryFromFile(file, fileName);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fileName.Contains("stream-selector", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
zipArchive.CreateEntryFromFile(file, fileName);
|
zipArchive.CreateEntryFromFile(file, fileName);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ public record PrepareTroubleshootingPlayback(
|
|||||||
StreamingMode StreamingMode,
|
StreamingMode StreamingMode,
|
||||||
int MediaItemId,
|
int MediaItemId,
|
||||||
int FFmpegProfileId,
|
int FFmpegProfileId,
|
||||||
|
string StreamSelector,
|
||||||
List<int> WatermarkIds,
|
List<int> WatermarkIds,
|
||||||
List<int> GraphicsElementIds,
|
List<int> GraphicsElementIds,
|
||||||
int? SubtitleId,
|
int? SubtitleId,
|
||||||
|
|||||||
+32
-28
@@ -108,6 +108,12 @@ public class PrepareTroubleshootingPlaybackHandler(
|
|||||||
//SongVideoMode = ChannelSongVideoMode.WithProgress
|
//SongVideoMode = ChannelSongVideoMode.WithProgress
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(request.StreamSelector))
|
||||||
|
{
|
||||||
|
channel.StreamSelectorMode = ChannelStreamSelectorMode.Custom;
|
||||||
|
channel.StreamSelector = request.StreamSelector;
|
||||||
|
}
|
||||||
|
|
||||||
List<WatermarkOptions> watermarks = [];
|
List<WatermarkOptions> watermarks = [];
|
||||||
if (request.WatermarkIds.Count > 0)
|
if (request.WatermarkIds.Count > 0)
|
||||||
{
|
{
|
||||||
@@ -211,7 +217,7 @@ public class PrepareTroubleshootingPlaybackHandler(
|
|||||||
new MediaItemAudioVersion(mediaItem, version),
|
new MediaItemAudioVersion(mediaItem, version),
|
||||||
videoPath,
|
videoPath,
|
||||||
mediaPath,
|
mediaPath,
|
||||||
_ => GetSelectedSubtitle(mediaItem, request),
|
_ => GetSubtitles(mediaItem, request),
|
||||||
string.Empty,
|
string.Empty,
|
||||||
string.Empty,
|
string.Empty,
|
||||||
string.Empty,
|
string.Empty,
|
||||||
@@ -240,35 +246,33 @@ public class PrepareTroubleshootingPlaybackHandler(
|
|||||||
return playoutItemResult;
|
return playoutItemResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static async Task<List<Subtitle>> GetSelectedSubtitle(
|
private static async Task<List<Subtitle>> GetSubtitles(MediaItem mediaItem, PrepareTroubleshootingPlayback request)
|
||||||
MediaItem mediaItem,
|
|
||||||
PrepareTroubleshootingPlayback request)
|
|
||||||
{
|
{
|
||||||
|
List<Subtitle> allSubtitles = mediaItem switch
|
||||||
|
{
|
||||||
|
Episode episode => await Optional(episode.EpisodeMetadata).Flatten().HeadOrNone()
|
||||||
|
.Map(mm => mm.Subtitles ?? [])
|
||||||
|
.IfNoneAsync([]),
|
||||||
|
Movie movie => await Optional(movie.MovieMetadata).Flatten().HeadOrNone()
|
||||||
|
.Map(mm => mm.Subtitles ?? [])
|
||||||
|
.IfNoneAsync([]),
|
||||||
|
OtherVideo otherVideo => await Optional(otherVideo.OtherVideoMetadata).Flatten().HeadOrNone()
|
||||||
|
.Map(mm => mm.Subtitles ?? [])
|
||||||
|
.IfNoneAsync([]),
|
||||||
|
_ => []
|
||||||
|
};
|
||||||
|
|
||||||
|
bool isMediaServer = mediaItem is PlexMovie or PlexEpisode or
|
||||||
|
JellyfinMovie or JellyfinEpisode or EmbyMovie or EmbyEpisode;
|
||||||
|
|
||||||
|
if (isMediaServer)
|
||||||
|
{
|
||||||
|
// closed captions are currently unsupported
|
||||||
|
allSubtitles.RemoveAll(s => s.Codec == "eia_608");
|
||||||
|
}
|
||||||
|
|
||||||
if (request.SubtitleId is not null)
|
if (request.SubtitleId is not null)
|
||||||
{
|
{
|
||||||
List<Subtitle> allSubtitles = mediaItem switch
|
|
||||||
{
|
|
||||||
Episode episode => await Optional(episode.EpisodeMetadata).Flatten().HeadOrNone()
|
|
||||||
.Map(mm => mm.Subtitles ?? [])
|
|
||||||
.IfNoneAsync([]),
|
|
||||||
Movie movie => await Optional(movie.MovieMetadata).Flatten().HeadOrNone()
|
|
||||||
.Map(mm => mm.Subtitles ?? [])
|
|
||||||
.IfNoneAsync([]),
|
|
||||||
OtherVideo otherVideo => await Optional(otherVideo.OtherVideoMetadata).Flatten().HeadOrNone()
|
|
||||||
.Map(mm => mm.Subtitles ?? [])
|
|
||||||
.IfNoneAsync([]),
|
|
||||||
_ => []
|
|
||||||
};
|
|
||||||
|
|
||||||
bool isMediaServer = mediaItem is PlexMovie or PlexEpisode or
|
|
||||||
JellyfinMovie or JellyfinEpisode or EmbyMovie or EmbyEpisode;
|
|
||||||
|
|
||||||
if (isMediaServer)
|
|
||||||
{
|
|
||||||
// closed captions are currently unsupported
|
|
||||||
allSubtitles.RemoveAll(s => s.Codec == "eia_608");
|
|
||||||
}
|
|
||||||
|
|
||||||
allSubtitles.RemoveAll(s => s.Id != request.SubtitleId.Value);
|
allSubtitles.RemoveAll(s => s.Id != request.SubtitleId.Value);
|
||||||
|
|
||||||
foreach (Subtitle subtitle in allSubtitles)
|
foreach (Subtitle subtitle in allSubtitles)
|
||||||
@@ -279,7 +283,7 @@ public class PrepareTroubleshootingPlaybackHandler(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return [];
|
return allSubtitles;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static async Task<Validation<BaseError, Tuple<MediaItem, string, string, FFmpegProfile>>> Validate(
|
private static async Task<Validation<BaseError, Tuple<MediaItem, string, string, FFmpegProfile>>> Validate(
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ namespace ErsatzTV.Application.Troubleshooting;
|
|||||||
|
|
||||||
public record StartTroubleshootingPlayback(
|
public record StartTroubleshootingPlayback(
|
||||||
Guid SessionId,
|
Guid SessionId,
|
||||||
|
string StreamSelector,
|
||||||
PlayoutItemResult PlayoutItemResult,
|
PlayoutItemResult PlayoutItemResult,
|
||||||
MediaItemInfo MediaItemInfo,
|
MediaItemInfo MediaItemInfo,
|
||||||
TroubleshootingInfo TroubleshootingInfo) : IRequest, IFFmpegWorkerRequest;
|
TroubleshootingInfo TroubleshootingInfo) : IRequest, IFFmpegWorkerRequest;
|
||||||
|
|||||||
@@ -61,6 +61,18 @@ public class StartTroubleshootingPlaybackHandler(
|
|||||||
troubleshootingInfoJson,
|
troubleshootingInfoJson,
|
||||||
cancellationToken);
|
cancellationToken);
|
||||||
|
|
||||||
|
// write stream selector
|
||||||
|
if (!string.IsNullOrWhiteSpace(request.StreamSelector))
|
||||||
|
{
|
||||||
|
string fullPath = Path.Combine(FileSystemLayout.ChannelStreamSelectorsFolder, request.StreamSelector);
|
||||||
|
if (File.Exists(fullPath))
|
||||||
|
{
|
||||||
|
File.Copy(
|
||||||
|
fullPath,
|
||||||
|
Path.Combine(FileSystemLayout.TranscodeTroubleshootingFolder, "stream-selector.yml"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
HardwareAccelerationKind hwAccel = request.TroubleshootingInfo.FFmpegProfiles.Head().HardwareAcceleration;
|
HardwareAccelerationKind hwAccel = request.TroubleshootingInfo.FFmpegProfiles.Head().HardwareAcceleration;
|
||||||
if (hwAccel is HardwareAccelerationKind.Qsv)
|
if (hwAccel is HardwareAccelerationKind.Qsv)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -34,6 +34,8 @@ public class TroubleshootController(
|
|||||||
[FromQuery]
|
[FromQuery]
|
||||||
List<int> graphicsElement,
|
List<int> graphicsElement,
|
||||||
[FromQuery]
|
[FromQuery]
|
||||||
|
string streamSelector,
|
||||||
|
[FromQuery]
|
||||||
int? subtitleId,
|
int? subtitleId,
|
||||||
[FromQuery]
|
[FromQuery]
|
||||||
int seekSeconds,
|
int seekSeconds,
|
||||||
@@ -48,6 +50,7 @@ public class TroubleshootController(
|
|||||||
streamingMode,
|
streamingMode,
|
||||||
mediaItem,
|
mediaItem,
|
||||||
ffmpegProfile,
|
ffmpegProfile,
|
||||||
|
streamSelector,
|
||||||
watermark,
|
watermark,
|
||||||
graphicsElement,
|
graphicsElement,
|
||||||
subtitleId,
|
subtitleId,
|
||||||
@@ -82,6 +85,7 @@ public class TroubleshootController(
|
|||||||
await channelWriter.WriteAsync(
|
await channelWriter.WriteAsync(
|
||||||
new StartTroubleshootingPlayback(
|
new StartTroubleshootingPlayback(
|
||||||
sessionId,
|
sessionId,
|
||||||
|
streamSelector,
|
||||||
playoutItemResult,
|
playoutItemResult,
|
||||||
mediaInfo,
|
mediaInfo,
|
||||||
troubleshootingInfo),
|
troubleshootingInfo),
|
||||||
@@ -141,7 +145,13 @@ public class TroubleshootController(
|
|||||||
Option<int> ss = seekSeconds > 0 ? seekSeconds : Option<int>.None;
|
Option<int> ss = seekSeconds > 0 ? seekSeconds : Option<int>.None;
|
||||||
|
|
||||||
Option<string> maybeArchivePath = await mediator.Send(
|
Option<string> maybeArchivePath = await mediator.Send(
|
||||||
new ArchiveTroubleshootingResults(mediaItem, ffmpegProfile, streamingMode, watermark, graphicsElement, ss),
|
new ArchiveTroubleshootingResults(
|
||||||
|
mediaItem,
|
||||||
|
ffmpegProfile,
|
||||||
|
streamingMode,
|
||||||
|
watermark,
|
||||||
|
graphicsElement,
|
||||||
|
ss),
|
||||||
cancellationToken);
|
cancellationToken);
|
||||||
|
|
||||||
foreach (string archivePath in maybeArchivePath)
|
foreach (string archivePath in maybeArchivePath)
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
@page "/system/troubleshooting/playback"
|
@page "/system/troubleshooting/playback"
|
||||||
|
@using ErsatzTV.Application.Channels
|
||||||
@using ErsatzTV.Application.FFmpegProfiles
|
@using ErsatzTV.Application.FFmpegProfiles
|
||||||
@using ErsatzTV.Application.Graphics
|
@using ErsatzTV.Application.Graphics
|
||||||
@using ErsatzTV.Application.MediaItems
|
@using ErsatzTV.Application.MediaItems
|
||||||
@@ -7,6 +8,7 @@
|
|||||||
@using ErsatzTV.Application.Watermarks
|
@using ErsatzTV.Application.Watermarks
|
||||||
@using ErsatzTV.Core.Notifications
|
@using ErsatzTV.Core.Notifications
|
||||||
@using MediatR.Courier
|
@using MediatR.Courier
|
||||||
|
@using Microsoft.AspNetCore.WebUtilities
|
||||||
@implements IDisposable
|
@implements IDisposable
|
||||||
@inject IMediator Mediator
|
@inject IMediator Mediator
|
||||||
@inject NavigationManager NavigationManager
|
@inject NavigationManager NavigationManager
|
||||||
@@ -55,6 +57,17 @@
|
|||||||
}
|
}
|
||||||
</MudSelect>
|
</MudSelect>
|
||||||
</MudStack>
|
</MudStack>
|
||||||
|
<MudStack Row="true" Breakpoint="Breakpoint.SmAndDown" Class="form-field-stack gap-md-8 mb-5">
|
||||||
|
<div class="d-flex">
|
||||||
|
<MudText>Stream Selector</MudText>
|
||||||
|
</div>
|
||||||
|
<MudSelect @bind-Value="_streamSelector" For="@(() => _streamSelector)" Clearable="true">
|
||||||
|
@foreach (string selector in _streamSelectors)
|
||||||
|
{
|
||||||
|
<MudSelectItem T="string" Value="@selector">@selector</MudSelectItem>
|
||||||
|
}
|
||||||
|
</MudSelect>
|
||||||
|
</MudStack>
|
||||||
<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">
|
||||||
<div class="d-flex">
|
<div class="d-flex">
|
||||||
<MudText>Streaming Mode</MudText>
|
<MudText>Streaming Mode</MudText>
|
||||||
@@ -68,7 +81,7 @@
|
|||||||
<div class="d-flex">
|
<div class="d-flex">
|
||||||
<MudText>Subtitle</MudText>
|
<MudText>Subtitle</MudText>
|
||||||
</div>
|
</div>
|
||||||
<MudSelect @bind-Value="_subtitleId" For="@(() => _subtitleId)" Clearable="true">
|
<MudSelect @bind-Value="_subtitleId" For="@(() => _subtitleId)" Clearable="true" Disabled="@(!string.IsNullOrWhiteSpace(_streamSelector))">
|
||||||
<MudSelectItem T="int?" Value="@((int?)null)">(none)</MudSelectItem>
|
<MudSelectItem T="int?" Value="@((int?)null)">(none)</MudSelectItem>
|
||||||
@foreach (SubtitleViewModel subtitleStream in _subtitleStreams)
|
@foreach (SubtitleViewModel subtitleStream in _subtitleStreams)
|
||||||
{
|
{
|
||||||
@@ -149,12 +162,14 @@
|
|||||||
private CancellationTokenSource _cts;
|
private CancellationTokenSource _cts;
|
||||||
|
|
||||||
private readonly List<FFmpegProfileViewModel> _ffmpegProfiles = [];
|
private readonly List<FFmpegProfileViewModel> _ffmpegProfiles = [];
|
||||||
|
private readonly List<string> _streamSelectors = [];
|
||||||
private readonly List<WatermarkViewModel> _watermarks = [];
|
private readonly List<WatermarkViewModel> _watermarks = [];
|
||||||
private readonly List<SubtitleViewModel> _subtitleStreams = [];
|
private readonly List<SubtitleViewModel> _subtitleStreams = [];
|
||||||
private readonly List<GraphicsElementViewModel> _graphicsElements = [];
|
private readonly List<GraphicsElementViewModel> _graphicsElements = [];
|
||||||
private MediaItemInfo _info;
|
private MediaItemInfo _info;
|
||||||
private StreamingMode _streamingMode = StreamingMode.HttpLiveStreamingSegmenter;
|
private StreamingMode _streamingMode = StreamingMode.HttpLiveStreamingSegmenter;
|
||||||
private int _ffmpegProfileId;
|
private int _ffmpegProfileId;
|
||||||
|
private string _streamSelector;
|
||||||
private IEnumerable<string> _watermarkNames = new System.Collections.Generic.HashSet<string>();
|
private IEnumerable<string> _watermarkNames = new System.Collections.Generic.HashSet<string>();
|
||||||
private IEnumerable<string> _graphicsElementNames = new System.Collections.Generic.HashSet<string>();
|
private IEnumerable<string> _graphicsElementNames = new System.Collections.Generic.HashSet<string>();
|
||||||
private bool _startFromBeginning;
|
private bool _startFromBeginning;
|
||||||
@@ -193,6 +208,10 @@
|
|||||||
_ffmpegProfileId = _ffmpegProfiles.Map(f => f.Id).Head();
|
_ffmpegProfileId = _ffmpegProfiles.Map(f => f.Id).Head();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_streamSelectors.Clear();
|
||||||
|
_streamSelectors.AddRange(await Mediator.Send(new GetChannelStreamSelectors(), token));
|
||||||
|
_streamSelector = null;
|
||||||
|
|
||||||
_watermarks.Clear();
|
_watermarks.Clear();
|
||||||
_watermarks.AddRange(await Mediator.Send(new GetAllWatermarks(), token));
|
_watermarks.AddRange(await Mediator.Send(new GetAllWatermarks(), token));
|
||||||
|
|
||||||
@@ -221,14 +240,21 @@
|
|||||||
|
|
||||||
private async Task PreviewChannel()
|
private async Task PreviewChannel()
|
||||||
{
|
{
|
||||||
var uri = new UriBuilder(NavigationManager.ToAbsoluteUri(NavigationManager.Uri));
|
var baseUri = NavigationManager.ToAbsoluteUri(NavigationManager.Uri).ToString();
|
||||||
uri.Path = uri.Path.Replace("/system/troubleshooting/playback", "/api/troubleshoot/playback.m3u8");
|
string apiUri = baseUri.Replace("/system/troubleshooting/playback", "/api/troubleshoot/playback.m3u8");
|
||||||
uri.Query = $"?mediaItem={MediaItemId}&ffmpegProfile={_ffmpegProfileId}&streamingMode={(int)_streamingMode}&seekSeconds={_seekSeconds}";
|
var queryString = new Dictionary<string, string>
|
||||||
|
{
|
||||||
|
["mediaItem"] = (MediaItemId ?? 0).ToString(),
|
||||||
|
["ffmpegProfile"] = _ffmpegProfileId.ToString(),
|
||||||
|
["streamingMode"] = ((int)_streamingMode).ToString(),
|
||||||
|
["seekSeconds"] = _seekSeconds.ToString()
|
||||||
|
};
|
||||||
|
|
||||||
foreach (string watermarkName in _watermarkNames)
|
foreach (string watermarkName in _watermarkNames)
|
||||||
{
|
{
|
||||||
foreach (WatermarkViewModel watermark in _watermarks.Where(wm => wm.Name == watermarkName))
|
foreach (WatermarkViewModel watermark in _watermarks.Where(wm => wm.Name == watermarkName))
|
||||||
{
|
{
|
||||||
uri.Query += $"&watermark={watermark.Id}";
|
queryString.Add("watermark", watermark.Id.ToString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -236,16 +262,21 @@
|
|||||||
{
|
{
|
||||||
foreach (GraphicsElementViewModel graphicsElement in _graphicsElements.Where(ge => ge.Name == graphicsElementName))
|
foreach (GraphicsElementViewModel graphicsElement in _graphicsElements.Where(ge => ge.Name == graphicsElementName))
|
||||||
{
|
{
|
||||||
uri.Query += $"&graphicsElement={graphicsElement.Id}";
|
queryString.Add("graphicsElement", graphicsElement.Id.ToString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_subtitleId is not null)
|
if (!string.IsNullOrWhiteSpace(_streamSelector))
|
||||||
{
|
{
|
||||||
uri.Query += $"&subtitleId={_subtitleId.Value}";
|
queryString.Add("streamSelector", _streamSelector);
|
||||||
|
}
|
||||||
|
else if (_subtitleId is not null)
|
||||||
|
{
|
||||||
|
queryString.Add("subtitleId", _subtitleId.Value.ToString());
|
||||||
}
|
}
|
||||||
|
|
||||||
await JsRuntime.InvokeVoidAsync("previewChannel", uri.ToString());
|
string uriWithQuery = QueryHelpers.AddQueryString(apiUri, queryString);
|
||||||
|
await JsRuntime.InvokeVoidAsync("previewChannel", uriWithQuery);
|
||||||
|
|
||||||
await Task.Delay(TimeSpan.FromSeconds(1));
|
await Task.Delay(TimeSpan.FromSeconds(1));
|
||||||
|
|
||||||
@@ -281,13 +312,20 @@
|
|||||||
|
|
||||||
private async Task DownloadResults()
|
private async Task DownloadResults()
|
||||||
{
|
{
|
||||||
var uri = $"api/troubleshoot/playback/archive?mediaItem={MediaItemId ?? 0}&ffmpegProfile={_ffmpegProfileId}&streamingMode={(int)_streamingMode}&seekSeconds={_seekSeconds}";
|
string apiUri = "api/troubleshoot/playback/archive";
|
||||||
|
var queryString = new Dictionary<string, string>
|
||||||
|
{
|
||||||
|
["mediaItem"] = (MediaItemId ?? 0).ToString(),
|
||||||
|
["ffmpegProfile"] = _ffmpegProfileId.ToString(),
|
||||||
|
["streamingMode"] = ((int)_streamingMode).ToString(),
|
||||||
|
["seekSeconds"] = _seekSeconds.ToString()
|
||||||
|
};
|
||||||
|
|
||||||
foreach (string watermarkName in _watermarkNames)
|
foreach (string watermarkName in _watermarkNames)
|
||||||
{
|
{
|
||||||
foreach (WatermarkViewModel watermark in _watermarks.Where(wm => wm.Name == watermarkName))
|
foreach (WatermarkViewModel watermark in _watermarks.Where(wm => wm.Name == watermarkName))
|
||||||
{
|
{
|
||||||
uri += $"&watermark={watermark.Id}";
|
queryString.Add("watermark", watermark.Id.ToString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -295,11 +333,12 @@
|
|||||||
{
|
{
|
||||||
foreach (GraphicsElementViewModel graphicsElement in _graphicsElements.Where(ge => ge.Name == graphicsElementName))
|
foreach (GraphicsElementViewModel graphicsElement in _graphicsElements.Where(ge => ge.Name == graphicsElementName))
|
||||||
{
|
{
|
||||||
uri += $"&graphicsElement={graphicsElement.Id}";
|
queryString.Add("graphicsElement", graphicsElement.Id.ToString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
await JsRuntime.InvokeVoidAsync("window.open", uri);
|
string uriWithQuery = QueryHelpers.AddQueryString(apiUri, queryString);
|
||||||
|
await JsRuntime.InvokeVoidAsync("window.open", uriWithQuery);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void HandleTroubleshootingCompleted(PlaybackTroubleshootingCompletedNotification result)
|
private void HandleTroubleshootingCompleted(PlaybackTroubleshootingCompletedNotification result)
|
||||||
|
|||||||
Reference in New Issue
Block a user