mirror of
https://github.com/jellyfin/jellyfin.git
synced 2025-07-09 03:04:24 -04:00
display additional transcoding info in dashboard
This commit is contained in:
parent
7049ad66f4
commit
f7cd7182d5
@ -1,5 +1,7 @@
|
|||||||
using MediaBrowser.Controller;
|
using MediaBrowser.Api.Playback;
|
||||||
|
using MediaBrowser.Controller;
|
||||||
using MediaBrowser.Controller.Plugins;
|
using MediaBrowser.Controller.Plugins;
|
||||||
|
using MediaBrowser.Controller.Session;
|
||||||
using MediaBrowser.Model.Logging;
|
using MediaBrowser.Model.Logging;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
@ -9,6 +11,7 @@ using System.IO;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using MediaBrowser.Model.Session;
|
||||||
|
|
||||||
namespace MediaBrowser.Api
|
namespace MediaBrowser.Api
|
||||||
{
|
{
|
||||||
@ -33,15 +36,18 @@ namespace MediaBrowser.Api
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private readonly IServerApplicationPaths _appPaths;
|
private readonly IServerApplicationPaths _appPaths;
|
||||||
|
|
||||||
|
private readonly ISessionManager _sessionManager;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="ApiEntryPoint" /> class.
|
/// Initializes a new instance of the <see cref="ApiEntryPoint" /> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="logger">The logger.</param>
|
/// <param name="logger">The logger.</param>
|
||||||
/// <param name="appPaths">The application paths.</param>
|
/// <param name="appPaths">The application paths.</param>
|
||||||
public ApiEntryPoint(ILogger logger, IServerApplicationPaths appPaths)
|
public ApiEntryPoint(ILogger logger, IServerApplicationPaths appPaths, ISessionManager sessionManager)
|
||||||
{
|
{
|
||||||
Logger = logger;
|
Logger = logger;
|
||||||
_appPaths = appPaths;
|
_appPaths = appPaths;
|
||||||
|
_sessionManager = sessionManager;
|
||||||
|
|
||||||
Instance = this;
|
Instance = this;
|
||||||
}
|
}
|
||||||
@ -115,10 +121,16 @@ namespace MediaBrowser.Api
|
|||||||
/// <param name="type">The type.</param>
|
/// <param name="type">The type.</param>
|
||||||
/// <param name="process">The process.</param>
|
/// <param name="process">The process.</param>
|
||||||
/// <param name="startTimeTicks">The start time ticks.</param>
|
/// <param name="startTimeTicks">The start time ticks.</param>
|
||||||
/// <param name="sourcePath">The source path.</param>
|
|
||||||
/// <param name="deviceId">The device id.</param>
|
/// <param name="deviceId">The device id.</param>
|
||||||
|
/// <param name="state">The state.</param>
|
||||||
/// <param name="cancellationTokenSource">The cancellation token source.</param>
|
/// <param name="cancellationTokenSource">The cancellation token source.</param>
|
||||||
public void OnTranscodeBeginning(string path, TranscodingJobType type, Process process, long? startTimeTicks, string sourcePath, string deviceId, CancellationTokenSource cancellationTokenSource)
|
public void OnTranscodeBeginning(string path,
|
||||||
|
TranscodingJobType type,
|
||||||
|
Process process,
|
||||||
|
long? startTimeTicks,
|
||||||
|
string deviceId,
|
||||||
|
StreamState state,
|
||||||
|
CancellationTokenSource cancellationTokenSource)
|
||||||
{
|
{
|
||||||
lock (_activeTranscodingJobs)
|
lock (_activeTranscodingJobs)
|
||||||
{
|
{
|
||||||
@ -129,10 +141,43 @@ namespace MediaBrowser.Api
|
|||||||
Process = process,
|
Process = process,
|
||||||
ActiveRequestCount = 1,
|
ActiveRequestCount = 1,
|
||||||
StartTimeTicks = startTimeTicks,
|
StartTimeTicks = startTimeTicks,
|
||||||
SourcePath = sourcePath,
|
|
||||||
DeviceId = deviceId,
|
DeviceId = deviceId,
|
||||||
CancellationTokenSource = cancellationTokenSource
|
CancellationTokenSource = cancellationTokenSource
|
||||||
});
|
});
|
||||||
|
|
||||||
|
ReportTranscodingProgress(state, null, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ReportTranscodingProgress(StreamState state, float? framerate, double? percentComplete)
|
||||||
|
{
|
||||||
|
var deviceId = state.Request.DeviceId;
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(deviceId))
|
||||||
|
{
|
||||||
|
var audioCodec = state.Request.AudioCodec;
|
||||||
|
var videoCodec = state.VideoRequest == null ? null : state.VideoRequest.VideoCodec;
|
||||||
|
|
||||||
|
if (string.Equals(state.OutputAudioCodec, "copy", StringComparison.OrdinalIgnoreCase) ||
|
||||||
|
string.IsNullOrEmpty(audioCodec))
|
||||||
|
{
|
||||||
|
audioCodec = state.OutputAudioCodec;
|
||||||
|
}
|
||||||
|
if (string.Equals(state.OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase) ||
|
||||||
|
string.IsNullOrEmpty(videoCodec))
|
||||||
|
{
|
||||||
|
videoCodec = state.OutputVideoCodec;
|
||||||
|
}
|
||||||
|
|
||||||
|
_sessionManager.ReportTranscodingInfo(deviceId, new TranscodingInfo
|
||||||
|
{
|
||||||
|
Bitrate = state.TotalOutputBitrate,
|
||||||
|
AudioCodec = audioCodec,
|
||||||
|
VideoCodec = videoCodec,
|
||||||
|
Container = state.OutputContainer,
|
||||||
|
Framerate = framerate,
|
||||||
|
CompletionPercentage = percentComplete
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -144,7 +189,8 @@ namespace MediaBrowser.Api
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="path">The path.</param>
|
/// <param name="path">The path.</param>
|
||||||
/// <param name="type">The type.</param>
|
/// <param name="type">The type.</param>
|
||||||
public void OnTranscodeFailedToStart(string path, TranscodingJobType type)
|
/// <param name="state">The state.</param>
|
||||||
|
public void OnTranscodeFailedToStart(string path, TranscodingJobType type, StreamState state)
|
||||||
{
|
{
|
||||||
lock (_activeTranscodingJobs)
|
lock (_activeTranscodingJobs)
|
||||||
{
|
{
|
||||||
@ -152,6 +198,11 @@ namespace MediaBrowser.Api
|
|||||||
|
|
||||||
_activeTranscodingJobs.Remove(job);
|
_activeTranscodingJobs.Remove(job);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(state.Request.DeviceId))
|
||||||
|
{
|
||||||
|
_sessionManager.ClearTranscodingInfo(state.Request.DeviceId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -437,7 +488,6 @@ namespace MediaBrowser.Api
|
|||||||
public Timer KillTimer { get; set; }
|
public Timer KillTimer { get; set; }
|
||||||
|
|
||||||
public long? StartTimeTicks { get; set; }
|
public long? StartTimeTicks { get; set; }
|
||||||
public string SourcePath { get; set; }
|
|
||||||
public string DeviceId { get; set; }
|
public string DeviceId { get; set; }
|
||||||
|
|
||||||
public CancellationTokenSource CancellationTokenSource { get; set; }
|
public CancellationTokenSource CancellationTokenSource { get; set; }
|
||||||
|
@ -126,7 +126,7 @@ namespace MediaBrowser.Api.Playback
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="state">The state.</param>
|
/// <param name="state">The state.</param>
|
||||||
/// <returns>System.String.</returns>
|
/// <returns>System.String.</returns>
|
||||||
protected string GetOutputFilePath(StreamState state)
|
private string GetOutputFilePath(StreamState state)
|
||||||
{
|
{
|
||||||
var folder = ServerConfigurationManager.ApplicationPaths.TranscodingTempPath;
|
var folder = ServerConfigurationManager.ApplicationPaths.TranscodingTempPath;
|
||||||
|
|
||||||
@ -726,12 +726,13 @@ namespace MediaBrowser.Api.Playback
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="request">The request.</param>
|
/// <param name="request">The request.</param>
|
||||||
/// <param name="audioStream">The audio stream.</param>
|
/// <param name="audioStream">The audio stream.</param>
|
||||||
|
/// <param name="outputAudioCodec">The output audio codec.</param>
|
||||||
/// <returns>System.Nullable{System.Int32}.</returns>
|
/// <returns>System.Nullable{System.Int32}.</returns>
|
||||||
private int? GetNumAudioChannelsParam(StreamRequest request, MediaStream audioStream)
|
private int? GetNumAudioChannelsParam(StreamRequest request, MediaStream audioStream, string outputAudioCodec)
|
||||||
{
|
{
|
||||||
if (audioStream != null)
|
if (audioStream != null)
|
||||||
{
|
{
|
||||||
var codec = request.AudioCodec ?? string.Empty;
|
var codec = outputAudioCodec ?? string.Empty;
|
||||||
|
|
||||||
if (audioStream.Channels > 2 && codec.IndexOf("wma", StringComparison.OrdinalIgnoreCase) != -1)
|
if (audioStream.Channels > 2 && codec.IndexOf("wma", StringComparison.OrdinalIgnoreCase) != -1)
|
||||||
{
|
{
|
||||||
@ -769,7 +770,7 @@ namespace MediaBrowser.Api.Playback
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="request">The request.</param>
|
/// <param name="request">The request.</param>
|
||||||
/// <returns>System.String.</returns>
|
/// <returns>System.String.</returns>
|
||||||
protected string GetAudioCodec(StreamRequest request)
|
private string GetAudioCodec(StreamRequest request)
|
||||||
{
|
{
|
||||||
var codec = request.AudioCodec;
|
var codec = request.AudioCodec;
|
||||||
|
|
||||||
@ -798,7 +799,7 @@ namespace MediaBrowser.Api.Playback
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="request">The request.</param>
|
/// <param name="request">The request.</param>
|
||||||
/// <returns>System.String.</returns>
|
/// <returns>System.String.</returns>
|
||||||
protected string GetVideoCodec(VideoStreamRequest request)
|
private string GetVideoCodec(VideoStreamRequest request)
|
||||||
{
|
{
|
||||||
var codec = request.VideoCodec;
|
var codec = request.VideoCodec;
|
||||||
|
|
||||||
@ -866,7 +867,7 @@ namespace MediaBrowser.Api.Playback
|
|||||||
|
|
||||||
Directory.CreateDirectory(Path.GetDirectoryName(outputPath));
|
Directory.CreateDirectory(Path.GetDirectoryName(outputPath));
|
||||||
|
|
||||||
if (state.IsInputVideo && state.VideoType == VideoType.Iso && state.IsoType.HasValue && IsoManager.CanMount(state.MediaPath))
|
if (state.VideoType == VideoType.Iso && state.IsoType.HasValue && IsoManager.CanMount(state.MediaPath))
|
||||||
{
|
{
|
||||||
state.IsoMount = await IsoManager.Mount(state.MediaPath, cancellationTokenSource.Token).ConfigureAwait(false);
|
state.IsoMount = await IsoManager.Mount(state.MediaPath, cancellationTokenSource.Token).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
@ -900,7 +901,13 @@ namespace MediaBrowser.Api.Playback
|
|||||||
EnableRaisingEvents = true
|
EnableRaisingEvents = true
|
||||||
};
|
};
|
||||||
|
|
||||||
ApiEntryPoint.Instance.OnTranscodeBeginning(outputPath, TranscodingJobType, process, state.Request.StartTimeTicks, state.MediaPath, state.Request.DeviceId, cancellationTokenSource);
|
ApiEntryPoint.Instance.OnTranscodeBeginning(outputPath,
|
||||||
|
TranscodingJobType,
|
||||||
|
process,
|
||||||
|
state.Request.StartTimeTicks,
|
||||||
|
state.Request.DeviceId,
|
||||||
|
state,
|
||||||
|
cancellationTokenSource);
|
||||||
|
|
||||||
var commandLineLogMessage = process.StartInfo.FileName + " " + process.StartInfo.Arguments;
|
var commandLineLogMessage = process.StartInfo.FileName + " " + process.StartInfo.Arguments;
|
||||||
Logger.Info(commandLineLogMessage);
|
Logger.Info(commandLineLogMessage);
|
||||||
@ -924,7 +931,7 @@ namespace MediaBrowser.Api.Playback
|
|||||||
{
|
{
|
||||||
Logger.ErrorException("Error starting ffmpeg", ex);
|
Logger.ErrorException("Error starting ffmpeg", ex);
|
||||||
|
|
||||||
ApiEntryPoint.Instance.OnTranscodeFailedToStart(outputPath, TranscodingJobType);
|
ApiEntryPoint.Instance.OnTranscodeFailedToStart(outputPath, TranscodingJobType, state);
|
||||||
|
|
||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
@ -932,10 +939,8 @@ 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();
|
||||||
|
|
||||||
#pragma warning disable 4014
|
|
||||||
// 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
|
||||||
process.StandardError.BaseStream.CopyToAsync(state.LogFileStream);
|
StartStreamingLog(state, process.StandardError.BaseStream, state.LogFileStream);
|
||||||
#pragma warning restore 4014
|
|
||||||
|
|
||||||
// Wait for the file to exist before proceeeding
|
// Wait for the file to exist before proceeeding
|
||||||
while (!File.Exists(outputPath))
|
while (!File.Exists(outputPath))
|
||||||
@ -956,6 +961,82 @@ namespace MediaBrowser.Api.Playback
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async void StartStreamingLog(StreamState state, Stream source, Stream target)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using (var reader = new StreamReader(source))
|
||||||
|
{
|
||||||
|
while (!reader.EndOfStream)
|
||||||
|
{
|
||||||
|
var line = await reader.ReadLineAsync().ConfigureAwait(false);
|
||||||
|
|
||||||
|
ParseLogLine(line, state);
|
||||||
|
|
||||||
|
var bytes = Encoding.UTF8.GetBytes(Environment.NewLine + line);
|
||||||
|
|
||||||
|
await target.WriteAsync(bytes, 0, bytes.Length).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logger.ErrorException("Error reading ffmpeg log", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ParseLogLine(string line, StreamState state)
|
||||||
|
{
|
||||||
|
float? framerate = null;
|
||||||
|
double? percent = null;
|
||||||
|
|
||||||
|
var parts = line.Split(' ');
|
||||||
|
|
||||||
|
var totalMs = state.RunTimeTicks.HasValue
|
||||||
|
? TimeSpan.FromTicks(state.RunTimeTicks.Value).TotalMilliseconds
|
||||||
|
: 0;
|
||||||
|
|
||||||
|
var startMs = state.Request.StartTimeTicks.HasValue
|
||||||
|
? TimeSpan.FromTicks(state.Request.StartTimeTicks.Value).TotalMilliseconds
|
||||||
|
: 0;
|
||||||
|
|
||||||
|
for (var i = 0; i < parts.Length; i++)
|
||||||
|
{
|
||||||
|
var part = parts[i];
|
||||||
|
|
||||||
|
if (string.Equals(part, "fps=", StringComparison.OrdinalIgnoreCase) &&
|
||||||
|
(i + 1 < parts.Length))
|
||||||
|
{
|
||||||
|
var rate = parts[i + 1];
|
||||||
|
float val;
|
||||||
|
|
||||||
|
if (float.TryParse(rate, NumberStyles.Any, UsCulture, out val))
|
||||||
|
{
|
||||||
|
framerate = val;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (state.RunTimeTicks.HasValue &&
|
||||||
|
part.StartsWith("time=", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
var time = part.Split(new[] { '=' }, 2).Last();
|
||||||
|
TimeSpan val;
|
||||||
|
|
||||||
|
if (TimeSpan.TryParse(time, UsCulture, out val))
|
||||||
|
{
|
||||||
|
var currentMs = startMs + val.TotalMilliseconds;
|
||||||
|
|
||||||
|
var percentVal = currentMs / totalMs;
|
||||||
|
percent = 100 * percentVal;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (framerate.HasValue || percent.HasValue)
|
||||||
|
{
|
||||||
|
ApiEntryPoint.Instance.ReportTranscodingProgress(state, framerate, percent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private int? GetVideoBitrateParamValue(VideoStreamRequest request, MediaStream videoStream)
|
private int? GetVideoBitrateParamValue(VideoStreamRequest request, MediaStream videoStream)
|
||||||
{
|
{
|
||||||
var bitrate = request.VideoBitRate;
|
var bitrate = request.VideoBitRate;
|
||||||
@ -1500,23 +1581,27 @@ namespace MediaBrowser.Api.Playback
|
|||||||
|
|
||||||
state.OutputAudioBitrate = GetAudioBitrateParam(state.Request, state.AudioStream);
|
state.OutputAudioBitrate = GetAudioBitrateParam(state.Request, state.AudioStream);
|
||||||
state.OutputAudioSampleRate = request.AudioSampleRate;
|
state.OutputAudioSampleRate = request.AudioSampleRate;
|
||||||
state.OutputAudioChannels = GetNumAudioChannelsParam(state.Request, state.AudioStream);
|
|
||||||
|
state.OutputAudioCodec = GetAudioCodec(state.Request);
|
||||||
|
|
||||||
if (videoRequest != null)
|
if (videoRequest != null)
|
||||||
{
|
{
|
||||||
|
state.OutputVideoCodec = GetVideoCodec(videoRequest);
|
||||||
state.OutputVideoBitrate = GetVideoBitrateParamValue(state.VideoRequest, state.VideoStream);
|
state.OutputVideoBitrate = GetVideoBitrateParamValue(state.VideoRequest, state.VideoStream);
|
||||||
|
|
||||||
if (state.VideoStream != null && CanStreamCopyVideo(videoRequest, state.VideoStream))
|
if (state.VideoStream != null && CanStreamCopyVideo(videoRequest, state.VideoStream))
|
||||||
{
|
{
|
||||||
videoRequest.VideoCodec = "copy";
|
state.OutputVideoCodec = "copy";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (state.AudioStream != null && CanStreamCopyAudio(request, state.AudioStream, state.SupportedAudioCodecs))
|
if (state.AudioStream != null && CanStreamCopyAudio(request, state.AudioStream, state.SupportedAudioCodecs))
|
||||||
{
|
{
|
||||||
request.AudioCodec = "copy";
|
state.OutputAudioCodec = "copy";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
state.OutputFilePath = GetOutputFilePath(state);
|
||||||
|
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1729,14 +1814,14 @@ namespace MediaBrowser.Api.Playback
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var audioCodec = state.Request.AudioCodec;
|
var audioCodec = state.OutputAudioCodec;
|
||||||
|
|
||||||
if (string.Equals(audioCodec, "copy", StringComparison.OrdinalIgnoreCase) && state.AudioStream != null)
|
if (string.Equals(audioCodec, "copy", StringComparison.OrdinalIgnoreCase) && state.AudioStream != null)
|
||||||
{
|
{
|
||||||
audioCodec = state.AudioStream.Codec;
|
audioCodec = state.AudioStream.Codec;
|
||||||
}
|
}
|
||||||
|
|
||||||
var videoCodec = state.VideoRequest == null ? null : state.VideoRequest.VideoCodec;
|
var videoCodec = state.OutputVideoCodec;
|
||||||
|
|
||||||
if (string.Equals(videoCodec, "copy", StringComparison.OrdinalIgnoreCase) && state.VideoStream != null)
|
if (string.Equals(videoCodec, "copy", StringComparison.OrdinalIgnoreCase) && state.VideoStream != null)
|
||||||
{
|
{
|
||||||
@ -1807,7 +1892,12 @@ namespace MediaBrowser.Api.Playback
|
|||||||
profile = DlnaManager.GetDefaultProfile();
|
profile = DlnaManager.GetDefaultProfile();
|
||||||
}
|
}
|
||||||
|
|
||||||
var audioCodec = state.Request.AudioCodec;
|
var audioCodec = state.OutputAudioCodec;
|
||||||
|
|
||||||
|
if (string.Equals(audioCodec, "copy", StringComparison.OrdinalIgnoreCase) && state.AudioStream != null)
|
||||||
|
{
|
||||||
|
audioCodec = state.AudioStream.Codec;
|
||||||
|
}
|
||||||
|
|
||||||
if (state.VideoRequest == null)
|
if (state.VideoRequest == null)
|
||||||
{
|
{
|
||||||
@ -1825,12 +1915,7 @@ namespace MediaBrowser.Api.Playback
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (string.Equals(audioCodec, "copy", StringComparison.OrdinalIgnoreCase) && state.AudioStream != null)
|
var videoCodec = state.OutputVideoCodec;
|
||||||
{
|
|
||||||
audioCodec = state.AudioStream.Codec;
|
|
||||||
}
|
|
||||||
|
|
||||||
var videoCodec = state.VideoRequest == null ? null : state.VideoRequest.VideoCodec;
|
|
||||||
|
|
||||||
if (string.Equals(videoCodec, "copy", StringComparison.OrdinalIgnoreCase) && state.VideoStream != null)
|
if (string.Equals(videoCodec, "copy", StringComparison.OrdinalIgnoreCase) && state.VideoStream != null)
|
||||||
{
|
{
|
||||||
|
@ -86,7 +86,7 @@ namespace MediaBrowser.Api.Playback.Hls
|
|||||||
|
|
||||||
var state = GetState(request, cancellationTokenSource.Token).Result;
|
var state = GetState(request, cancellationTokenSource.Token).Result;
|
||||||
|
|
||||||
var playlist = GetOutputFilePath(state);
|
var playlist = state.OutputFilePath;
|
||||||
|
|
||||||
if (File.Exists(playlist))
|
if (File.Exists(playlist))
|
||||||
{
|
{
|
||||||
@ -307,7 +307,7 @@ namespace MediaBrowser.Api.Playback.Hls
|
|||||||
|
|
||||||
if (hlsVideoRequest != null)
|
if (hlsVideoRequest != null)
|
||||||
{
|
{
|
||||||
if (hlsVideoRequest.AppendBaselineStream && state.IsInputVideo)
|
if (hlsVideoRequest.AppendBaselineStream)
|
||||||
{
|
{
|
||||||
var lowBitratePath = Path.Combine(Path.GetDirectoryName(outputPath), Path.GetFileNameWithoutExtension(outputPath) + "-low.m3u8");
|
var lowBitratePath = Path.Combine(Path.GetDirectoryName(outputPath), Path.GetFileNameWithoutExtension(outputPath) + "-low.m3u8");
|
||||||
|
|
||||||
|
@ -89,7 +89,7 @@ namespace MediaBrowser.Api.Playback.Hls
|
|||||||
|
|
||||||
var state = await GetState(request, CancellationToken.None).ConfigureAwait(false);
|
var state = await GetState(request, CancellationToken.None).ConfigureAwait(false);
|
||||||
|
|
||||||
var playlistPath = Path.ChangeExtension(GetOutputFilePath(state), ".m3u8");
|
var playlistPath = Path.ChangeExtension(state.OutputFilePath, ".m3u8");
|
||||||
|
|
||||||
var path = GetSegmentPath(playlistPath, index);
|
var path = GetSegmentPath(playlistPath, index);
|
||||||
|
|
||||||
@ -231,7 +231,7 @@ namespace MediaBrowser.Api.Playback.Hls
|
|||||||
|
|
||||||
protected override string GetAudioArguments(StreamState state)
|
protected override string GetAudioArguments(StreamState state)
|
||||||
{
|
{
|
||||||
var codec = GetAudioCodec(state.Request);
|
var codec = state.OutputAudioCodec;
|
||||||
|
|
||||||
if (codec.Equals("copy", StringComparison.OrdinalIgnoreCase))
|
if (codec.Equals("copy", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
@ -266,7 +266,7 @@ namespace MediaBrowser.Api.Playback.Hls
|
|||||||
|
|
||||||
protected override string GetVideoArguments(StreamState state, bool performSubtitleConversion)
|
protected override string GetVideoArguments(StreamState state, bool performSubtitleConversion)
|
||||||
{
|
{
|
||||||
var codec = GetVideoCodec(state.VideoRequest);
|
var codec = state.OutputVideoCodec;
|
||||||
|
|
||||||
// See if we can save come cpu cycles by avoiding encoding
|
// See if we can save come cpu cycles by avoiding encoding
|
||||||
if (codec.Equals("copy", StringComparison.OrdinalIgnoreCase))
|
if (codec.Equals("copy", StringComparison.OrdinalIgnoreCase))
|
||||||
|
@ -118,7 +118,7 @@ namespace MediaBrowser.Api.Playback.Hls
|
|||||||
/// <returns>System.String.</returns>
|
/// <returns>System.String.</returns>
|
||||||
protected override string GetAudioArguments(StreamState state)
|
protected override string GetAudioArguments(StreamState state)
|
||||||
{
|
{
|
||||||
var codec = GetAudioCodec(state.Request);
|
var codec = state.OutputAudioCodec;
|
||||||
|
|
||||||
if (codec.Equals("copy", StringComparison.OrdinalIgnoreCase))
|
if (codec.Equals("copy", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
@ -160,7 +160,7 @@ namespace MediaBrowser.Api.Playback.Hls
|
|||||||
protected override string GetVideoArguments(StreamState state,
|
protected override string GetVideoArguments(StreamState state,
|
||||||
bool performSubtitleConversion)
|
bool performSubtitleConversion)
|
||||||
{
|
{
|
||||||
var codec = GetVideoCodec(state.VideoRequest);
|
var codec = state.OutputVideoCodec;
|
||||||
|
|
||||||
// See if we can save come cpu cycles by avoiding encoding
|
// See if we can save come cpu cycles by avoiding encoding
|
||||||
if (codec.Equals("copy", StringComparison.OrdinalIgnoreCase))
|
if (codec.Equals("copy", StringComparison.OrdinalIgnoreCase))
|
||||||
|
@ -78,8 +78,6 @@ namespace MediaBrowser.Api.Playback.Progressive
|
|||||||
/// <exception cref="System.InvalidOperationException">Only aac and mp3 audio codecs are supported.</exception>
|
/// <exception cref="System.InvalidOperationException">Only aac and mp3 audio codecs are supported.</exception>
|
||||||
protected override string GetCommandLineArguments(string outputPath, StreamState state, bool performSubtitleConversions)
|
protected override string GetCommandLineArguments(string outputPath, StreamState state, bool performSubtitleConversions)
|
||||||
{
|
{
|
||||||
var request = state.Request;
|
|
||||||
|
|
||||||
var audioTranscodeParams = new List<string>();
|
var audioTranscodeParams = new List<string>();
|
||||||
|
|
||||||
var bitrate = state.OutputAudioBitrate;
|
var bitrate = state.OutputAudioBitrate;
|
||||||
|
@ -45,55 +45,53 @@ namespace MediaBrowser.Api.Playback.Progressive
|
|||||||
return ext;
|
return ext;
|
||||||
}
|
}
|
||||||
|
|
||||||
var videoRequest = state.Request as VideoStreamRequest;
|
var isVideoRequest = state.VideoRequest != null;
|
||||||
|
|
||||||
// Try to infer based on the desired video codec
|
// Try to infer based on the desired video codec
|
||||||
if (videoRequest != null && !string.IsNullOrEmpty(videoRequest.VideoCodec))
|
if (isVideoRequest)
|
||||||
{
|
{
|
||||||
if (state.IsInputVideo)
|
var videoCodec = state.VideoRequest.VideoCodec;
|
||||||
{
|
|
||||||
if (string.Equals(videoRequest.VideoCodec, "h264", StringComparison.OrdinalIgnoreCase))
|
if (string.Equals(videoCodec, "h264", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
return ".ts";
|
return ".ts";
|
||||||
}
|
}
|
||||||
if (string.Equals(videoRequest.VideoCodec, "theora", StringComparison.OrdinalIgnoreCase))
|
if (string.Equals(videoCodec, "theora", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
return ".ogv";
|
return ".ogv";
|
||||||
}
|
}
|
||||||
if (string.Equals(videoRequest.VideoCodec, "vpx", StringComparison.OrdinalIgnoreCase))
|
if (string.Equals(videoCodec, "vpx", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
return ".webm";
|
return ".webm";
|
||||||
}
|
}
|
||||||
if (string.Equals(videoRequest.VideoCodec, "wmv", StringComparison.OrdinalIgnoreCase))
|
if (string.Equals(videoCodec, "wmv", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
return ".asf";
|
return ".asf";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Try to infer based on the desired audio codec
|
// Try to infer based on the desired audio codec
|
||||||
if (!string.IsNullOrEmpty(state.Request.AudioCodec))
|
if (!isVideoRequest)
|
||||||
{
|
{
|
||||||
if (!state.IsInputVideo)
|
var audioCodec = state.Request.AudioCodec;
|
||||||
{
|
|
||||||
if (string.Equals("aac", state.Request.AudioCodec, StringComparison.OrdinalIgnoreCase))
|
if (string.Equals("aac", audioCodec, StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
return ".aac";
|
return ".aac";
|
||||||
}
|
}
|
||||||
if (string.Equals("mp3", state.Request.AudioCodec, StringComparison.OrdinalIgnoreCase))
|
if (string.Equals("mp3", audioCodec, StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
return ".mp3";
|
return ".mp3";
|
||||||
}
|
}
|
||||||
if (string.Equals("vorbis", state.Request.AudioCodec, StringComparison.OrdinalIgnoreCase))
|
if (string.Equals("vorbis", audioCodec, StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
return ".ogg";
|
return ".ogg";
|
||||||
}
|
}
|
||||||
if (string.Equals("wma", state.Request.AudioCodec, StringComparison.OrdinalIgnoreCase))
|
if (string.Equals("wma", audioCodec, StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
return ".wma";
|
return ".wma";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -134,7 +132,7 @@ namespace MediaBrowser.Api.Playback.Progressive
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var outputPath = GetOutputFilePath(state);
|
var outputPath = state.OutputFilePath;
|
||||||
var outputPathExists = File.Exists(outputPath);
|
var outputPathExists = File.Exists(outputPath);
|
||||||
|
|
||||||
var isStatic = request.Static ||
|
var isStatic = request.Static ||
|
||||||
@ -243,7 +241,7 @@ namespace MediaBrowser.Api.Playback.Progressive
|
|||||||
private async Task<object> GetStreamResult(StreamState state, IDictionary<string, string> responseHeaders, bool isHeadRequest)
|
private async Task<object> GetStreamResult(StreamState state, IDictionary<string, string> responseHeaders, bool isHeadRequest)
|
||||||
{
|
{
|
||||||
// Use the command line args with a dummy playlist path
|
// Use the command line args with a dummy playlist path
|
||||||
var outputPath = GetOutputFilePath(state);
|
var outputPath = state.OutputFilePath;
|
||||||
|
|
||||||
responseHeaders["Accept-Ranges"] = "none";
|
responseHeaders["Accept-Ranges"] = "none";
|
||||||
|
|
||||||
|
@ -95,7 +95,7 @@ namespace MediaBrowser.Api.Playback.Progressive
|
|||||||
protected override string GetCommandLineArguments(string outputPath, StreamState state, bool performSubtitleConversions)
|
protected override string GetCommandLineArguments(string outputPath, StreamState state, bool performSubtitleConversions)
|
||||||
{
|
{
|
||||||
// Get the output codec name
|
// Get the output codec name
|
||||||
var videoCodec = GetVideoCodec(state.VideoRequest);
|
var videoCodec = state.OutputVideoCodec;
|
||||||
|
|
||||||
var format = string.Empty;
|
var format = string.Empty;
|
||||||
var keyFrame = string.Empty;
|
var keyFrame = string.Empty;
|
||||||
@ -190,10 +190,8 @@ namespace MediaBrowser.Api.Playback.Progressive
|
|||||||
return string.Empty;
|
return string.Empty;
|
||||||
}
|
}
|
||||||
|
|
||||||
var request = state.Request;
|
|
||||||
|
|
||||||
// Get the output codec name
|
// Get the output codec name
|
||||||
var codec = GetAudioCodec(request);
|
var codec = state.OutputAudioCodec;
|
||||||
|
|
||||||
if (codec.Equals("copy", StringComparison.OrdinalIgnoreCase))
|
if (codec.Equals("copy", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
|
@ -163,6 +163,9 @@ namespace MediaBrowser.Api.Playback
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public string OutputFilePath { get; set; }
|
||||||
|
public string OutputVideoCodec { get; set; }
|
||||||
|
public string OutputAudioCodec { get; set; }
|
||||||
public int? OutputAudioChannels;
|
public int? OutputAudioChannels;
|
||||||
public int? OutputAudioSampleRate;
|
public int? OutputAudioSampleRate;
|
||||||
public int? OutputAudioBitrate;
|
public int? OutputAudioBitrate;
|
||||||
|
@ -226,5 +226,18 @@ namespace MediaBrowser.Controller.Session
|
|||||||
/// <param name="sessionId">The session identifier.</param>
|
/// <param name="sessionId">The session identifier.</param>
|
||||||
/// <param name="capabilities">The capabilities.</param>
|
/// <param name="capabilities">The capabilities.</param>
|
||||||
void ReportCapabilities(string sessionId, SessionCapabilities capabilities);
|
void ReportCapabilities(string sessionId, SessionCapabilities capabilities);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Reports the transcoding information.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="deviceId">The device identifier.</param>
|
||||||
|
/// <param name="info">The information.</param>
|
||||||
|
void ReportTranscodingInfo(string deviceId, TranscodingInfo info);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Clears the transcoding information.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="deviceId">The device identifier.</param>
|
||||||
|
void ClearTranscodingInfo(string deviceId);
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -122,6 +122,8 @@ namespace MediaBrowser.Controller.Session
|
|||||||
/// <value>The supported commands.</value>
|
/// <value>The supported commands.</value>
|
||||||
public List<string> SupportedCommands { get; set; }
|
public List<string> SupportedCommands { get; set; }
|
||||||
|
|
||||||
|
public TranscodingInfo TranscodingInfo { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets a value indicating whether this instance is active.
|
/// Gets a value indicating whether this instance is active.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -56,4 +56,15 @@
|
|||||||
/// <value>The play method.</value>
|
/// <value>The play method.</value>
|
||||||
public PlayMethod? PlayMethod { get; set; }
|
public PlayMethod? PlayMethod { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public class TranscodingInfo
|
||||||
|
{
|
||||||
|
public string AudioCodec { get; set; }
|
||||||
|
public string VideoCodec { get; set; }
|
||||||
|
public string Container { get; set; }
|
||||||
|
public int? Bitrate { get; set; }
|
||||||
|
|
||||||
|
public float? Framerate { get; set; }
|
||||||
|
public double? CompletionPercentage { get; set; }
|
||||||
|
}
|
||||||
}
|
}
|
@ -137,6 +137,8 @@ namespace MediaBrowser.Model.Session
|
|||||||
|
|
||||||
public PlayerStateInfo PlayState { get; set; }
|
public PlayerStateInfo PlayState { get; set; }
|
||||||
|
|
||||||
|
public TranscodingInfo TranscodingInfo { get; set; }
|
||||||
|
|
||||||
public event PropertyChangedEventHandler PropertyChanged;
|
public event PropertyChangedEventHandler PropertyChanged;
|
||||||
|
|
||||||
public SessionInfoDto()
|
public SessionInfoDto()
|
||||||
|
@ -26,7 +26,7 @@ namespace MediaBrowser.Providers.Folders
|
|||||||
|
|
||||||
public bool Supports(IHasImages item)
|
public bool Supports(IHasImages item)
|
||||||
{
|
{
|
||||||
return item is ICollectionFolder;
|
return item is CollectionFolder;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int Order
|
public int Order
|
||||||
|
@ -347,6 +347,11 @@ namespace MediaBrowser.Server.Implementations.Session
|
|||||||
{
|
{
|
||||||
session.NowPlayingItem = null;
|
session.NowPlayingItem = null;
|
||||||
session.PlayState = new PlayerStateInfo();
|
session.PlayState = new PlayerStateInfo();
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(session.DeviceId))
|
||||||
|
{
|
||||||
|
ClearTranscodingInfo(session.DeviceId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private string GetSessionKey(string clientType, string appVersion, string deviceId)
|
private string GetSessionKey(string clientType, string appVersion, string deviceId)
|
||||||
@ -459,6 +464,11 @@ namespace MediaBrowser.Server.Implementations.Session
|
|||||||
|
|
||||||
UpdateNowPlayingItem(session, info, libraryItem);
|
UpdateNowPlayingItem(session, info, libraryItem);
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(session.DeviceId))
|
||||||
|
{
|
||||||
|
ClearTranscodingInfo(session.DeviceId);
|
||||||
|
}
|
||||||
|
|
||||||
session.QueueableMediaTypes = info.QueueableMediaTypes;
|
session.QueueableMediaTypes = info.QueueableMediaTypes;
|
||||||
|
|
||||||
var users = GetUsers(session);
|
var users = GetUsers(session);
|
||||||
@ -1264,7 +1274,8 @@ namespace MediaBrowser.Server.Implementations.Session
|
|||||||
UserName = session.UserName,
|
UserName = session.UserName,
|
||||||
NowPlayingItem = session.NowPlayingItem,
|
NowPlayingItem = session.NowPlayingItem,
|
||||||
SupportsRemoteControl = session.SupportsMediaControl,
|
SupportsRemoteControl = session.SupportsMediaControl,
|
||||||
PlayState = session.PlayState
|
PlayState = session.PlayState,
|
||||||
|
TranscodingInfo = session.TranscodingInfo
|
||||||
};
|
};
|
||||||
|
|
||||||
if (session.UserId.HasValue)
|
if (session.UserId.HasValue)
|
||||||
@ -1490,5 +1501,20 @@ namespace MediaBrowser.Server.Implementations.Session
|
|||||||
|
|
||||||
session.NowViewingItem = item;
|
session.NowViewingItem = item;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void ReportTranscodingInfo(string deviceId, TranscodingInfo info)
|
||||||
|
{
|
||||||
|
var session = Sessions.FirstOrDefault(i => string.Equals(i.DeviceId, deviceId));
|
||||||
|
|
||||||
|
if (session != null)
|
||||||
|
{
|
||||||
|
session.TranscodingInfo = info;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ClearTranscodingInfo(string deviceId)
|
||||||
|
{
|
||||||
|
ReportTranscodingInfo(deviceId, null);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
Loading…
x
Reference in New Issue
Block a user