|
@@ -286,13 +286,25 @@ namespace MediaBrowser.Api.Playback
|
|
|
|
|
|
protected string GetH264Encoder(StreamState state)
|
|
protected string GetH264Encoder(StreamState state)
|
|
{
|
|
{
|
|
- if (string.Equals(ApiEntryPoint.Instance.GetEncodingOptions().HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase))
|
|
|
|
|
|
+ // Only use alternative encoders for video files.
|
|
|
|
+ // When using concat with folder rips, if the mfx session fails to initialize, ffmpeg will be stuck retrying and will not exit gracefully
|
|
|
|
+ // Since transcoding of folder rips is expiremental anyway, it's not worth adding additional variables such as this.
|
|
|
|
+ if (state.VideoType == VideoType.VideoFile)
|
|
{
|
|
{
|
|
- // It's currently failing on live tv
|
|
|
|
- if (state.RunTimeTicks.HasValue)
|
|
|
|
|
|
+ if (string.Equals(ApiEntryPoint.Instance.GetEncodingOptions().HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase) ||
|
|
|
|
+ string.Equals(ApiEntryPoint.Instance.GetEncodingOptions().HardwareAccelerationType, "h264_qsv", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
{
|
|
return "h264_qsv";
|
|
return "h264_qsv";
|
|
}
|
|
}
|
|
|
|
+
|
|
|
|
+ if (string.Equals(ApiEntryPoint.Instance.GetEncodingOptions().HardwareAccelerationType, "nvenc", StringComparison.OrdinalIgnoreCase))
|
|
|
|
+ {
|
|
|
|
+ return "h264_nvenc";
|
|
|
|
+ }
|
|
|
|
+ if (string.Equals(ApiEntryPoint.Instance.GetEncodingOptions().HardwareAccelerationType, "h264_omx", StringComparison.OrdinalIgnoreCase))
|
|
|
|
+ {
|
|
|
|
+ return "h264_omx";
|
|
|
|
+ }
|
|
}
|
|
}
|
|
|
|
|
|
return "libx264";
|
|
return "libx264";
|
|
@@ -332,10 +344,10 @@ namespace MediaBrowser.Api.Playback
|
|
|
|
|
|
}
|
|
}
|
|
|
|
|
|
- // h264 (libnvenc)
|
|
|
|
- else if (string.Equals(videoCodec, "libnvenc", StringComparison.OrdinalIgnoreCase))
|
|
|
|
|
|
+ // h264 (h264_nvenc)
|
|
|
|
+ else if (string.Equals(videoCodec, "h264_nvenc", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
{
|
|
- param = "-preset high-performance";
|
|
|
|
|
|
+ param = "-preset default";
|
|
}
|
|
}
|
|
|
|
|
|
// webm
|
|
// webm
|
|
@@ -397,15 +409,18 @@ namespace MediaBrowser.Api.Playback
|
|
|
|
|
|
if (!string.IsNullOrEmpty(state.VideoRequest.Profile))
|
|
if (!string.IsNullOrEmpty(state.VideoRequest.Profile))
|
|
{
|
|
{
|
|
- param += " -profile:v " + state.VideoRequest.Profile;
|
|
|
|
|
|
+ if (!string.Equals(videoCodec, "h264_omx", StringComparison.OrdinalIgnoreCase))
|
|
|
|
+ {
|
|
|
|
+ // not supported by h264_omx
|
|
|
|
+ param += " -profile:v " + state.VideoRequest.Profile;
|
|
|
|
+ }
|
|
}
|
|
}
|
|
|
|
|
|
if (!string.IsNullOrEmpty(state.VideoRequest.Level))
|
|
if (!string.IsNullOrEmpty(state.VideoRequest.Level))
|
|
{
|
|
{
|
|
- var h264Encoder = GetH264Encoder(state);
|
|
|
|
-
|
|
|
|
- // h264_qsv and libnvenc expect levels to be expressed as a decimal. libx264 supports decimal and non-decimal format
|
|
|
|
- if (String.Equals(h264Encoder, "h264_qsv", StringComparison.OrdinalIgnoreCase) || String.Equals(h264Encoder, "libnvenc", StringComparison.OrdinalIgnoreCase))
|
|
|
|
|
|
+ // h264_qsv and h264_nvenc expect levels to be expressed as a decimal. libx264 supports decimal and non-decimal format
|
|
|
|
+ if (string.Equals(videoCodec, "h264_qsv", StringComparison.OrdinalIgnoreCase) ||
|
|
|
|
+ string.Equals(videoCodec, "h264_nvenc", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
{
|
|
switch (state.VideoRequest.Level)
|
|
switch (state.VideoRequest.Level)
|
|
{
|
|
{
|
|
@@ -441,13 +456,20 @@ namespace MediaBrowser.Api.Playback
|
|
break;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
- else
|
|
|
|
|
|
+ else if (!string.Equals(videoCodec, "h264_omx", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
{
|
|
param += " -level " + state.VideoRequest.Level;
|
|
param += " -level " + state.VideoRequest.Level;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
- return "-pix_fmt yuv420p " + param;
|
|
|
|
|
|
+ if (!string.Equals(videoCodec, "h264_omx", StringComparison.OrdinalIgnoreCase) &&
|
|
|
|
+ !string.Equals(videoCodec, "h264_qsv", StringComparison.OrdinalIgnoreCase) &&
|
|
|
|
+ !string.Equals(videoCodec, "h264_nvenc", StringComparison.OrdinalIgnoreCase))
|
|
|
|
+ {
|
|
|
|
+ param = "-pix_fmt yuv420p " + param;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return param;
|
|
}
|
|
}
|
|
|
|
|
|
protected string GetAudioFilterParam(StreamState state, bool isHls)
|
|
protected string GetAudioFilterParam(StreamState state, bool isHls)
|
|
@@ -460,7 +482,7 @@ namespace MediaBrowser.Api.Playback
|
|
// Boost volume to 200% when downsampling from 6ch to 2ch
|
|
// Boost volume to 200% when downsampling from 6ch to 2ch
|
|
if (channels.HasValue && channels.Value <= 2)
|
|
if (channels.HasValue && channels.Value <= 2)
|
|
{
|
|
{
|
|
- if (state.AudioStream != null && state.AudioStream.Channels.HasValue && state.AudioStream.Channels.Value > 5)
|
|
|
|
|
|
+ if (state.AudioStream != null && state.AudioStream.Channels.HasValue && state.AudioStream.Channels.Value > 5 && !ApiEntryPoint.Instance.GetEncodingOptions().DownMixAudioBoost.Equals(1))
|
|
{
|
|
{
|
|
volParam = ",volume=" + ApiEntryPoint.Instance.GetEncodingOptions().DownMixAudioBoost.ToString(UsCulture);
|
|
volParam = ",volume=" + ApiEntryPoint.Instance.GetEncodingOptions().DownMixAudioBoost.ToString(UsCulture);
|
|
}
|
|
}
|
|
@@ -563,14 +585,6 @@ namespace MediaBrowser.Api.Playback
|
|
filters.Add(string.Format("scale=trunc(oh*a/2)*2:min(ih\\,{0})", maxHeightParam));
|
|
filters.Add(string.Format("scale=trunc(oh*a/2)*2:min(ih\\,{0})", maxHeightParam));
|
|
}
|
|
}
|
|
|
|
|
|
- if (string.Equals(outputVideoCodec, "h264_qsv", StringComparison.OrdinalIgnoreCase))
|
|
|
|
- {
|
|
|
|
- if (filters.Count > 1)
|
|
|
|
- {
|
|
|
|
- //filters[filters.Count - 1] += ":flags=fast_bilinear";
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
var output = string.Empty;
|
|
var output = string.Empty;
|
|
|
|
|
|
if (state.SubtitleStream != null && state.SubtitleStream.IsTextSubtitleStream && state.VideoRequest.SubtitleMethod == SubtitleDeliveryMethod.Encode)
|
|
if (state.SubtitleStream != null && state.SubtitleStream.IsTextSubtitleStream && state.VideoRequest.SubtitleMethod == SubtitleDeliveryMethod.Encode)
|
|
@@ -614,7 +628,7 @@ namespace MediaBrowser.Api.Playback
|
|
|
|
|
|
if (!string.IsNullOrEmpty(state.SubtitleStream.Language))
|
|
if (!string.IsNullOrEmpty(state.SubtitleStream.Language))
|
|
{
|
|
{
|
|
- var charenc = SubtitleEncoder.GetSubtitleFileCharacterSet(subtitlePath, state.MediaSource.Protocol, CancellationToken.None).Result;
|
|
|
|
|
|
+ var charenc = SubtitleEncoder.GetSubtitleFileCharacterSet(subtitlePath, state.SubtitleStream.Language, state.MediaSource.Protocol, CancellationToken.None).Result;
|
|
|
|
|
|
if (!string.IsNullOrEmpty(charenc))
|
|
if (!string.IsNullOrEmpty(charenc))
|
|
{
|
|
{
|
|
@@ -712,15 +726,16 @@ namespace MediaBrowser.Api.Playback
|
|
inputChannels = null;
|
|
inputChannels = null;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ int? resultChannels = null;
|
|
var codec = outputAudioCodec ?? string.Empty;
|
|
var codec = outputAudioCodec ?? string.Empty;
|
|
|
|
|
|
if (codec.IndexOf("wma", StringComparison.OrdinalIgnoreCase) != -1)
|
|
if (codec.IndexOf("wma", StringComparison.OrdinalIgnoreCase) != -1)
|
|
{
|
|
{
|
|
// wmav2 currently only supports two channel output
|
|
// wmav2 currently only supports two channel output
|
|
- return Math.Min(2, inputChannels ?? 2);
|
|
|
|
|
|
+ resultChannels = Math.Min(2, inputChannels ?? 2);
|
|
}
|
|
}
|
|
|
|
|
|
- if (request.MaxAudioChannels.HasValue)
|
|
|
|
|
|
+ else if (request.MaxAudioChannels.HasValue)
|
|
{
|
|
{
|
|
var channelLimit = codec.IndexOf("mp3", StringComparison.OrdinalIgnoreCase) != -1
|
|
var channelLimit = codec.IndexOf("mp3", StringComparison.OrdinalIgnoreCase) != -1
|
|
? 2
|
|
? 2
|
|
@@ -732,10 +747,18 @@ namespace MediaBrowser.Api.Playback
|
|
}
|
|
}
|
|
|
|
|
|
// If we don't have any media info then limit it to 5 to prevent encoding errors due to asking for too many channels
|
|
// 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, channelLimit);
|
|
|
|
|
|
+ resultChannels = Math.Min(request.MaxAudioChannels.Value, channelLimit);
|
|
}
|
|
}
|
|
|
|
|
|
- return request.AudioChannels;
|
|
|
|
|
|
+ if (resultChannels.HasValue && !string.Equals(codec, "copy", StringComparison.OrdinalIgnoreCase))
|
|
|
|
+ {
|
|
|
|
+ if (request.TranscodingMaxAudioChannels.HasValue)
|
|
|
|
+ {
|
|
|
|
+ resultChannels = Math.Min(request.TranscodingMaxAudioChannels.Value, resultChannels.Value);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return resultChannels ?? request.AudioChannels;
|
|
}
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// <summary>
|
|
@@ -821,9 +844,22 @@ namespace MediaBrowser.Api.Playback
|
|
/// <returns>System.String.</returns>
|
|
/// <returns>System.String.</returns>
|
|
protected string GetVideoDecoder(StreamState state)
|
|
protected string GetVideoDecoder(StreamState state)
|
|
{
|
|
{
|
|
- if (string.Equals(ApiEntryPoint.Instance.GetEncodingOptions().HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase))
|
|
|
|
|
|
+ if (string.Equals(state.OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase))
|
|
|
|
+ {
|
|
|
|
+ return null;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // Only use alternative encoders for video files.
|
|
|
|
+ // When using concat with folder rips, if the mfx session fails to initialize, ffmpeg will be stuck retrying and will not exit gracefully
|
|
|
|
+ // Since transcoding of folder rips is expiremental anyway, it's not worth adding additional variables such as this.
|
|
|
|
+ if (state.VideoType != VideoType.VideoFile)
|
|
|
|
+ {
|
|
|
|
+ return null;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (state.VideoStream != null && !string.IsNullOrWhiteSpace(state.VideoStream.Codec))
|
|
{
|
|
{
|
|
- if (state.VideoStream != null && !string.IsNullOrWhiteSpace(state.VideoStream.Codec))
|
|
|
|
|
|
+ if (string.Equals(ApiEntryPoint.Instance.GetEncodingOptions().HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
{
|
|
switch (state.MediaSource.VideoStream.Codec.ToLower())
|
|
switch (state.MediaSource.VideoStream.Codec.ToLower())
|
|
{
|
|
{
|
|
@@ -831,6 +867,7 @@ namespace MediaBrowser.Api.Playback
|
|
case "h264":
|
|
case "h264":
|
|
if (MediaEncoder.SupportsDecoder("h264_qsv"))
|
|
if (MediaEncoder.SupportsDecoder("h264_qsv"))
|
|
{
|
|
{
|
|
|
|
+ // Seeing stalls and failures with decoding. Not worth it compared to encoding.
|
|
return "-c:v h264_qsv ";
|
|
return "-c:v h264_qsv ";
|
|
}
|
|
}
|
|
break;
|
|
break;
|
|
@@ -947,14 +984,24 @@ namespace MediaBrowser.Api.Playback
|
|
|
|
|
|
await AcquireResources(state, cancellationTokenSource).ConfigureAwait(false);
|
|
await AcquireResources(state, cancellationTokenSource).ConfigureAwait(false);
|
|
|
|
|
|
- var transcodingId = Guid.NewGuid().ToString("N");
|
|
|
|
- var commandLineArgs = GetCommandLineArguments(outputPath, state, true);
|
|
|
|
-
|
|
|
|
- if (ApiEntryPoint.Instance.GetEncodingOptions().EnableDebugLogging)
|
|
|
|
|
|
+ if (state.VideoRequest != null && !string.Equals(state.OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
{
|
|
- commandLineArgs = "-loglevel debug " + commandLineArgs;
|
|
|
|
|
|
+ var auth = AuthorizationContext.GetAuthorizationInfo(Request);
|
|
|
|
+ if (!string.IsNullOrWhiteSpace(auth.UserId))
|
|
|
|
+ {
|
|
|
|
+ var user = UserManager.GetUserById(auth.UserId);
|
|
|
|
+ if (!user.Policy.EnableVideoPlaybackTranscoding)
|
|
|
|
+ {
|
|
|
|
+ ApiEntryPoint.Instance.OnTranscodeFailedToStart(outputPath, TranscodingJobType, state);
|
|
|
|
+
|
|
|
|
+ throw new ArgumentException("User does not have access to video transcoding");
|
|
|
|
+ }
|
|
|
|
+ }
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ var transcodingId = Guid.NewGuid().ToString("N");
|
|
|
|
+ var commandLineArgs = GetCommandLineArguments(outputPath, state, true);
|
|
|
|
+
|
|
var process = new Process
|
|
var process = new Process
|
|
{
|
|
{
|
|
StartInfo = new ProcessStartInfo
|
|
StartInfo = new ProcessStartInfo
|
|
@@ -963,7 +1010,7 @@ namespace MediaBrowser.Api.Playback
|
|
UseShellExecute = false,
|
|
UseShellExecute = false,
|
|
|
|
|
|
// Must consume both stdout and stderr or deadlocks may occur
|
|
// Must consume both stdout and stderr or deadlocks may occur
|
|
- RedirectStandardOutput = true,
|
|
|
|
|
|
+ //RedirectStandardOutput = true,
|
|
RedirectStandardError = true,
|
|
RedirectStandardError = true,
|
|
RedirectStandardInput = true,
|
|
RedirectStandardInput = true,
|
|
|
|
|
|
@@ -995,7 +1042,17 @@ namespace MediaBrowser.Api.Playback
|
|
var commandLineLogMessage = process.StartInfo.FileName + " " + process.StartInfo.Arguments;
|
|
var commandLineLogMessage = process.StartInfo.FileName + " " + process.StartInfo.Arguments;
|
|
Logger.Info(commandLineLogMessage);
|
|
Logger.Info(commandLineLogMessage);
|
|
|
|
|
|
- var logFilePath = Path.Combine(ServerConfigurationManager.ApplicationPaths.LogDirectoryPath, "transcode-" + Guid.NewGuid() + ".txt");
|
|
|
|
|
|
+ var logFilePrefix = "transcode";
|
|
|
|
+ if (state.VideoRequest != null && string.Equals(state.OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase) && string.Equals(state.OutputAudioCodec, "copy", StringComparison.OrdinalIgnoreCase))
|
|
|
|
+ {
|
|
|
|
+ logFilePrefix = "directstream";
|
|
|
|
+ }
|
|
|
|
+ else if (state.VideoRequest != null && string.Equals(state.OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase))
|
|
|
|
+ {
|
|
|
|
+ logFilePrefix = "remux";
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ var logFilePath = Path.Combine(ServerConfigurationManager.ApplicationPaths.LogDirectoryPath, logFilePrefix + "-" + Guid.NewGuid() + ".txt");
|
|
FileSystem.CreateDirectory(Path.GetDirectoryName(logFilePath));
|
|
FileSystem.CreateDirectory(Path.GetDirectoryName(logFilePath));
|
|
|
|
|
|
// FFMpeg writes debug/error info to stderr. This is useful when debugging so let's put it in the log directory.
|
|
// FFMpeg writes debug/error info to stderr. This is useful when debugging so let's put it in the log directory.
|
|
@@ -1020,13 +1077,13 @@ namespace MediaBrowser.Api.Playback
|
|
}
|
|
}
|
|
|
|
|
|
// MUST read both stdout and stderr asynchronously or a deadlock may occurr
|
|
// MUST read both stdout and stderr asynchronously or a deadlock may occurr
|
|
- process.BeginOutputReadLine();
|
|
|
|
|
|
+ //process.BeginOutputReadLine();
|
|
|
|
|
|
// Important - don't await the log task or we won't be able to kill ffmpeg when the user stops playback
|
|
// Important - don't await the log task or we won't be able to kill ffmpeg when the user stops playback
|
|
- StartStreamingLog(transcodingJob, state, process.StandardError.BaseStream, state.LogFileStream);
|
|
|
|
|
|
+ Task.Run(() => StartStreamingLog(transcodingJob, state, process.StandardError.BaseStream, state.LogFileStream));
|
|
|
|
|
|
// Wait for the file to exist before proceeeding
|
|
// Wait for the file to exist before proceeeding
|
|
- while (!FileSystem.FileExists(state.WaitForPath ?? outputPath) && !transcodingJob.HasExited)
|
|
|
|
|
|
+ while (!FileSystem.FileExists(state.WaitForPath ?? outputPath) && !transcodingJob.HasExited)
|
|
{
|
|
{
|
|
await Task.Delay(100, cancellationTokenSource.Token).ConfigureAwait(false);
|
|
await Task.Delay(100, cancellationTokenSource.Token).ConfigureAwait(false);
|
|
}
|
|
}
|
|
@@ -1066,7 +1123,7 @@ namespace MediaBrowser.Api.Playback
|
|
return true;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
|
|
- private async void StartStreamingLog(TranscodingJob transcodingJob, StreamState state, Stream source, Stream target)
|
|
|
|
|
|
+ private async Task StartStreamingLog(TranscodingJob transcodingJob, StreamState state, Stream source, Stream target)
|
|
{
|
|
{
|
|
try
|
|
try
|
|
{
|
|
{
|
|
@@ -1172,7 +1229,7 @@ namespace MediaBrowser.Api.Playback
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
- private int? GetVideoBitrateParamValue(VideoStreamRequest request, MediaStream videoStream)
|
|
|
|
|
|
+ private int? GetVideoBitrateParamValue(VideoStreamRequest request, MediaStream videoStream, string outputVideoCodec)
|
|
{
|
|
{
|
|
var bitrate = request.VideoBitRate;
|
|
var bitrate = request.VideoBitRate;
|
|
|
|
|
|
@@ -1197,6 +1254,18 @@ namespace MediaBrowser.Api.Playback
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ if (bitrate.HasValue)
|
|
|
|
+ {
|
|
|
|
+ var inputVideoCodec = videoStream == null ? null : videoStream.Codec;
|
|
|
|
+ bitrate = ResolutionNormalizer.ScaleBitrate(bitrate.Value, inputVideoCodec, outputVideoCodec);
|
|
|
|
+
|
|
|
|
+ // If a max bitrate was requested, don't let the scaled bitrate exceed it
|
|
|
|
+ if (request.VideoBitRate.HasValue)
|
|
|
|
+ {
|
|
|
|
+ bitrate = Math.Min(bitrate.Value, request.VideoBitRate.Value);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
return bitrate;
|
|
return bitrate;
|
|
}
|
|
}
|
|
|
|
|
|
@@ -1452,10 +1521,7 @@ namespace MediaBrowser.Api.Playback
|
|
}
|
|
}
|
|
else if (i == 19)
|
|
else if (i == 19)
|
|
{
|
|
{
|
|
- if (videoRequest != null)
|
|
|
|
- {
|
|
|
|
- videoRequest.Cabac = string.Equals("true", val, StringComparison.OrdinalIgnoreCase);
|
|
|
|
- }
|
|
|
|
|
|
+ // cabac no longer used
|
|
}
|
|
}
|
|
else if (i == 20)
|
|
else if (i == 20)
|
|
{
|
|
{
|
|
@@ -1481,6 +1547,13 @@ namespace MediaBrowser.Api.Playback
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else if (i == 25)
|
|
else if (i == 25)
|
|
|
|
+ {
|
|
|
|
+ if (videoRequest != null)
|
|
|
|
+ {
|
|
|
|
+ videoRequest.ForceLiveStream = string.Equals("true", val, StringComparison.OrdinalIgnoreCase);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ else if (i == 26)
|
|
{
|
|
{
|
|
if (!string.IsNullOrWhiteSpace(val) && videoRequest != null)
|
|
if (!string.IsNullOrWhiteSpace(val) && videoRequest != null)
|
|
{
|
|
{
|
|
@@ -1491,6 +1564,17 @@ namespace MediaBrowser.Api.Playback
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
+ else if (i == 27)
|
|
|
|
+ {
|
|
|
|
+ request.TranscodingMaxAudioChannels = int.Parse(val, UsCulture);
|
|
|
|
+ }
|
|
|
|
+ else if (i == 28)
|
|
|
|
+ {
|
|
|
|
+ if (videoRequest != null)
|
|
|
|
+ {
|
|
|
|
+ videoRequest.EnableSubtitlesInManifest = string.Equals("true", val, StringComparison.OrdinalIgnoreCase);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
@@ -1582,7 +1666,8 @@ namespace MediaBrowser.Api.Playback
|
|
var state = new StreamState(MediaSourceManager, Logger)
|
|
var state = new StreamState(MediaSourceManager, Logger)
|
|
{
|
|
{
|
|
Request = request,
|
|
Request = request,
|
|
- RequestedUrl = url
|
|
|
|
|
|
+ RequestedUrl = url,
|
|
|
|
+ UserAgent = Request.UserAgent
|
|
};
|
|
};
|
|
|
|
|
|
//if ((Request.UserAgent ?? string.Empty).IndexOf("iphone", StringComparison.OrdinalIgnoreCase) != -1 ||
|
|
//if ((Request.UserAgent ?? string.Empty).IndexOf("iphone", StringComparison.OrdinalIgnoreCase) != -1 ||
|
|
@@ -1595,7 +1680,8 @@ namespace MediaBrowser.Api.Playback
|
|
if (!string.IsNullOrWhiteSpace(request.AudioCodec))
|
|
if (!string.IsNullOrWhiteSpace(request.AudioCodec))
|
|
{
|
|
{
|
|
state.SupportedAudioCodecs = request.AudioCodec.Split(',').Where(i => !string.IsNullOrWhiteSpace(i)).ToList();
|
|
state.SupportedAudioCodecs = request.AudioCodec.Split(',').Where(i => !string.IsNullOrWhiteSpace(i)).ToList();
|
|
- state.Request.AudioCodec = state.SupportedAudioCodecs.FirstOrDefault();
|
|
|
|
|
|
+ state.Request.AudioCodec = state.SupportedAudioCodecs.FirstOrDefault(i => MediaEncoder.CanEncodeToAudioCodec(i))
|
|
|
|
+ ?? state.SupportedAudioCodecs.FirstOrDefault();
|
|
}
|
|
}
|
|
|
|
|
|
var item = LibraryManager.GetItemById(request.Id);
|
|
var item = LibraryManager.GetItemById(request.Id);
|
|
@@ -1649,14 +1735,14 @@ namespace MediaBrowser.Api.Playback
|
|
if (videoRequest != null)
|
|
if (videoRequest != null)
|
|
{
|
|
{
|
|
state.OutputVideoCodec = state.VideoRequest.VideoCodec;
|
|
state.OutputVideoCodec = state.VideoRequest.VideoCodec;
|
|
- state.OutputVideoBitrate = GetVideoBitrateParamValue(state.VideoRequest, state.VideoStream);
|
|
|
|
|
|
+ state.OutputVideoBitrate = GetVideoBitrateParamValue(state.VideoRequest, state.VideoStream, state.OutputVideoCodec);
|
|
|
|
|
|
if (state.OutputVideoBitrate.HasValue)
|
|
if (state.OutputVideoBitrate.HasValue)
|
|
{
|
|
{
|
|
var resolution = ResolutionNormalizer.Normalize(
|
|
var resolution = ResolutionNormalizer.Normalize(
|
|
- state.VideoStream == null ? (int?)null : state.VideoStream.BitRate,
|
|
|
|
- state.OutputVideoBitrate.Value,
|
|
|
|
- state.VideoStream == null ? null : state.VideoStream.Codec,
|
|
|
|
|
|
+ state.VideoStream == null ? (int?)null : state.VideoStream.BitRate,
|
|
|
|
+ state.OutputVideoBitrate.Value,
|
|
|
|
+ state.VideoStream == null ? null : state.VideoStream.Codec,
|
|
state.OutputVideoCodec,
|
|
state.OutputVideoCodec,
|
|
videoRequest.MaxWidth,
|
|
videoRequest.MaxWidth,
|
|
videoRequest.MaxHeight);
|
|
videoRequest.MaxHeight);
|
|
@@ -1680,12 +1766,25 @@ namespace MediaBrowser.Api.Playback
|
|
|
|
|
|
private void TryStreamCopy(StreamState state, VideoStreamRequest videoRequest)
|
|
private void TryStreamCopy(StreamState state, VideoStreamRequest videoRequest)
|
|
{
|
|
{
|
|
- if (state.VideoStream != null && CanStreamCopyVideo(videoRequest, state.VideoStream))
|
|
|
|
|
|
+ if (state.VideoStream != null && CanStreamCopyVideo(state))
|
|
{
|
|
{
|
|
state.OutputVideoCodec = "copy";
|
|
state.OutputVideoCodec = "copy";
|
|
}
|
|
}
|
|
|
|
+ else
|
|
|
|
+ {
|
|
|
|
+ // If the user doesn't have access to transcoding, then force stream copy, regardless of whether it will be compatible or not
|
|
|
|
+ var auth = AuthorizationContext.GetAuthorizationInfo(Request);
|
|
|
|
+ if (!string.IsNullOrWhiteSpace(auth.UserId))
|
|
|
|
+ {
|
|
|
|
+ var user = UserManager.GetUserById(auth.UserId);
|
|
|
|
+ if (!user.Policy.EnableVideoPlaybackTranscoding)
|
|
|
|
+ {
|
|
|
|
+ state.OutputVideoCodec = "copy";
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
|
|
- if (state.AudioStream != null && CanStreamCopyAudio(videoRequest, state.AudioStream, state.SupportedAudioCodecs))
|
|
|
|
|
|
+ if (state.AudioStream != null && CanStreamCopyAudio(state, state.SupportedAudioCodecs))
|
|
{
|
|
{
|
|
state.OutputAudioCodec = "copy";
|
|
state.OutputAudioCodec = "copy";
|
|
}
|
|
}
|
|
@@ -1773,8 +1872,11 @@ namespace MediaBrowser.Api.Playback
|
|
state.MediaSource = mediaSource;
|
|
state.MediaSource = mediaSource;
|
|
}
|
|
}
|
|
|
|
|
|
- protected virtual bool CanStreamCopyVideo(VideoStreamRequest request, MediaStream videoStream)
|
|
|
|
|
|
+ protected virtual bool CanStreamCopyVideo(StreamState state)
|
|
{
|
|
{
|
|
|
|
+ var request = state.VideoRequest;
|
|
|
|
+ var videoStream = state.VideoStream;
|
|
|
|
+
|
|
if (videoStream.IsInterlaced)
|
|
if (videoStream.IsInterlaced)
|
|
{
|
|
{
|
|
return false;
|
|
return false;
|
|
@@ -1784,7 +1886,7 @@ namespace MediaBrowser.Api.Playback
|
|
{
|
|
{
|
|
return false;
|
|
return false;
|
|
}
|
|
}
|
|
-
|
|
|
|
|
|
+
|
|
// Can't stream copy if we're burning in subtitles
|
|
// Can't stream copy if we're burning in subtitles
|
|
if (request.SubtitleStreamIndex.HasValue)
|
|
if (request.SubtitleStreamIndex.HasValue)
|
|
{
|
|
{
|
|
@@ -1794,6 +1896,15 @@ namespace MediaBrowser.Api.Playback
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ if (string.Equals("h264", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
|
|
|
|
+ {
|
|
|
|
+ if (videoStream.IsAVC.HasValue && !videoStream.IsAVC.Value)
|
|
|
|
+ {
|
|
|
|
+ Logger.Debug("Cannot stream copy video. Stream is marked as not AVC");
|
|
|
|
+ return false;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
// Source and target codecs must match
|
|
// Source and target codecs must match
|
|
if (!string.Equals(request.VideoCodec, videoStream.Codec, StringComparison.OrdinalIgnoreCase))
|
|
if (!string.Equals(request.VideoCodec, videoStream.Codec, StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
{
|
|
@@ -1805,10 +1916,10 @@ namespace MediaBrowser.Api.Playback
|
|
{
|
|
{
|
|
if (string.IsNullOrEmpty(videoStream.Profile))
|
|
if (string.IsNullOrEmpty(videoStream.Profile))
|
|
{
|
|
{
|
|
- return false;
|
|
|
|
|
|
+ //return false;
|
|
}
|
|
}
|
|
|
|
|
|
- if (!string.Equals(request.Profile, videoStream.Profile, StringComparison.OrdinalIgnoreCase))
|
|
|
|
|
|
+ if (!string.IsNullOrEmpty(videoStream.Profile) && !string.Equals(request.Profile, videoStream.Profile, StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
{
|
|
var currentScore = GetVideoProfileScore(videoStream.Profile);
|
|
var currentScore = GetVideoProfileScore(videoStream.Profile);
|
|
var requestedScore = GetVideoProfileScore(request.Profile);
|
|
var requestedScore = GetVideoProfileScore(request.Profile);
|
|
@@ -1884,24 +1995,16 @@ namespace MediaBrowser.Api.Playback
|
|
{
|
|
{
|
|
if (!videoStream.Level.HasValue)
|
|
if (!videoStream.Level.HasValue)
|
|
{
|
|
{
|
|
- return false;
|
|
|
|
|
|
+ //return false;
|
|
}
|
|
}
|
|
|
|
|
|
- if (videoStream.Level.Value > requestLevel)
|
|
|
|
|
|
+ if (videoStream.Level.HasValue && videoStream.Level.Value > requestLevel)
|
|
{
|
|
{
|
|
return false;
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
- if (request.Cabac.HasValue && request.Cabac.Value)
|
|
|
|
- {
|
|
|
|
- if (videoStream.IsCabac.HasValue && !videoStream.IsCabac.Value)
|
|
|
|
- {
|
|
|
|
- return false;
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
return request.EnableAutoStreamCopy;
|
|
return request.EnableAutoStreamCopy;
|
|
}
|
|
}
|
|
|
|
|
|
@@ -1921,8 +2024,11 @@ namespace MediaBrowser.Api.Playback
|
|
return Array.FindIndex(list.ToArray(), t => string.Equals(t, profile, StringComparison.OrdinalIgnoreCase));
|
|
return Array.FindIndex(list.ToArray(), t => string.Equals(t, profile, StringComparison.OrdinalIgnoreCase));
|
|
}
|
|
}
|
|
|
|
|
|
- protected virtual bool CanStreamCopyAudio(VideoStreamRequest request, MediaStream audioStream, List<string> supportedAudioCodecs)
|
|
|
|
|
|
+ protected virtual bool CanStreamCopyAudio(StreamState state, List<string> supportedAudioCodecs)
|
|
{
|
|
{
|
|
|
|
+ var request = state.VideoRequest;
|
|
|
|
+ var audioStream = state.AudioStream;
|
|
|
|
+
|
|
// Source and target codecs must match
|
|
// Source and target codecs must match
|
|
if (string.IsNullOrEmpty(audioStream.Codec) || !supportedAudioCodecs.Contains(audioStream.Codec, StringComparer.OrdinalIgnoreCase))
|
|
if (string.IsNullOrEmpty(audioStream.Codec) || !supportedAudioCodecs.Contains(audioStream.Codec, StringComparer.OrdinalIgnoreCase))
|
|
{
|
|
{
|
|
@@ -2028,7 +2134,6 @@ namespace MediaBrowser.Api.Playback
|
|
state.TargetPacketLength,
|
|
state.TargetPacketLength,
|
|
state.TargetTimestamp,
|
|
state.TargetTimestamp,
|
|
state.IsTargetAnamorphic,
|
|
state.IsTargetAnamorphic,
|
|
- state.IsTargetCabac,
|
|
|
|
state.TargetRefFrames,
|
|
state.TargetRefFrames,
|
|
state.TargetVideoStreamCount,
|
|
state.TargetVideoStreamCount,
|
|
state.TargetAudioStreamCount,
|
|
state.TargetAudioStreamCount,
|
|
@@ -2054,6 +2159,8 @@ namespace MediaBrowser.Api.Playback
|
|
if (state.VideoRequest != null)
|
|
if (state.VideoRequest != null)
|
|
{
|
|
{
|
|
state.VideoRequest.CopyTimestamps = transcodingProfile.CopyTimestamps;
|
|
state.VideoRequest.CopyTimestamps = transcodingProfile.CopyTimestamps;
|
|
|
|
+ state.VideoRequest.ForceLiveStream = transcodingProfile.ForceLiveStream;
|
|
|
|
+ state.VideoRequest.EnableSubtitlesInManifest = transcodingProfile.EnableSubtitlesInManifest;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
@@ -2131,7 +2238,6 @@ namespace MediaBrowser.Api.Playback
|
|
state.TargetPacketLength,
|
|
state.TargetPacketLength,
|
|
state.TranscodeSeekInfo,
|
|
state.TranscodeSeekInfo,
|
|
state.IsTargetAnamorphic,
|
|
state.IsTargetAnamorphic,
|
|
- state.IsTargetCabac,
|
|
|
|
state.TargetRefFrames,
|
|
state.TargetRefFrames,
|
|
state.TargetVideoStreamCount,
|
|
state.TargetVideoStreamCount,
|
|
state.TargetAudioStreamCount,
|
|
state.TargetAudioStreamCount,
|
|
@@ -2218,12 +2324,13 @@ namespace MediaBrowser.Api.Playback
|
|
|
|
|
|
if (state.VideoRequest != null)
|
|
if (state.VideoRequest != null)
|
|
{
|
|
{
|
|
|
|
+ // Important: If this is ever re-enabled, make sure not to use it with wtv because it breaks seeking
|
|
if (string.Equals(state.OutputContainer, "mkv", StringComparison.OrdinalIgnoreCase) && state.VideoRequest.CopyTimestamps)
|
|
if (string.Equals(state.OutputContainer, "mkv", StringComparison.OrdinalIgnoreCase) && state.VideoRequest.CopyTimestamps)
|
|
{
|
|
{
|
|
- inputModifier += " -noaccurate_seek";
|
|
|
|
|
|
+ //inputModifier += " -noaccurate_seek";
|
|
}
|
|
}
|
|
}
|
|
}
|
|
-
|
|
|
|
|
|
+
|
|
return inputModifier;
|
|
return inputModifier;
|
|
}
|
|
}
|
|
|
|
|