Cleanup and refactor streambuilder

This commit is contained in:
Shadowghost 2022-03-26 12:11:00 +01:00
parent 3cb7fe5012
commit 697efec86e
7 changed files with 274 additions and 255 deletions

View File

@ -195,7 +195,7 @@ namespace Emby.Dlna.Didl
{ {
var sources = _mediaSourceManager.GetStaticMediaSources(video, true, _user); var sources = _mediaSourceManager.GetStaticMediaSources(video, true, _user);
streamInfo = new StreamBuilder(_mediaEncoder, _logger).BuildVideoItem(new VideoOptions streamInfo = new StreamBuilder(_mediaEncoder, _logger).GetOptimalVideoStream(new MediaOptions
{ {
ItemId = video.Id, ItemId = video.Id,
MediaSources = sources.ToArray(), MediaSources = sources.ToArray(),
@ -537,7 +537,7 @@ namespace Emby.Dlna.Didl
{ {
var sources = _mediaSourceManager.GetStaticMediaSources(audio, true, _user); var sources = _mediaSourceManager.GetStaticMediaSources(audio, true, _user);
streamInfo = new StreamBuilder(_mediaEncoder, _logger).BuildAudioItem(new AudioOptions streamInfo = new StreamBuilder(_mediaEncoder, _logger).GetOptimalAudioStream(new MediaOptions
{ {
ItemId = audio.Id, ItemId = audio.Id,
MediaSources = sources.ToArray(), MediaSources = sources.ToArray(),

View File

@ -585,7 +585,7 @@ namespace Emby.Dlna.PlayTo
{ {
return new PlaylistItem return new PlaylistItem
{ {
StreamInfo = new StreamBuilder(_mediaEncoder, _logger).BuildVideoItem(new VideoOptions StreamInfo = new StreamBuilder(_mediaEncoder, _logger).GetOptimalVideoStream(new MediaOptions
{ {
ItemId = item.Id, ItemId = item.Id,
MediaSources = mediaSources, MediaSources = mediaSources,
@ -605,7 +605,7 @@ namespace Emby.Dlna.PlayTo
{ {
return new PlaylistItem return new PlaylistItem
{ {
StreamInfo = new StreamBuilder(_mediaEncoder, _logger).BuildAudioItem(new AudioOptions StreamInfo = new StreamBuilder(_mediaEncoder, _logger).GetOptimalAudioStream(new MediaOptions
{ {
ItemId = item.Id, ItemId = item.Id,
MediaSources = mediaSources, MediaSources = mediaSources,

View File

@ -181,7 +181,7 @@ namespace Jellyfin.Api.Helpers
{ {
var streamBuilder = new StreamBuilder(_mediaEncoder, _logger); var streamBuilder = new StreamBuilder(_mediaEncoder, _logger);
var options = new VideoOptions var options = new MediaOptions
{ {
MediaSources = new[] { mediaSource }, MediaSources = new[] { mediaSource },
Context = EncodingContext.Streaming, Context = EncodingContext.Streaming,
@ -244,8 +244,8 @@ namespace Jellyfin.Api.Helpers
// Beginning of Playback Determination // Beginning of Playback Determination
var streamInfo = string.Equals(item.MediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase) var streamInfo = string.Equals(item.MediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase)
? streamBuilder.BuildAudioItem(options) ? streamBuilder.GetOptimalAudioStream(options)
: streamBuilder.BuildVideoItem(options); : streamBuilder.GetOptimalVideoStream(options);
if (streamInfo is not null) if (streamInfo is not null)
{ {

View File

@ -7,11 +7,11 @@ using MediaBrowser.Model.Dto;
namespace MediaBrowser.Model.Dlna namespace MediaBrowser.Model.Dlna
{ {
/// <summary> /// <summary>
/// Class AudioOptions. /// Class MediaOptions.
/// </summary> /// </summary>
public class AudioOptions public class MediaOptions
{ {
public AudioOptions() public MediaOptions()
{ {
Context = EncodingContext.Streaming; Context = EncodingContext.Streaming;
@ -27,8 +27,16 @@ namespace MediaBrowser.Model.Dlna
public bool ForceDirectStream { get; set; } public bool ForceDirectStream { get; set; }
/// <summary>
/// Gets or sets an override for allowing stream copy.
/// </summary>
public bool AllowAudioStreamCopy { get; set; } public bool AllowAudioStreamCopy { get; set; }
/// <summary>
/// Gets or sets an override for allowing stream copy.
/// </summary>
public bool AllowVideoStreamCopy { get; set; }
public Guid ItemId { get; set; } public Guid ItemId { get; set; }
public MediaSourceInfo[] MediaSources { get; set; } public MediaSourceInfo[] MediaSources { get; set; }
@ -65,6 +73,16 @@ namespace MediaBrowser.Model.Dlna
/// <value>The audio transcoding bitrate.</value> /// <value>The audio transcoding bitrate.</value>
public int? AudioTranscodingBitrate { get; set; } public int? AudioTranscodingBitrate { get; set; }
/// <summary>
/// Gets or sets an override for the audio stream index.
/// </summary>
public int? AudioStreamIndex { get; set; }
/// <summary>
/// Gets or sets an override for the subtitle stream index.
/// </summary>
public int? SubtitleStreamIndex { get; set; }
/// <summary> /// <summary>
/// Gets the maximum bitrate. /// Gets the maximum bitrate.
/// </summary> /// </summary>

View File

@ -1,5 +1,4 @@
#nullable disable #nullable disable
#pragma warning disable CS1591
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
@ -13,6 +12,9 @@ using Microsoft.Extensions.Logging;
namespace MediaBrowser.Model.Dlna namespace MediaBrowser.Model.Dlna
{ {
/// <summary>
/// Class StreamBuilder.
/// </summary>
public class StreamBuilder public class StreamBuilder
{ {
// Aliases // Aliases
@ -24,35 +26,49 @@ namespace MediaBrowser.Model.Dlna
private readonly ILogger _logger; private readonly ILogger _logger;
private readonly ITranscoderSupport _transcoderSupport; private readonly ITranscoderSupport _transcoderSupport;
/// <summary>
/// Initializes a new instance of the <see cref="StreamBuilder"/> class.
/// </summary>
/// <param name="transcoderSupport">The <see cref="ITranscoderSupport"/> object.</param>
/// <param name="logger">The <see cref="ILogger"/> object.</param>
public StreamBuilder(ITranscoderSupport transcoderSupport, ILogger logger) public StreamBuilder(ITranscoderSupport transcoderSupport, ILogger logger)
{ {
_transcoderSupport = transcoderSupport; _transcoderSupport = transcoderSupport;
_logger = logger; _logger = logger;
} }
/// <summary>
/// Initializes a new instance of the <see cref="StreamBuilder"/> class.
/// </summary>
/// <param name="logger">The <see cref="ILogger"/> object.</param>
public StreamBuilder(ILogger<StreamBuilder> logger) public StreamBuilder(ILogger<StreamBuilder> logger)
: this(new FullTranscoderSupport(), logger) : this(new FullTranscoderSupport(), logger)
{ {
} }
public StreamInfo BuildAudioItem(AudioOptions options) /// <summary>
/// Gets the optimal audio stream.
/// </summary>
/// <param name="options">The <see cref="MediaOptions"/> object to get the audio stream from.</param>
/// <returns>The <see cref="StreamInfo"/> of the optimal audio stream.</returns>
public StreamInfo GetOptimalAudioStream(MediaOptions options)
{ {
ValidateAudioInput(options); ValidateMediaOptions(options, false);
var mediaSources = new List<MediaSourceInfo>(); var mediaSources = new List<MediaSourceInfo>();
foreach (MediaSourceInfo i in options.MediaSources) foreach (var mediaSource in options.MediaSources)
{ {
if (string.IsNullOrEmpty(options.MediaSourceId) || if (string.IsNullOrEmpty(options.MediaSourceId) ||
string.Equals(i.Id, options.MediaSourceId, StringComparison.OrdinalIgnoreCase)) string.Equals(mediaSource.Id, options.MediaSourceId, StringComparison.OrdinalIgnoreCase))
{ {
mediaSources.Add(i); mediaSources.Add(mediaSource);
} }
} }
var streams = new List<StreamInfo>(); var streams = new List<StreamInfo>();
foreach (MediaSourceInfo i in mediaSources) foreach (MediaSourceInfo i in mediaSources)
{ {
StreamInfo streamInfo = BuildAudioItem(i, options); StreamInfo streamInfo = GetOptimalAudioStream(i, options);
if (streamInfo is not null) if (streamInfo is not null)
{ {
streams.Add(streamInfo); streams.Add(streamInfo);
@ -68,9 +84,115 @@ namespace MediaBrowser.Model.Dlna
return GetOptimalStream(streams, options.GetMaxBitrate(true) ?? 0); return GetOptimalStream(streams, options.GetMaxBitrate(true) ?? 0);
} }
public StreamInfo BuildVideoItem(VideoOptions options) private StreamInfo GetOptimalAudioStream(MediaSourceInfo item, MediaOptions options)
{ {
ValidateInput(options); var playlistItem = new StreamInfo
{
ItemId = options.ItemId,
MediaType = DlnaProfileType.Audio,
MediaSource = item,
RunTimeTicks = item.RunTimeTicks,
Context = options.Context,
DeviceProfile = options.Profile
};
if (options.ForceDirectPlay)
{
playlistItem.PlayMethod = PlayMethod.DirectPlay;
playlistItem.Container = NormalizeMediaSourceFormatIntoSingleContainer(item.Container, options.Profile, DlnaProfileType.Audio);
return playlistItem;
}
if (options.ForceDirectStream)
{
playlistItem.PlayMethod = PlayMethod.DirectStream;
playlistItem.Container = NormalizeMediaSourceFormatIntoSingleContainer(item.Container, options.Profile, DlnaProfileType.Audio);
return playlistItem;
}
MediaStream audioStream = item.GetDefaultAudioStream(null);
var directPlayInfo = GetAudioDirectPlayProfile(item, audioStream, options);
var directPlayMethod = directPlayInfo.PlayMethod;
var transcodeReasons = directPlayInfo.TranscodeReasons;
var inputAudioChannels = audioStream?.Channels;
var inputAudioBitrate = audioStream?.BitDepth;
var inputAudioSampleRate = audioStream?.SampleRate;
var inputAudioBitDepth = audioStream?.BitDepth;
if (directPlayMethod.HasValue)
{
var profile = options.Profile;
var audioFailureConditions = GetProfileConditionsForAudio(profile.CodecProfiles, item.Container, audioStream?.Codec, inputAudioChannels, inputAudioBitrate, inputAudioSampleRate, inputAudioBitDepth, true);
var audioFailureReasons = AggregateFailureConditions(item, profile, "AudioCodecProfile", audioFailureConditions);
transcodeReasons |= audioFailureReasons;
if (audioFailureReasons == 0)
{
playlistItem.PlayMethod = directPlayMethod.Value;
playlistItem.Container = NormalizeMediaSourceFormatIntoSingleContainer(item.Container, options.Profile, DlnaProfileType.Audio, directPlayInfo.Profile);
return playlistItem;
}
}
TranscodingProfile transcodingProfile = null;
foreach (TranscodingProfile i in options.Profile.TranscodingProfiles)
{
if (i.Type == playlistItem.MediaType
&& i.Context == options.Context
&& _transcoderSupport.CanEncodeToAudioCodec(i.AudioCodec ?? i.Container))
{
transcodingProfile = i;
break;
}
}
if (transcodingProfile != null)
{
if (!item.SupportsTranscoding)
{
return null;
}
SetStreamInfoOptionsFromTranscodingProfile(item, playlistItem, transcodingProfile);
var audioTranscodingConditions = GetProfileConditionsForAudio(options.Profile.CodecProfiles, transcodingProfile.Container, transcodingProfile.AudioCodec, inputAudioChannels, inputAudioBitrate, inputAudioSampleRate, inputAudioBitDepth, false).ToArray();
ApplyTranscodingConditions(playlistItem, audioTranscodingConditions, null, true, true);
// Honor requested max channels
playlistItem.GlobalMaxAudioChannels = options.MaxAudioChannels;
var configuredBitrate = options.GetMaxBitrate(true);
long transcodingBitrate = options.AudioTranscodingBitrate ??
(options.Context == EncodingContext.Streaming ? options.Profile.MusicStreamingTranscodingBitrate : null) ??
configuredBitrate ??
128000;
if (configuredBitrate.HasValue)
{
transcodingBitrate = Math.Min(configuredBitrate.Value, transcodingBitrate);
}
var longBitrate = Math.Min(transcodingBitrate, playlistItem.AudioBitrate ?? transcodingBitrate);
playlistItem.AudioBitrate = longBitrate > int.MaxValue ? int.MaxValue : Convert.ToInt32(longBitrate);
}
playlistItem.TranscodeReasons = transcodeReasons;
return playlistItem;
}
/// <summary>
/// Gets the optimal video stream.
/// </summary>
/// <param name="options">The <see cref="MediaOptions"/> object to get the video stream from.</param>
/// <returns>The <see cref="StreamInfo"/> of the optimal video stream.</returns>
public StreamInfo GetOptimalVideoStream(MediaOptions options)
{
ValidateMediaOptions(options, true);
var mediaSources = new List<MediaSourceInfo>(); var mediaSources = new List<MediaSourceInfo>();
foreach (MediaSourceInfo i in options.MediaSources) foreach (MediaSourceInfo i in options.MediaSources)
@ -236,6 +358,14 @@ namespace MediaBrowser.Model.Dlna
} }
} }
/// <summary>
/// Normalizes input container.
/// </summary>
/// <param name="inputContainer">The input container.</param>
/// <param name="profile">The <see cref="DeviceProfile"/>.</param>
/// <param name="type">The <see cref="DlnaProfileType"/>.</param>
/// <param name="playProfile">The <see cref="DirectPlayProfile"/> object to get the video stream from.</param>
/// <returns>The the normalized input container.</returns>
public static string NormalizeMediaSourceFormatIntoSingleContainer(string inputContainer, DeviceProfile profile, DlnaProfileType type, DirectPlayProfile playProfile = null) public static string NormalizeMediaSourceFormatIntoSingleContainer(string inputContainer, DeviceProfile profile, DlnaProfileType type, DirectPlayProfile playProfile = null)
{ {
if (string.IsNullOrEmpty(inputContainer)) if (string.IsNullOrEmpty(inputContainer))
@ -264,108 +394,7 @@ namespace MediaBrowser.Model.Dlna
return formats[0]; return formats[0];
} }
private StreamInfo BuildAudioItem(MediaSourceInfo item, AudioOptions options) private (DirectPlayProfile Profile, PlayMethod? PlayMethod, TranscodeReason TranscodeReasons) GetAudioDirectPlayProfile(MediaSourceInfo item, MediaStream audioStream, MediaOptions options)
{
StreamInfo playlistItem = new StreamInfo
{
ItemId = options.ItemId,
MediaType = DlnaProfileType.Audio,
MediaSource = item,
RunTimeTicks = item.RunTimeTicks,
Context = options.Context,
DeviceProfile = options.Profile
};
if (options.ForceDirectPlay)
{
playlistItem.PlayMethod = PlayMethod.DirectPlay;
playlistItem.Container = NormalizeMediaSourceFormatIntoSingleContainer(item.Container, options.Profile, DlnaProfileType.Audio);
return playlistItem;
}
if (options.ForceDirectStream)
{
playlistItem.PlayMethod = PlayMethod.DirectStream;
playlistItem.Container = NormalizeMediaSourceFormatIntoSingleContainer(item.Container, options.Profile, DlnaProfileType.Audio);
return playlistItem;
}
var audioStream = item.GetDefaultAudioStream(null);
var directPlayInfo = GetAudioDirectPlayProfile(item, audioStream, options);
var directPlayMethod = directPlayInfo.PlayMethod;
var transcodeReasons = directPlayInfo.TranscodeReasons;
int? inputAudioChannels = audioStream?.Channels;
int? inputAudioBitrate = audioStream?.BitDepth;
int? inputAudioSampleRate = audioStream?.SampleRate;
int? inputAudioBitDepth = audioStream?.BitDepth;
if (directPlayMethod.HasValue)
{
var profile = options.Profile;
var audioFailureConditions = GetProfileConditionsForAudio(profile.CodecProfiles, item.Container, audioStream?.Codec, inputAudioChannels, inputAudioBitrate, inputAudioSampleRate, inputAudioBitDepth, true);
var audioFailureReasons = AggregateFailureConditions(item, profile, "AudioCodecProfile", audioFailureConditions);
transcodeReasons |= audioFailureReasons;
if (audioFailureReasons == 0)
{
playlistItem.PlayMethod = directPlayMethod.Value;
playlistItem.Container = NormalizeMediaSourceFormatIntoSingleContainer(item.Container, options.Profile, DlnaProfileType.Audio, directPlayInfo.Profile);
return playlistItem;
}
}
TranscodingProfile transcodingProfile = null;
foreach (var i in options.Profile.TranscodingProfiles)
{
if (i.Type == playlistItem.MediaType
&& i.Context == options.Context
&& _transcoderSupport.CanEncodeToAudioCodec(i.AudioCodec ?? i.Container))
{
transcodingProfile = i;
break;
}
}
if (transcodingProfile is not null)
{
if (!item.SupportsTranscoding)
{
return null;
}
SetStreamInfoOptionsFromTranscodingProfile(item, playlistItem, transcodingProfile);
var audioTranscodingConditions = GetProfileConditionsForAudio(options.Profile.CodecProfiles, transcodingProfile.Container, transcodingProfile.AudioCodec, inputAudioChannels, inputAudioBitrate, inputAudioSampleRate, inputAudioBitDepth, false).ToArray();
ApplyTranscodingConditions(playlistItem, audioTranscodingConditions, null, true, true);
// Honor requested max channels
playlistItem.GlobalMaxAudioChannels = options.MaxAudioChannels;
var configuredBitrate = options.GetMaxBitrate(true);
long transcodingBitrate = options.AudioTranscodingBitrate ??
(options.Context == EncodingContext.Streaming ? options.Profile.MusicStreamingTranscodingBitrate : null) ??
configuredBitrate ??
128000;
if (configuredBitrate.HasValue)
{
transcodingBitrate = Math.Min(configuredBitrate.Value, transcodingBitrate);
}
var longBitrate = Math.Min(transcodingBitrate, playlistItem.AudioBitrate ?? transcodingBitrate);
playlistItem.AudioBitrate = longBitrate > int.MaxValue ? int.MaxValue : Convert.ToInt32(longBitrate);
}
playlistItem.TranscodeReasons = transcodeReasons;
return playlistItem;
}
private (DirectPlayProfile Profile, PlayMethod? PlayMethod, TranscodeReason TranscodeReasons) GetAudioDirectPlayProfile(MediaSourceInfo item, MediaStream audioStream, AudioOptions options)
{ {
var directPlayProfile = options.Profile.DirectPlayProfiles var directPlayProfile = options.Profile.DirectPlayProfiles
.FirstOrDefault(x => x.Type == DlnaProfileType.Audio && IsAudioDirectPlaySupported(x, item, audioStream)); .FirstOrDefault(x => x.Type == DlnaProfileType.Audio && IsAudioDirectPlaySupported(x, item, audioStream));
@ -388,7 +417,7 @@ namespace MediaBrowser.Model.Dlna
// If device requirements are satisfied then allow both direct stream and direct play // If device requirements are satisfied then allow both direct stream and direct play
if (item.SupportsDirectPlay) if (item.SupportsDirectPlay)
{ {
if (IsItemBitrateEligibleForDirectPlayback(item, options.GetMaxBitrate(true) ?? 0, PlayMethod.DirectPlay)) if (!IsBitrateLimitExceeded(item, options.GetMaxBitrate(true) ?? 0))
{ {
if (options.EnableDirectPlay) if (options.EnableDirectPlay)
{ {
@ -404,7 +433,7 @@ namespace MediaBrowser.Model.Dlna
// While options takes the network and other factors into account. Only applies to direct stream // While options takes the network and other factors into account. Only applies to direct stream
if (item.SupportsDirectStream) if (item.SupportsDirectStream)
{ {
if (IsItemBitrateEligibleForDirectPlayback(item, options.GetMaxBitrate(true) ?? 0, PlayMethod.DirectStream)) if (!IsBitrateLimitExceeded(item, options.GetMaxBitrate(true) ?? 0))
{ {
if (options.EnableDirectStream) if (options.EnableDirectStream)
{ {
@ -427,7 +456,6 @@ namespace MediaBrowser.Model.Dlna
var containerSupported = false; var containerSupported = false;
var audioSupported = false; var audioSupported = false;
var videoSupported = false; var videoSupported = false;
TranscodeReason reasons = 0;
foreach (var profile in directPlayProfiles) foreach (var profile in directPlayProfiles)
{ {
@ -447,6 +475,7 @@ namespace MediaBrowser.Model.Dlna
} }
} }
TranscodeReason reasons = 0;
if (!containerSupported) if (!containerSupported)
{ {
reasons |= TranscodeReason.ContainerNotSupported; reasons |= TranscodeReason.ContainerNotSupported;
@ -547,7 +576,7 @@ namespace MediaBrowser.Model.Dlna
} }
} }
private static void SetStreamInfoOptionsFromDirectPlayProfile(VideoOptions options, MediaSourceInfo item, StreamInfo playlistItem, DirectPlayProfile directPlayProfile) private static void SetStreamInfoOptionsFromDirectPlayProfile(MediaOptions options, MediaSourceInfo item, StreamInfo playlistItem, DirectPlayProfile directPlayProfile)
{ {
var container = NormalizeMediaSourceFormatIntoSingleContainer(item.Container, options.Profile, DlnaProfileType.Video, directPlayProfile); var container = NormalizeMediaSourceFormatIntoSingleContainer(item.Container, options.Profile, DlnaProfileType.Video, directPlayProfile);
var protocol = "http"; var protocol = "http";
@ -562,7 +591,7 @@ namespace MediaBrowser.Model.Dlna
playlistItem.AudioCodecs = ContainerProfile.SplitValue(directPlayProfile.AudioCodec); playlistItem.AudioCodecs = ContainerProfile.SplitValue(directPlayProfile.AudioCodec);
} }
private StreamInfo BuildVideoItem(MediaSourceInfo item, VideoOptions options) private StreamInfo BuildVideoItem(MediaSourceInfo item, MediaOptions options)
{ {
ArgumentNullException.ThrowIfNull(item); ArgumentNullException.ThrowIfNull(item);
@ -601,11 +630,15 @@ namespace MediaBrowser.Model.Dlna
var videoStream = item.VideoStream; var videoStream = item.VideoStream;
var directPlayBitrateEligibility = IsBitrateEligibleForDirectPlayback(item, options.GetMaxBitrate(false) ?? 0, options, PlayMethod.DirectPlay); var bitrateLimitExceeded = IsBitrateLimitExceeded(item, options.GetMaxBitrate(false) ?? 0);
var directStreamBitrateEligibility = IsBitrateEligibleForDirectPlayback(item, options.GetMaxBitrate(false) ?? 0, options, PlayMethod.DirectStream); var isEligibleForDirectPlay = options.EnableDirectPlay && (options.ForceDirectPlay || !bitrateLimitExceeded);
bool isEligibleForDirectPlay = options.EnableDirectPlay && (options.ForceDirectPlay || directPlayBitrateEligibility == 0); var isEligibleForDirectStream = options.EnableDirectStream && (options.ForceDirectStream || !bitrateLimitExceeded);
bool isEligibleForDirectStream = options.EnableDirectStream && (options.ForceDirectStream || directStreamBitrateEligibility == 0); TranscodeReason transcodeReasons = 0;
var transcodeReasons = directPlayBitrateEligibility | directStreamBitrateEligibility;
if (bitrateLimitExceeded)
{
transcodeReasons = TranscodeReason.ContainerBitrateExceedsLimit;
}
_logger.LogDebug( _logger.LogDebug(
"Profile: {0}, Path: {1}, isEligibleForDirectPlay: {2}, isEligibleForDirectStream: {3}", "Profile: {0}, Path: {1}, isEligibleForDirectPlay: {2}, isEligibleForDirectStream: {3}",
@ -702,7 +735,7 @@ namespace MediaBrowser.Model.Dlna
} }
} }
_logger.LogInformation( _logger.LogDebug(
"StreamBuilder.BuildVideoItem( Profile={0}, Path={1}, AudioStreamIndex={2}, SubtitleStreamIndex={3} ) => ( PlayMethod={4}, TranscodeReason={5} ) {6}", "StreamBuilder.BuildVideoItem( Profile={0}, Path={1}, AudioStreamIndex={2}, SubtitleStreamIndex={3} ) => ( PlayMethod={4}, TranscodeReason={5} ) {6}",
options.Profile.Name ?? "Anonymous Profile", options.Profile.Name ?? "Anonymous Profile",
item.Path ?? "Unknown path", item.Path ?? "Unknown path",
@ -716,7 +749,7 @@ namespace MediaBrowser.Model.Dlna
return playlistItem; return playlistItem;
} }
private TranscodingProfile GetVideoTranscodeProfile(MediaSourceInfo item, VideoOptions options, MediaStream videoStream, MediaStream audioStream, IEnumerable<MediaStream> candidateAudioStreams, MediaStream subtitleStream, StreamInfo playlistItem) private TranscodingProfile GetVideoTranscodeProfile(MediaSourceInfo item, MediaOptions options, MediaStream videoStream, MediaStream audioStream, IEnumerable<MediaStream> candidateAudioStreams, MediaStream subtitleStream, StreamInfo playlistItem)
{ {
if (!(item.SupportsTranscoding || item.SupportsDirectStream)) if (!(item.SupportsTranscoding || item.SupportsDirectStream))
{ {
@ -763,7 +796,7 @@ namespace MediaBrowser.Model.Dlna
return transcodingProfiles.FirstOrDefault(); return transcodingProfiles.FirstOrDefault();
} }
private void BuildStreamVideoItem(StreamInfo playlistItem, VideoOptions options, MediaSourceInfo item, MediaStream videoStream, MediaStream audioStream, IEnumerable<MediaStream> candidateAudioStreams, string container, string videoCodec, string audioCodec) private void BuildStreamVideoItem(StreamInfo playlistItem, MediaOptions options, MediaSourceInfo item, MediaStream videoStream, MediaStream audioStream, IEnumerable<MediaStream> candidateAudioStreams, string container, string videoCodec, string audioCodec)
{ {
// Prefer matching video codecs // Prefer matching video codecs
var videoCodecs = ContainerProfile.SplitValue(videoCodec); var videoCodecs = ContainerProfile.SplitValue(videoCodec);
@ -867,7 +900,7 @@ namespace MediaBrowser.Model.Dlna
// Honor requested max channels // Honor requested max channels
playlistItem.GlobalMaxAudioChannels = options.MaxAudioChannels; playlistItem.GlobalMaxAudioChannels = options.MaxAudioChannels;
int audioBitrate = GetAudioBitrate(options.GetMaxBitrate(false) ?? 0, playlistItem.TargetAudioCodec, audioStream, playlistItem); int audioBitrate = GetAudioBitrate(options.GetMaxBitrate(true) ?? 0, playlistItem.TargetAudioCodec, audioStream, playlistItem);
playlistItem.AudioBitrate = Math.Min(playlistItem.AudioBitrate ?? audioBitrate, audioBitrate); playlistItem.AudioBitrate = Math.Min(playlistItem.AudioBitrate ?? audioBitrate, audioBitrate);
bool? isSecondaryAudio = audioStream is null ? null : item.IsSecondaryAudio(audioStream); bool? isSecondaryAudio = audioStream is null ? null : item.IsSecondaryAudio(audioStream);
@ -882,14 +915,14 @@ namespace MediaBrowser.Model.Dlna
i.ContainsAnyCodec(audioCodec, container) && i.ContainsAnyCodec(audioCodec, container) &&
i.ApplyConditions.All(applyCondition => ConditionProcessor.IsVideoAudioConditionSatisfied(applyCondition, audioChannels, inputAudioBitrate, inputAudioSampleRate, inputAudioBitDepth, audioProfile, isSecondaryAudio))); i.ApplyConditions.All(applyCondition => ConditionProcessor.IsVideoAudioConditionSatisfied(applyCondition, audioChannels, inputAudioBitrate, inputAudioSampleRate, inputAudioBitDepth, audioProfile, isSecondaryAudio)));
isFirstAppliedCodecProfile = true; isFirstAppliedCodecProfile = true;
foreach (var i in appliedAudioConditions) foreach (var codecProfile in appliedAudioConditions)
{ {
var transcodingAudioCodecs = ContainerProfile.SplitValue(audioCodec); var transcodingAudioCodecs = ContainerProfile.SplitValue(audioCodec);
foreach (var transcodingAudioCodec in transcodingAudioCodecs) foreach (var transcodingAudioCodec in transcodingAudioCodecs)
{ {
if (i.ContainsAnyCodec(transcodingAudioCodec, container)) if (codecProfile.ContainsAnyCodec(transcodingAudioCodec, container))
{ {
ApplyTranscodingConditions(playlistItem, i.Conditions, transcodingAudioCodec, true, isFirstAppliedCodecProfile); ApplyTranscodingConditions(playlistItem, codecProfile.Conditions, transcodingAudioCodec, true, isFirstAppliedCodecProfile);
isFirstAppliedCodecProfile = false; isFirstAppliedCodecProfile = false;
break; break;
} }
@ -1050,7 +1083,7 @@ namespace MediaBrowser.Model.Dlna
} }
private (DirectPlayProfile Profile, PlayMethod? PlayMethod, int? AudioStreamIndex, TranscodeReason TranscodeReasons) GetVideoDirectPlayProfile( private (DirectPlayProfile Profile, PlayMethod? PlayMethod, int? AudioStreamIndex, TranscodeReason TranscodeReasons) GetVideoDirectPlayProfile(
VideoOptions options, MediaOptions options,
MediaSourceInfo mediaSource, MediaSourceInfo mediaSource,
MediaStream videoStream, MediaStream videoStream,
MediaStream audioStream, MediaStream audioStream,
@ -1237,7 +1270,7 @@ namespace MediaBrowser.Model.Dlna
return (Profile: null, PlayMethod: null, AudioStreamIndex: null, TranscodeReasons: failureReasons); return (Profile: null, PlayMethod: null, AudioStreamIndex: null, TranscodeReasons: failureReasons);
} }
private TranscodeReason CheckVideoAudioStreamDirectPlay(VideoOptions options, MediaSourceInfo mediaSource, string container, MediaStream audioStream) private TranscodeReason CheckVideoAudioStreamDirectPlay(MediaOptions options, MediaSourceInfo mediaSource, string container, MediaStream audioStream)
{ {
var profile = options.Profile; var profile = options.Profile;
var audioFailureConditions = GetProfileConditionsForVideoAudio(profile.CodecProfiles, container, audioStream.Codec, audioStream.Channels, audioStream.BitRate, audioStream.SampleRate, audioStream.BitDepth, audioStream.Profile, mediaSource.IsSecondaryAudio(audioStream)); var audioFailureConditions = GetProfileConditionsForVideoAudio(profile.CodecProfiles, container, audioStream.Codec, audioStream.Channels, audioStream.BitRate, audioStream.SampleRate, audioStream.BitDepth, audioStream.Profile, mediaSource.IsSecondaryAudio(audioStream));
@ -1274,23 +1307,17 @@ namespace MediaBrowser.Model.Dlna
mediaSource.Path ?? "Unknown path"); mediaSource.Path ?? "Unknown path");
} }
private TranscodeReason IsBitrateEligibleForDirectPlayback( /// <summary>
MediaSourceInfo item, /// Normalizes input container.
long maxBitrate, /// </summary>
VideoOptions options, /// <param name="mediaSource">The <see cref="MediaSourceInfo"/>.</param>
PlayMethod playMethod) /// <param name="subtitleStream">The <see cref="MediaStream"/> of the subtitle stream.</param>
{ /// <param name="subtitleProfiles">The list of supported <see cref="SubtitleProfile"/>s.</param>
bool result = IsItemBitrateEligibleForDirectPlayback(item, maxBitrate, playMethod); /// <param name="playMethod">The <see cref="PlayMethod"/>.</param>
if (!result) /// <param name="transcoderSupport">The <see cref="ITranscoderSupport"/>.</param>
{ /// <param name="outputContainer">The output container.</param>
return TranscodeReason.ContainerBitrateExceedsLimit; /// <param name="transcodingSubProtocol">The subtitle transoding protocol.</param>
} /// <returns>The the normalized input container.</returns>
else
{
return 0;
}
}
public static SubtitleProfile GetSubtitleProfile( public static SubtitleProfile GetSubtitleProfile(
MediaSourceInfo mediaSource, MediaSourceInfo mediaSource,
MediaStream subtitleStream, MediaStream subtitleStream,
@ -1448,14 +1475,8 @@ namespace MediaBrowser.Model.Dlna
return null; return null;
} }
private bool IsItemBitrateEligibleForDirectPlayback(MediaSourceInfo item, long maxBitrate, PlayMethod playMethod) private bool IsBitrateLimitExceeded(MediaSourceInfo item, long maxBitrate)
{ {
// Don't restrict by bitrate if coming from an external domain
if (item.IsRemote)
{
return true;
}
long requestedMaxBitrate = maxBitrate > 0 ? maxBitrate : 1000000; long requestedMaxBitrate = maxBitrate > 0 ? maxBitrate : 1000000;
// If we don't know the bitrate, then force a transcode if requested max bitrate is under 40 mbps // If we don't know the bitrate, then force a transcode if requested max bitrate is under 40 mbps
@ -1464,40 +1485,22 @@ namespace MediaBrowser.Model.Dlna
if (itemBitrate > requestedMaxBitrate) if (itemBitrate > requestedMaxBitrate)
{ {
_logger.LogDebug( _logger.LogDebug(
"Bitrate exceeds {PlayBackMethod} limit: media bitrate: {MediaBitrate}, max bitrate: {MaxBitrate}", "Bitrate exceeds limit: media bitrate: {MediaBitrate}, max bitrate: {MaxBitrate}",
playMethod,
itemBitrate, itemBitrate,
requestedMaxBitrate); requestedMaxBitrate);
return false; return true;
} }
return true; return false;
} }
private static void ValidateInput(VideoOptions options) private static void ValidateMediaOptions(MediaOptions options, Boolean IsMediaSource)
{
ValidateAudioInput(options);
if (options.AudioStreamIndex.HasValue && string.IsNullOrEmpty(options.MediaSourceId))
{
throw new ArgumentException("MediaSourceId is required when a specific audio stream is requested");
}
if (options.SubtitleStreamIndex.HasValue && string.IsNullOrEmpty(options.MediaSourceId))
{
throw new ArgumentException("MediaSourceId is required when a specific subtitle stream is requested");
}
}
private static void ValidateAudioInput(AudioOptions options)
{ {
if (options.ItemId.Equals(default)) if (options.ItemId.Equals(default))
{ {
throw new ArgumentException("ItemId is required"); ArgumentException.ThrowIfNullOrEmpty(options.DeviceId);
} }
ArgumentException.ThrowIfNullOrEmpty(options.DeviceId);
if (options.Profile is null) if (options.Profile is null)
{ {
throw new ArgumentException("Profile is required"); throw new ArgumentException("Profile is required");
@ -1507,6 +1510,19 @@ namespace MediaBrowser.Model.Dlna
{ {
throw new ArgumentException("MediaSources is required"); throw new ArgumentException("MediaSources is required");
} }
if (IsMediaSource)
{
if (options.AudioStreamIndex.HasValue && string.IsNullOrEmpty(options.MediaSourceId))
{
throw new ArgumentException("MediaSourceId is required when a specific audio stream is requested");
}
if (options.SubtitleStreamIndex.HasValue && string.IsNullOrEmpty(options.MediaSourceId))
{
throw new ArgumentException("MediaSourceId is required when a specific subtitle stream is requested");
}
}
} }
private static IEnumerable<ProfileCondition> GetProfileConditionsForVideoAudio( private static IEnumerable<ProfileCondition> GetProfileConditionsForVideoAudio(
@ -1824,8 +1840,8 @@ namespace MediaBrowser.Model.Dlna
continue; continue;
} }
// change from split by | to comma // Change from split by | to comma
// strip spaces to avoid having to encode // Strip spaces to avoid having to encode
var values = value var values = value
.Split('|', StringSplitOptions.RemoveEmptyEntries); .Split('|', StringSplitOptions.RemoveEmptyEntries);

View File

@ -1,16 +0,0 @@
#pragma warning disable CS1591
namespace MediaBrowser.Model.Dlna
{
/// <summary>
/// Class VideoOptions.
/// </summary>
public class VideoOptions : AudioOptions
{
public int? AudioStreamIndex { get; set; }
public int? SubtitleStreamIndex { get; set; }
public bool AllowVideoStreamCopy { get; set; }
}
}

View File

@ -164,7 +164,7 @@ namespace Jellyfin.Model.Tests
[InlineData("Tizen4-4K-5.1", "mkv-vp9-vorbis-vtt-2600k", PlayMethod.DirectPlay)] [InlineData("Tizen4-4K-5.1", "mkv-vp9-vorbis-vtt-2600k", PlayMethod.DirectPlay)]
public async Task BuildVideoItemSimple(string deviceName, string mediaSource, PlayMethod? playMethod, TranscodeReason why = (TranscodeReason)0, string transcodeMode = "DirectStream", string transcodeProtocol = "") public async Task BuildVideoItemSimple(string deviceName, string mediaSource, PlayMethod? playMethod, TranscodeReason why = (TranscodeReason)0, string transcodeMode = "DirectStream", string transcodeProtocol = "")
{ {
var options = await GetVideoOptions(deviceName, mediaSource); var options = await GetMediaOptions(deviceName, mediaSource);
BuildVideoItemSimpleTest(options, playMethod, why, transcodeMode, transcodeProtocol); BuildVideoItemSimpleTest(options, playMethod, why, transcodeMode, transcodeProtocol);
} }
@ -262,7 +262,7 @@ namespace Jellyfin.Model.Tests
[InlineData("Tizen4-4K-5.1", "mkv-vp9-vorbis-vtt-2600k", PlayMethod.DirectPlay)] [InlineData("Tizen4-4K-5.1", "mkv-vp9-vorbis-vtt-2600k", PlayMethod.DirectPlay)]
public async Task BuildVideoItemWithFirstExplicitStream(string deviceName, string mediaSource, PlayMethod? playMethod, TranscodeReason why = (TranscodeReason)0, string transcodeMode = "DirectStream", string transcodeProtocol = "") public async Task BuildVideoItemWithFirstExplicitStream(string deviceName, string mediaSource, PlayMethod? playMethod, TranscodeReason why = (TranscodeReason)0, string transcodeMode = "DirectStream", string transcodeProtocol = "")
{ {
var options = await GetVideoOptions(deviceName, mediaSource); var options = await GetMediaOptions(deviceName, mediaSource);
options.AudioStreamIndex = 1; options.AudioStreamIndex = 1;
options.SubtitleStreamIndex = options.MediaSources[0].MediaStreams.Count - 1; options.SubtitleStreamIndex = options.MediaSources[0].MediaStreams.Count - 1;
@ -298,7 +298,7 @@ namespace Jellyfin.Model.Tests
[InlineData("Tizen4-4K-5.1", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.DirectPlay, (TranscodeReason)0, "Remux")] [InlineData("Tizen4-4K-5.1", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.DirectPlay, (TranscodeReason)0, "Remux")]
public async Task BuildVideoItemWithDirectPlayExplicitStreams(string deviceName, string mediaSource, PlayMethod? playMethod, TranscodeReason why = (TranscodeReason)0, string transcodeMode = "DirectStream", string transcodeProtocol = "") public async Task BuildVideoItemWithDirectPlayExplicitStreams(string deviceName, string mediaSource, PlayMethod? playMethod, TranscodeReason why = (TranscodeReason)0, string transcodeMode = "DirectStream", string transcodeProtocol = "")
{ {
var options = await GetVideoOptions(deviceName, mediaSource); var options = await GetMediaOptions(deviceName, mediaSource);
var streamCount = options.MediaSources[0].MediaStreams.Count; var streamCount = options.MediaSources[0].MediaStreams.Count;
if (streamCount > 0) if (streamCount > 0)
{ {
@ -311,7 +311,7 @@ namespace Jellyfin.Model.Tests
Assert.Equal(streamInfo?.SubtitleStreamIndex, options.SubtitleStreamIndex); Assert.Equal(streamInfo?.SubtitleStreamIndex, options.SubtitleStreamIndex);
} }
private StreamInfo? BuildVideoItemSimpleTest(VideoOptions options, PlayMethod? playMethod, TranscodeReason why, string transcodeMode, string transcodeProtocol) private StreamInfo? BuildVideoItemSimpleTest(MediaOptions options, PlayMethod? playMethod, TranscodeReason why, string transcodeMode, string transcodeProtocol)
{ {
if (string.IsNullOrEmpty(transcodeProtocol)) if (string.IsNullOrEmpty(transcodeProtocol))
{ {
@ -320,28 +320,28 @@ namespace Jellyfin.Model.Tests
var builder = GetStreamBuilder(); var builder = GetStreamBuilder();
var val = builder.BuildVideoItem(options); var streamInfo = builder.GetOptimalVideoStream(options);
Assert.NotNull(val); Assert.NotNull(streamInfo);
if (playMethod is not null) if (playMethod is not null)
{ {
Assert.Equal(playMethod, val.PlayMethod); Assert.Equal(playMethod, streamInfo.PlayMethod);
} }
Assert.Equal(why, val.TranscodeReasons); Assert.Equal(why, streamInfo.TranscodeReasons);
var audioStreamIndexInput = options.AudioStreamIndex; var audioStreamIndexInput = options.AudioStreamIndex;
var targetVideoStream = val.TargetVideoStream; var targetVideoStream = streamInfo.TargetVideoStream;
var targetAudioStream = val.TargetAudioStream; var targetAudioStream = streamInfo.TargetAudioStream;
var mediaSource = options.MediaSources.First(source => source.Id == val.MediaSourceId); var mediaSource = options.MediaSources.First(source => source.Id == streamInfo.MediaSourceId);
Assert.NotNull(mediaSource); Assert.NotNull(mediaSource);
var videoStreams = mediaSource.MediaStreams.Where(stream => stream.Type == MediaStreamType.Video); var videoStreams = mediaSource.MediaStreams.Where(stream => stream.Type == MediaStreamType.Video);
var audioStreams = mediaSource.MediaStreams.Where(stream => stream.Type == MediaStreamType.Audio); var audioStreams = mediaSource.MediaStreams.Where(stream => stream.Type == MediaStreamType.Audio);
// TODO: Check AudioStreamIndex vs options.AudioStreamIndex // TODO: Check AudioStreamIndex vs options.AudioStreamIndex
var inputAudioStream = mediaSource.GetDefaultAudioStream(audioStreamIndexInput ?? mediaSource.DefaultAudioStreamIndex); var inputAudioStream = mediaSource.GetDefaultAudioStream(audioStreamIndexInput ?? mediaSource.DefaultAudioStreamIndex);
var uri = ParseUri(val); var uri = ParseUri(streamInfo);
if (playMethod == PlayMethod.DirectPlay) if (playMethod == PlayMethod.DirectPlay)
{ {
@ -351,98 +351,99 @@ namespace Jellyfin.Model.Tests
// Assert.Contains(uri.Extension, containers); // Assert.Contains(uri.Extension, containers);
// Check expected video codec (1) // Check expected video codec (1)
Assert.Contains(targetVideoStream.Codec, val.TargetVideoCodec); Assert.Contains(targetVideoStream.Codec, streamInfo.TargetVideoCodec);
Assert.Single(val.TargetVideoCodec); Assert.Single(streamInfo.TargetVideoCodec);
// Check expected audio codecs (1) // Check expected audio codecs (1)
Assert.Contains(targetAudioStream.Codec, val.TargetAudioCodec); Assert.Contains(targetAudioStream.Codec, streamInfo.TargetAudioCodec);
Assert.Single(val.TargetAudioCodec); Assert.Single(streamInfo.TargetAudioCodec);
// Assert.Single(val.AudioCodecs); // Assert.Single(val.AudioCodecs);
if (transcodeMode.Equals("DirectStream", StringComparison.Ordinal)) if (transcodeMode.Equals("DirectStream", StringComparison.Ordinal))
{ {
Assert.Equal(val.Container, uri.Extension); Assert.Equal(streamInfo.Container, uri.Extension);
} }
} }
else if (playMethod == PlayMethod.DirectStream || playMethod == PlayMethod.Transcode) else if (playMethod == PlayMethod.DirectStream || playMethod == PlayMethod.Transcode)
{ {
Assert.NotNull(val.Container); Assert.NotNull(streamInfo.Container);
Assert.NotEmpty(val.VideoCodecs); Assert.NotEmpty(streamInfo.VideoCodecs);
Assert.NotEmpty(val.AudioCodecs); Assert.NotEmpty(streamInfo.AudioCodecs);
// Check expected container (todo: this could be a test param) // Check expected container (todo: this could be a test param)
if (transcodeProtocol.Equals("http", StringComparison.Ordinal)) if (transcodeProtocol.Equals("http", StringComparison.Ordinal))
{ {
// Assert.Equal("webm", val.Container); // Assert.Equal("webm", val.Container);
Assert.Equal(val.Container, uri.Extension); Assert.Equal(streamInfo.Container, uri.Extension);
Assert.Equal("stream", uri.Filename); Assert.Equal("stream", uri.Filename);
Assert.Equal("http", val.SubProtocol); Assert.Equal("http", streamInfo.SubProtocol);
} }
else if (transcodeProtocol.Equals("HLS.mp4", StringComparison.Ordinal)) else if (transcodeProtocol.Equals("HLS.mp4", StringComparison.Ordinal))
{ {
Assert.Equal("mp4", val.Container); Assert.Equal("mp4", streamInfo.Container);
Assert.Equal("m3u8", uri.Extension); Assert.Equal("m3u8", uri.Extension);
Assert.Equal("master", uri.Filename); Assert.Equal("master", uri.Filename);
Assert.Equal("hls", val.SubProtocol); Assert.Equal("hls", streamInfo.SubProtocol);
} }
else else
{ {
Assert.Equal("ts", val.Container); Assert.Equal("ts", streamInfo.Container);
Assert.Equal("m3u8", uri.Extension); Assert.Equal("m3u8", uri.Extension);
Assert.Equal("master", uri.Filename); Assert.Equal("master", uri.Filename);
Assert.Equal("hls", val.SubProtocol); Assert.Equal("hls", streamInfo.SubProtocol);
} }
// Full transcode // Full transcode
if (transcodeMode.Equals("Transcode", StringComparison.Ordinal)) if (transcodeMode.Equals("Transcode", StringComparison.Ordinal))
{ {
if ((val.TranscodeReasons & (StreamBuilder.ContainerReasons | TranscodeReason.DirectPlayError)) == 0) if ((streamInfo.TranscodeReasons & (StreamBuilder.ContainerReasons | TranscodeReason.DirectPlayError)) == 0)
{ {
Assert.All( Assert.All(
videoStreams, videoStreams,
stream => Assert.DoesNotContain(stream.Codec, val.VideoCodecs)); stream => Assert.DoesNotContain(stream.Codec, streamInfo.VideoCodecs));
} }
// TODO: Fill out tests here // TODO: fill out tests here
} }
// DirectStream and Remux // DirectStream and Remux
else else
{ {
// Check expected video codec (1) // Check expected video codec (1)
Assert.Contains(targetVideoStream.Codec, val.TargetVideoCodec); Assert.Contains(targetVideoStream.Codec, streamInfo.TargetVideoCodec);
Assert.Single(val.TargetVideoCodec); Assert.Single(streamInfo.TargetVideoCodec);
if (transcodeMode.Equals("DirectStream", StringComparison.Ordinal)) if (transcodeMode.Equals("DirectStream", StringComparison.Ordinal))
{ {
// Check expected audio codecs (1) // Check expected audio codecs (1)
if (!targetAudioStream.IsExternal) if (!targetAudioStream.IsExternal)
{ {
if (val.TranscodeReasons.HasFlag(TranscodeReason.ContainerNotSupported)) // Check expected audio codecs (1)
if (streamInfo.TranscodeReasons.HasFlag(TranscodeReason.ContainerNotSupported))
{ {
Assert.Contains(targetAudioStream.Codec, val.AudioCodecs); Assert.Contains(targetAudioStream.Codec, streamInfo.AudioCodecs);
} }
else else
{ {
Assert.DoesNotContain(targetAudioStream.Codec, val.AudioCodecs); Assert.DoesNotContain(targetAudioStream.Codec, streamInfo.AudioCodecs);
} }
} }
} }
else if (transcodeMode.Equals("Remux", StringComparison.Ordinal)) else if (transcodeMode.Equals("Remux", StringComparison.Ordinal))
{ {
// Check expected audio codecs (1) // Check expected audio codecs (1)
Assert.Contains(targetAudioStream.Codec, val.AudioCodecs); Assert.Contains(targetAudioStream.Codec, streamInfo.AudioCodecs);
Assert.Single(val.AudioCodecs); Assert.Single(streamInfo.AudioCodecs);
} }
// Video details // Video details
var videoStream = targetVideoStream; var videoStream = targetVideoStream;
Assert.False(val.EstimateContentLength); Assert.False(streamInfo.EstimateContentLength);
Assert.Equal(TranscodeSeekInfo.Auto, val.TranscodeSeekInfo); Assert.Equal(TranscodeSeekInfo.Auto, streamInfo.TranscodeSeekInfo);
Assert.Contains(videoStream.Profile?.ToLowerInvariant() ?? string.Empty, val.TargetVideoProfile?.Split(",").Select(s => s.ToLowerInvariant()) ?? Array.Empty<string>()); Assert.Contains(videoStream.Profile?.ToLowerInvariant() ?? string.Empty, streamInfo.TargetVideoProfile?.Split(",").Select(s => s.ToLowerInvariant()) ?? Array.Empty<string>());
Assert.Equal(videoStream.Level, val.TargetVideoLevel); Assert.Equal(videoStream.Level, streamInfo.TargetVideoLevel);
Assert.Equal(videoStream.BitDepth, val.TargetVideoBitDepth); Assert.Equal(videoStream.BitDepth, streamInfo.TargetVideoBitDepth);
Assert.InRange(val.VideoBitrate.GetValueOrDefault(), videoStream.BitRate.GetValueOrDefault(), int.MaxValue); Assert.InRange(streamInfo.VideoBitrate.GetValueOrDefault(), videoStream.BitRate.GetValueOrDefault(), int.MaxValue);
// Audio codec not supported // Audio codec not supported
if ((why & TranscodeReason.AudioCodecNotSupported) != 0) if ((why & TranscodeReason.AudioCodecNotSupported) != 0)
@ -453,7 +454,7 @@ namespace Jellyfin.Model.Tests
// TODO:fixme // TODO:fixme
if (!targetAudioStream.IsExternal) if (!targetAudioStream.IsExternal)
{ {
Assert.DoesNotContain(targetAudioStream.Codec, val.AudioCodecs); Assert.DoesNotContain(targetAudioStream.Codec, streamInfo.AudioCodecs);
} }
} }
@ -465,7 +466,7 @@ namespace Jellyfin.Model.Tests
{ {
if (!stream.IsExternal) if (!stream.IsExternal)
{ {
Assert.DoesNotContain(stream.Codec, val.AudioCodecs); Assert.DoesNotContain(stream.Codec, streamInfo.AudioCodecs);
} }
}); });
} }
@ -474,14 +475,14 @@ namespace Jellyfin.Model.Tests
} }
else if (playMethod is null) else if (playMethod is null)
{ {
Assert.Null(val.SubProtocol); Assert.Null(streamInfo.SubProtocol);
Assert.Equal("stream", uri.Filename); Assert.Equal("stream", uri.Filename);
Assert.False(val.EstimateContentLength); Assert.False(streamInfo.EstimateContentLength);
Assert.Equal(TranscodeSeekInfo.Auto, val.TranscodeSeekInfo); Assert.Equal(TranscodeSeekInfo.Auto, streamInfo.TranscodeSeekInfo);
} }
return val; return streamInfo;
} }
private static async ValueTask<T> TestData<T>(string name) private static async ValueTask<T> TestData<T>(string name)
@ -507,7 +508,7 @@ namespace Jellyfin.Model.Tests
return new StreamBuilder(transcodeSupport.Object, logger); return new StreamBuilder(transcodeSupport.Object, logger);
} }
private static async ValueTask<VideoOptions> GetVideoOptions(string deviceProfile, params string[] sources) private static async ValueTask<MediaOptions> GetMediaOptions(string deviceProfile, params string[] sources)
{ {
var mediaSources = sources.Select(src => TestData<MediaSourceInfo>(src)) var mediaSources = sources.Select(src => TestData<MediaSourceInfo>(src))
.Select(val => val.Result) .Select(val => val.Result)
@ -516,7 +517,7 @@ namespace Jellyfin.Model.Tests
var dp = await TestData<DeviceProfile>(deviceProfile); var dp = await TestData<DeviceProfile>(deviceProfile);
return new VideoOptions() return new MediaOptions()
{ {
ItemId = new Guid("11D229B7-2D48-4B95-9F9B-49F6AB75E613"), ItemId = new Guid("11D229B7-2D48-4B95-9F9B-49F6AB75E613"),
MediaSourceId = mediaSourceId, MediaSourceId = mediaSourceId,