Merge pull request #1159 from Bond-009/streamjob

Trying to make sense of the streaming code
This commit is contained in:
Anthony Lavado 2019-06-04 00:20:43 -04:00 committed by GitHub
commit f631b2ecdc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 172 additions and 235 deletions

View File

@ -415,7 +415,7 @@ namespace MediaBrowser.Api
public void OnTranscodeEndRequest(TranscodingJob job) public void OnTranscodeEndRequest(TranscodingJob job)
{ {
job.ActiveRequestCount--; job.ActiveRequestCount--;
//Logger.LogDebug("OnTranscodeEndRequest job.ActiveRequestCount={0}", job.ActiveRequestCount); Logger.LogDebug("OnTranscodeEndRequest job.ActiveRequestCount={0}", job.ActiveRequestCount);
if (job.ActiveRequestCount <= 0) if (job.ActiveRequestCount <= 0)
{ {
PingTimer(job, false); PingTimer(job, false);
@ -428,7 +428,7 @@ namespace MediaBrowser.Api
throw new ArgumentNullException(nameof(playSessionId)); throw new ArgumentNullException(nameof(playSessionId));
} }
//Logger.LogDebug("PingTranscodingJob PlaySessionId={0} isUsedPaused: {1}", playSessionId, isUserPaused); Logger.LogDebug("PingTranscodingJob PlaySessionId={0} isUsedPaused: {1}", playSessionId, isUserPaused);
List<TranscodingJob> jobs; List<TranscodingJob> jobs;
@ -443,7 +443,7 @@ namespace MediaBrowser.Api
{ {
if (isUserPaused.HasValue) if (isUserPaused.HasValue)
{ {
//Logger.LogDebug("Setting job.IsUserPaused to {0}. jobId: {1}", isUserPaused, job.Id); Logger.LogDebug("Setting job.IsUserPaused to {0}. jobId: {1}", isUserPaused, job.Id);
job.IsUserPaused = isUserPaused.Value; job.IsUserPaused = isUserPaused.Value;
} }
PingTimer(job, true); PingTimer(job, true);
@ -601,7 +601,6 @@ namespace MediaBrowser.Api
{ {
Logger.LogInformation("Stopping ffmpeg process with q command for {Path}", job.Path); Logger.LogInformation("Stopping ffmpeg process with q command for {Path}", job.Path);
//process.Kill();
process.StandardInput.WriteLine("q"); process.StandardInput.WriteLine("q");
// Need to wait because killing is asynchronous // Need to wait because killing is asynchronous
@ -701,7 +700,7 @@ namespace MediaBrowser.Api
{ {
try try
{ {
//Logger.LogDebug("Deleting HLS file {0}", file); Logger.LogDebug("Deleting HLS file {0}", file);
_fileSystem.DeleteFile(file); _fileSystem.DeleteFile(file);
} }
catch (FileNotFoundException) catch (FileNotFoundException)
@ -840,12 +839,12 @@ namespace MediaBrowser.Api
{ {
if (KillTimer == null) if (KillTimer == null)
{ {
//Logger.LogDebug("Starting kill timer at {0}ms. JobId {1} PlaySessionId {2}", intervalMs, Id, PlaySessionId); Logger.LogDebug("Starting kill timer at {0}ms. JobId {1} PlaySessionId {2}", intervalMs, Id, PlaySessionId);
KillTimer = new Timer(new TimerCallback(callback), this, intervalMs, Timeout.Infinite); KillTimer = new Timer(new TimerCallback(callback), this, intervalMs, Timeout.Infinite);
} }
else else
{ {
//Logger.LogDebug("Changing kill timer to {0}ms. JobId {1} PlaySessionId {2}", intervalMs, Id, PlaySessionId); Logger.LogDebug("Changing kill timer to {0}ms. JobId {1} PlaySessionId {2}", intervalMs, Id, PlaySessionId);
KillTimer.Change(intervalMs, Timeout.Infinite); KillTimer.Change(intervalMs, Timeout.Infinite);
} }
} }
@ -864,7 +863,7 @@ namespace MediaBrowser.Api
{ {
var intervalMs = PingTimeout; var intervalMs = PingTimeout;
//Logger.LogDebug("Changing kill timer to {0}ms. JobId {1} PlaySessionId {2}", intervalMs, Id, PlaySessionId); Logger.LogDebug("Changing kill timer to {0}ms. JobId {1} PlaySessionId {2}", intervalMs, Id, PlaySessionId);
KillTimer.Change(intervalMs, Timeout.Infinite); KillTimer.Change(intervalMs, Timeout.Infinite);
} }
} }

View File

@ -8,7 +8,6 @@ using System.Text;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Extensions;
using MediaBrowser.Controller;
using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Devices; using MediaBrowser.Controller.Devices;
using MediaBrowser.Controller.Dlna; using MediaBrowser.Controller.Dlna;
@ -16,7 +15,6 @@ using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.MediaEncoding; using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Net;
using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Diagnostics;
using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Dlna;
using MediaBrowser.Model.Dto; using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities; using MediaBrowser.Model.Entities;
@ -32,6 +30,8 @@ namespace MediaBrowser.Api.Playback
/// </summary> /// </summary>
public abstract class BaseStreamingService : BaseApiService public abstract class BaseStreamingService : BaseApiService
{ {
protected virtual bool EnableOutputInSubFolder => false;
/// <summary> /// <summary>
/// Gets or sets the application paths. /// Gets or sets the application paths.
/// </summary> /// </summary>
@ -65,15 +65,25 @@ namespace MediaBrowser.Api.Playback
protected IFileSystem FileSystem { get; private set; } protected IFileSystem FileSystem { get; private set; }
protected IDlnaManager DlnaManager { get; private set; } protected IDlnaManager DlnaManager { get; private set; }
protected IDeviceManager DeviceManager { get; private set; } protected IDeviceManager DeviceManager { get; private set; }
protected ISubtitleEncoder SubtitleEncoder { get; private set; } protected ISubtitleEncoder SubtitleEncoder { get; private set; }
protected IMediaSourceManager MediaSourceManager { get; private set; } protected IMediaSourceManager MediaSourceManager { get; private set; }
protected IJsonSerializer JsonSerializer { get; private set; } protected IJsonSerializer JsonSerializer { get; private set; }
protected IAuthorizationContext AuthorizationContext { get; private set; } protected IAuthorizationContext AuthorizationContext { get; private set; }
protected EncodingHelper EncodingHelper { get; set; } protected EncodingHelper EncodingHelper { get; set; }
/// <summary>
/// Gets the type of the transcoding job.
/// </summary>
/// <value>The type of the transcoding job.</value>
protected abstract TranscodingJobType TranscodingJobType { get; }
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="BaseStreamingService" /> class. /// Initializes a new instance of the <see cref="BaseStreamingService" /> class.
/// </summary> /// </summary>
@ -112,12 +122,6 @@ namespace MediaBrowser.Api.Playback
/// </summary> /// </summary>
protected abstract string GetCommandLineArguments(string outputPath, EncodingOptions encodingOptions, StreamState state, bool isEncoding); protected abstract string GetCommandLineArguments(string outputPath, EncodingOptions encodingOptions, StreamState state, bool isEncoding);
/// <summary>
/// Gets the type of the transcoding job.
/// </summary>
/// <value>The type of the transcoding job.</value>
protected abstract TranscodingJobType TranscodingJobType { get; }
/// <summary> /// <summary>
/// Gets the output file extension. /// Gets the output file extension.
/// </summary> /// </summary>
@ -133,31 +137,24 @@ namespace MediaBrowser.Api.Playback
/// </summary> /// </summary>
private string GetOutputFilePath(StreamState state, EncodingOptions encodingOptions, string outputFileExtension) private string GetOutputFilePath(StreamState state, EncodingOptions encodingOptions, string outputFileExtension)
{ {
var folder = ServerConfigurationManager.ApplicationPaths.TranscodingTempPath;
var data = GetCommandLineArguments("dummy\\dummy", encodingOptions, state, false); var data = GetCommandLineArguments("dummy\\dummy", encodingOptions, state, false);
data += "-" + (state.Request.DeviceId ?? string.Empty); data += "-" + (state.Request.DeviceId ?? string.Empty)
data += "-" + (state.Request.PlaySessionId ?? string.Empty); + "-" + (state.Request.PlaySessionId ?? string.Empty);
var dataHash = data.GetMD5().ToString("N"); var filename = data.GetMD5().ToString("N");
var ext = outputFileExtension.ToLowerInvariant();
var folder = ServerConfigurationManager.ApplicationPaths.TranscodingTempPath;
if (EnableOutputInSubFolder) if (EnableOutputInSubFolder)
{ {
return Path.Combine(folder, dataHash, dataHash + (outputFileExtension ?? string.Empty).ToLowerInvariant()); return Path.Combine(folder, filename, filename + ext);
} }
return Path.Combine(folder, dataHash + (outputFileExtension ?? string.Empty).ToLowerInvariant()); return Path.Combine(folder, filename + ext);
} }
protected virtual bool EnableOutputInSubFolder => false; protected virtual string GetDefaultH264Preset() => "superfast";
protected readonly CultureInfo UsCulture = new CultureInfo("en-US");
protected virtual string GetDefaultH264Preset()
{
return "superfast";
}
private async Task AcquireResources(StreamState state, CancellationTokenSource cancellationTokenSource) private async Task AcquireResources(StreamState state, CancellationTokenSource cancellationTokenSource)
{ {
@ -171,7 +168,6 @@ namespace MediaBrowser.Api.Playback
var liveStreamResponse = await MediaSourceManager.OpenLiveStream(new LiveStreamRequest var liveStreamResponse = await MediaSourceManager.OpenLiveStream(new LiveStreamRequest
{ {
OpenToken = state.MediaSource.OpenToken OpenToken = state.MediaSource.OpenToken
}, cancellationTokenSource.Token).ConfigureAwait(false); }, cancellationTokenSource.Token).ConfigureAwait(false);
EncodingHelper.AttachMediaSourceInfo(state, liveStreamResponse.MediaSource, state.RequestedUrl); EncodingHelper.AttachMediaSourceInfo(state, liveStreamResponse.MediaSource, state.RequestedUrl);
@ -209,22 +205,16 @@ namespace MediaBrowser.Api.Playback
if (state.VideoRequest != null && !string.Equals(state.OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase)) if (state.VideoRequest != null && !string.Equals(state.OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase))
{ {
var auth = AuthorizationContext.GetAuthorizationInfo(Request); var auth = AuthorizationContext.GetAuthorizationInfo(Request);
if (auth.User != null) if (auth.User != null && !auth.User.Policy.EnableVideoPlaybackTranscoding)
{ {
if (!auth.User.Policy.EnableVideoPlaybackTranscoding) ApiEntryPoint.Instance.OnTranscodeFailedToStart(outputPath, TranscodingJobType, state);
{
ApiEntryPoint.Instance.OnTranscodeFailedToStart(outputPath, TranscodingJobType, state);
throw new ArgumentException("User does not have access to video transcoding"); throw new ArgumentException("User does not have access to video transcoding");
}
} }
} }
var encodingOptions = ApiEntryPoint.Instance.GetEncodingOptions(); var encodingOptions = ApiEntryPoint.Instance.GetEncodingOptions();
var transcodingId = Guid.NewGuid().ToString("N");
var commandLineArgs = GetCommandLineArguments(outputPath, encodingOptions, state, true);
var process = new Process() var process = new Process()
{ {
StartInfo = new ProcessStartInfo() StartInfo = new ProcessStartInfo()
@ -239,7 +229,7 @@ namespace MediaBrowser.Api.Playback
RedirectStandardInput = true, RedirectStandardInput = true,
FileName = MediaEncoder.EncoderPath, FileName = MediaEncoder.EncoderPath,
Arguments = commandLineArgs, Arguments = GetCommandLineArguments(outputPath, encodingOptions, state, true),
WorkingDirectory = string.IsNullOrWhiteSpace(workingDirectory) ? null : workingDirectory, WorkingDirectory = string.IsNullOrWhiteSpace(workingDirectory) ? null : workingDirectory,
ErrorDialog = false ErrorDialog = false
@ -250,7 +240,7 @@ namespace MediaBrowser.Api.Playback
var transcodingJob = ApiEntryPoint.Instance.OnTranscodeBeginning(outputPath, var transcodingJob = ApiEntryPoint.Instance.OnTranscodeBeginning(outputPath,
state.Request.PlaySessionId, state.Request.PlaySessionId,
state.MediaSource.LiveStreamId, state.MediaSource.LiveStreamId,
transcodingId, Guid.NewGuid().ToString("N"),
TranscodingJobType, TranscodingJobType,
process, process,
state.Request.DeviceId, state.Request.DeviceId,
@ -261,27 +251,26 @@ namespace MediaBrowser.Api.Playback
Logger.LogInformation(commandLineLogMessage); Logger.LogInformation(commandLineLogMessage);
var logFilePrefix = "ffmpeg-transcode"; var logFilePrefix = "ffmpeg-transcode";
if (state.VideoRequest != null) if (state.VideoRequest != null
&& string.Equals(state.OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase))
{ {
if (string.Equals(state.OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase) if (string.Equals(state.OutputAudioCodec, "copy", StringComparison.OrdinalIgnoreCase))
&& string.Equals(state.OutputAudioCodec, "copy", StringComparison.OrdinalIgnoreCase))
{ {
logFilePrefix = "ffmpeg-directstream"; logFilePrefix = "ffmpeg-directstream";
} }
else if (string.Equals(state.OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase)) else
{ {
logFilePrefix = "ffmpeg-remux"; logFilePrefix = "ffmpeg-remux";
} }
} }
var logFilePath = Path.Combine(ServerConfigurationManager.ApplicationPaths.LogDirectoryPath, logFilePrefix + "-" + Guid.NewGuid() + ".txt"); var logFilePath = Path.Combine(ServerConfigurationManager.ApplicationPaths.LogDirectoryPath, logFilePrefix + "-" + Guid.NewGuid() + ".txt");
Directory.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.
state.LogFileStream = FileSystem.GetFileStream(logFilePath, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read, true); Stream logStream = FileSystem.GetFileStream(logFilePath, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read, true);
var commandLineLogMessageBytes = Encoding.UTF8.GetBytes(Request.AbsoluteUri + Environment.NewLine + Environment.NewLine + JsonSerializer.SerializeToString(state.MediaSource) + Environment.NewLine + Environment.NewLine + commandLineLogMessage + Environment.NewLine + Environment.NewLine); var commandLineLogMessageBytes = Encoding.UTF8.GetBytes(Request.AbsoluteUri + Environment.NewLine + Environment.NewLine + JsonSerializer.SerializeToString(state.MediaSource) + Environment.NewLine + Environment.NewLine + commandLineLogMessage + Environment.NewLine + Environment.NewLine);
await state.LogFileStream.WriteAsync(commandLineLogMessageBytes, 0, commandLineLogMessageBytes.Length, cancellationTokenSource.Token).ConfigureAwait(false); await logStream.WriteAsync(commandLineLogMessageBytes, 0, commandLineLogMessageBytes.Length, cancellationTokenSource.Token).ConfigureAwait(false);
process.Exited += (sender, args) => OnFfMpegProcessExited(process, transcodingJob, state); process.Exited += (sender, args) => OnFfMpegProcessExited(process, transcodingJob, state);
@ -298,13 +287,10 @@ namespace MediaBrowser.Api.Playback
throw; throw;
} }
// MUST read both stdout and stderr asynchronously or a deadlock may occurr
//process.BeginOutputReadLine();
state.TranscodingJob = transcodingJob; state.TranscodingJob = transcodingJob;
// 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
new JobLogger(Logger).StartStreamingLog(state, process.StandardError.BaseStream, state.LogFileStream); _ = new JobLogger(Logger).StartStreamingLog(state, process.StandardError.BaseStream, logStream);
// Wait for the file to exist before proceeeding // Wait for the file to exist before proceeeding
while (!File.Exists(state.WaitForPath ?? outputPath) && !transcodingJob.HasExited) while (!File.Exists(state.WaitForPath ?? outputPath) && !transcodingJob.HasExited)
@ -368,25 +354,16 @@ namespace MediaBrowser.Api.Playback
Logger.LogDebug("Disposing stream resources"); Logger.LogDebug("Disposing stream resources");
state.Dispose(); state.Dispose();
try if (process.ExitCode == 0)
{ {
Logger.LogInformation("FFMpeg exited with code {0}", process.ExitCode); Logger.LogInformation("FFMpeg exited with code 0");
} }
catch else
{ {
Logger.LogError("FFMpeg exited with an error."); Logger.LogError("FFMpeg exited with code {0}", process.ExitCode);
} }
// This causes on exited to be called twice: process.Dispose();
//try
//{
// // Dispose the process
// process.Dispose();
//}
//catch (Exception ex)
//{
// Logger.LogError(ex, "Error disposing ffmpeg.");
//}
} }
/// <summary> /// <summary>
@ -439,55 +416,55 @@ namespace MediaBrowser.Api.Playback
{ {
if (videoRequest != null) if (videoRequest != null)
{ {
videoRequest.AudioStreamIndex = int.Parse(val, UsCulture); videoRequest.AudioStreamIndex = int.Parse(val, CultureInfo.InvariantCulture);
} }
} }
else if (i == 7) else if (i == 7)
{ {
if (videoRequest != null) if (videoRequest != null)
{ {
videoRequest.SubtitleStreamIndex = int.Parse(val, UsCulture); videoRequest.SubtitleStreamIndex = int.Parse(val, CultureInfo.InvariantCulture);
} }
} }
else if (i == 8) else if (i == 8)
{ {
if (videoRequest != null) if (videoRequest != null)
{ {
videoRequest.VideoBitRate = int.Parse(val, UsCulture); videoRequest.VideoBitRate = int.Parse(val, CultureInfo.InvariantCulture);
} }
} }
else if (i == 9) else if (i == 9)
{ {
request.AudioBitRate = int.Parse(val, UsCulture); request.AudioBitRate = int.Parse(val, CultureInfo.InvariantCulture);
} }
else if (i == 10) else if (i == 10)
{ {
request.MaxAudioChannels = int.Parse(val, UsCulture); request.MaxAudioChannels = int.Parse(val, CultureInfo.InvariantCulture);
} }
else if (i == 11) else if (i == 11)
{ {
if (videoRequest != null) if (videoRequest != null)
{ {
videoRequest.MaxFramerate = float.Parse(val, UsCulture); videoRequest.MaxFramerate = float.Parse(val, CultureInfo.InvariantCulture);
} }
} }
else if (i == 12) else if (i == 12)
{ {
if (videoRequest != null) if (videoRequest != null)
{ {
videoRequest.MaxWidth = int.Parse(val, UsCulture); videoRequest.MaxWidth = int.Parse(val, CultureInfo.InvariantCulture);
} }
} }
else if (i == 13) else if (i == 13)
{ {
if (videoRequest != null) if (videoRequest != null)
{ {
videoRequest.MaxHeight = int.Parse(val, UsCulture); videoRequest.MaxHeight = int.Parse(val, CultureInfo.InvariantCulture);
} }
} }
else if (i == 14) else if (i == 14)
{ {
request.StartTimeTicks = long.Parse(val, UsCulture); request.StartTimeTicks = long.Parse(val, CultureInfo.InvariantCulture);
} }
else if (i == 15) else if (i == 15)
{ {
@ -500,14 +477,14 @@ namespace MediaBrowser.Api.Playback
{ {
if (videoRequest != null) if (videoRequest != null)
{ {
videoRequest.MaxRefFrames = int.Parse(val, UsCulture); videoRequest.MaxRefFrames = int.Parse(val, CultureInfo.InvariantCulture);
} }
} }
else if (i == 17) else if (i == 17)
{ {
if (videoRequest != null) if (videoRequest != null)
{ {
videoRequest.MaxVideoBitDepth = int.Parse(val, UsCulture); videoRequest.MaxVideoBitDepth = int.Parse(val, CultureInfo.InvariantCulture);
} }
} }
else if (i == 18) else if (i == 18)
@ -556,7 +533,7 @@ namespace MediaBrowser.Api.Playback
} }
else if (i == 26) else if (i == 26)
{ {
request.TranscodingMaxAudioChannels = int.Parse(val, UsCulture); request.TranscodingMaxAudioChannels = int.Parse(val, CultureInfo.InvariantCulture);
} }
else if (i == 27) else if (i == 27)
{ {
@ -643,16 +620,25 @@ namespace MediaBrowser.Api.Playback
return null; return null;
} }
if (value.IndexOf("npt=", StringComparison.OrdinalIgnoreCase) != 0) const string Npt = "npt=";
if (!value.StartsWith(Npt, StringComparison.OrdinalIgnoreCase))
{ {
throw new ArgumentException("Invalid timeseek header"); throw new ArgumentException("Invalid timeseek header");
} }
value = value.Substring(4).Split(new[] { '-' }, 2)[0]; int index = value.IndexOf('-');
if (index == -1)
{
value = value.Substring(Npt.Length);
}
else
{
value = value.Substring(Npt.Length, index);
}
if (value.IndexOf(':') == -1) if (value.IndexOf(':') == -1)
{ {
// Parses npt times in the format of '417.33' // Parses npt times in the format of '417.33'
if (double.TryParse(value, NumberStyles.Any, UsCulture, out var seconds)) if (double.TryParse(value, NumberStyles.Any, CultureInfo.InvariantCulture, out var seconds))
{ {
return TimeSpan.FromSeconds(seconds).Ticks; return TimeSpan.FromSeconds(seconds).Ticks;
} }
@ -667,7 +653,7 @@ namespace MediaBrowser.Api.Playback
foreach (var time in tokens) foreach (var time in tokens)
{ {
if (double.TryParse(time, NumberStyles.Any, UsCulture, out var digit)) if (double.TryParse(time, NumberStyles.Any, CultureInfo.InvariantCulture, out var digit))
{ {
secondsSum += digit * timeFactor; secondsSum += digit * timeFactor;
} }
@ -707,7 +693,7 @@ namespace MediaBrowser.Api.Playback
var enableDlnaHeaders = !string.IsNullOrWhiteSpace(request.Params) /*|| var enableDlnaHeaders = !string.IsNullOrWhiteSpace(request.Params) /*||
string.Equals(Request.Headers.Get("GetContentFeatures.DLNA.ORG"), "1", StringComparison.OrdinalIgnoreCase)*/; string.Equals(Request.Headers.Get("GetContentFeatures.DLNA.ORG"), "1", StringComparison.OrdinalIgnoreCase)*/;
var state = new StreamState(MediaSourceManager, Logger, TranscodingJobType) var state = new StreamState(MediaSourceManager, TranscodingJobType)
{ {
Request = request, Request = request,
RequestedUrl = url, RequestedUrl = url,
@ -728,13 +714,10 @@ namespace MediaBrowser.Api.Playback
// state.SegmentLength = 6; // state.SegmentLength = 6;
//} //}
if (state.VideoRequest != null) if (state.VideoRequest != null && !string.IsNullOrWhiteSpace(state.VideoRequest.VideoCodec))
{ {
if (!string.IsNullOrWhiteSpace(state.VideoRequest.VideoCodec)) state.SupportedVideoCodecs = state.VideoRequest.VideoCodec.Split(',').Where(i => !string.IsNullOrWhiteSpace(i)).ToArray();
{ state.VideoRequest.VideoCodec = state.SupportedVideoCodecs.FirstOrDefault();
state.SupportedVideoCodecs = state.VideoRequest.VideoCodec.Split(',').Where(i => !string.IsNullOrWhiteSpace(i)).ToArray();
state.VideoRequest.VideoCodec = state.SupportedVideoCodecs.FirstOrDefault();
}
} }
if (!string.IsNullOrWhiteSpace(request.AudioCodec)) if (!string.IsNullOrWhiteSpace(request.AudioCodec))
@ -779,12 +762,12 @@ namespace MediaBrowser.Api.Playback
var mediaSources = (await MediaSourceManager.GetPlayackMediaSources(LibraryManager.GetItemById(request.Id), null, false, false, cancellationToken).ConfigureAwait(false)).ToList(); var mediaSources = (await MediaSourceManager.GetPlayackMediaSources(LibraryManager.GetItemById(request.Id), null, false, false, cancellationToken).ConfigureAwait(false)).ToList();
mediaSource = string.IsNullOrEmpty(request.MediaSourceId) mediaSource = string.IsNullOrEmpty(request.MediaSourceId)
? mediaSources.First() ? mediaSources[0]
: mediaSources.FirstOrDefault(i => string.Equals(i.Id, request.MediaSourceId)); : mediaSources.Find(i => string.Equals(i.Id, request.MediaSourceId));
if (mediaSource == null && request.MediaSourceId.Equals(request.Id)) if (mediaSource == null && request.MediaSourceId.Equals(request.Id))
{ {
mediaSource = mediaSources.First(); mediaSource = mediaSources[0];
} }
} }
} }
@ -834,11 +817,11 @@ namespace MediaBrowser.Api.Playback
if (state.OutputVideoBitrate.HasValue && !string.Equals(state.OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase)) if (state.OutputVideoBitrate.HasValue && !string.Equals(state.OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase))
{ {
var resolution = ResolutionNormalizer.Normalize( var resolution = ResolutionNormalizer.Normalize(
state.VideoStream == null ? (int?)null : state.VideoStream.BitRate, state.VideoStream?.BitRate,
state.VideoStream == null ? (int?)null : state.VideoStream.Width, state.VideoStream?.Width,
state.VideoStream == null ? (int?)null : state.VideoStream.Height, state.VideoStream?.Height,
state.OutputVideoBitrate.Value, state.OutputVideoBitrate.Value,
state.VideoStream == null ? null : state.VideoStream.Codec, state.VideoStream?.Codec,
state.OutputVideoCodec, state.OutputVideoCodec,
videoRequest.MaxWidth, videoRequest.MaxWidth,
videoRequest.MaxHeight); videoRequest.MaxHeight);
@ -846,17 +829,13 @@ namespace MediaBrowser.Api.Playback
videoRequest.MaxWidth = resolution.MaxWidth; videoRequest.MaxWidth = resolution.MaxWidth;
videoRequest.MaxHeight = resolution.MaxHeight; videoRequest.MaxHeight = resolution.MaxHeight;
} }
}
ApplyDeviceProfileSettings(state); ApplyDeviceProfileSettings(state);
}
else
{
ApplyDeviceProfileSettings(state);
}
var ext = string.IsNullOrWhiteSpace(state.OutputContainer) var ext = string.IsNullOrWhiteSpace(state.OutputContainer)
? GetOutputFileExtension(state) ? GetOutputFileExtension(state)
: ("." + state.OutputContainer); : ('.' + state.OutputContainer);
var encodingOptions = ApiEntryPoint.Instance.GetEncodingOptions(); var encodingOptions = ApiEntryPoint.Instance.GetEncodingOptions();
@ -970,18 +949,18 @@ namespace MediaBrowser.Api.Playback
responseHeaders["transferMode.dlna.org"] = string.IsNullOrEmpty(transferMode) ? "Streaming" : transferMode; responseHeaders["transferMode.dlna.org"] = string.IsNullOrEmpty(transferMode) ? "Streaming" : transferMode;
responseHeaders["realTimeInfo.dlna.org"] = "DLNA.ORG_TLAG=*"; responseHeaders["realTimeInfo.dlna.org"] = "DLNA.ORG_TLAG=*";
if (string.Equals(GetHeader("getMediaInfo.sec"), "1", StringComparison.OrdinalIgnoreCase)) if (state.RunTimeTicks.HasValue)
{ {
if (state.RunTimeTicks.HasValue) if (string.Equals(GetHeader("getMediaInfo.sec"), "1", StringComparison.OrdinalIgnoreCase))
{ {
var ms = TimeSpan.FromTicks(state.RunTimeTicks.Value).TotalMilliseconds; var ms = TimeSpan.FromTicks(state.RunTimeTicks.Value).TotalMilliseconds;
responseHeaders["MediaInfo.sec"] = string.Format("SEC_Duration={0};", Convert.ToInt32(ms).ToString(CultureInfo.InvariantCulture)); responseHeaders["MediaInfo.sec"] = string.Format("SEC_Duration={0};", Convert.ToInt32(ms).ToString(CultureInfo.InvariantCulture));
} }
}
if (state.RunTimeTicks.HasValue && !isStaticallyStreamed && profile != null) if (!isStaticallyStreamed && profile != null)
{ {
AddTimeSeekResponseHeaders(state, responseHeaders); AddTimeSeekResponseHeaders(state, responseHeaders);
}
} }
if (profile == null) if (profile == null)
@ -1046,8 +1025,8 @@ namespace MediaBrowser.Api.Playback
private void AddTimeSeekResponseHeaders(StreamState state, IDictionary<string, string> responseHeaders) private void AddTimeSeekResponseHeaders(StreamState state, IDictionary<string, string> responseHeaders)
{ {
var runtimeSeconds = TimeSpan.FromTicks(state.RunTimeTicks.Value).TotalSeconds.ToString(UsCulture); var runtimeSeconds = TimeSpan.FromTicks(state.RunTimeTicks.Value).TotalSeconds.ToString(CultureInfo.InvariantCulture);
var startSeconds = TimeSpan.FromTicks(state.Request.StartTimeTicks ?? 0).TotalSeconds.ToString(UsCulture); var startSeconds = TimeSpan.FromTicks(state.Request.StartTimeTicks ?? 0).TotalSeconds.ToString(CultureInfo.InvariantCulture);
responseHeaders["TimeSeekRange.dlna.org"] = string.Format("npt={0}-{1}/{1}", startSeconds, runtimeSeconds); responseHeaders["TimeSeekRange.dlna.org"] = string.Format("npt={0}-{1}/{1}", startSeconds, runtimeSeconds);
responseHeaders["X-AvailableSeekRange"] = string.Format("1 npt={0}-{1}", startSeconds, runtimeSeconds); responseHeaders["X-AvailableSeekRange"] = string.Format("1 npt={0}-{1}", startSeconds, runtimeSeconds);

View File

@ -1,17 +1,14 @@
using System; using System;
using System.IO;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.MediaEncoding; using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Dlna;
using MediaBrowser.Model.Net;
using Microsoft.Extensions.Logging;
namespace MediaBrowser.Api.Playback namespace MediaBrowser.Api.Playback
{ {
public class StreamState : EncodingJobInfo, IDisposable public class StreamState : EncodingJobInfo, IDisposable
{ {
private readonly ILogger _logger;
private readonly IMediaSourceManager _mediaSourceManager; private readonly IMediaSourceManager _mediaSourceManager;
private bool _disposed = false;
public string RequestedUrl { get; set; } public string RequestedUrl { get; set; }
@ -30,11 +27,6 @@ namespace MediaBrowser.Api.Playback
public VideoStreamRequest VideoRequest => Request as VideoStreamRequest; public VideoStreamRequest VideoRequest => Request as VideoStreamRequest;
/// <summary>
/// Gets or sets the log file stream.
/// </summary>
/// <value>The log file stream.</value>
public Stream LogFileStream { get; set; }
public IDirectStreamProvider DirectStreamProvider { get; set; } public IDirectStreamProvider DirectStreamProvider { get; set; }
public string WaitForPath { get; set; } public string WaitForPath { get; set; }
@ -72,6 +64,7 @@ namespace MediaBrowser.Api.Playback
{ {
return 3; return 3;
} }
return 6; return 6;
} }
@ -94,82 +87,57 @@ namespace MediaBrowser.Api.Playback
public string UserAgent { get; set; } public string UserAgent { get; set; }
public StreamState(IMediaSourceManager mediaSourceManager, ILogger logger, TranscodingJobType transcodingType)
: base(transcodingType)
{
_mediaSourceManager = mediaSourceManager;
_logger = logger;
}
public bool EstimateContentLength { get; set; } public bool EstimateContentLength { get; set; }
public TranscodeSeekInfo TranscodeSeekInfo { get; set; } public TranscodeSeekInfo TranscodeSeekInfo { get; set; }
public bool EnableDlnaHeaders { get; set; } public bool EnableDlnaHeaders { get; set; }
public override void Dispose()
{
DisposeTranscodingThrottler();
DisposeLogStream();
DisposeLiveStream();
TranscodingJob = null;
}
private void DisposeTranscodingThrottler()
{
if (TranscodingThrottler != null)
{
try
{
TranscodingThrottler.Dispose();
}
catch (Exception ex)
{
_logger.LogError(ex, "Error disposing TranscodingThrottler");
}
TranscodingThrottler = null;
}
}
private void DisposeLogStream()
{
if (LogFileStream != null)
{
try
{
LogFileStream.Dispose();
}
catch (Exception ex)
{
_logger.LogError(ex, "Error disposing log stream");
}
LogFileStream = null;
}
}
private async void DisposeLiveStream()
{
if (MediaSource.RequiresClosing && string.IsNullOrWhiteSpace(Request.LiveStreamId) && !string.IsNullOrWhiteSpace(MediaSource.LiveStreamId))
{
try
{
await _mediaSourceManager.CloseLiveStream(MediaSource.LiveStreamId).ConfigureAwait(false);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error closing media source");
}
}
}
public DeviceProfile DeviceProfile { get; set; } public DeviceProfile DeviceProfile { get; set; }
public TranscodingJob TranscodingJob; public TranscodingJob TranscodingJob { get; set; }
public StreamState(IMediaSourceManager mediaSourceManager, TranscodingJobType transcodingType)
: base(transcodingType)
{
_mediaSourceManager = mediaSourceManager;
}
public override void ReportTranscodingProgress(TimeSpan? transcodingPosition, float framerate, double? percentComplete, long bytesTranscoded, int? bitRate) public override void ReportTranscodingProgress(TimeSpan? transcodingPosition, float framerate, double? percentComplete, long bytesTranscoded, int? bitRate)
{ {
ApiEntryPoint.Instance.ReportTranscodingProgress(TranscodingJob, this, transcodingPosition, framerate, percentComplete, bytesTranscoded, bitRate); ApiEntryPoint.Instance.ReportTranscodingProgress(TranscodingJob, this, transcodingPosition, framerate, percentComplete, bytesTranscoded, bitRate);
} }
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (_disposed)
{
return;
}
if (disposing)
{
// REVIEW: Is this the right place for this?
if (MediaSource.RequiresClosing
&& string.IsNullOrWhiteSpace(Request.LiveStreamId)
&& !string.IsNullOrWhiteSpace(MediaSource.LiveStreamId))
{
_mediaSourceManager.CloseLiveStream(MediaSource.LiveStreamId).GetAwaiter().GetResult();
}
TranscodingThrottler?.Dispose();
}
TranscodingThrottler = null;
TranscodingJob = null;
_disposed = true;
}
} }
} }

View File

@ -374,14 +374,14 @@ namespace MediaBrowser.Controller.MediaEncoding
{ {
get get
{ {
if (BaseRequest.Static || string.Equals(OutputAudioCodec, "copy", StringComparison.OrdinalIgnoreCase)) if (BaseRequest.Static
|| string.Equals(OutputAudioCodec, "copy", StringComparison.OrdinalIgnoreCase))
{ {
if (AudioStream != null) if (AudioStream != null)
{ {
return AudioStream.SampleRate; return AudioStream.SampleRate;
} }
} }
else if (BaseRequest.AudioSampleRate.HasValue) else if (BaseRequest.AudioSampleRate.HasValue)
{ {
// Don't exceed what the encoder supports // Don't exceed what the encoder supports
@ -397,7 +397,8 @@ namespace MediaBrowser.Controller.MediaEncoding
{ {
get get
{ {
if (BaseRequest.Static || string.Equals(OutputAudioCodec, "copy", StringComparison.OrdinalIgnoreCase)) if (BaseRequest.Static
|| string.Equals(OutputAudioCodec, "copy", StringComparison.OrdinalIgnoreCase))
{ {
if (AudioStream != null) if (AudioStream != null)
{ {
@ -405,13 +406,6 @@ namespace MediaBrowser.Controller.MediaEncoding
} }
} }
//else if (BaseRequest.AudioSampleRate.HasValue)
//{
// // Don't exceed what the encoder supports
// // Seeing issues of attempting to encode to 88200
// return Math.Min(44100, BaseRequest.AudioSampleRate.Value);
//}
return null; return null;
} }
} }
@ -446,7 +440,8 @@ namespace MediaBrowser.Controller.MediaEncoding
{ {
get get
{ {
if (BaseRequest.Static || string.Equals(OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase)) if (BaseRequest.Static
|| string.Equals(OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase))
{ {
return VideoStream?.BitDepth; return VideoStream?.BitDepth;
} }
@ -463,7 +458,8 @@ namespace MediaBrowser.Controller.MediaEncoding
{ {
get get
{ {
if (BaseRequest.Static || string.Equals(OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase)) if (BaseRequest.Static
|| string.Equals(OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase))
{ {
return VideoStream?.RefFrames; return VideoStream?.RefFrames;
} }
@ -479,7 +475,8 @@ namespace MediaBrowser.Controller.MediaEncoding
{ {
get get
{ {
if (BaseRequest.Static || string.Equals(OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase)) if (BaseRequest.Static
|| string.Equals(OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase))
{ {
return VideoStream == null ? null : (VideoStream.AverageFrameRate ?? VideoStream.RealFrameRate); return VideoStream == null ? null : (VideoStream.AverageFrameRate ?? VideoStream.RealFrameRate);
} }
@ -545,7 +542,8 @@ namespace MediaBrowser.Controller.MediaEncoding
{ {
get get
{ {
if (BaseRequest.Static || string.Equals(OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase)) if (BaseRequest.Static
|| string.Equals(OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase))
{ {
return VideoStream?.CodecTag; return VideoStream?.CodecTag;
} }
@ -558,7 +556,8 @@ namespace MediaBrowser.Controller.MediaEncoding
{ {
get get
{ {
if (BaseRequest.Static || string.Equals(OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase)) if (BaseRequest.Static
|| string.Equals(OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase))
{ {
return VideoStream?.IsAnamorphic; return VideoStream?.IsAnamorphic;
} }
@ -571,14 +570,12 @@ namespace MediaBrowser.Controller.MediaEncoding
{ {
get get
{ {
var codec = OutputVideoCodec; if (string.Equals(OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase))
if (string.Equals(codec, "copy", StringComparison.OrdinalIgnoreCase))
{ {
return VideoStream?.Codec; return VideoStream?.Codec;
} }
return codec; return OutputVideoCodec;
} }
} }
@ -586,14 +583,12 @@ namespace MediaBrowser.Controller.MediaEncoding
{ {
get get
{ {
var codec = OutputAudioCodec; if (string.Equals(OutputAudioCodec, "copy", StringComparison.OrdinalIgnoreCase))
if (string.Equals(codec, "copy", StringComparison.OrdinalIgnoreCase))
{ {
return AudioStream?.Codec; return AudioStream?.Codec;
} }
return codec; return OutputAudioCodec;
} }
} }
@ -601,7 +596,8 @@ namespace MediaBrowser.Controller.MediaEncoding
{ {
get get
{ {
if (BaseRequest.Static || string.Equals(OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase)) if (BaseRequest.Static
|| string.Equals(OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase))
{ {
return VideoStream?.IsInterlaced; return VideoStream?.IsInterlaced;
} }
@ -636,6 +632,7 @@ namespace MediaBrowser.Controller.MediaEncoding
{ {
return GetMediaStreamCount(MediaStreamType.Video, int.MaxValue); return GetMediaStreamCount(MediaStreamType.Video, int.MaxValue);
} }
return GetMediaStreamCount(MediaStreamType.Video, 1); return GetMediaStreamCount(MediaStreamType.Video, 1);
} }
} }
@ -648,17 +645,12 @@ namespace MediaBrowser.Controller.MediaEncoding
{ {
return GetMediaStreamCount(MediaStreamType.Audio, int.MaxValue); return GetMediaStreamCount(MediaStreamType.Audio, int.MaxValue);
} }
return GetMediaStreamCount(MediaStreamType.Audio, 1); return GetMediaStreamCount(MediaStreamType.Audio, 1);
} }
} }
public int HlsListSize public int HlsListSize => 0;
{
get
{
return 0;
}
}
private int? GetMediaStreamCount(MediaStreamType type, int limit) private int? GetMediaStreamCount(MediaStreamType type, int limit)
{ {
@ -677,10 +669,6 @@ namespace MediaBrowser.Controller.MediaEncoding
{ {
Progress.Report(percentComplete.Value); Progress.Report(percentComplete.Value);
} }
public virtual void Dispose()
{
}
} }
/// <summary> /// <summary>

View File

@ -3,6 +3,7 @@ using System.Globalization;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using System.Threading.Tasks;
using MediaBrowser.Model.Extensions; using MediaBrowser.Model.Extensions;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
@ -18,10 +19,11 @@ namespace MediaBrowser.Controller.MediaEncoding
_logger = logger; _logger = logger;
} }
public async void StartStreamingLog(EncodingJobInfo state, Stream source, Stream target) public async Task StartStreamingLog(EncodingJobInfo state, Stream source, Stream target)
{ {
try try
{ {
using (target)
using (var reader = new StreamReader(source)) using (var reader = new StreamReader(source))
{ {
while (!reader.EndOfStream && reader.BaseStream.CanRead) while (!reader.EndOfStream && reader.BaseStream.CanRead)
@ -97,8 +99,7 @@ namespace MediaBrowser.Controller.MediaEncoding
{ {
var currentMs = startMs + val.TotalMilliseconds; var currentMs = startMs + val.TotalMilliseconds;
var percentVal = currentMs / totalMs; percent = 100.0 * currentMs / totalMs;
percent = 100 * percentVal;
transcodingPosition = val; transcodingPosition = val;
} }

View File

@ -13,7 +13,8 @@ namespace MediaBrowser.Model.Dlna
_profile = profile; _profile = profile;
} }
public string BuildImageHeader(string container, public string BuildImageHeader(
string container,
int? width, int? width,
int? height, int? height,
bool isDirectStream, bool isDirectStream,
@ -28,8 +29,7 @@ namespace MediaBrowser.Model.Dlna
DlnaFlags.InteractiveTransferMode | DlnaFlags.InteractiveTransferMode |
DlnaFlags.DlnaV15; DlnaFlags.DlnaV15;
string dlnaflags = string.Format(";DLNA.ORG_FLAGS={0}", string dlnaflags = string.Format(";DLNA.ORG_FLAGS={0}", DlnaMaps.FlagsToString(flagValue));
DlnaMaps.FlagsToString(flagValue));
ResponseProfile mediaProfile = _profile.GetImageMediaProfile(container, ResponseProfile mediaProfile = _profile.GetImageMediaProfile(container,
width, width,
@ -37,7 +37,7 @@ namespace MediaBrowser.Model.Dlna
if (string.IsNullOrEmpty(orgPn)) if (string.IsNullOrEmpty(orgPn))
{ {
orgPn = mediaProfile == null ? null : mediaProfile.OrgPn; orgPn = mediaProfile?.OrgPn;
} }
if (string.IsNullOrEmpty(orgPn)) if (string.IsNullOrEmpty(orgPn))
@ -50,7 +50,8 @@ namespace MediaBrowser.Model.Dlna
return (contentFeatures + orgOp + orgCi + dlnaflags).Trim(';'); return (contentFeatures + orgOp + orgCi + dlnaflags).Trim(';');
} }
public string BuildAudioHeader(string container, public string BuildAudioHeader(
string container,
string audioCodec, string audioCodec,
int? audioBitrate, int? audioBitrate,
int? audioSampleRate, int? audioSampleRate,
@ -102,7 +103,8 @@ namespace MediaBrowser.Model.Dlna
return (contentFeatures + orgOp + orgCi + dlnaflags).Trim(';'); return (contentFeatures + orgOp + orgCi + dlnaflags).Trim(';');
} }
public List<string> BuildVideoHeader(string container, public List<string> BuildVideoHeader(
string container,
string videoCodec, string videoCodec,
string audioCodec, string audioCodec,
int? width, int? width,
@ -206,7 +208,7 @@ namespace MediaBrowser.Model.Dlna
return contentFeatureList; return contentFeatureList;
} }
private string GetImageOrgPnValue(string container, int? width, int? height) private static string GetImageOrgPnValue(string container, int? width, int? height)
{ {
MediaFormatProfile? format = new MediaFormatProfileResolver() MediaFormatProfile? format = new MediaFormatProfileResolver()
.ResolveImageFormat(container, .ResolveImageFormat(container,
@ -216,7 +218,7 @@ namespace MediaBrowser.Model.Dlna
return format.HasValue ? format.Value.ToString() : null; return format.HasValue ? format.Value.ToString() : null;
} }
private string GetAudioOrgPnValue(string container, int? audioBitrate, int? audioSampleRate, int? audioChannels) private static string GetAudioOrgPnValue(string container, int? audioBitrate, int? audioSampleRate, int? audioChannels)
{ {
MediaFormatProfile? format = new MediaFormatProfileResolver() MediaFormatProfile? format = new MediaFormatProfileResolver()
.ResolveAudioFormat(container, .ResolveAudioFormat(container,
@ -227,7 +229,7 @@ namespace MediaBrowser.Model.Dlna
return format.HasValue ? format.Value.ToString() : null; return format.HasValue ? format.Value.ToString() : null;
} }
private string[] GetVideoOrgPnValue(string container, string videoCodec, string audioCodec, int? width, int? height, TransportStreamTimestamp timestamp) private static string[] GetVideoOrgPnValue(string container, string videoCodec, string audioCodec, int? width, int? height, TransportStreamTimestamp timestamp)
{ {
return new MediaFormatProfileResolver().ResolveVideoFormat(container, videoCodec, audioCodec, width, height, timestamp); return new MediaFormatProfileResolver().ResolveVideoFormat(container, videoCodec, audioCodec, width, height, timestamp);
} }