|
@@ -22,6 +22,8 @@ using System.Text;
|
|
|
using System.Threading;
|
|
|
using System.Threading.Tasks;
|
|
|
using CommonIO;
|
|
|
+using MediaBrowser.Common.Net;
|
|
|
+using MediaBrowser.Controller;
|
|
|
|
|
|
namespace MediaBrowser.Api.Playback
|
|
|
{
|
|
@@ -69,6 +71,9 @@ namespace MediaBrowser.Api.Playback
|
|
|
protected IZipClient ZipClient { get; private set; }
|
|
|
protected IJsonSerializer JsonSerializer { get; private set; }
|
|
|
|
|
|
+ public static IServerApplicationHost AppHost;
|
|
|
+ public static IHttpClient HttpClient;
|
|
|
+
|
|
|
/// <summary>
|
|
|
/// Initializes a new instance of the <see cref="BaseStreamingService" /> class.
|
|
|
/// </summary>
|
|
@@ -286,28 +291,46 @@ namespace MediaBrowser.Api.Playback
|
|
|
|
|
|
protected string GetH264Encoder(StreamState state)
|
|
|
{
|
|
|
+ var defaultEncoder = "libx264";
|
|
|
+
|
|
|
// 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)
|
|
|
{
|
|
|
- if (string.Equals(ApiEntryPoint.Instance.GetEncodingOptions().HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase) ||
|
|
|
- string.Equals(ApiEntryPoint.Instance.GetEncodingOptions().HardwareAccelerationType, "h264_qsv", StringComparison.OrdinalIgnoreCase))
|
|
|
+ var encodingOptions = ApiEntryPoint.Instance.GetEncodingOptions();
|
|
|
+ var hwType = encodingOptions.HardwareAccelerationType;
|
|
|
+
|
|
|
+ if (string.Equals(hwType, "qsv", StringComparison.OrdinalIgnoreCase) ||
|
|
|
+ string.Equals(hwType, "h264_qsv", StringComparison.OrdinalIgnoreCase))
|
|
|
{
|
|
|
- return "h264_qsv";
|
|
|
+ return GetAvailableEncoder("h264_qsv", defaultEncoder);
|
|
|
}
|
|
|
|
|
|
- if (string.Equals(ApiEntryPoint.Instance.GetEncodingOptions().HardwareAccelerationType, "nvenc", StringComparison.OrdinalIgnoreCase))
|
|
|
+ if (string.Equals(hwType, "nvenc", StringComparison.OrdinalIgnoreCase))
|
|
|
+ {
|
|
|
+ return GetAvailableEncoder("h264_nvenc", defaultEncoder);
|
|
|
+ }
|
|
|
+ if (string.Equals(hwType, "h264_omx", StringComparison.OrdinalIgnoreCase))
|
|
|
{
|
|
|
- return "h264_nvenc";
|
|
|
+ return GetAvailableEncoder("h264_omx", defaultEncoder);
|
|
|
}
|
|
|
- if (string.Equals(ApiEntryPoint.Instance.GetEncodingOptions().HardwareAccelerationType, "h264_omx", StringComparison.OrdinalIgnoreCase))
|
|
|
+ if (string.Equals(hwType, "vaapi", StringComparison.OrdinalIgnoreCase) && !string.IsNullOrWhiteSpace(encodingOptions.VaapiDevice))
|
|
|
{
|
|
|
- return "h264_omx";
|
|
|
+ return GetAvailableEncoder("h264_vaapi", defaultEncoder);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- return "libx264";
|
|
|
+ return defaultEncoder;
|
|
|
+ }
|
|
|
+
|
|
|
+ private string GetAvailableEncoder(string preferredEncoder, string defaultEncoder)
|
|
|
+ {
|
|
|
+ if (MediaEncoder.SupportsEncoder(preferredEncoder))
|
|
|
+ {
|
|
|
+ return preferredEncoder;
|
|
|
+ }
|
|
|
+ return defaultEncoder;
|
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
@@ -409,7 +432,8 @@ namespace MediaBrowser.Api.Playback
|
|
|
|
|
|
if (!string.IsNullOrEmpty(state.VideoRequest.Profile))
|
|
|
{
|
|
|
- if (!string.Equals(videoCodec, "h264_omx", StringComparison.OrdinalIgnoreCase))
|
|
|
+ if (!string.Equals(videoCodec, "h264_omx", StringComparison.OrdinalIgnoreCase) &&
|
|
|
+ !string.Equals(videoCodec, "h264_vaapi", StringComparison.OrdinalIgnoreCase))
|
|
|
{
|
|
|
// not supported by h264_omx
|
|
|
param += " -profile:v " + state.VideoRequest.Profile;
|
|
@@ -464,7 +488,8 @@ namespace MediaBrowser.Api.Playback
|
|
|
|
|
|
if (!string.Equals(videoCodec, "h264_omx", StringComparison.OrdinalIgnoreCase) &&
|
|
|
!string.Equals(videoCodec, "h264_qsv", StringComparison.OrdinalIgnoreCase) &&
|
|
|
- !string.Equals(videoCodec, "h264_nvenc", StringComparison.OrdinalIgnoreCase))
|
|
|
+ !string.Equals(videoCodec, "h264_nvenc", StringComparison.OrdinalIgnoreCase) &&
|
|
|
+ !string.Equals(videoCodec, "h264_vaapi", StringComparison.OrdinalIgnoreCase))
|
|
|
{
|
|
|
param = "-pix_fmt yuv420p " + param;
|
|
|
}
|
|
@@ -530,59 +555,97 @@ namespace MediaBrowser.Api.Playback
|
|
|
|
|
|
var filters = new List<string>();
|
|
|
|
|
|
- if (state.DeInterlace)
|
|
|
+ if (string.Equals(outputVideoCodec, "h264_vaapi", StringComparison.OrdinalIgnoreCase))
|
|
|
+ {
|
|
|
+ filters.Add("format=nv12|vaapi");
|
|
|
+ filters.Add("hwupload");
|
|
|
+ }
|
|
|
+ else if (state.DeInterlace && !string.Equals(outputVideoCodec, "h264_vaapi", StringComparison.OrdinalIgnoreCase))
|
|
|
{
|
|
|
filters.Add("yadif=0:-1:0");
|
|
|
}
|
|
|
|
|
|
- // If fixed dimensions were supplied
|
|
|
- if (request.Width.HasValue && request.Height.HasValue)
|
|
|
+ if (string.Equals(outputVideoCodec, "h264_vaapi", StringComparison.OrdinalIgnoreCase))
|
|
|
{
|
|
|
- var widthParam = request.Width.Value.ToString(UsCulture);
|
|
|
- var heightParam = request.Height.Value.ToString(UsCulture);
|
|
|
+ // Work around vaapi's reduced scaling features
|
|
|
+ var scaler = "scale_vaapi";
|
|
|
|
|
|
- filters.Add(string.Format("scale=trunc({0}/2)*2:trunc({1}/2)*2", widthParam, heightParam));
|
|
|
- }
|
|
|
+ // Given the input dimensions (inputWidth, inputHeight), determine the output dimensions
|
|
|
+ // (outputWidth, outputHeight). The user may request precise output dimensions or maximum
|
|
|
+ // output dimensions. Output dimensions are guaranteed to be even.
|
|
|
+ decimal inputWidth = Convert.ToDecimal(state.VideoStream.Width);
|
|
|
+ decimal inputHeight = Convert.ToDecimal(state.VideoStream.Height);
|
|
|
+ decimal outputWidth = request.Width.HasValue ? Convert.ToDecimal(request.Width.Value) : inputWidth;
|
|
|
+ decimal outputHeight = request.Height.HasValue ? Convert.ToDecimal(request.Height.Value) : inputHeight;
|
|
|
+ decimal maximumWidth = request.MaxWidth.HasValue ? Convert.ToDecimal(request.MaxWidth.Value) : outputWidth;
|
|
|
+ decimal maximumHeight = request.MaxHeight.HasValue ? Convert.ToDecimal(request.MaxHeight.Value) : outputHeight;
|
|
|
|
|
|
- // If Max dimensions were supplied, for width selects lowest even number between input width and width req size and selects lowest even number from in width*display aspect and requested size
|
|
|
- else if (request.MaxWidth.HasValue && request.MaxHeight.HasValue)
|
|
|
- {
|
|
|
- var maxWidthParam = request.MaxWidth.Value.ToString(UsCulture);
|
|
|
- var maxHeightParam = request.MaxHeight.Value.ToString(UsCulture);
|
|
|
+ if (outputWidth > maximumWidth || outputHeight > maximumHeight)
|
|
|
+ {
|
|
|
+ var scale = Math.Min(maximumWidth / outputWidth, maximumHeight / outputHeight);
|
|
|
+ outputWidth = Math.Min(maximumWidth, Math.Truncate(outputWidth * scale));
|
|
|
+ outputHeight = Math.Min(maximumHeight, Math.Truncate(outputHeight * scale));
|
|
|
+ }
|
|
|
|
|
|
- filters.Add(string.Format("scale=trunc(min(max(iw\\,ih*dar)\\,min({0}\\,{1}*dar))/2)*2:trunc(min(max(iw/dar\\,ih)\\,min({0}/dar\\,{1}))/2)*2", maxWidthParam, maxHeightParam));
|
|
|
- }
|
|
|
+ outputWidth = 2 * Math.Truncate(outputWidth / 2);
|
|
|
+ outputHeight = 2 * Math.Truncate(outputHeight / 2);
|
|
|
|
|
|
- // If a fixed width was requested
|
|
|
- else if (request.Width.HasValue)
|
|
|
+ if (outputWidth != inputWidth || outputHeight != inputHeight)
|
|
|
+ {
|
|
|
+ filters.Add(string.Format("{0}=w={1}:h={2}", scaler, outputWidth.ToString(UsCulture), outputHeight.ToString(UsCulture)));
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else
|
|
|
{
|
|
|
- var widthParam = request.Width.Value.ToString(UsCulture);
|
|
|
+ // If fixed dimensions were supplied
|
|
|
+ if (request.Width.HasValue && request.Height.HasValue)
|
|
|
+ {
|
|
|
+ var widthParam = request.Width.Value.ToString(UsCulture);
|
|
|
+ var heightParam = request.Height.Value.ToString(UsCulture);
|
|
|
|
|
|
- filters.Add(string.Format("scale={0}:trunc(ow/a/2)*2", widthParam));
|
|
|
- }
|
|
|
+ filters.Add(string.Format("scale=trunc({0}/2)*2:trunc({1}/2)*2", widthParam, heightParam));
|
|
|
+ }
|
|
|
|
|
|
- // If a fixed height was requested
|
|
|
- else if (request.Height.HasValue)
|
|
|
- {
|
|
|
- var heightParam = request.Height.Value.ToString(UsCulture);
|
|
|
+ // If Max dimensions were supplied, for width selects lowest even number between input width and width req size and selects lowest even number from in width*display aspect and requested size
|
|
|
+ else if (request.MaxWidth.HasValue && request.MaxHeight.HasValue)
|
|
|
+ {
|
|
|
+ var maxWidthParam = request.MaxWidth.Value.ToString(UsCulture);
|
|
|
+ var maxHeightParam = request.MaxHeight.Value.ToString(UsCulture);
|
|
|
|
|
|
- filters.Add(string.Format("scale=trunc(oh*a/2)*2:{0}", heightParam));
|
|
|
- }
|
|
|
+ filters.Add(string.Format("scale=trunc(min(max(iw\\,ih*dar)\\,min({0}\\,{1}*dar))/2)*2:trunc(min(max(iw/dar\\,ih)\\,min({0}/dar\\,{1}))/2)*2", maxWidthParam, maxHeightParam));
|
|
|
+ }
|
|
|
|
|
|
- // If a max width was requested
|
|
|
- else if (request.MaxWidth.HasValue)
|
|
|
- {
|
|
|
- var maxWidthParam = request.MaxWidth.Value.ToString(UsCulture);
|
|
|
+ // If a fixed width was requested
|
|
|
+ else if (request.Width.HasValue)
|
|
|
+ {
|
|
|
+ var widthParam = request.Width.Value.ToString(UsCulture);
|
|
|
|
|
|
- filters.Add(string.Format("scale=trunc(min(max(iw\\,ih*dar)\\,{0})/2)*2:trunc(ow/dar/2)*2", maxWidthParam));
|
|
|
- }
|
|
|
+ filters.Add(string.Format("scale={0}:trunc(ow/a/2)*2", widthParam));
|
|
|
+ }
|
|
|
|
|
|
- // If a max height was requested
|
|
|
- else if (request.MaxHeight.HasValue)
|
|
|
- {
|
|
|
- var maxHeightParam = request.MaxHeight.Value.ToString(UsCulture);
|
|
|
+ // If a fixed height was requested
|
|
|
+ else if (request.Height.HasValue)
|
|
|
+ {
|
|
|
+ var heightParam = request.Height.Value.ToString(UsCulture);
|
|
|
+
|
|
|
+ filters.Add(string.Format("scale=trunc(oh*a/2)*2:{0}", heightParam));
|
|
|
+ }
|
|
|
+
|
|
|
+ // If a max width was requested
|
|
|
+ else if (request.MaxWidth.HasValue)
|
|
|
+ {
|
|
|
+ var maxWidthParam = request.MaxWidth.Value.ToString(UsCulture);
|
|
|
+
|
|
|
+ filters.Add(string.Format("scale=trunc(min(max(iw\\,ih*dar)\\,{0})/2)*2:trunc(ow/dar/2)*2", maxWidthParam));
|
|
|
+ }
|
|
|
+
|
|
|
+ // If a max height was requested
|
|
|
+ else if (request.MaxHeight.HasValue)
|
|
|
+ {
|
|
|
+ var maxHeightParam = request.MaxHeight.Value.ToString(UsCulture);
|
|
|
|
|
|
- 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(max(iw/dar\\,ih)\\,{0})", maxHeightParam));
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
var output = string.Empty;
|
|
@@ -917,6 +980,15 @@ namespace MediaBrowser.Api.Playback
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ if (state.VideoRequest != null)
|
|
|
+ {
|
|
|
+ var encodingOptions = ApiEntryPoint.Instance.GetEncodingOptions();
|
|
|
+ if (GetVideoEncoder(state).IndexOf("vaapi", StringComparison.OrdinalIgnoreCase) != -1)
|
|
|
+ {
|
|
|
+ arg = "-hwaccel vaapi -hwaccel_output_format vaapi -vaapi_device " + encodingOptions.VaapiDevice + " " + arg;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
return arg.Trim();
|
|
|
}
|
|
|
|
|
@@ -1042,14 +1114,14 @@ namespace MediaBrowser.Api.Playback
|
|
|
var commandLineLogMessage = process.StartInfo.FileName + " " + process.StartInfo.Arguments;
|
|
|
Logger.Info(commandLineLogMessage);
|
|
|
|
|
|
- var logFilePrefix = "transcode";
|
|
|
+ var logFilePrefix = "ffmpeg-transcode";
|
|
|
if (state.VideoRequest != null && string.Equals(state.OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase) && string.Equals(state.OutputAudioCodec, "copy", StringComparison.OrdinalIgnoreCase))
|
|
|
{
|
|
|
- logFilePrefix = "directstream";
|
|
|
+ logFilePrefix = "ffmpeg-directstream";
|
|
|
}
|
|
|
else if (state.VideoRequest != null && string.Equals(state.OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase))
|
|
|
{
|
|
|
- logFilePrefix = "remux";
|
|
|
+ logFilePrefix = "ffmpeg-remux";
|
|
|
}
|
|
|
|
|
|
var logFilePath = Path.Combine(ServerConfigurationManager.ApplicationPaths.LogDirectoryPath, logFilePrefix + "-" + Guid.NewGuid() + ".txt");
|
|
@@ -1080,7 +1152,7 @@ namespace MediaBrowser.Api.Playback
|
|
|
//process.BeginOutputReadLine();
|
|
|
|
|
|
// Important - don't await the log task or we won't be able to kill ffmpeg when the user stops playback
|
|
|
- Task.Run(() => StartStreamingLog(transcodingJob, state, process.StandardError.BaseStream, state.LogFileStream));
|
|
|
+ var task = Task.Run(() => StartStreamingLog(transcodingJob, state, process.StandardError.BaseStream, state.LogFileStream));
|
|
|
|
|
|
// Wait for the file to exist before proceeeding
|
|
|
while (!FileSystem.FileExists(state.WaitForPath ?? outputPath) && !transcodingJob.HasExited)
|
|
@@ -1099,28 +1171,30 @@ namespace MediaBrowser.Api.Playback
|
|
|
}
|
|
|
|
|
|
StartThrottler(state, transcodingJob);
|
|
|
+ ReportUsage(state);
|
|
|
|
|
|
return transcodingJob;
|
|
|
}
|
|
|
|
|
|
private void StartThrottler(StreamState state, TranscodingJob transcodingJob)
|
|
|
{
|
|
|
- if (EnableThrottling(state) && state.InputProtocol == MediaProtocol.File &&
|
|
|
- state.RunTimeTicks.HasValue &&
|
|
|
- state.VideoType == VideoType.VideoFile &&
|
|
|
- !string.Equals(state.OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase))
|
|
|
+ if (EnableThrottling(state) && !string.Equals(state.OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase))
|
|
|
{
|
|
|
- if (state.RunTimeTicks.Value >= TimeSpan.FromMinutes(5).Ticks && state.IsInputVideo)
|
|
|
- {
|
|
|
- transcodingJob.TranscodingThrottler = state.TranscodingThrottler = new TranscodingThrottler(transcodingJob, Logger, ServerConfigurationManager);
|
|
|
- state.TranscodingThrottler.Start();
|
|
|
- }
|
|
|
+ transcodingJob.TranscodingThrottler = state.TranscodingThrottler = new TranscodingThrottler(transcodingJob, Logger, ServerConfigurationManager);
|
|
|
+ state.TranscodingThrottler.Start();
|
|
|
}
|
|
|
}
|
|
|
|
|
|
protected virtual bool EnableThrottling(StreamState state)
|
|
|
{
|
|
|
- return true;
|
|
|
+ // do not use throttling with hardware encoders
|
|
|
+ return state.InputProtocol == MediaProtocol.File &&
|
|
|
+ state.RunTimeTicks.HasValue &&
|
|
|
+ state.RunTimeTicks.Value >= TimeSpan.FromMinutes(5).Ticks &&
|
|
|
+ state.IsInputVideo &&
|
|
|
+ state.VideoType == VideoType.VideoFile &&
|
|
|
+ !string.Equals(state.OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase) &&
|
|
|
+ string.Equals(GetVideoEncoder(state), "libx264", StringComparison.OrdinalIgnoreCase);
|
|
|
}
|
|
|
|
|
|
private async Task StartStreamingLog(TranscodingJob transcodingJob, StreamState state, Stream source, Stream target)
|
|
@@ -1158,6 +1232,7 @@ namespace MediaBrowser.Api.Playback
|
|
|
double? percent = null;
|
|
|
TimeSpan? transcodingPosition = null;
|
|
|
long? bytesTranscoded = null;
|
|
|
+ int? bitRate = null;
|
|
|
|
|
|
var parts = line.Split(' ');
|
|
|
|
|
@@ -1221,11 +1296,32 @@ namespace MediaBrowser.Api.Playback
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
+ else if (part.StartsWith("bitrate=", StringComparison.OrdinalIgnoreCase))
|
|
|
+ {
|
|
|
+ var rate = part.Split(new[] { '=' }, 2).Last();
|
|
|
+
|
|
|
+ int? scale = null;
|
|
|
+ if (rate.IndexOf("kbits/s", StringComparison.OrdinalIgnoreCase) != -1)
|
|
|
+ {
|
|
|
+ scale = 1024;
|
|
|
+ rate = rate.Replace("kbits/s", string.Empty, StringComparison.OrdinalIgnoreCase);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (scale.HasValue)
|
|
|
+ {
|
|
|
+ float val;
|
|
|
+
|
|
|
+ if (float.TryParse(rate, NumberStyles.Any, UsCulture, out val))
|
|
|
+ {
|
|
|
+ bitRate = (int)Math.Ceiling(val * scale.Value);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
if (framerate.HasValue || percent.HasValue)
|
|
|
{
|
|
|
- ApiEntryPoint.Instance.ReportTranscodingProgress(transcodingJob, state, transcodingPosition, framerate, percent, bytesTranscoded);
|
|
|
+ ApiEntryPoint.Instance.ReportTranscodingProgress(transcodingJob, state, transcodingPosition, framerate, percent, bytesTranscoded, bitRate);
|
|
|
}
|
|
|
}
|
|
|
|
|
@@ -1547,13 +1643,6 @@ namespace MediaBrowser.Api.Playback
|
|
|
}
|
|
|
}
|
|
|
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)
|
|
|
{
|
|
@@ -1564,17 +1653,21 @@ namespace MediaBrowser.Api.Playback
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
- else if (i == 27)
|
|
|
+ else if (i == 26)
|
|
|
{
|
|
|
request.TranscodingMaxAudioChannels = int.Parse(val, UsCulture);
|
|
|
}
|
|
|
- else if (i == 28)
|
|
|
+ else if (i == 27)
|
|
|
{
|
|
|
if (videoRequest != null)
|
|
|
{
|
|
|
videoRequest.EnableSubtitlesInManifest = string.Equals("true", val, StringComparison.OrdinalIgnoreCase);
|
|
|
}
|
|
|
}
|
|
|
+ else if (i == 28)
|
|
|
+ {
|
|
|
+ request.Tag = val;
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
|
|
@@ -1788,6 +1881,19 @@ namespace MediaBrowser.Api.Playback
|
|
|
{
|
|
|
state.OutputAudioCodec = "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.EnableAudioPlaybackTranscoding)
|
|
|
+ {
|
|
|
+ state.OutputAudioCodec = "copy";
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
private void AttachMediaSourceInfo(StreamState state,
|
|
@@ -2159,13 +2265,127 @@ namespace MediaBrowser.Api.Playback
|
|
|
if (state.VideoRequest != null)
|
|
|
{
|
|
|
state.VideoRequest.CopyTimestamps = transcodingProfile.CopyTimestamps;
|
|
|
- state.VideoRequest.ForceLiveStream = transcodingProfile.ForceLiveStream;
|
|
|
state.VideoRequest.EnableSubtitlesInManifest = transcodingProfile.EnableSubtitlesInManifest;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ private async void ReportUsage(StreamState state)
|
|
|
+ {
|
|
|
+ try
|
|
|
+ {
|
|
|
+ await ReportUsageInternal(state).ConfigureAwait(false);
|
|
|
+ }
|
|
|
+ catch
|
|
|
+ {
|
|
|
+
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private Task ReportUsageInternal(StreamState state)
|
|
|
+ {
|
|
|
+ if (!ServerConfigurationManager.Configuration.EnableAnonymousUsageReporting)
|
|
|
+ {
|
|
|
+ return Task.FromResult(true);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!MediaEncoder.IsDefaultEncoderPath)
|
|
|
+ {
|
|
|
+ return Task.FromResult(true);
|
|
|
+ }
|
|
|
+
|
|
|
+ var dict = new Dictionary<string, string>();
|
|
|
+
|
|
|
+ var outputAudio = GetAudioEncoder(state);
|
|
|
+ if (!string.IsNullOrWhiteSpace(outputAudio))
|
|
|
+ {
|
|
|
+ dict["outputAudio"] = outputAudio;
|
|
|
+ }
|
|
|
+
|
|
|
+ var outputVideo = GetVideoEncoder(state);
|
|
|
+ if (!string.IsNullOrWhiteSpace(outputVideo))
|
|
|
+ {
|
|
|
+ dict["outputVideo"] = outputVideo;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (ServerConfigurationManager.Configuration.CodecsUsed.Contains(outputAudio ?? string.Empty, StringComparer.OrdinalIgnoreCase) &&
|
|
|
+ ServerConfigurationManager.Configuration.CodecsUsed.Contains(outputVideo ?? string.Empty, StringComparer.OrdinalIgnoreCase))
|
|
|
+ {
|
|
|
+ return Task.FromResult(true);
|
|
|
+ }
|
|
|
+
|
|
|
+ dict["id"] = AppHost.SystemId;
|
|
|
+ dict["type"] = state.VideoRequest == null ? "Audio" : "Video";
|
|
|
+
|
|
|
+ var audioStream = state.AudioStream;
|
|
|
+ if (audioStream != null && !string.IsNullOrWhiteSpace(audioStream.Codec))
|
|
|
+ {
|
|
|
+ dict["inputAudio"] = audioStream.Codec;
|
|
|
+ }
|
|
|
+
|
|
|
+ var videoStream = state.VideoStream;
|
|
|
+ if (videoStream != null && !string.IsNullOrWhiteSpace(videoStream.Codec))
|
|
|
+ {
|
|
|
+ dict["inputVideo"] = videoStream.Codec;
|
|
|
+ }
|
|
|
+
|
|
|
+ var cert = GetType().Assembly.GetModules().First().GetSignerCertificate();
|
|
|
+ if (cert != null)
|
|
|
+ {
|
|
|
+ dict["assemblySig"] = cert.GetCertHashString();
|
|
|
+ dict["certSubject"] = cert.Subject ?? string.Empty;
|
|
|
+ dict["certIssuer"] = cert.Issuer ?? string.Empty;
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ return Task.FromResult(true);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (state.SupportedAudioCodecs.Count > 0)
|
|
|
+ {
|
|
|
+ dict["supportedAudioCodecs"] = string.Join(",", state.SupportedAudioCodecs.ToArray());
|
|
|
+ }
|
|
|
+
|
|
|
+ var auth = AuthorizationContext.GetAuthorizationInfo(Request);
|
|
|
+
|
|
|
+ dict["appName"] = auth.Client ?? string.Empty;
|
|
|
+ dict["appVersion"] = auth.Version ?? string.Empty;
|
|
|
+ dict["device"] = auth.Device ?? string.Empty;
|
|
|
+ dict["deviceId"] = auth.DeviceId ?? string.Empty;
|
|
|
+ dict["context"] = "streaming";
|
|
|
+
|
|
|
+ //Logger.Info(JsonSerializer.SerializeToString(dict));
|
|
|
+ if (!ServerConfigurationManager.Configuration.CodecsUsed.Contains(outputAudio ?? string.Empty, StringComparer.OrdinalIgnoreCase))
|
|
|
+ {
|
|
|
+ var list = ServerConfigurationManager.Configuration.CodecsUsed.ToList();
|
|
|
+ list.Add(outputAudio);
|
|
|
+ ServerConfigurationManager.Configuration.CodecsUsed = list.ToArray();
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!ServerConfigurationManager.Configuration.CodecsUsed.Contains(outputVideo ?? string.Empty, StringComparer.OrdinalIgnoreCase))
|
|
|
+ {
|
|
|
+ var list = ServerConfigurationManager.Configuration.CodecsUsed.ToList();
|
|
|
+ list.Add(outputVideo);
|
|
|
+ ServerConfigurationManager.Configuration.CodecsUsed = list.ToArray();
|
|
|
+ }
|
|
|
+
|
|
|
+ ServerConfigurationManager.SaveConfiguration();
|
|
|
+
|
|
|
+ //Logger.Info(JsonSerializer.SerializeToString(dict));
|
|
|
+ var options = new HttpRequestOptions()
|
|
|
+ {
|
|
|
+ Url = "https://mb3admin.com/admin/service/transcoding/report",
|
|
|
+ CancellationToken = CancellationToken.None,
|
|
|
+ LogRequest = false,
|
|
|
+ LogErrors = false
|
|
|
+ };
|
|
|
+ options.RequestContent = JsonSerializer.SerializeToString(dict);
|
|
|
+ options.RequestContentType = "application/json";
|
|
|
+
|
|
|
+ return HttpClient.Post(options);
|
|
|
+ }
|
|
|
+
|
|
|
/// <summary>
|
|
|
/// Adds the dlna headers.
|
|
|
/// </summary>
|