|
@@ -1,7 +1,12 @@
|
|
|
+#pragma warning disable CA1819 // Properties should not return arrays
|
|
|
+
|
|
|
using System;
|
|
|
using System.Collections.Generic;
|
|
|
using System.Globalization;
|
|
|
+using System.Linq;
|
|
|
+using System.Text;
|
|
|
using Jellyfin.Data.Enums;
|
|
|
+using Jellyfin.Extensions;
|
|
|
using MediaBrowser.Model.Drawing;
|
|
|
using MediaBrowser.Model.Dto;
|
|
|
using MediaBrowser.Model.Entities;
|
|
@@ -871,202 +876,279 @@ public class StreamInfo
|
|
|
/// </summary>
|
|
|
/// <param name="baseUrl">The base Url.</param>
|
|
|
/// <param name="accessToken">The access Token.</param>
|
|
|
+ /// <param name="query">Optional extra query.</param>
|
|
|
/// <returns>A querystring representation of this object.</returns>
|
|
|
- public string ToUrl(string baseUrl, string? accessToken)
|
|
|
+ public string ToUrl(string? baseUrl, string? accessToken, string? query)
|
|
|
{
|
|
|
- ArgumentException.ThrowIfNullOrEmpty(baseUrl);
|
|
|
+ var sb = new StringBuilder();
|
|
|
+ if (!string.IsNullOrEmpty(baseUrl))
|
|
|
+ {
|
|
|
+ sb.Append(baseUrl.TrimEnd('/'));
|
|
|
+ }
|
|
|
|
|
|
- List<string> list = [];
|
|
|
- foreach (NameValuePair pair in BuildParams(this, accessToken))
|
|
|
+ if (MediaType == DlnaProfileType.Audio)
|
|
|
{
|
|
|
- if (string.IsNullOrEmpty(pair.Value))
|
|
|
- {
|
|
|
- continue;
|
|
|
- }
|
|
|
+ sb.Append("/audio/");
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ sb.Append("/videos/");
|
|
|
+ }
|
|
|
|
|
|
- // Try to keep the url clean by omitting defaults
|
|
|
- if (string.Equals(pair.Name, "StartTimeTicks", StringComparison.OrdinalIgnoreCase)
|
|
|
- && string.Equals(pair.Value, "0", StringComparison.OrdinalIgnoreCase))
|
|
|
- {
|
|
|
- continue;
|
|
|
- }
|
|
|
+ sb.Append(ItemId);
|
|
|
|
|
|
- if (string.Equals(pair.Name, "SubtitleStreamIndex", StringComparison.OrdinalIgnoreCase)
|
|
|
- && string.Equals(pair.Value, "-1", StringComparison.OrdinalIgnoreCase))
|
|
|
- {
|
|
|
- continue;
|
|
|
- }
|
|
|
+ if (SubProtocol == MediaStreamProtocol.hls)
|
|
|
+ {
|
|
|
+ sb.Append("/master.m3u8?");
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ sb.Append("/stream");
|
|
|
|
|
|
- if (string.Equals(pair.Name, "Static", StringComparison.OrdinalIgnoreCase)
|
|
|
- && string.Equals(pair.Value, "false", StringComparison.OrdinalIgnoreCase))
|
|
|
+ if (!string.IsNullOrEmpty(Container))
|
|
|
{
|
|
|
- continue;
|
|
|
+ sb.Append('.');
|
|
|
+ sb.Append(Container);
|
|
|
}
|
|
|
|
|
|
- var encodedValue = pair.Value.Replace(" ", "%20", StringComparison.Ordinal);
|
|
|
+ sb.Append('?');
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!string.IsNullOrEmpty(DeviceProfileId))
|
|
|
+ {
|
|
|
+ sb.Append("&DeviceProfileId=");
|
|
|
+ sb.Append(DeviceProfileId);
|
|
|
+ }
|
|
|
|
|
|
- list.Add(string.Format(CultureInfo.InvariantCulture, "{0}={1}", pair.Name, encodedValue));
|
|
|
+ if (!string.IsNullOrEmpty(DeviceId))
|
|
|
+ {
|
|
|
+ sb.Append("&DeviceId=");
|
|
|
+ sb.Append(DeviceId);
|
|
|
}
|
|
|
|
|
|
- string queryString = string.Join('&', list);
|
|
|
+ if (!string.IsNullOrEmpty(MediaSourceId))
|
|
|
+ {
|
|
|
+ sb.Append("&MediaSourceId=");
|
|
|
+ sb.Append(MediaSourceId);
|
|
|
+ }
|
|
|
|
|
|
- return GetUrl(baseUrl, queryString);
|
|
|
- }
|
|
|
+ // default true so don't store.
|
|
|
+ if (IsDirectStream)
|
|
|
+ {
|
|
|
+ sb.Append("&Static=true");
|
|
|
+ }
|
|
|
|
|
|
- private string GetUrl(string baseUrl, string queryString)
|
|
|
- {
|
|
|
- ArgumentException.ThrowIfNullOrEmpty(baseUrl);
|
|
|
+ if (VideoCodecs.Count != 0)
|
|
|
+ {
|
|
|
+ sb.Append("&VideoCodec=");
|
|
|
+ sb.AppendJoin(',', VideoCodecs);
|
|
|
+ }
|
|
|
|
|
|
- string extension = string.IsNullOrEmpty(Container) ? string.Empty : "." + Container;
|
|
|
+ if (AudioCodecs.Count != 0)
|
|
|
+ {
|
|
|
+ sb.Append("&AudioCodec=");
|
|
|
+ sb.AppendJoin(',', AudioCodecs);
|
|
|
+ }
|
|
|
|
|
|
- baseUrl = baseUrl.TrimEnd('/');
|
|
|
+ if (AudioStreamIndex.HasValue)
|
|
|
+ {
|
|
|
+ sb.Append("&AudioStreamIndex=");
|
|
|
+ sb.Append(AudioStreamIndex.Value.ToString(CultureInfo.InvariantCulture));
|
|
|
+ }
|
|
|
|
|
|
- if (MediaType == DlnaProfileType.Audio)
|
|
|
+ if (SubtitleStreamIndex.HasValue && SubtitleDeliveryMethod != SubtitleDeliveryMethod.External && SubtitleStreamIndex != -1)
|
|
|
{
|
|
|
- if (SubProtocol == MediaStreamProtocol.hls)
|
|
|
- {
|
|
|
- return string.Format(CultureInfo.InvariantCulture, "{0}/audio/{1}/master.m3u8?{2}", baseUrl, ItemId, queryString);
|
|
|
- }
|
|
|
+ sb.Append("&SubtitleStreamIndex=");
|
|
|
+ sb.Append(SubtitleStreamIndex.Value.ToString(CultureInfo.InvariantCulture));
|
|
|
+ sb.Append("&SubtitleMethod=");
|
|
|
+ sb.Append(SubtitleDeliveryMethod.ToString());
|
|
|
+ }
|
|
|
|
|
|
- return string.Format(CultureInfo.InvariantCulture, "{0}/audio/{1}/stream{2}?{3}", baseUrl, ItemId, extension, queryString);
|
|
|
+ if (VideoBitrate.HasValue)
|
|
|
+ {
|
|
|
+ sb.Append("&VideoBitrate=");
|
|
|
+ sb.Append(VideoBitrate.Value.ToString(CultureInfo.InvariantCulture));
|
|
|
}
|
|
|
|
|
|
- if (SubProtocol == MediaStreamProtocol.hls)
|
|
|
+ if (AudioBitrate.HasValue)
|
|
|
{
|
|
|
- return string.Format(CultureInfo.InvariantCulture, "{0}/videos/{1}/master.m3u8?{2}", baseUrl, ItemId, queryString);
|
|
|
+ sb.Append("&AudioBitrate=");
|
|
|
+ sb.Append(AudioBitrate.Value.ToString(CultureInfo.InvariantCulture));
|
|
|
}
|
|
|
|
|
|
- return string.Format(CultureInfo.InvariantCulture, "{0}/videos/{1}/stream{2}?{3}", baseUrl, ItemId, extension, queryString);
|
|
|
- }
|
|
|
+ if (AudioSampleRate.HasValue)
|
|
|
+ {
|
|
|
+ sb.Append("&AudioSampleRate=");
|
|
|
+ sb.Append(AudioSampleRate.Value.ToString(CultureInfo.InvariantCulture));
|
|
|
+ }
|
|
|
|
|
|
- private static List<NameValuePair> BuildParams(StreamInfo item, string? accessToken)
|
|
|
- {
|
|
|
- List<NameValuePair> list = [];
|
|
|
+ if (MaxFramerate.HasValue)
|
|
|
+ {
|
|
|
+ sb.Append("&MaxFramerate=");
|
|
|
+ sb.Append(MaxFramerate.Value.ToString(CultureInfo.InvariantCulture));
|
|
|
+ }
|
|
|
|
|
|
- string audioCodecs = item.AudioCodecs.Count == 0 ?
|
|
|
- string.Empty :
|
|
|
- string.Join(',', item.AudioCodecs);
|
|
|
+ if (MaxWidth.HasValue)
|
|
|
+ {
|
|
|
+ sb.Append("&MaxWidth=");
|
|
|
+ sb.Append(MaxWidth.Value.ToString(CultureInfo.InvariantCulture));
|
|
|
+ }
|
|
|
|
|
|
- string videoCodecs = item.VideoCodecs.Count == 0 ?
|
|
|
- string.Empty :
|
|
|
- string.Join(',', item.VideoCodecs);
|
|
|
+ if (MaxHeight.HasValue)
|
|
|
+ {
|
|
|
+ sb.Append("&MaxHeight=");
|
|
|
+ sb.Append(MaxHeight.Value.ToString(CultureInfo.InvariantCulture));
|
|
|
+ }
|
|
|
|
|
|
- list.Add(new NameValuePair("DeviceProfileId", item.DeviceProfileId ?? string.Empty));
|
|
|
- list.Add(new NameValuePair("DeviceId", item.DeviceId ?? string.Empty));
|
|
|
- list.Add(new NameValuePair("MediaSourceId", item.MediaSourceId ?? string.Empty));
|
|
|
- list.Add(new NameValuePair("Static", item.IsDirectStream.ToString(CultureInfo.InvariantCulture).ToLowerInvariant()));
|
|
|
- list.Add(new NameValuePair("VideoCodec", videoCodecs));
|
|
|
- list.Add(new NameValuePair("AudioCodec", audioCodecs));
|
|
|
- list.Add(new NameValuePair("AudioStreamIndex", item.AudioStreamIndex.HasValue ? item.AudioStreamIndex.Value.ToString(CultureInfo.InvariantCulture) : string.Empty));
|
|
|
- list.Add(new NameValuePair("SubtitleStreamIndex", item.SubtitleStreamIndex.HasValue && (item.AlwaysBurnInSubtitleWhenTranscoding || item.SubtitleDeliveryMethod != SubtitleDeliveryMethod.External) ? item.SubtitleStreamIndex.Value.ToString(CultureInfo.InvariantCulture) : string.Empty));
|
|
|
- list.Add(new NameValuePair("VideoBitrate", item.VideoBitrate.HasValue ? item.VideoBitrate.Value.ToString(CultureInfo.InvariantCulture) : string.Empty));
|
|
|
- list.Add(new NameValuePair("AudioBitrate", item.AudioBitrate.HasValue ? item.AudioBitrate.Value.ToString(CultureInfo.InvariantCulture) : string.Empty));
|
|
|
- list.Add(new NameValuePair("AudioSampleRate", item.AudioSampleRate.HasValue ? item.AudioSampleRate.Value.ToString(CultureInfo.InvariantCulture) : string.Empty));
|
|
|
+ if (SubProtocol == MediaStreamProtocol.hls)
|
|
|
+ {
|
|
|
+ if (!string.IsNullOrEmpty(Container))
|
|
|
+ {
|
|
|
+ sb.Append("&SegmentContainer=");
|
|
|
+ sb.Append(Container);
|
|
|
+ }
|
|
|
|
|
|
- list.Add(new NameValuePair("MaxFramerate", item.MaxFramerate.HasValue ? item.MaxFramerate.Value.ToString(CultureInfo.InvariantCulture) : string.Empty));
|
|
|
- list.Add(new NameValuePair("MaxWidth", item.MaxWidth.HasValue ? item.MaxWidth.Value.ToString(CultureInfo.InvariantCulture) : string.Empty));
|
|
|
- list.Add(new NameValuePair("MaxHeight", item.MaxHeight.HasValue ? item.MaxHeight.Value.ToString(CultureInfo.InvariantCulture) : string.Empty));
|
|
|
+ if (SegmentLength.HasValue)
|
|
|
+ {
|
|
|
+ sb.Append("&SegmentLength=");
|
|
|
+ sb.Append(SegmentLength.Value.ToString(CultureInfo.InvariantCulture));
|
|
|
+ }
|
|
|
|
|
|
- long startPositionTicks = item.StartPositionTicks;
|
|
|
+ if (MinSegments.HasValue)
|
|
|
+ {
|
|
|
+ sb.Append("&MinSegments=");
|
|
|
+ sb.Append(MinSegments.Value.ToString(CultureInfo.InvariantCulture));
|
|
|
+ }
|
|
|
|
|
|
- if (item.SubProtocol == MediaStreamProtocol.hls)
|
|
|
- {
|
|
|
- list.Add(new NameValuePair("StartTimeTicks", string.Empty));
|
|
|
+ sb.Append("&BreakOnNonKeyFrames=");
|
|
|
+ sb.Append(BreakOnNonKeyFrames.ToString(CultureInfo.InvariantCulture));
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
- list.Add(new NameValuePair("StartTimeTicks", startPositionTicks.ToString(CultureInfo.InvariantCulture)));
|
|
|
+ if (StartPositionTicks != 0)
|
|
|
+ {
|
|
|
+ sb.Append("&StartTimeTicks=");
|
|
|
+ sb.Append(StartPositionTicks.ToString(CultureInfo.InvariantCulture));
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
- list.Add(new NameValuePair("PlaySessionId", item.PlaySessionId ?? string.Empty));
|
|
|
- list.Add(new NameValuePair("ApiKey", accessToken ?? string.Empty));
|
|
|
+ if (!string.IsNullOrEmpty(PlaySessionId))
|
|
|
+ {
|
|
|
+ sb.Append("&PlaySessionId=");
|
|
|
+ sb.Append(PlaySessionId);
|
|
|
+ }
|
|
|
|
|
|
- string? liveStreamId = item.MediaSource?.LiveStreamId;
|
|
|
- list.Add(new NameValuePair("LiveStreamId", liveStreamId ?? string.Empty));
|
|
|
+ if (!string.IsNullOrEmpty(accessToken))
|
|
|
+ {
|
|
|
+ sb.Append("&ApiKey=");
|
|
|
+ sb.Append(accessToken);
|
|
|
+ }
|
|
|
|
|
|
- list.Add(new NameValuePair("SubtitleMethod", item.SubtitleStreamIndex.HasValue && item.SubtitleDeliveryMethod != SubtitleDeliveryMethod.External ? item.SubtitleDeliveryMethod.ToString() : string.Empty));
|
|
|
+ var liveStreamId = MediaSource?.LiveStreamId;
|
|
|
+ if (!string.IsNullOrEmpty(liveStreamId))
|
|
|
+ {
|
|
|
+ sb.Append("&LiveStreamId=");
|
|
|
+ sb.Append(liveStreamId);
|
|
|
+ }
|
|
|
|
|
|
- if (!item.IsDirectStream)
|
|
|
+ if (!IsDirectStream)
|
|
|
{
|
|
|
- if (item.RequireNonAnamorphic)
|
|
|
+ if (RequireNonAnamorphic)
|
|
|
{
|
|
|
- list.Add(new NameValuePair("RequireNonAnamorphic", item.RequireNonAnamorphic.ToString(CultureInfo.InvariantCulture).ToLowerInvariant()));
|
|
|
+ sb.Append("&RequireNonAnamorphic=");
|
|
|
+ sb.Append(RequireNonAnamorphic.ToString(CultureInfo.InvariantCulture));
|
|
|
}
|
|
|
|
|
|
- list.Add(new NameValuePair("TranscodingMaxAudioChannels", item.TranscodingMaxAudioChannels.HasValue ? item.TranscodingMaxAudioChannels.Value.ToString(CultureInfo.InvariantCulture) : string.Empty));
|
|
|
-
|
|
|
- if (item.EnableSubtitlesInManifest)
|
|
|
+ if (TranscodingMaxAudioChannels.HasValue)
|
|
|
{
|
|
|
- list.Add(new NameValuePair("EnableSubtitlesInManifest", item.EnableSubtitlesInManifest.ToString(CultureInfo.InvariantCulture).ToLowerInvariant()));
|
|
|
+ sb.Append("&TranscodingMaxAudioChannels=");
|
|
|
+ sb.Append(TranscodingMaxAudioChannels.Value.ToString(CultureInfo.InvariantCulture));
|
|
|
}
|
|
|
|
|
|
- if (item.EnableMpegtsM2TsMode)
|
|
|
+ if (EnableSubtitlesInManifest)
|
|
|
{
|
|
|
- list.Add(new NameValuePair("EnableMpegtsM2TsMode", item.EnableMpegtsM2TsMode.ToString(CultureInfo.InvariantCulture).ToLowerInvariant()));
|
|
|
+ sb.Append("&EnableSubtitlesInManifest=");
|
|
|
+ sb.Append(EnableSubtitlesInManifest.ToString(CultureInfo.InvariantCulture));
|
|
|
}
|
|
|
|
|
|
- if (item.EstimateContentLength)
|
|
|
+ if (EnableMpegtsM2TsMode)
|
|
|
{
|
|
|
- list.Add(new NameValuePair("EstimateContentLength", item.EstimateContentLength.ToString(CultureInfo.InvariantCulture).ToLowerInvariant()));
|
|
|
+ sb.Append("&EnableMpegtsM2TsMode=");
|
|
|
+ sb.Append(EnableMpegtsM2TsMode.ToString(CultureInfo.InvariantCulture));
|
|
|
}
|
|
|
|
|
|
- if (item.TranscodeSeekInfo != TranscodeSeekInfo.Auto)
|
|
|
+ if (EstimateContentLength)
|
|
|
{
|
|
|
- list.Add(new NameValuePair("TranscodeSeekInfo", item.TranscodeSeekInfo.ToString().ToLowerInvariant()));
|
|
|
+ sb.Append("&EstimateContentLength=");
|
|
|
+ sb.Append(EstimateContentLength.ToString(CultureInfo.InvariantCulture));
|
|
|
}
|
|
|
|
|
|
- if (item.CopyTimestamps)
|
|
|
+ if (TranscodeSeekInfo != TranscodeSeekInfo.Auto)
|
|
|
{
|
|
|
- list.Add(new NameValuePair("CopyTimestamps", item.CopyTimestamps.ToString(CultureInfo.InvariantCulture).ToLowerInvariant()));
|
|
|
+ sb.Append("&TranscodeSeekInfo=");
|
|
|
+ sb.Append(TranscodeSeekInfo.ToString());
|
|
|
}
|
|
|
|
|
|
- list.Add(new NameValuePair("RequireAvc", item.RequireAvc.ToString(CultureInfo.InvariantCulture).ToLowerInvariant()));
|
|
|
-
|
|
|
- list.Add(new NameValuePair("EnableAudioVbrEncoding", item.EnableAudioVbrEncoding.ToString(CultureInfo.InvariantCulture).ToLowerInvariant()));
|
|
|
- }
|
|
|
-
|
|
|
- list.Add(new NameValuePair("Tag", item.MediaSource?.ETag ?? string.Empty));
|
|
|
-
|
|
|
- string subtitleCodecs = item.SubtitleCodecs.Count == 0 ?
|
|
|
- string.Empty :
|
|
|
- string.Join(",", item.SubtitleCodecs);
|
|
|
-
|
|
|
- list.Add(new NameValuePair("SubtitleCodec", item.SubtitleStreamIndex.HasValue && item.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Embed ? subtitleCodecs : string.Empty));
|
|
|
-
|
|
|
- if (item.SubProtocol == MediaStreamProtocol.hls)
|
|
|
- {
|
|
|
- list.Add(new NameValuePair("SegmentContainer", item.Container ?? string.Empty));
|
|
|
+ if (CopyTimestamps)
|
|
|
+ {
|
|
|
+ sb.Append("&CopyTimestamps=");
|
|
|
+ sb.Append(CopyTimestamps.ToString(CultureInfo.InvariantCulture));
|
|
|
+ }
|
|
|
|
|
|
- if (item.SegmentLength.HasValue)
|
|
|
+ if (RequireAvc)
|
|
|
{
|
|
|
- list.Add(new NameValuePair("SegmentLength", item.SegmentLength.Value.ToString(CultureInfo.InvariantCulture)));
|
|
|
+ sb.Append("&RequireAvc=");
|
|
|
+ sb.Append(RequireAvc.ToString(CultureInfo.InvariantCulture));
|
|
|
}
|
|
|
|
|
|
- if (item.MinSegments.HasValue)
|
|
|
+ if (EnableAudioVbrEncoding)
|
|
|
{
|
|
|
- list.Add(new NameValuePair("MinSegments", item.MinSegments.Value.ToString(CultureInfo.InvariantCulture)));
|
|
|
+ sb.Append("EnableAudioVbrEncoding=");
|
|
|
+ sb.Append(EnableAudioVbrEncoding.ToString(CultureInfo.InvariantCulture).ToLowerInvariant());
|
|
|
}
|
|
|
+ }
|
|
|
|
|
|
- list.Add(new NameValuePair("BreakOnNonKeyFrames", item.BreakOnNonKeyFrames.ToString(CultureInfo.InvariantCulture)));
|
|
|
+ var etag = MediaSource?.ETag;
|
|
|
+ if (!string.IsNullOrEmpty(etag))
|
|
|
+ {
|
|
|
+ sb.Append("&Tag=");
|
|
|
+ sb.Append(etag);
|
|
|
}
|
|
|
|
|
|
- foreach (var pair in item.StreamOptions)
|
|
|
+ if (SubtitleStreamIndex.HasValue && SubtitleDeliveryMethod != SubtitleDeliveryMethod.External)
|
|
|
{
|
|
|
- if (string.IsNullOrEmpty(pair.Value))
|
|
|
- {
|
|
|
- continue;
|
|
|
- }
|
|
|
+ sb.Append("&SubtitleMethod=");
|
|
|
+ sb.AppendJoin(',', SubtitleDeliveryMethod);
|
|
|
+ }
|
|
|
|
|
|
- // strip spaces to avoid having to encode h264 profile names
|
|
|
- list.Add(new NameValuePair(pair.Key, pair.Value.Replace(" ", string.Empty, StringComparison.Ordinal)));
|
|
|
+ if (SubtitleStreamIndex.HasValue && SubtitleDeliveryMethod == SubtitleDeliveryMethod.Embed && SubtitleCodecs.Count != 0)
|
|
|
+ {
|
|
|
+ sb.Append("&SubtitleCodec=");
|
|
|
+ sb.AppendJoin(',', SubtitleCodecs);
|
|
|
}
|
|
|
|
|
|
- if (!item.IsDirectStream)
|
|
|
+ foreach (var pair in StreamOptions)
|
|
|
{
|
|
|
- list.Add(new NameValuePair("TranscodeReasons", item.TranscodeReasons.ToString()));
|
|
|
+ // Strip spaces to avoid having to encode h264 profile names
|
|
|
+ sb.Append('&');
|
|
|
+ sb.Append(pair.Key);
|
|
|
+ sb.Append('=');
|
|
|
+ sb.Append(pair.Value.Replace(" ", string.Empty, StringComparison.Ordinal));
|
|
|
}
|
|
|
|
|
|
- return list;
|
|
|
+ var transcodeReasonsValues = TranscodeReasons.GetUniqueFlags().ToArray();
|
|
|
+ if (!IsDirectStream && transcodeReasonsValues.Length > 0)
|
|
|
+ {
|
|
|
+ sb.Append("&TranscodeReasons=");
|
|
|
+ sb.AppendJoin(',', transcodeReasonsValues);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!string.IsNullOrEmpty(query))
|
|
|
+ {
|
|
|
+ sb.Append(query);
|
|
|
+ }
|
|
|
+
|
|
|
+ return sb.ToString();
|
|
|
}
|
|
|
|
|
|
/// <summary>
|