diff options
Diffstat (limited to 'MediaBrowser.Api/Playback/BaseStreamingService.cs')
| -rw-r--r-- | MediaBrowser.Api/Playback/BaseStreamingService.cs | 375 |
1 files changed, 242 insertions, 133 deletions
diff --git a/MediaBrowser.Api/Playback/BaseStreamingService.cs b/MediaBrowser.Api/Playback/BaseStreamingService.cs index f1b84875d..0858a0347 100644 --- a/MediaBrowser.Api/Playback/BaseStreamingService.cs +++ b/MediaBrowser.Api/Playback/BaseStreamingService.cs @@ -1,4 +1,4 @@ -using System.Net.WebSockets; +using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Extensions; using MediaBrowser.Common.IO; using MediaBrowser.Controller.Channels; @@ -14,7 +14,6 @@ using MediaBrowser.Model.Drawing; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; using MediaBrowser.Model.IO; -using MediaBrowser.Model.LiveTv; using MediaBrowser.Model.MediaInfo; using System; using System.Collections.Generic; @@ -120,13 +119,14 @@ namespace MediaBrowser.Api.Playback /// <returns>System.String.</returns> private string GetOutputFilePath(StreamState state) { - var folder = ServerConfigurationManager.ApplicationPaths.TranscodingTempPath; - + var folder = Path.Combine(ServerConfigurationManager.ApplicationPaths.TranscodingTempPath, EncodingContext.Streaming.ToString().ToLower()); + var outputFileExtension = GetOutputFileExtension(state); var data = GetCommandLineArguments("dummy\\dummy", "dummyTranscodingId", state, false); data += "-" + (state.Request.DeviceId ?? string.Empty); + data += "-" + (state.Request.ClientTime ?? string.Empty); return Path.Combine(folder, data.GetMD5().ToString("N") + (outputFileExtension ?? string.Empty).ToLower()); } @@ -203,6 +203,10 @@ namespace MediaBrowser.Api.Playback { args += " -map -0:s"; } + else if (state.SubtitleStream.IsExternal && !state.SubtitleStream.IsTextSubtitleStream) + { + args += " -map 1:0 -sn"; + } return args; } @@ -246,7 +250,7 @@ namespace MediaBrowser.Api.Playback protected EncodingQuality GetQualitySetting() { - var quality = ServerConfigurationManager.Configuration.MediaEncodingQuality; + var quality = ApiEntryPoint.Instance.GetEncodingOptions().EncodingQuality; if (quality == EncodingQuality.Auto) { @@ -267,9 +271,14 @@ namespace MediaBrowser.Api.Playback /// Gets the number of threads. /// </summary> /// <returns>System.Int32.</returns> - /// <exception cref="System.Exception">Unrecognized MediaEncodingQuality value.</exception> protected int GetNumberOfThreads(StreamState state, bool isWebm) { + if (isWebm) + { + // Recommended per docs + return Math.Max(Environment.ProcessorCount - 1, 2); + } + // Use more when this is true. -re will keep cpu usage under control if (state.ReadInputAtNativeFramerate) { @@ -298,6 +307,21 @@ namespace MediaBrowser.Api.Playback } } + protected string H264Encoder + { + get + { + var lib = ApiEntryPoint.Instance.GetEncodingOptions().H264Encoder; + + if (!string.IsNullOrWhiteSpace(lib)) + { + return lib; + } + + return "libx264"; + } + } + /// <summary> /// Gets the video bitrate to specify on the command line /// </summary> @@ -314,7 +338,7 @@ namespace MediaBrowser.Api.Playback var qualitySetting = GetQualitySetting(); - if (string.Equals(videoCodec, "libx264", StringComparison.OrdinalIgnoreCase)) + if (string.Equals(videoCodec, H264Encoder, StringComparison.OrdinalIgnoreCase)) { switch (qualitySetting) { @@ -438,7 +462,7 @@ namespace MediaBrowser.Api.Playback { if (state.AudioStream != null && state.AudioStream.Channels.HasValue && state.AudioStream.Channels.Value > 5) { - volParam = ",volume=" + ServerConfigurationManager.Configuration.DownMixAudioBoost.ToString(UsCulture); + volParam = ",volume=" + ApiEntryPoint.Instance.GetEncodingOptions().DownMixAudioBoost.ToString(UsCulture); } } @@ -647,9 +671,18 @@ namespace MediaBrowser.Api.Playback videoSizeParam = string.Format(",scale={0}:{1}", state.VideoStream.Width.Value.ToString(UsCulture), state.VideoStream.Height.Value.ToString(UsCulture)); } - return string.Format(" -filter_complex \"[0:{0}]format=yuva444p{3},lut=u=128:v=128:y=gammaval(.3)[sub] ; [0:{1}] [sub] overlay{2}\"", - state.SubtitleStream.Index, - state.VideoStream.Index, + var mapPrefix = state.SubtitleStream.IsExternal ? + 1 : + 0; + + var subtitleStreamIndex = state.SubtitleStream.IsExternal + ? 0 + : state.SubtitleStream.Index; + + return string.Format(" -filter_complex \"[{0}:{1}]format=yuva444p{4},lut=u=128:v=128:y=gammaval(.3)[sub] ; [0:{2}] [sub] overlay{3}\"", + mapPrefix.ToString(UsCulture), + subtitleStreamIndex.ToString(UsCulture), + state.VideoStream.Index.ToString(UsCulture), outputSizeParam, videoSizeParam); } @@ -696,7 +729,8 @@ namespace MediaBrowser.Api.Playback return Math.Min(request.MaxAudioChannels.Value, audioStream.Channels.Value); } - return request.MaxAudioChannels.Value; + // If we don't have any media info then limit it to 5 to prevent encoding errors due to asking for too many channels + return Math.Min(request.MaxAudioChannels.Value, 5); } return request.AudioChannels; @@ -709,8 +743,10 @@ namespace MediaBrowser.Api.Playback /// <returns><c>true</c> if the specified stream is H264; otherwise, <c>false</c>.</returns> protected bool IsH264(MediaStream stream) { - return stream.Codec.IndexOf("264", StringComparison.OrdinalIgnoreCase) != -1 || - stream.Codec.IndexOf("avc", StringComparison.OrdinalIgnoreCase) != -1; + var codec = stream.Codec ?? string.Empty; + + return codec.IndexOf("264", StringComparison.OrdinalIgnoreCase) != -1 || + codec.IndexOf("avc", StringComparison.OrdinalIgnoreCase) != -1; } /// <summary> @@ -755,7 +791,7 @@ namespace MediaBrowser.Api.Playback { if (string.Equals(codec, "h264", StringComparison.OrdinalIgnoreCase)) { - return "libx264"; + return H264Encoder; } if (string.Equals(codec, "vpx", StringComparison.OrdinalIgnoreCase)) { @@ -778,24 +814,9 @@ namespace MediaBrowser.Api.Playback private bool SupportsThrottleWithStream { - // TODO: These checks are a hack. - // They should go through the IHttpServer interface or IServerManager to find out this information - get { -#if __MonoCS__ - return true; -#endif - - try - { - new ClientWebSocket(); - return true; - } - catch - { - return false; - } + return false; } } @@ -807,6 +828,21 @@ namespace MediaBrowser.Api.Playback /// <returns>System.String.</returns> protected string GetInputArgument(string transcodingJobId, StreamState state) { + var arg = "-i " + GetInputPathArgument(transcodingJobId, state); + + if (state.SubtitleStream != null) + { + if (state.SubtitleStream.IsExternal && !state.SubtitleStream.IsTextSubtitleStream) + { + arg += " -i " + state.SubtitleStream.Path; + } + } + + return arg; + } + + private string GetInputPathArgument(string transcodingJobId, StreamState state) + { if (state.InputProtocol == MediaProtocol.File && state.RunTimeTicks.HasValue && state.VideoType == VideoType.VideoFile && @@ -857,20 +893,12 @@ namespace MediaBrowser.Api.Playback state.LiveTvStreamId = streamInfo.Id; - if (!string.IsNullOrEmpty(streamInfo.Path)) - { - state.MediaPath = streamInfo.Path; - state.InputProtocol = MediaProtocol.File; + state.MediaPath = streamInfo.Path; + state.InputProtocol = streamInfo.Protocol; - await Task.Delay(1500, cancellationTokenSource.Token).ConfigureAwait(false); - } - else if (!string.IsNullOrEmpty(streamInfo.Url)) - { - state.MediaPath = streamInfo.Url; - state.InputProtocol = MediaProtocol.Http; - } + await Task.Delay(1500, cancellationTokenSource.Token).ConfigureAwait(false); - AttachMediaStreamInfo(state, streamInfo.MediaStreams, state.VideoRequest, state.RequestedUrl); + AttachMediaStreamInfo(state, streamInfo, state.VideoRequest, state.RequestedUrl); checkCodecs = true; } @@ -881,20 +909,12 @@ namespace MediaBrowser.Api.Playback state.LiveTvStreamId = streamInfo.Id; - if (!string.IsNullOrEmpty(streamInfo.Path)) - { - state.MediaPath = streamInfo.Path; - state.InputProtocol = MediaProtocol.File; + state.MediaPath = streamInfo.Path; + state.InputProtocol = streamInfo.Protocol; - await Task.Delay(1500, cancellationTokenSource.Token).ConfigureAwait(false); - } - else if (!string.IsNullOrEmpty(streamInfo.Url)) - { - state.MediaPath = streamInfo.Url; - state.InputProtocol = MediaProtocol.Http; - } + await Task.Delay(1500, cancellationTokenSource.Token).ConfigureAwait(false); - AttachMediaStreamInfo(state, streamInfo.MediaStreams, state.VideoRequest, state.RequestedUrl); + AttachMediaStreamInfo(state, streamInfo, state.VideoRequest, state.RequestedUrl); checkCodecs = true; } @@ -921,15 +941,13 @@ namespace MediaBrowser.Api.Playback /// <param name="state">The state.</param> /// <param name="outputPath">The output path.</param> /// <param name="cancellationTokenSource">The cancellation token source.</param> + /// <param name="workingDirectory">The working directory.</param> /// <returns>Task.</returns> - /// <exception cref="System.InvalidOperationException">ffmpeg was not found at + MediaEncoder.EncoderPath</exception> - protected async Task<TranscodingJob> StartFfMpeg(StreamState state, string outputPath, CancellationTokenSource cancellationTokenSource) + protected async Task<TranscodingJob> StartFfMpeg(StreamState state, + string outputPath, + CancellationTokenSource cancellationTokenSource, + string workingDirectory = null) { - if (!File.Exists(MediaEncoder.EncoderPath)) - { - throw new InvalidOperationException("ffmpeg was not found at " + MediaEncoder.EncoderPath); - } - Directory.CreateDirectory(Path.GetDirectoryName(outputPath)); await AcquireResources(state, cancellationTokenSource).ConfigureAwait(false); @@ -937,7 +955,7 @@ namespace MediaBrowser.Api.Playback var transcodingId = Guid.NewGuid().ToString("N"); var commandLineArgs = GetCommandLineArguments(outputPath, transcodingId, state, true); - if (ServerConfigurationManager.Configuration.EnableDebugEncodingLogging) + if (ApiEntryPoint.Instance.GetEncodingOptions().EnableDebugLogging) { commandLineArgs = "-loglevel debug " + commandLineArgs; } @@ -955,7 +973,6 @@ namespace MediaBrowser.Api.Playback RedirectStandardInput = true, FileName = MediaEncoder.EncoderPath, - WorkingDirectory = Path.GetDirectoryName(MediaEncoder.EncoderPath), Arguments = commandLineArgs, WindowStyle = ProcessWindowStyle.Hidden, @@ -965,6 +982,11 @@ namespace MediaBrowser.Api.Playback EnableRaisingEvents = true }; + if (!string.IsNullOrWhiteSpace(workingDirectory)) + { + process.StartInfo.WorkingDirectory = workingDirectory; + } + var transcodingJob = ApiEntryPoint.Instance.OnTranscodeBeginning(outputPath, transcodingId, TranscodingJobType, @@ -1007,11 +1029,21 @@ namespace MediaBrowser.Api.Playback StartStreamingLog(transcodingJob, state, process.StandardError.BaseStream, state.LogFileStream); // Wait for the file to exist before proceeeding - while (!File.Exists(outputPath)) + while (!File.Exists(outputPath) && !transcodingJob.HasExited) { await Task.Delay(100, cancellationTokenSource.Token).ConfigureAwait(false); } + if (state.IsInputVideo && transcodingJob.Type == TranscodingJobType.Progressive) + { + await Task.Delay(1000, cancellationTokenSource.Token).ConfigureAwait(false); + + if (state.ReadInputAtNativeFramerate) + { + await Task.Delay(1500, cancellationTokenSource.Token).ConfigureAwait(false); + } + } + return transcodingJob; } @@ -1101,7 +1133,7 @@ namespace MediaBrowser.Api.Playback if (scale.HasValue) { long val; - + if (long.TryParse(size, NumberStyles.Any, UsCulture, out val)) { bytesTranscoded = val * scale.Value; @@ -1390,6 +1422,38 @@ namespace MediaBrowser.Api.Playback videoRequest.Level = val; } } + else if (i == 16) + { + request.ClientTime = val; + } + else if (i == 17) + { + if (videoRequest != null) + { + videoRequest.MaxRefFrames = int.Parse(val, UsCulture); + } + } + else if (i == 18) + { + if (videoRequest != null) + { + videoRequest.MaxVideoBitDepth = int.Parse(val, UsCulture); + } + } + else if (i == 19) + { + if (videoRequest != null) + { + videoRequest.Profile = val; + } + } + else if (i == 20) + { + if (videoRequest != null) + { + videoRequest.Cabac = string.Equals("true", val, StringComparison.OrdinalIgnoreCase); + } + } } } @@ -1525,23 +1589,14 @@ namespace MediaBrowser.Api.Playback state.MediaPath = mediaUrl; state.InputProtocol = MediaProtocol.Http; } - else - { - // No media info, so this is probably needed - state.DeInterlace = true; - } - - if (recording.RecordingInfo.Status == RecordingStatus.InProgress) - { - state.ReadInputAtNativeFramerate = true; - } state.RunTimeTicks = recording.RunTimeTicks; - + state.DeInterlace = true; state.OutputAudioSync = "1000"; state.InputVideoSync = "-1"; state.InputAudioSync = "1"; state.InputContainer = recording.Container; + state.ReadInputAtNativeFramerate = source.ReadAtNativeFramerate; } else if (item is LiveTvChannel) { @@ -1551,11 +1606,7 @@ namespace MediaBrowser.Api.Playback state.IsInputVideo = string.Equals(channel.MediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase); mediaStreams = new List<MediaStream>(); - state.ReadInputAtNativeFramerate = true; - state.OutputAudioSync = "1000"; state.DeInterlace = true; - state.InputVideoSync = "-1"; - state.InputAudioSync = "1"; // Just to prevent this from being null and causing other methods to fail state.MediaPath = string.Empty; @@ -1570,6 +1621,7 @@ namespace MediaBrowser.Api.Playback state.RemoteHttpHeaders = mediaSource.RequiredHttpHeaders; state.InputBitrate = mediaSource.Bitrate; state.InputFileSize = mediaSource.Size; + state.ReadInputAtNativeFramerate = mediaSource.ReadAtNativeFramerate; mediaStreams = mediaSource.MediaStreams; } else @@ -1586,8 +1638,11 @@ namespace MediaBrowser.Api.Playback state.InputContainer = mediaSource.Container; state.InputFileSize = mediaSource.Size; state.InputBitrate = mediaSource.Bitrate; + state.ReadInputAtNativeFramerate = mediaSource.ReadAtNativeFramerate; + + var video = item as Video; - if (item is Video) + if (video != null) { state.IsInputVideo = true; @@ -1609,30 +1664,17 @@ namespace MediaBrowser.Api.Playback state.RunTimeTicks = mediaSource.RunTimeTicks; } - // If it's a wtv and we don't have media info, we will probably need to deinterlace - if (string.Equals(state.InputContainer, "wtv", StringComparison.OrdinalIgnoreCase) && - mediaStreams.Count == 0) - { - state.DeInterlace = true; - } - - if (state.InputProtocol == MediaProtocol.Rtmp) - { - state.ReadInputAtNativeFramerate = true; - } - var videoRequest = request as VideoStreamRequest; AttachMediaStreamInfo(state, mediaStreams, videoRequest, url); - state.SegmentLength = state.ReadInputAtNativeFramerate ? 5 : 7; - state.HlsListSize = state.ReadInputAtNativeFramerate ? 100 : 1440; - var container = Path.GetExtension(state.RequestedUrl); if (string.IsNullOrEmpty(container)) { - container = request.Static ? state.InputContainer : Path.GetExtension(GetOutputFilePath(state)); + container = request.Static ? + state.InputContainer : + (Path.GetExtension(GetOutputFilePath(state)) ?? string.Empty).TrimStart('.'); } state.OutputContainer = (container ?? string.Empty).TrimStart('.'); @@ -1648,6 +1690,17 @@ namespace MediaBrowser.Api.Playback { state.OutputVideoCodec = GetVideoCodec(videoRequest); state.OutputVideoBitrate = GetVideoBitrateParamValue(state.VideoRequest, state.VideoStream); + + if (state.OutputVideoBitrate.HasValue) + { + var resolution = ResolutionNormalizer.Normalize(state.OutputVideoBitrate.Value, + state.OutputVideoCodec, + videoRequest.MaxWidth, + videoRequest.MaxHeight); + + videoRequest.MaxWidth = resolution.MaxWidth; + videoRequest.MaxHeight = resolution.MaxHeight; + } } ApplyDeviceProfileSettings(state); @@ -1659,7 +1712,7 @@ namespace MediaBrowser.Api.Playback state.OutputVideoCodec = "copy"; } - if (state.AudioStream != null && CanStreamCopyAudio(request, state.AudioStream, state.SupportedAudioCodecs)) + if (state.AudioStream != null && CanStreamCopyAudio(videoRequest, state.AudioStream, state.SupportedAudioCodecs)) { state.OutputAudioCodec = "copy"; } @@ -1671,6 +1724,31 @@ namespace MediaBrowser.Api.Playback } private void AttachMediaStreamInfo(StreamState state, + ChannelMediaInfo mediaInfo, + VideoStreamRequest videoRequest, + string requestedUrl) + { + var mediaSource = mediaInfo.ToMediaSource(); + + state.InputProtocol = mediaSource.Protocol; + state.MediaPath = mediaSource.Path; + state.RunTimeTicks = mediaSource.RunTimeTicks; + state.RemoteHttpHeaders = mediaSource.RequiredHttpHeaders; + state.InputBitrate = mediaSource.Bitrate; + state.InputFileSize = mediaSource.Size; + state.ReadInputAtNativeFramerate = mediaSource.ReadAtNativeFramerate; + + if (state.ReadInputAtNativeFramerate) + { + state.OutputAudioSync = "1000"; + state.InputVideoSync = "-1"; + state.InputAudioSync = "1"; + } + + AttachMediaStreamInfo(state, mediaSource.MediaStreams, videoRequest, requestedUrl); + } + + private void AttachMediaStreamInfo(StreamState state, List<MediaStream> mediaStreams, VideoStreamRequest videoRequest, string requestedUrl) @@ -1710,7 +1788,7 @@ namespace MediaBrowser.Api.Playback string mediaSourceId, CancellationToken cancellationToken) { - var channelMediaSources = await ChannelManager.GetChannelItemMediaSources(id, cancellationToken) + var channelMediaSources = await ChannelManager.GetChannelItemMediaSources(id, true, cancellationToken) .ConfigureAwait(false); var list = channelMediaSources.ToList(); @@ -1752,9 +1830,23 @@ namespace MediaBrowser.Api.Playback } // If client is requesting a specific video profile, it must match the source - if (!string.IsNullOrEmpty(request.Profile) && !string.Equals(request.Profile, videoStream.Profile, StringComparison.OrdinalIgnoreCase)) + if (!string.IsNullOrEmpty(request.Profile)) { - return false; + if (string.IsNullOrEmpty(videoStream.Profile)) + { + return false; + } + + if (!string.Equals(request.Profile, videoStream.Profile, StringComparison.OrdinalIgnoreCase)) + { + var currentScore = GetVideoProfileScore(videoStream.Profile); + var requestedScore = GetVideoProfileScore(request.Profile); + + if (currentScore == -1 || currentScore > requestedScore) + { + return false; + } + } } // Video width must fall within requested value @@ -1796,6 +1888,22 @@ namespace MediaBrowser.Api.Playback } } + if (request.MaxVideoBitDepth.HasValue) + { + if (videoStream.BitDepth.HasValue && videoStream.BitDepth.Value > request.MaxVideoBitDepth.Value) + { + return false; + } + } + + if (request.MaxRefFrames.HasValue) + { + if (videoStream.RefFrames.HasValue && videoStream.RefFrames.Value > request.MaxRefFrames.Value) + { + return false; + } + } + // If a specific level was requested, the source must match or be less than if (!string.IsNullOrEmpty(request.Level)) { @@ -1815,10 +1923,34 @@ namespace MediaBrowser.Api.Playback } } + if (request.Cabac.HasValue && request.Cabac.Value) + { + if (videoStream.IsCabac.HasValue && !videoStream.IsCabac.Value) + { + return false; + } + } + return request.EnableAutoStreamCopy; } - private bool CanStreamCopyAudio(StreamRequest request, MediaStream audioStream, List<string> supportedAudioCodecs) + private int GetVideoProfileScore(string profile) + { + var list = new List<string> + { + "Constrained Baseline", + "Baseline", + "Extended", + "Main", + "High", + "Progressive High", + "Constrained High" + }; + + return Array.FindIndex(list.ToArray(), t => string.Equals(t, profile, StringComparison.OrdinalIgnoreCase)); + } + + private bool CanStreamCopyAudio(VideoStreamRequest request, MediaStream audioStream, List<string> supportedAudioCodecs) { // Source and target codecs must match if (string.IsNullOrEmpty(audioStream.Codec) || !supportedAudioCodecs.Contains(audioStream.Codec, StringComparer.OrdinalIgnoreCase)) @@ -1866,7 +1998,7 @@ namespace MediaBrowser.Api.Playback } } - return true; + return request.EnableAutoStreamCopy; } private void ApplyDeviceProfileSettings(StreamState state) @@ -1890,19 +2022,9 @@ namespace MediaBrowser.Api.Playback return; } - var audioCodec = state.OutputAudioCodec; + var audioCodec = state.ActualOutputAudioCodec; - if (string.Equals(audioCodec, "copy", StringComparison.OrdinalIgnoreCase) && state.AudioStream != null) - { - audioCodec = state.AudioStream.Codec; - } - - var videoCodec = state.OutputVideoCodec; - - if (string.Equals(videoCodec, "copy", StringComparison.OrdinalIgnoreCase) && state.VideoStream != null) - { - videoCodec = state.VideoStream.Codec; - } + var videoCodec = state.ActualOutputVideoCodec; var mediaProfile = state.VideoRequest == null ? profile.GetAudioMediaProfile(state.OutputContainer, audioCodec, state.OutputAudioChannels, state.OutputAudioBitrate) : @@ -1921,6 +2043,7 @@ namespace MediaBrowser.Api.Playback state.TargetPacketLength, state.TargetTimestamp, state.IsTargetAnamorphic, + state.IsTargetCabac, state.TargetRefFrames); if (mediaProfile != null) @@ -1937,11 +2060,6 @@ namespace MediaBrowser.Api.Playback state.EstimateContentLength = transcodingProfile.EstimateContentLength; state.EnableMpegtsM2TsMode = transcodingProfile.EnableMpegtsM2TsMode; state.TranscodeSeekInfo = transcodingProfile.TranscodeSeekInfo; - - if (state.VideoRequest != null && string.IsNullOrWhiteSpace(state.VideoRequest.Profile)) - { - state.VideoRequest.Profile = transcodingProfile.VideoProfile; - } } } @@ -1970,12 +2088,7 @@ namespace MediaBrowser.Api.Playback profile = DlnaManager.GetDefaultProfile(); } - var audioCodec = state.OutputAudioCodec; - - if (string.Equals(audioCodec, "copy", StringComparison.OrdinalIgnoreCase) && state.AudioStream != null) - { - audioCodec = state.AudioStream.Codec; - } + var audioCodec = state.ActualOutputAudioCodec; if (state.VideoRequest == null) { @@ -1993,12 +2106,7 @@ namespace MediaBrowser.Api.Playback } else { - var videoCodec = state.OutputVideoCodec; - - if (string.Equals(videoCodec, "copy", StringComparison.OrdinalIgnoreCase) && state.VideoStream != null) - { - videoCodec = state.VideoStream.Codec; - } + var videoCodec = state.ActualOutputVideoCodec; responseHeaders["contentFeatures.dlna.org"] = new ContentFeatureBuilder(profile) .BuildVideoHeader( @@ -2020,6 +2128,7 @@ namespace MediaBrowser.Api.Playback state.TargetPacketLength, state.TranscodeSeekInfo, state.IsTargetAnamorphic, + state.IsTargetCabac, state.TargetRefFrames ).FirstOrDefault() ?? string.Empty; |
