mirror of
https://github.com/jellyfin/jellyfin.git
synced 2024-09-07 11:57:55 +02:00
Merging in latest dev
This commit is contained in:
commit
abf12569ba
209 changed files with 4092 additions and 1719 deletions
|
@ -132,7 +132,7 @@ namespace MediaBrowser.Api
|
|||
/// Called when [transcode beginning].
|
||||
/// </summary>
|
||||
/// <param name="path">The path.</param>
|
||||
/// <param name="streamId">The stream identifier.</param>
|
||||
/// <param name="playSessionId">The play session identifier.</param>
|
||||
/// <param name="transcodingJobId">The transcoding job identifier.</param>
|
||||
/// <param name="type">The type.</param>
|
||||
/// <param name="process">The process.</param>
|
||||
|
@ -141,7 +141,7 @@ namespace MediaBrowser.Api
|
|||
/// <param name="cancellationTokenSource">The cancellation token source.</param>
|
||||
/// <returns>TranscodingJob.</returns>
|
||||
public TranscodingJob OnTranscodeBeginning(string path,
|
||||
string streamId,
|
||||
string playSessionId,
|
||||
string transcodingJobId,
|
||||
TranscodingJobType type,
|
||||
Process process,
|
||||
|
@ -160,7 +160,7 @@ namespace MediaBrowser.Api
|
|||
DeviceId = deviceId,
|
||||
CancellationTokenSource = cancellationTokenSource,
|
||||
Id = transcodingJobId,
|
||||
StreamId = streamId
|
||||
PlaySessionId = playSessionId
|
||||
};
|
||||
|
||||
_activeTranscodingJobs.Add(job);
|
||||
|
@ -187,7 +187,7 @@ namespace MediaBrowser.Api
|
|||
|
||||
if (!string.IsNullOrWhiteSpace(deviceId))
|
||||
{
|
||||
var audioCodec = state.ActualOutputVideoCodec;
|
||||
var audioCodec = state.ActualOutputAudioCodec;
|
||||
var videoCodec = state.ActualOutputVideoCodec;
|
||||
|
||||
_sessionManager.ReportTranscodingInfo(deviceId, new TranscodingInfo
|
||||
|
@ -293,19 +293,57 @@ namespace MediaBrowser.Api
|
|||
|
||||
if (job.ActiveRequestCount == 0)
|
||||
{
|
||||
// TODO: Lower this hls timeout
|
||||
var timerDuration = job.Type == TranscodingJobType.Progressive ?
|
||||
1000 :
|
||||
14400000;
|
||||
PingTimer(job, true);
|
||||
}
|
||||
}
|
||||
internal void PingTranscodingJob(string deviceId, string playSessionId)
|
||||
{
|
||||
if (string.IsNullOrEmpty(deviceId))
|
||||
{
|
||||
throw new ArgumentNullException("deviceId");
|
||||
}
|
||||
|
||||
if (job.KillTimer == null)
|
||||
var jobs = new List<TranscodingJob>();
|
||||
|
||||
lock (_activeTranscodingJobs)
|
||||
{
|
||||
// This is really only needed for HLS.
|
||||
// Progressive streams can stop on their own reliably
|
||||
jobs = jobs.Where(j =>
|
||||
{
|
||||
if (string.Equals(deviceId, j.DeviceId, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return string.Equals(playSessionId, j.PlaySessionId, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
return false;
|
||||
|
||||
}).ToList();
|
||||
}
|
||||
|
||||
foreach (var job in jobs)
|
||||
{
|
||||
PingTimer(job, false);
|
||||
}
|
||||
}
|
||||
|
||||
private void PingTimer(TranscodingJob job, bool startTimerIfNeeded)
|
||||
{
|
||||
// TODO: Lower this hls timeout
|
||||
var timerDuration = job.Type == TranscodingJobType.Progressive ?
|
||||
1000 :
|
||||
1800000;
|
||||
|
||||
if (job.KillTimer == null)
|
||||
{
|
||||
if (startTimerIfNeeded)
|
||||
{
|
||||
job.KillTimer = new Timer(OnTranscodeKillTimerStopped, job, timerDuration, Timeout.Infinite);
|
||||
}
|
||||
else
|
||||
{
|
||||
job.KillTimer.Change(timerDuration, Timeout.Infinite);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
job.KillTimer.Change(timerDuration, Timeout.Infinite);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -324,10 +362,10 @@ namespace MediaBrowser.Api
|
|||
/// Kills the single transcoding job.
|
||||
/// </summary>
|
||||
/// <param name="deviceId">The device id.</param>
|
||||
/// <param name="streamId">The stream identifier.</param>
|
||||
/// <param name="playSessionId">The play session identifier.</param>
|
||||
/// <param name="deleteFiles">The delete files.</param>
|
||||
/// <returns>Task.</returns>
|
||||
internal void KillTranscodingJobs(string deviceId, string streamId, Func<string, bool> deleteFiles)
|
||||
internal void KillTranscodingJobs(string deviceId, string playSessionId, Func<string, bool> deleteFiles)
|
||||
{
|
||||
if (string.IsNullOrEmpty(deviceId))
|
||||
{
|
||||
|
@ -338,7 +376,7 @@ namespace MediaBrowser.Api
|
|||
{
|
||||
if (string.Equals(deviceId, j.DeviceId, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return string.IsNullOrWhiteSpace(streamId) || string.Equals(streamId, j.StreamId, StringComparison.OrdinalIgnoreCase);
|
||||
return string.IsNullOrWhiteSpace(playSessionId) || string.Equals(playSessionId, j.PlaySessionId, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
return false;
|
||||
|
@ -539,10 +577,10 @@ namespace MediaBrowser.Api
|
|||
public class TranscodingJob
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the stream identifier.
|
||||
/// Gets or sets the play session identifier.
|
||||
/// </summary>
|
||||
/// <value>The stream identifier.</value>
|
||||
public string StreamId { get; set; }
|
||||
/// <value>The play session identifier.</value>
|
||||
public string PlaySessionId { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets the path.
|
||||
/// </summary>
|
||||
|
|
|
@ -123,7 +123,7 @@ namespace MediaBrowser.Api
|
|||
|
||||
public void Post(AutoSetMetadataOptions request)
|
||||
{
|
||||
_configurationManager.DisableMetadataService("Media Browser Xml");
|
||||
_configurationManager.DisableMetadataService("Emby Xml");
|
||||
_configurationManager.SaveConfiguration();
|
||||
}
|
||||
|
||||
|
|
|
@ -186,6 +186,9 @@ namespace MediaBrowser.Api.LiveTv
|
|||
[ApiMember(Name = "IsMovie", Description = "Optional filter for movies.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET,POST")]
|
||||
public bool? IsMovie { get; set; }
|
||||
|
||||
[ApiMember(Name = "IsSports", Description = "Optional filter for sports.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET,POST")]
|
||||
public bool? IsSports { get; set; }
|
||||
|
||||
[ApiMember(Name = "StartIndex", Description = "Optional. The record index to start at. All items with a lower index will be dropped from the results.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
|
||||
public int? StartIndex { get; set; }
|
||||
|
||||
|
@ -218,6 +221,9 @@ namespace MediaBrowser.Api.LiveTv
|
|||
[ApiMember(Name = "HasAired", Description = "Optional. Filter by programs that have completed airing, or not.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")]
|
||||
public bool? HasAired { get; set; }
|
||||
|
||||
[ApiMember(Name = "IsSports", Description = "Optional filter for sports.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET,POST")]
|
||||
public bool? IsSports { get; set; }
|
||||
|
||||
[ApiMember(Name = "IsMovie", Description = "Optional filter for movies.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")]
|
||||
public bool? IsMovie { get; set; }
|
||||
}
|
||||
|
@ -422,6 +428,7 @@ namespace MediaBrowser.Api.LiveTv
|
|||
query.SortBy = (request.SortBy ?? String.Empty).Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
query.SortOrder = request.SortOrder;
|
||||
query.IsMovie = request.IsMovie;
|
||||
query.IsSports = request.IsSports;
|
||||
query.Genres = (request.Genres ?? String.Empty).Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
|
||||
var result = await _liveTvManager.GetPrograms(query, CancellationToken.None).ConfigureAwait(false);
|
||||
|
@ -437,7 +444,8 @@ namespace MediaBrowser.Api.LiveTv
|
|||
IsAiring = request.IsAiring,
|
||||
Limit = request.Limit,
|
||||
HasAired = request.HasAired,
|
||||
IsMovie = request.IsMovie
|
||||
IsMovie = request.IsMovie,
|
||||
IsSports = request.IsSports
|
||||
};
|
||||
|
||||
var result = await _liveTvManager.GetRecommendedPrograms(query, CancellationToken.None).ConfigureAwait(false);
|
||||
|
|
|
@ -1,12 +1,10 @@
|
|||
using MediaBrowser.Common.Extensions;
|
||||
using MediaBrowser.Common.IO;
|
||||
using MediaBrowser.Controller.Channels;
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
using MediaBrowser.Controller.Devices;
|
||||
using MediaBrowser.Controller.Dlna;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Controller.LiveTv;
|
||||
using MediaBrowser.Controller.MediaEncoding;
|
||||
using MediaBrowser.Model.Configuration;
|
||||
using MediaBrowser.Model.Dlna;
|
||||
|
@ -65,7 +63,6 @@ namespace MediaBrowser.Api.Playback
|
|||
|
||||
protected IFileSystem FileSystem { get; private set; }
|
||||
|
||||
protected ILiveTvManager LiveTvManager { get; private set; }
|
||||
protected IDlnaManager DlnaManager { get; private set; }
|
||||
protected IDeviceManager DeviceManager { get; private set; }
|
||||
protected ISubtitleEncoder SubtitleEncoder { get; private set; }
|
||||
|
@ -75,14 +72,13 @@ namespace MediaBrowser.Api.Playback
|
|||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="BaseStreamingService" /> class.
|
||||
/// </summary>
|
||||
protected BaseStreamingService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, ILiveTvManager liveTvManager, IDlnaManager dlnaManager, ISubtitleEncoder subtitleEncoder, IDeviceManager deviceManager, IMediaSourceManager mediaSourceManager, IZipClient zipClient)
|
||||
protected BaseStreamingService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, IDlnaManager dlnaManager, ISubtitleEncoder subtitleEncoder, IDeviceManager deviceManager, IMediaSourceManager mediaSourceManager, IZipClient zipClient)
|
||||
{
|
||||
ZipClient = zipClient;
|
||||
MediaSourceManager = mediaSourceManager;
|
||||
DeviceManager = deviceManager;
|
||||
SubtitleEncoder = subtitleEncoder;
|
||||
DlnaManager = dlnaManager;
|
||||
LiveTvManager = liveTvManager;
|
||||
FileSystem = fileSystem;
|
||||
ServerConfigurationManager = serverConfig;
|
||||
UserManager = userManager;
|
||||
|
@ -95,11 +91,10 @@ namespace MediaBrowser.Api.Playback
|
|||
/// Gets the command line arguments.
|
||||
/// </summary>
|
||||
/// <param name="outputPath">The output path.</param>
|
||||
/// <param name="transcodingJobId">The transcoding job identifier.</param>
|
||||
/// <param name="state">The state.</param>
|
||||
/// <param name="isEncoding">if set to <c>true</c> [is encoding].</param>
|
||||
/// <returns>System.String.</returns>
|
||||
protected abstract string GetCommandLineArguments(string outputPath, string transcodingJobId, StreamState state, bool isEncoding);
|
||||
protected abstract string GetCommandLineArguments(string outputPath, StreamState state, bool isEncoding);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the type of the transcoding job.
|
||||
|
@ -128,10 +123,10 @@ namespace MediaBrowser.Api.Playback
|
|||
|
||||
var outputFileExtension = GetOutputFileExtension(state);
|
||||
|
||||
var data = GetCommandLineArguments("dummy\\dummy", "dummyTranscodingId", state, false);
|
||||
var data = GetCommandLineArguments("dummy\\dummy", state, false);
|
||||
|
||||
data += "-" + (state.Request.DeviceId ?? string.Empty);
|
||||
data += "-" + (state.Request.StreamId ?? string.Empty);
|
||||
data += "-" + (state.Request.PlaySessionId ?? string.Empty);
|
||||
data += "-" + (state.Request.ClientTime ?? string.Empty);
|
||||
|
||||
var dataHash = data.GetMD5().ToString("N");
|
||||
|
@ -704,7 +699,7 @@ namespace MediaBrowser.Api.Playback
|
|||
|
||||
if (!string.IsNullOrEmpty(state.SubtitleStream.Language))
|
||||
{
|
||||
var charenc = SubtitleEncoder.GetSubtitleFileCharacterSet(subtitlePath);
|
||||
var charenc = SubtitleEncoder.GetSubtitleFileCharacterSet(subtitlePath, state.MediaSource.Protocol, CancellationToken.None).Result;
|
||||
|
||||
if (!string.IsNullOrEmpty(charenc))
|
||||
{
|
||||
|
@ -719,8 +714,10 @@ namespace MediaBrowser.Api.Playback
|
|||
seconds.ToString(UsCulture));
|
||||
}
|
||||
|
||||
var mediaPath = state.MediaPath ?? string.Empty;
|
||||
|
||||
return string.Format("subtitles='{0}:si={1}',setpts=PTS -{2}/TB",
|
||||
state.MediaPath.Replace('\\', '/').Replace(":/", "\\:/"),
|
||||
mediaPath.Replace('\\', '/').Replace(":/", "\\:/"),
|
||||
state.InternalSubtitleStreamOffset.ToString(UsCulture),
|
||||
seconds.ToString(UsCulture));
|
||||
}
|
||||
|
@ -895,12 +892,11 @@ namespace MediaBrowser.Api.Playback
|
|||
/// <summary>
|
||||
/// Gets the input argument.
|
||||
/// </summary>
|
||||
/// <param name="transcodingJobId">The transcoding job identifier.</param>
|
||||
/// <param name="state">The state.</param>
|
||||
/// <returns>System.String.</returns>
|
||||
protected string GetInputArgument(string transcodingJobId, StreamState state)
|
||||
protected string GetInputArgument(StreamState state)
|
||||
{
|
||||
var arg = "-i " + GetInputPathArgument(transcodingJobId, state);
|
||||
var arg = "-i " + GetInputPathArgument(state);
|
||||
|
||||
if (state.SubtitleStream != null)
|
||||
{
|
||||
|
@ -913,27 +909,18 @@ namespace MediaBrowser.Api.Playback
|
|||
return arg;
|
||||
}
|
||||
|
||||
private string GetInputPathArgument(string transcodingJobId, StreamState state)
|
||||
private string GetInputPathArgument(StreamState state)
|
||||
{
|
||||
//if (state.InputProtocol == MediaProtocol.File &&
|
||||
// state.RunTimeTicks.HasValue &&
|
||||
// state.VideoType == VideoType.VideoFile &&
|
||||
// !string.Equals(state.OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase))
|
||||
//{
|
||||
// if (state.RunTimeTicks.Value >= TimeSpan.FromMinutes(5).Ticks && state.IsInputVideo)
|
||||
// {
|
||||
// }
|
||||
//}
|
||||
|
||||
var protocol = state.InputProtocol;
|
||||
var mediaPath = state.MediaPath ?? string.Empty;
|
||||
|
||||
var inputPath = new[] { state.MediaPath };
|
||||
var inputPath = new[] { mediaPath };
|
||||
|
||||
if (state.IsInputVideo)
|
||||
{
|
||||
if (!(state.VideoType == VideoType.Iso && state.IsoMount == null))
|
||||
{
|
||||
inputPath = MediaEncoderHelpers.GetInputArgument(state.MediaPath, state.InputProtocol, state.IsoMount, state.PlayableStreamFileNames);
|
||||
inputPath = MediaEncoderHelpers.GetInputArgument(mediaPath, state.InputProtocol, state.IsoMount, state.PlayableStreamFileNames);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -947,55 +934,25 @@ namespace MediaBrowser.Api.Playback
|
|||
state.IsoMount = await IsoManager.Mount(state.MediaPath, cancellationTokenSource.Token).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(state.MediaPath))
|
||||
if (state.MediaSource.RequiresOpening)
|
||||
{
|
||||
var checkCodecs = false;
|
||||
|
||||
if (string.Equals(state.ItemType, typeof(LiveTvChannel).Name))
|
||||
var liveStreamResponse = await MediaSourceManager.OpenLiveStream(new LiveStreamRequest
|
||||
{
|
||||
var streamInfo = await LiveTvManager.GetChannelStream(state.Request.Id, cancellationTokenSource.Token).ConfigureAwait(false);
|
||||
OpenToken = state.MediaSource.OpenToken
|
||||
|
||||
state.LiveTvStreamId = streamInfo.Id;
|
||||
}, false, cancellationTokenSource.Token).ConfigureAwait(false);
|
||||
|
||||
state.MediaPath = streamInfo.Path;
|
||||
state.InputProtocol = streamInfo.Protocol;
|
||||
AttachMediaSourceInfo(state, liveStreamResponse.MediaSource, state.VideoRequest, state.RequestedUrl);
|
||||
|
||||
await Task.Delay(1500, cancellationTokenSource.Token).ConfigureAwait(false);
|
||||
|
||||
AttachMediaStreamInfo(state, streamInfo, state.VideoRequest, state.RequestedUrl);
|
||||
checkCodecs = true;
|
||||
}
|
||||
|
||||
else if (string.Equals(state.ItemType, typeof(LiveTvVideoRecording).Name) ||
|
||||
string.Equals(state.ItemType, typeof(LiveTvAudioRecording).Name))
|
||||
if (state.VideoRequest != null)
|
||||
{
|
||||
var streamInfo = await LiveTvManager.GetRecordingStream(state.Request.Id, cancellationTokenSource.Token).ConfigureAwait(false);
|
||||
|
||||
state.LiveTvStreamId = streamInfo.Id;
|
||||
|
||||
state.MediaPath = streamInfo.Path;
|
||||
state.InputProtocol = streamInfo.Protocol;
|
||||
|
||||
await Task.Delay(1500, cancellationTokenSource.Token).ConfigureAwait(false);
|
||||
|
||||
AttachMediaStreamInfo(state, streamInfo, state.VideoRequest, state.RequestedUrl);
|
||||
checkCodecs = true;
|
||||
TryStreamCopy(state, state.VideoRequest);
|
||||
}
|
||||
}
|
||||
|
||||
var videoRequest = state.VideoRequest;
|
||||
|
||||
if (videoRequest != null && checkCodecs)
|
||||
{
|
||||
if (state.VideoStream != null && CanStreamCopyVideo(videoRequest, state.VideoStream))
|
||||
{
|
||||
state.OutputVideoCodec = "copy";
|
||||
}
|
||||
|
||||
if (state.AudioStream != null && CanStreamCopyAudio(videoRequest, state.AudioStream, state.SupportedAudioCodecs))
|
||||
{
|
||||
state.OutputAudioCodec = "copy";
|
||||
}
|
||||
}
|
||||
if (state.MediaSource.BufferMs.HasValue)
|
||||
{
|
||||
await Task.Delay(state.MediaSource.BufferMs.Value, cancellationTokenSource.Token).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1017,7 +974,7 @@ namespace MediaBrowser.Api.Playback
|
|||
await AcquireResources(state, cancellationTokenSource).ConfigureAwait(false);
|
||||
|
||||
var transcodingId = Guid.NewGuid().ToString("N");
|
||||
var commandLineArgs = GetCommandLineArguments(outputPath, transcodingId, state, true);
|
||||
var commandLineArgs = GetCommandLineArguments(outputPath, state, true);
|
||||
|
||||
if (ApiEntryPoint.Instance.GetEncodingOptions().EnableDebugLogging)
|
||||
{
|
||||
|
@ -1052,7 +1009,7 @@ namespace MediaBrowser.Api.Playback
|
|||
}
|
||||
|
||||
var transcodingJob = ApiEntryPoint.Instance.OnTranscodeBeginning(outputPath,
|
||||
state.Request.StreamId,
|
||||
state.Request.PlaySessionId,
|
||||
transcodingId,
|
||||
TranscodingJobType,
|
||||
process,
|
||||
|
@ -1123,7 +1080,7 @@ namespace MediaBrowser.Api.Playback
|
|||
{
|
||||
if (state.RunTimeTicks.Value >= TimeSpan.FromMinutes(5).Ticks && state.IsInputVideo)
|
||||
{
|
||||
transcodingJob.TranscodingThrottler = state.TranscodingThrottler = new TranscodingThrottler(transcodingJob, Logger);
|
||||
transcodingJob.TranscodingThrottler = state.TranscodingThrottler = new TranscodingThrottler(transcodingJob, Logger, ServerConfigurationManager);
|
||||
state.TranscodingThrottler.Start();
|
||||
}
|
||||
}
|
||||
|
@ -1554,7 +1511,11 @@ namespace MediaBrowser.Api.Playback
|
|||
}
|
||||
else if (i == 21)
|
||||
{
|
||||
request.StreamId = val;
|
||||
request.PlaySessionId = val;
|
||||
}
|
||||
else if (i == 22)
|
||||
{
|
||||
request.LiveStreamId = val;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1644,7 +1605,7 @@ namespace MediaBrowser.Api.Playback
|
|||
request.AudioCodec = InferAudioCodec(url);
|
||||
}
|
||||
|
||||
var state = new StreamState(LiveTvManager, Logger)
|
||||
var state = new StreamState(MediaSourceManager, Logger)
|
||||
{
|
||||
Request = request,
|
||||
RequestedUrl = url
|
||||
|
@ -1658,109 +1619,28 @@ namespace MediaBrowser.Api.Playback
|
|||
|
||||
var item = LibraryManager.GetItemById(request.Id);
|
||||
|
||||
List<MediaStream> mediaStreams = null;
|
||||
state.IsInputVideo = string.Equals(item.MediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
state.ItemType = item.GetType().Name;
|
||||
state.ItemId = item.Id.ToString("N");
|
||||
var archivable = item as IArchivable;
|
||||
state.IsInputArchive = archivable != null && archivable.IsArchive;
|
||||
|
||||
if (item is ILiveTvRecording)
|
||||
{
|
||||
var recording = await LiveTvManager.GetInternalRecording(request.Id, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
state.VideoType = VideoType.VideoFile;
|
||||
state.IsInputVideo = string.Equals(recording.MediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
var path = recording.RecordingInfo.Path;
|
||||
var mediaUrl = recording.RecordingInfo.Url;
|
||||
|
||||
var source = string.IsNullOrEmpty(request.MediaSourceId)
|
||||
? recording.GetMediaSources(false).First()
|
||||
: MediaSourceManager.GetStaticMediaSource(recording, request.MediaSourceId, false);
|
||||
|
||||
mediaStreams = source.MediaStreams;
|
||||
|
||||
// Just to prevent this from being null and causing other methods to fail
|
||||
state.MediaPath = string.Empty;
|
||||
|
||||
if (!string.IsNullOrEmpty(path))
|
||||
{
|
||||
state.MediaPath = path;
|
||||
state.InputProtocol = MediaProtocol.File;
|
||||
}
|
||||
else if (!string.IsNullOrEmpty(mediaUrl))
|
||||
{
|
||||
state.MediaPath = mediaUrl;
|
||||
state.InputProtocol = MediaProtocol.Http;
|
||||
}
|
||||
|
||||
state.RunTimeTicks = recording.RunTimeTicks;
|
||||
state.DeInterlace = true;
|
||||
state.OutputAudioSync = "1000";
|
||||
state.InputVideoSync = "-1";
|
||||
state.InputAudioSync = "1";
|
||||
state.InputContainer = recording.Container;
|
||||
state.ReadInputAtNativeFramerate = source.ReadAtNativeFramerate;
|
||||
}
|
||||
else if (item is LiveTvChannel)
|
||||
{
|
||||
var channel = LiveTvManager.GetInternalChannel(request.Id);
|
||||
|
||||
state.VideoType = VideoType.VideoFile;
|
||||
state.IsInputVideo = string.Equals(channel.MediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase);
|
||||
mediaStreams = new List<MediaStream>();
|
||||
|
||||
state.DeInterlace = true;
|
||||
|
||||
// Just to prevent this from being null and causing other methods to fail
|
||||
state.MediaPath = string.Empty;
|
||||
}
|
||||
else
|
||||
MediaSourceInfo mediaSource = null;
|
||||
if (string.IsNullOrWhiteSpace(request.LiveStreamId))
|
||||
{
|
||||
var mediaSources = await MediaSourceManager.GetPlayackMediaSources(request.Id, false, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
var mediaSource = string.IsNullOrEmpty(request.MediaSourceId)
|
||||
? mediaSources.First()
|
||||
: mediaSources.First(i => string.Equals(i.Id, request.MediaSourceId));
|
||||
|
||||
mediaStreams = mediaSource.MediaStreams;
|
||||
|
||||
state.MediaPath = mediaSource.Path;
|
||||
state.InputProtocol = mediaSource.Protocol;
|
||||
state.InputContainer = mediaSource.Container;
|
||||
state.InputFileSize = mediaSource.Size;
|
||||
state.InputBitrate = mediaSource.Bitrate;
|
||||
state.ReadInputAtNativeFramerate = mediaSource.ReadAtNativeFramerate;
|
||||
state.RunTimeTicks = mediaSource.RunTimeTicks;
|
||||
state.RemoteHttpHeaders = mediaSource.RequiredHttpHeaders;
|
||||
|
||||
var video = item as Video;
|
||||
|
||||
if (video != null)
|
||||
{
|
||||
state.IsInputVideo = true;
|
||||
|
||||
if (mediaSource.VideoType.HasValue)
|
||||
{
|
||||
state.VideoType = mediaSource.VideoType.Value;
|
||||
}
|
||||
|
||||
state.IsoType = mediaSource.IsoType;
|
||||
|
||||
state.PlayableStreamFileNames = mediaSource.PlayableStreamFileNames.ToList();
|
||||
|
||||
if (mediaSource.Timestamp.HasValue)
|
||||
{
|
||||
state.InputTimestamp = mediaSource.Timestamp.Value;
|
||||
}
|
||||
}
|
||||
|
||||
mediaSource = string.IsNullOrEmpty(request.MediaSourceId)
|
||||
? mediaSources.First()
|
||||
: mediaSources.First(i => string.Equals(i.Id, request.MediaSourceId));
|
||||
}
|
||||
else
|
||||
{
|
||||
mediaSource = await MediaSourceManager.GetLiveStream(request.LiveStreamId, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
var videoRequest = request as VideoStreamRequest;
|
||||
|
||||
AttachMediaStreamInfo(state, mediaStreams, videoRequest, url);
|
||||
AttachMediaSourceInfo(state, mediaSource, videoRequest, url);
|
||||
|
||||
var container = Path.GetExtension(state.RequestedUrl);
|
||||
|
||||
|
@ -1801,15 +1681,7 @@ namespace MediaBrowser.Api.Playback
|
|||
|
||||
if (videoRequest != null)
|
||||
{
|
||||
if (state.VideoStream != null && CanStreamCopyVideo(videoRequest, state.VideoStream))
|
||||
{
|
||||
state.OutputVideoCodec = "copy";
|
||||
}
|
||||
|
||||
if (state.AudioStream != null && CanStreamCopyAudio(videoRequest, state.AudioStream, state.SupportedAudioCodecs))
|
||||
{
|
||||
state.OutputAudioCodec = "copy";
|
||||
}
|
||||
TryStreamCopy(state, videoRequest);
|
||||
}
|
||||
|
||||
state.OutputFilePath = GetOutputFilePath(state);
|
||||
|
@ -1817,11 +1689,47 @@ namespace MediaBrowser.Api.Playback
|
|||
return state;
|
||||
}
|
||||
|
||||
private void AttachMediaStreamInfo(StreamState state,
|
||||
private void TryStreamCopy(StreamState state, VideoStreamRequest videoRequest)
|
||||
{
|
||||
if (state.VideoStream != null && CanStreamCopyVideo(videoRequest, state.VideoStream))
|
||||
{
|
||||
state.OutputVideoCodec = "copy";
|
||||
}
|
||||
|
||||
if (state.AudioStream != null && CanStreamCopyAudio(videoRequest, state.AudioStream, state.SupportedAudioCodecs))
|
||||
{
|
||||
state.OutputAudioCodec = "copy";
|
||||
}
|
||||
}
|
||||
|
||||
private void AttachMediaSourceInfo(StreamState state,
|
||||
MediaSourceInfo mediaSource,
|
||||
VideoStreamRequest videoRequest,
|
||||
string requestedUrl)
|
||||
{
|
||||
state.MediaPath = mediaSource.Path;
|
||||
state.InputProtocol = mediaSource.Protocol;
|
||||
state.InputContainer = mediaSource.Container;
|
||||
state.InputFileSize = mediaSource.Size;
|
||||
state.InputBitrate = mediaSource.Bitrate;
|
||||
state.ReadInputAtNativeFramerate = mediaSource.ReadAtNativeFramerate;
|
||||
state.RunTimeTicks = mediaSource.RunTimeTicks;
|
||||
state.RemoteHttpHeaders = mediaSource.RequiredHttpHeaders;
|
||||
|
||||
if (mediaSource.VideoType.HasValue)
|
||||
{
|
||||
state.VideoType = mediaSource.VideoType.Value;
|
||||
}
|
||||
|
||||
state.IsoType = mediaSource.IsoType;
|
||||
|
||||
state.PlayableStreamFileNames = mediaSource.PlayableStreamFileNames.ToList();
|
||||
|
||||
if (mediaSource.Timestamp.HasValue)
|
||||
{
|
||||
state.InputTimestamp = mediaSource.Timestamp.Value;
|
||||
}
|
||||
|
||||
state.InputProtocol = mediaSource.Protocol;
|
||||
state.MediaPath = mediaSource.Path;
|
||||
state.RunTimeTicks = mediaSource.RunTimeTicks;
|
||||
|
@ -1830,21 +1738,16 @@ namespace MediaBrowser.Api.Playback
|
|||
state.InputFileSize = mediaSource.Size;
|
||||
state.ReadInputAtNativeFramerate = mediaSource.ReadAtNativeFramerate;
|
||||
|
||||
if (state.ReadInputAtNativeFramerate)
|
||||
if (state.ReadInputAtNativeFramerate ||
|
||||
mediaSource.Protocol == MediaProtocol.File && string.Equals(mediaSource.Container, "wtv", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
state.OutputAudioSync = "1000";
|
||||
state.InputVideoSync = "-1";
|
||||
state.InputAudioSync = "1";
|
||||
}
|
||||
|
||||
AttachMediaStreamInfo(state, mediaSource.MediaStreams, videoRequest, requestedUrl);
|
||||
}
|
||||
var mediaStreams = mediaSource.MediaStreams;
|
||||
|
||||
private void AttachMediaStreamInfo(StreamState state,
|
||||
List<MediaStream> mediaStreams,
|
||||
VideoStreamRequest videoRequest,
|
||||
string requestedUrl)
|
||||
{
|
||||
if (videoRequest != null)
|
||||
{
|
||||
if (string.IsNullOrEmpty(videoRequest.VideoCodec))
|
||||
|
@ -1873,7 +1776,7 @@ namespace MediaBrowser.Api.Playback
|
|||
state.AudioStream = GetMediaStream(mediaStreams, null, MediaStreamType.Audio, true);
|
||||
}
|
||||
|
||||
state.AllMediaStreams = mediaStreams;
|
||||
state.MediaSource = mediaSource;
|
||||
}
|
||||
|
||||
private bool CanStreamCopyVideo(VideoStreamRequest request, MediaStream videoStream)
|
||||
|
@ -2109,7 +2012,6 @@ namespace MediaBrowser.Api.Playback
|
|||
}
|
||||
|
||||
var audioCodec = state.ActualOutputAudioCodec;
|
||||
|
||||
var videoCodec = state.ActualOutputVideoCodec;
|
||||
|
||||
var mediaProfile = state.VideoRequest == null ?
|
||||
|
@ -2130,7 +2032,9 @@ namespace MediaBrowser.Api.Playback
|
|||
state.TargetTimestamp,
|
||||
state.IsTargetAnamorphic,
|
||||
state.IsTargetCabac,
|
||||
state.TargetRefFrames);
|
||||
state.TargetRefFrames,
|
||||
state.TargetVideoStreamCount,
|
||||
state.TargetAudioStreamCount);
|
||||
|
||||
if (mediaProfile != null)
|
||||
{
|
||||
|
@ -2215,7 +2119,9 @@ namespace MediaBrowser.Api.Playback
|
|||
state.TranscodeSeekInfo,
|
||||
state.IsTargetAnamorphic,
|
||||
state.IsTargetCabac,
|
||||
state.TargetRefFrames
|
||||
state.TargetRefFrames,
|
||||
state.TargetVideoStreamCount,
|
||||
state.TargetAudioStreamCount
|
||||
|
||||
).FirstOrDefault() ?? string.Empty;
|
||||
}
|
||||
|
|
|
@ -5,7 +5,6 @@ using MediaBrowser.Controller.Configuration;
|
|||
using MediaBrowser.Controller.Devices;
|
||||
using MediaBrowser.Controller.Dlna;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Controller.LiveTv;
|
||||
using MediaBrowser.Controller.MediaEncoding;
|
||||
using MediaBrowser.Controller.Net;
|
||||
using MediaBrowser.Model.IO;
|
||||
|
@ -54,7 +53,7 @@ namespace MediaBrowser.Api.Playback.Dash
|
|||
|
||||
public class MpegDashService : BaseHlsService
|
||||
{
|
||||
public MpegDashService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, ILiveTvManager liveTvManager, IDlnaManager dlnaManager, ISubtitleEncoder subtitleEncoder, IDeviceManager deviceManager, IMediaSourceManager mediaSourceManager, IZipClient zipClient, INetworkManager networkManager) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, fileSystem, liveTvManager, dlnaManager, subtitleEncoder, deviceManager, mediaSourceManager, zipClient)
|
||||
public MpegDashService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, IDlnaManager dlnaManager, ISubtitleEncoder subtitleEncoder, IDeviceManager deviceManager, IMediaSourceManager mediaSourceManager, IZipClient zipClient, INetworkManager networkManager) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, fileSystem, dlnaManager, subtitleEncoder, deviceManager, mediaSourceManager, zipClient)
|
||||
{
|
||||
NetworkManager = networkManager;
|
||||
}
|
||||
|
@ -160,7 +159,7 @@ namespace MediaBrowser.Api.Playback.Dash
|
|||
// If the playlist doesn't already exist, startup ffmpeg
|
||||
try
|
||||
{
|
||||
ApiEntryPoint.Instance.KillTranscodingJobs(request.DeviceId, request.StreamId, p => false);
|
||||
ApiEntryPoint.Instance.KillTranscodingJobs(request.DeviceId, request.PlaySessionId, p => false);
|
||||
|
||||
if (currentTranscodingIndex.HasValue)
|
||||
{
|
||||
|
@ -447,7 +446,7 @@ namespace MediaBrowser.Api.Playback.Dash
|
|||
return args;
|
||||
}
|
||||
|
||||
protected override string GetCommandLineArguments(string outputPath, string transcodingJobId, StreamState state, bool isEncoding)
|
||||
protected override string GetCommandLineArguments(string outputPath, StreamState state, bool isEncoding)
|
||||
{
|
||||
// test url http://192.168.1.2:8096/videos/233e8905d559a8f230db9bffd2ac9d6d/master.mpd?mediasourceid=233e8905d559a8f230db9bffd2ac9d6d&videocodec=h264&audiocodec=aac&maxwidth=1280&videobitrate=500000&audiobitrate=128000&profile=baseline&level=3
|
||||
// Good info on i-frames http://blog.streamroot.io/encode-multi-bitrate-videos-mpeg-dash-mse-based-media-players/
|
||||
|
@ -461,7 +460,7 @@ namespace MediaBrowser.Api.Playback.Dash
|
|||
|
||||
var args = string.Format("{0} {1} -map_metadata -1 -threads {2} {3} {4} -copyts {5} -f dash -init_seg_name \"{6}\" -media_seg_name \"{7}\" -use_template 0 -use_timeline 1 -min_seg_duration {8} -y \"{9}\"",
|
||||
inputModifier,
|
||||
GetInputArgument(transcodingJobId, state),
|
||||
GetInputArgument(state),
|
||||
threads,
|
||||
GetMapArgs(state),
|
||||
GetVideoArguments(state),
|
||||
|
@ -518,25 +517,14 @@ namespace MediaBrowser.Api.Playback.Dash
|
|||
|
||||
private async Task WaitForSegment(string playlist, string segment, CancellationToken cancellationToken)
|
||||
{
|
||||
var tmpPath = playlist + ".tmp";
|
||||
|
||||
var segmentFilename = Path.GetFileName(segment);
|
||||
|
||||
Logger.Debug("Waiting for {0} in {1}", segmentFilename, playlist);
|
||||
|
||||
while (true)
|
||||
{
|
||||
FileStream fileStream;
|
||||
try
|
||||
{
|
||||
fileStream = FileSystem.GetFileStream(tmpPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, true);
|
||||
}
|
||||
catch (IOException)
|
||||
{
|
||||
fileStream = FileSystem.GetFileStream(playlist, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, true);
|
||||
}
|
||||
// Need to use FileShare.ReadWrite because we're reading the file at the same time it's being written
|
||||
using (fileStream)
|
||||
using (var fileStream = GetPlaylistFileStream(playlist))
|
||||
{
|
||||
using (var reader = new StreamReader(fileStream))
|
||||
{
|
||||
|
|
|
@ -3,7 +3,6 @@ using MediaBrowser.Controller.Configuration;
|
|||
using MediaBrowser.Controller.Devices;
|
||||
using MediaBrowser.Controller.Dlna;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Controller.LiveTv;
|
||||
using MediaBrowser.Controller.MediaEncoding;
|
||||
using MediaBrowser.Model.Extensions;
|
||||
using MediaBrowser.Model.IO;
|
||||
|
@ -22,7 +21,7 @@ namespace MediaBrowser.Api.Playback.Hls
|
|||
/// </summary>
|
||||
public abstract class BaseHlsService : BaseStreamingService
|
||||
{
|
||||
protected BaseHlsService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, ILiveTvManager liveTvManager, IDlnaManager dlnaManager, ISubtitleEncoder subtitleEncoder, IDeviceManager deviceManager, IMediaSourceManager mediaSourceManager, IZipClient zipClient) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, fileSystem, liveTvManager, dlnaManager, subtitleEncoder, deviceManager, mediaSourceManager, zipClient)
|
||||
protected BaseHlsService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, IDlnaManager dlnaManager, ISubtitleEncoder subtitleEncoder, IDeviceManager deviceManager, IMediaSourceManager mediaSourceManager, IZipClient zipClient) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, fileSystem, dlnaManager, subtitleEncoder, deviceManager, mediaSourceManager, zipClient)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -186,7 +185,7 @@ namespace MediaBrowser.Api.Playback.Hls
|
|||
while (true)
|
||||
{
|
||||
// Need to use FileShare.ReadWrite because we're reading the file at the same time it's being written
|
||||
using (var fileStream = FileSystem.GetFileStream(playlist, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, true))
|
||||
using (var fileStream = GetPlaylistFileStream(playlist))
|
||||
{
|
||||
using (var reader = new StreamReader(fileStream))
|
||||
{
|
||||
|
@ -212,7 +211,21 @@ namespace MediaBrowser.Api.Playback.Hls
|
|||
}
|
||||
}
|
||||
|
||||
protected override string GetCommandLineArguments(string outputPath, string transcodingJobId, StreamState state, bool isEncoding)
|
||||
protected Stream GetPlaylistFileStream(string path)
|
||||
{
|
||||
var tmpPath = path + ".tmp";
|
||||
|
||||
try
|
||||
{
|
||||
return FileSystem.GetFileStream(tmpPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, true);
|
||||
}
|
||||
catch (IOException)
|
||||
{
|
||||
return FileSystem.GetFileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, true);
|
||||
}
|
||||
}
|
||||
|
||||
protected override string GetCommandLineArguments(string outputPath, StreamState state, bool isEncoding)
|
||||
{
|
||||
var hlsVideoRequest = state.VideoRequest as GetHlsVideoStream;
|
||||
|
||||
|
@ -240,7 +253,7 @@ namespace MediaBrowser.Api.Playback.Hls
|
|||
var args = string.Format("{0} {1} {2} -map_metadata -1 -threads {3} {4} {5} -sc_threshold 0 {6} -hls_time {7} -start_number {8} -hls_list_size {9}{10} -y \"{11}\"",
|
||||
itsOffset,
|
||||
inputModifier,
|
||||
GetInputArgument(transcodingJobId, state),
|
||||
GetInputArgument(state),
|
||||
threads,
|
||||
GetMapArgs(state),
|
||||
GetVideoArguments(state),
|
||||
|
|
|
@ -4,7 +4,6 @@ using MediaBrowser.Controller.Configuration;
|
|||
using MediaBrowser.Controller.Devices;
|
||||
using MediaBrowser.Controller.Dlna;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Controller.LiveTv;
|
||||
using MediaBrowser.Controller.MediaEncoding;
|
||||
using MediaBrowser.Controller.Net;
|
||||
using MediaBrowser.Model.Dlna;
|
||||
|
@ -62,7 +61,7 @@ namespace MediaBrowser.Api.Playback.Hls
|
|||
|
||||
public class DynamicHlsService : BaseHlsService
|
||||
{
|
||||
public DynamicHlsService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, ILiveTvManager liveTvManager, IDlnaManager dlnaManager, ISubtitleEncoder subtitleEncoder, IDeviceManager deviceManager, IMediaSourceManager mediaSourceManager, IZipClient zipClient, INetworkManager networkManager) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, fileSystem, liveTvManager, dlnaManager, subtitleEncoder, deviceManager, mediaSourceManager, zipClient)
|
||||
public DynamicHlsService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, IDlnaManager dlnaManager, ISubtitleEncoder subtitleEncoder, IDeviceManager deviceManager, IMediaSourceManager mediaSourceManager, IZipClient zipClient, INetworkManager networkManager) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, fileSystem, dlnaManager, subtitleEncoder, deviceManager, mediaSourceManager, zipClient)
|
||||
{
|
||||
NetworkManager = networkManager;
|
||||
}
|
||||
|
@ -135,7 +134,7 @@ namespace MediaBrowser.Api.Playback.Hls
|
|||
// If the playlist doesn't already exist, startup ffmpeg
|
||||
try
|
||||
{
|
||||
ApiEntryPoint.Instance.KillTranscodingJobs(request.DeviceId, request.StreamId, p => false);
|
||||
ApiEntryPoint.Instance.KillTranscodingJobs(request.DeviceId, request.PlaySessionId, p => false);
|
||||
|
||||
if (currentTranscodingIndex.HasValue)
|
||||
{
|
||||
|
@ -300,7 +299,7 @@ namespace MediaBrowser.Api.Playback.Hls
|
|||
|
||||
var segmentFilename = Path.GetFileName(segmentPath);
|
||||
|
||||
using (var fileStream = FileSystem.GetFileStream(playlistPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, true))
|
||||
using (var fileStream = GetPlaylistFileStream(playlistPath))
|
||||
{
|
||||
using (var reader = new StreamReader(fileStream))
|
||||
{
|
||||
|
@ -414,7 +413,8 @@ namespace MediaBrowser.Api.Playback.Hls
|
|||
|
||||
var request = (GetMasterHlsVideoStream)state.Request;
|
||||
|
||||
var subtitleStreams = state.AllMediaStreams
|
||||
var subtitleStreams = state.MediaSource
|
||||
.MediaStreams
|
||||
.Where(i => i.IsTextSubtitleStream)
|
||||
.ToList();
|
||||
|
||||
|
@ -684,7 +684,7 @@ namespace MediaBrowser.Api.Playback.Hls
|
|||
return args;
|
||||
}
|
||||
|
||||
protected override string GetCommandLineArguments(string outputPath, string transcodingJobId, StreamState state, bool isEncoding)
|
||||
protected override string GetCommandLineArguments(string outputPath, StreamState state, bool isEncoding)
|
||||
{
|
||||
var threads = GetNumberOfThreads(state, false);
|
||||
|
||||
|
@ -697,9 +697,9 @@ namespace MediaBrowser.Api.Playback.Hls
|
|||
{
|
||||
var outputTsArg = Path.Combine(Path.GetDirectoryName(outputPath), Path.GetFileNameWithoutExtension(outputPath)) + "%d.ts";
|
||||
|
||||
return string.Format("{0} {1} -map_metadata -1 -threads {2} {3} {4} -copyts -flags -global_header {5} -f segment -segment_time {6} -segment_start_number {7} -segment_list \"{8}\" -y \"{9}\"",
|
||||
return string.Format("{0} {1} -map_metadata -1 -threads {2} {3} {4} -flags -global_header -sc_threshold 0 {5} -f segment -segment_time {6} -segment_start_number {7} -segment_list \"{8}\" -y \"{9}\"",
|
||||
inputModifier,
|
||||
GetInputArgument(transcodingJobId, state),
|
||||
GetInputArgument(state),
|
||||
threads,
|
||||
GetMapArgs(state),
|
||||
GetVideoArguments(state),
|
||||
|
@ -711,9 +711,9 @@ namespace MediaBrowser.Api.Playback.Hls
|
|||
).Trim();
|
||||
}
|
||||
|
||||
return string.Format("{0} {1} -map_metadata -1 -threads {2} {3} {4} -copyts -flags -global_header {5} -hls_time {6} -start_number {7} -hls_list_size {8} -y \"{9}\"",
|
||||
return string.Format("{0} {1} -map_metadata -1 -threads {2} {3} {4} -flags -global_header -sc_threshold 0 {5} -hls_time {6} -start_number {7} -hls_list_size {8} -y \"{9}\"",
|
||||
inputModifier,
|
||||
GetInputArgument(transcodingJobId, state),
|
||||
GetInputArgument(state),
|
||||
threads,
|
||||
GetMapArgs(state),
|
||||
GetVideoArguments(state),
|
||||
|
|
|
@ -54,8 +54,8 @@ namespace MediaBrowser.Api.Playback.Hls
|
|||
[ApiMember(Name = "DeviceId", Description = "The device id of the client requesting. Used to stop encoding processes when needed.", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "DELETE")]
|
||||
public string DeviceId { get; set; }
|
||||
|
||||
[ApiMember(Name = "StreamId", Description = "The stream id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "DELETE")]
|
||||
public string StreamId { get; set; }
|
||||
[ApiMember(Name = "PlaySessionId", Description = "The play session id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "DELETE")]
|
||||
public string PlaySessionId { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -95,7 +95,7 @@ namespace MediaBrowser.Api.Playback.Hls
|
|||
|
||||
public void Delete(StopEncodingProcess request)
|
||||
{
|
||||
ApiEntryPoint.Instance.KillTranscodingJobs(request.DeviceId, request.StreamId, path => true);
|
||||
ApiEntryPoint.Instance.KillTranscodingJobs(request.DeviceId, request.PlaySessionId, path => true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
@ -42,7 +42,7 @@ namespace MediaBrowser.Api.Playback.Hls
|
|||
/// </summary>
|
||||
public class VideoHlsService : BaseHlsService
|
||||
{
|
||||
public VideoHlsService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, ILiveTvManager liveTvManager, IDlnaManager dlnaManager, ISubtitleEncoder subtitleEncoder, IDeviceManager deviceManager, IMediaSourceManager mediaSourceManager, IZipClient zipClient) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, fileSystem, liveTvManager, dlnaManager, subtitleEncoder, deviceManager, mediaSourceManager, zipClient)
|
||||
public VideoHlsService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, IDlnaManager dlnaManager, ISubtitleEncoder subtitleEncoder, IDeviceManager deviceManager, IMediaSourceManager mediaSourceManager, IZipClient zipClient) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, fileSystem, dlnaManager, subtitleEncoder, deviceManager, mediaSourceManager, zipClient)
|
||||
{
|
||||
}
|
||||
|
||||
|
|
|
@ -1,8 +1,12 @@
|
|||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Controller.Devices;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Controller.Net;
|
||||
using MediaBrowser.Model.Dlna;
|
||||
using MediaBrowser.Model.Dto;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.MediaInfo;
|
||||
using MediaBrowser.Model.Session;
|
||||
using ServiceStack;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
@ -13,7 +17,7 @@ using System.Threading.Tasks;
|
|||
namespace MediaBrowser.Api.Playback
|
||||
{
|
||||
[Route("/Items/{Id}/MediaInfo", "GET", Summary = "Gets live playback media info for an item")]
|
||||
public class GetLiveMediaInfo : IReturn<LiveMediaInfoResult>
|
||||
public class GetLiveMediaInfo : IReturn<PlaybackInfoResponse>
|
||||
{
|
||||
[ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
|
||||
public string Id { get; set; }
|
||||
|
@ -23,7 +27,7 @@ namespace MediaBrowser.Api.Playback
|
|||
}
|
||||
|
||||
[Route("/Items/{Id}/PlaybackInfo", "GET", Summary = "Gets live playback media info for an item")]
|
||||
public class GetPlaybackInfo : IReturn<LiveMediaInfoResult>
|
||||
public class GetPlaybackInfo : IReturn<PlaybackInfoResponse>
|
||||
{
|
||||
[ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
|
||||
public string Id { get; set; }
|
||||
|
@ -32,45 +36,338 @@ namespace MediaBrowser.Api.Playback
|
|||
public string UserId { get; set; }
|
||||
}
|
||||
|
||||
[Route("/Items/{Id}/PlaybackInfo", "POST", Summary = "Gets live playback media info for an item")]
|
||||
public class GetPostedPlaybackInfo : PlaybackInfoRequest, IReturn<PlaybackInfoResponse>
|
||||
{
|
||||
}
|
||||
|
||||
[Route("/LiveStreams/Open", "POST", Summary = "Opens a media source")]
|
||||
public class OpenMediaSource : LiveStreamRequest, IReturn<LiveStreamResponse>
|
||||
{
|
||||
}
|
||||
|
||||
[Route("/LiveStreams/Close", "POST", Summary = "Closes a media source")]
|
||||
public class CloseMediaSource : IReturnVoid
|
||||
{
|
||||
[ApiMember(Name = "LiveStreamId", Description = "LiveStreamId", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")]
|
||||
public string LiveStreamId { get; set; }
|
||||
}
|
||||
|
||||
[Authenticated]
|
||||
public class MediaInfoService : BaseApiService
|
||||
{
|
||||
private readonly IMediaSourceManager _mediaSourceManager;
|
||||
private readonly IDeviceManager _deviceManager;
|
||||
private readonly ILibraryManager _libraryManager;
|
||||
|
||||
public MediaInfoService(IMediaSourceManager mediaSourceManager)
|
||||
public MediaInfoService(IMediaSourceManager mediaSourceManager, IDeviceManager deviceManager, ILibraryManager libraryManager)
|
||||
{
|
||||
_mediaSourceManager = mediaSourceManager;
|
||||
_deviceManager = deviceManager;
|
||||
_libraryManager = libraryManager;
|
||||
}
|
||||
|
||||
public Task<object> Get(GetPlaybackInfo request)
|
||||
public async Task<object> Get(GetPlaybackInfo request)
|
||||
{
|
||||
return GetPlaybackInfo(request.Id, request.UserId);
|
||||
var result = await GetPlaybackInfo(request.Id, request.UserId).ConfigureAwait(false);
|
||||
return ToOptimizedResult(result);
|
||||
}
|
||||
|
||||
public Task<object> Get(GetLiveMediaInfo request)
|
||||
public async Task<object> Get(GetLiveMediaInfo request)
|
||||
{
|
||||
return GetPlaybackInfo(request.Id, request.UserId);
|
||||
var result = await GetPlaybackInfo(request.Id, request.UserId).ConfigureAwait(false);
|
||||
return ToOptimizedResult(result);
|
||||
}
|
||||
|
||||
private async Task<object> GetPlaybackInfo(string id, string userId)
|
||||
public async Task<object> Post(OpenMediaSource request)
|
||||
{
|
||||
IEnumerable<MediaSourceInfo> mediaSources;
|
||||
var result = new LiveMediaInfoResult();
|
||||
var authInfo = AuthorizationContext.GetAuthorizationInfo(Request);
|
||||
|
||||
try
|
||||
var result = await _mediaSourceManager.OpenLiveStream(request, false, CancellationToken.None).ConfigureAwait(false);
|
||||
|
||||
var profile = request.DeviceProfile;
|
||||
if (profile == null)
|
||||
{
|
||||
mediaSources = await _mediaSourceManager.GetPlayackMediaSources(id, userId, true, CancellationToken.None).ConfigureAwait(false);
|
||||
}
|
||||
catch (PlaybackException ex)
|
||||
{
|
||||
mediaSources = new List<MediaSourceInfo>();
|
||||
result.ErrorCode = ex.ErrorCode;
|
||||
var caps = _deviceManager.GetCapabilities(authInfo.DeviceId);
|
||||
if (caps != null)
|
||||
{
|
||||
profile = caps.DeviceProfile;
|
||||
}
|
||||
}
|
||||
|
||||
result.MediaSources = mediaSources.ToList();
|
||||
result.StreamId = Guid.NewGuid().ToString("N");
|
||||
if (profile != null)
|
||||
{
|
||||
var item = _libraryManager.GetItemById(request.ItemId);
|
||||
|
||||
SetDeviceSpecificData(item, result.MediaSource, profile, authInfo, request.MaxStreamingBitrate,
|
||||
request.StartTimeTicks ?? 0, result.MediaSource.Id, request.AudioStreamIndex,
|
||||
request.SubtitleStreamIndex, request.PlaySessionId);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(result.MediaSource.TranscodingUrl))
|
||||
{
|
||||
result.MediaSource.TranscodingUrl += "&LiveStreamId=" + result.MediaSource.LiveStreamId;
|
||||
}
|
||||
}
|
||||
|
||||
return ToOptimizedResult(result);
|
||||
}
|
||||
|
||||
public void Post(CloseMediaSource request)
|
||||
{
|
||||
var task = _mediaSourceManager.CloseLiveStream(request.LiveStreamId, CancellationToken.None);
|
||||
Task.WaitAll(task);
|
||||
}
|
||||
|
||||
public async Task<object> Post(GetPostedPlaybackInfo request)
|
||||
{
|
||||
var info = await GetPlaybackInfo(request.Id, request.UserId, request.MediaSourceId, request.LiveStreamId).ConfigureAwait(false);
|
||||
var authInfo = AuthorizationContext.GetAuthorizationInfo(Request);
|
||||
|
||||
var profile = request.DeviceProfile;
|
||||
if (profile == null)
|
||||
{
|
||||
var caps = _deviceManager.GetCapabilities(authInfo.DeviceId);
|
||||
if (caps != null)
|
||||
{
|
||||
profile = caps.DeviceProfile;
|
||||
}
|
||||
}
|
||||
|
||||
if (profile != null)
|
||||
{
|
||||
var mediaSourceId = request.MediaSourceId;
|
||||
SetDeviceSpecificData(request.Id, info, profile, authInfo, request.MaxStreamingBitrate, request.StartTimeTicks ?? 0, mediaSourceId, request.AudioStreamIndex, request.SubtitleStreamIndex);
|
||||
}
|
||||
|
||||
return ToOptimizedResult(info);
|
||||
}
|
||||
|
||||
private async Task<PlaybackInfoResponse> GetPlaybackInfo(string id, string userId, string mediaSourceId = null, string liveStreamId = null)
|
||||
{
|
||||
var result = new PlaybackInfoResponse();
|
||||
|
||||
if (string.IsNullOrWhiteSpace(liveStreamId))
|
||||
{
|
||||
IEnumerable<MediaSourceInfo> mediaSources;
|
||||
try
|
||||
{
|
||||
mediaSources = await _mediaSourceManager.GetPlayackMediaSources(id, userId, true, CancellationToken.None).ConfigureAwait(false);
|
||||
}
|
||||
catch (PlaybackException ex)
|
||||
{
|
||||
mediaSources = new List<MediaSourceInfo>();
|
||||
result.ErrorCode = ex.ErrorCode;
|
||||
}
|
||||
|
||||
result.MediaSources = mediaSources.ToList();
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(mediaSourceId))
|
||||
{
|
||||
result.MediaSources = result.MediaSources
|
||||
.Where(i => string.Equals(i.Id, mediaSourceId, StringComparison.OrdinalIgnoreCase))
|
||||
.ToList();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var mediaSource = await _mediaSourceManager.GetLiveStream(liveStreamId, CancellationToken.None).ConfigureAwait(false);
|
||||
|
||||
result.MediaSources = new List<MediaSourceInfo> { mediaSource };
|
||||
}
|
||||
|
||||
if (result.MediaSources.Count == 0)
|
||||
{
|
||||
if (!result.ErrorCode.HasValue)
|
||||
{
|
||||
result.ErrorCode = PlaybackErrorCode.NoCompatibleStream;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
result.PlaySessionId = Guid.NewGuid().ToString("N");
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private void SetDeviceSpecificData(string itemId,
|
||||
PlaybackInfoResponse result,
|
||||
DeviceProfile profile,
|
||||
AuthorizationInfo auth,
|
||||
int? maxBitrate,
|
||||
long startTimeTicks,
|
||||
string mediaSourceId,
|
||||
int? audioStreamIndex,
|
||||
int? subtitleStreamIndex)
|
||||
{
|
||||
var item = _libraryManager.GetItemById(itemId);
|
||||
|
||||
foreach (var mediaSource in result.MediaSources)
|
||||
{
|
||||
SetDeviceSpecificData(item, mediaSource, profile, auth, maxBitrate, startTimeTicks, mediaSourceId, audioStreamIndex, subtitleStreamIndex, result.PlaySessionId);
|
||||
}
|
||||
|
||||
SortMediaSources(result);
|
||||
}
|
||||
|
||||
private void SetDeviceSpecificData(BaseItem item,
|
||||
MediaSourceInfo mediaSource,
|
||||
DeviceProfile profile,
|
||||
AuthorizationInfo auth,
|
||||
int? maxBitrate,
|
||||
long startTimeTicks,
|
||||
string mediaSourceId,
|
||||
int? audioStreamIndex,
|
||||
int? subtitleStreamIndex,
|
||||
string playSessionId)
|
||||
{
|
||||
var streamBuilder = new StreamBuilder();
|
||||
|
||||
var options = new VideoOptions
|
||||
{
|
||||
MediaSources = new List<MediaSourceInfo> { mediaSource },
|
||||
Context = EncodingContext.Streaming,
|
||||
DeviceId = auth.DeviceId,
|
||||
ItemId = item.Id.ToString("N"),
|
||||
Profile = profile,
|
||||
MaxBitrate = maxBitrate
|
||||
};
|
||||
|
||||
if (string.Equals(mediaSourceId, mediaSource.Id, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
options.MediaSourceId = mediaSourceId;
|
||||
options.AudioStreamIndex = audioStreamIndex;
|
||||
options.SubtitleStreamIndex = subtitleStreamIndex;
|
||||
}
|
||||
|
||||
if (mediaSource.SupportsDirectPlay)
|
||||
{
|
||||
var supportsDirectStream = mediaSource.SupportsDirectStream;
|
||||
|
||||
// Dummy this up to fool StreamBuilder
|
||||
mediaSource.SupportsDirectStream = true;
|
||||
|
||||
// The MediaSource supports direct stream, now test to see if the client supports it
|
||||
var streamInfo = string.Equals(item.MediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase) ?
|
||||
streamBuilder.BuildAudioItem(options) :
|
||||
streamBuilder.BuildVideoItem(options);
|
||||
|
||||
if (streamInfo == null || !streamInfo.IsDirectStream)
|
||||
{
|
||||
mediaSource.SupportsDirectPlay = false;
|
||||
}
|
||||
|
||||
// Set this back to what it was
|
||||
mediaSource.SupportsDirectStream = supportsDirectStream;
|
||||
|
||||
if (streamInfo != null)
|
||||
{
|
||||
SetDeviceSpecificSubtitleInfo(streamInfo, mediaSource, auth.Token);
|
||||
}
|
||||
}
|
||||
|
||||
if (mediaSource.SupportsDirectStream)
|
||||
{
|
||||
// The MediaSource supports direct stream, now test to see if the client supports it
|
||||
var streamInfo = string.Equals(item.MediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase) ?
|
||||
streamBuilder.BuildAudioItem(options) :
|
||||
streamBuilder.BuildVideoItem(options);
|
||||
|
||||
if (streamInfo == null || !streamInfo.IsDirectStream)
|
||||
{
|
||||
mediaSource.SupportsDirectStream = false;
|
||||
}
|
||||
|
||||
if (streamInfo != null)
|
||||
{
|
||||
SetDeviceSpecificSubtitleInfo(streamInfo, mediaSource, auth.Token);
|
||||
}
|
||||
}
|
||||
|
||||
if (mediaSource.SupportsTranscoding)
|
||||
{
|
||||
// The MediaSource supports direct stream, now test to see if the client supports it
|
||||
var streamInfo = string.Equals(item.MediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase) ?
|
||||
streamBuilder.BuildAudioItem(options) :
|
||||
streamBuilder.BuildVideoItem(options);
|
||||
|
||||
if (streamInfo != null)
|
||||
{
|
||||
streamInfo.PlaySessionId = playSessionId;
|
||||
SetDeviceSpecificSubtitleInfo(streamInfo, mediaSource, auth.Token);
|
||||
}
|
||||
|
||||
if (streamInfo != null && streamInfo.PlayMethod == PlayMethod.Transcode)
|
||||
{
|
||||
streamInfo.StartPositionTicks = startTimeTicks;
|
||||
mediaSource.TranscodingUrl = streamInfo.ToUrl("-", auth.Token).TrimStart('-');
|
||||
mediaSource.TranscodingContainer = streamInfo.Container;
|
||||
mediaSource.TranscodingSubProtocol = streamInfo.SubProtocol;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void SetDeviceSpecificSubtitleInfo(StreamInfo info, MediaSourceInfo mediaSource, string accessToken)
|
||||
{
|
||||
var profiles = info.GetSubtitleProfiles(false, "-", accessToken);
|
||||
mediaSource.DefaultSubtitleStreamIndex = info.SubtitleStreamIndex;
|
||||
|
||||
foreach (var profile in profiles)
|
||||
{
|
||||
foreach (var stream in mediaSource.MediaStreams)
|
||||
{
|
||||
if (stream.Type == MediaStreamType.Subtitle && stream.Index == profile.Index)
|
||||
{
|
||||
stream.DeliveryMethod = profile.DeliveryMethod;
|
||||
|
||||
if (profile.DeliveryMethod == SubtitleDeliveryMethod.External)
|
||||
{
|
||||
stream.DeliveryUrl = profile.Url.TrimStart('-');
|
||||
stream.IsExternalUrl = profile.IsExternalUrl;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void SortMediaSources(PlaybackInfoResponse result)
|
||||
{
|
||||
var originalList = result.MediaSources.ToList();
|
||||
|
||||
result.MediaSources = result.MediaSources.OrderBy(i =>
|
||||
{
|
||||
// Nothing beats direct playing a file
|
||||
if (i.SupportsDirectPlay && i.Protocol == MediaProtocol.File)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 1;
|
||||
|
||||
}).ThenBy(i =>
|
||||
{
|
||||
// Let's assume direct streaming a file is just as desirable as direct playing a remote url
|
||||
if (i.SupportsDirectPlay || i.SupportsDirectStream)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 1;
|
||||
|
||||
}).ThenBy(i =>
|
||||
{
|
||||
switch (i.Protocol)
|
||||
{
|
||||
case MediaProtocol.File:
|
||||
return 0;
|
||||
default:
|
||||
return 1;
|
||||
}
|
||||
|
||||
}).ThenBy(originalList.IndexOf)
|
||||
.ToList();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -31,7 +31,7 @@ namespace MediaBrowser.Api.Playback.Progressive
|
|||
/// </summary>
|
||||
public class AudioService : BaseProgressiveStreamingService
|
||||
{
|
||||
public AudioService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, ILiveTvManager liveTvManager, IDlnaManager dlnaManager, ISubtitleEncoder subtitleEncoder, IDeviceManager deviceManager, IMediaSourceManager mediaSourceManager, IZipClient zipClient, IImageProcessor imageProcessor, IHttpClient httpClient) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, fileSystem, liveTvManager, dlnaManager, subtitleEncoder, deviceManager, mediaSourceManager, zipClient, imageProcessor, httpClient)
|
||||
public AudioService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, IDlnaManager dlnaManager, ISubtitleEncoder subtitleEncoder, IDeviceManager deviceManager, IMediaSourceManager mediaSourceManager, IZipClient zipClient, IImageProcessor imageProcessor, IHttpClient httpClient) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, fileSystem, dlnaManager, subtitleEncoder, deviceManager, mediaSourceManager, zipClient, imageProcessor, httpClient)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -55,7 +55,7 @@ namespace MediaBrowser.Api.Playback.Progressive
|
|||
return ProcessRequest(request, true);
|
||||
}
|
||||
|
||||
protected override string GetCommandLineArguments(string outputPath, string transcodingJobId, StreamState state, bool isEncoding)
|
||||
protected override string GetCommandLineArguments(string outputPath, StreamState state, bool isEncoding)
|
||||
{
|
||||
var audioTranscodeParams = new List<string>();
|
||||
|
||||
|
@ -84,7 +84,7 @@ namespace MediaBrowser.Api.Playback.Progressive
|
|||
|
||||
return string.Format("{0} {1} -threads {2}{3} {4} -id3v2_version 3 -write_id3v1 1 -y \"{5}\"",
|
||||
inputModifier,
|
||||
GetInputArgument(transcodingJobId, state),
|
||||
GetInputArgument(state),
|
||||
threads,
|
||||
vn,
|
||||
string.Join(" ", audioTranscodeParams.ToArray()),
|
||||
|
|
|
@ -27,7 +27,7 @@ namespace MediaBrowser.Api.Playback.Progressive
|
|||
protected readonly IImageProcessor ImageProcessor;
|
||||
protected readonly IHttpClient HttpClient;
|
||||
|
||||
protected BaseProgressiveStreamingService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, ILiveTvManager liveTvManager, IDlnaManager dlnaManager, ISubtitleEncoder subtitleEncoder, IDeviceManager deviceManager, IMediaSourceManager mediaSourceManager, IZipClient zipClient, IImageProcessor imageProcessor, IHttpClient httpClient) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, fileSystem, liveTvManager, dlnaManager, subtitleEncoder, deviceManager, mediaSourceManager, zipClient)
|
||||
protected BaseProgressiveStreamingService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, IDlnaManager dlnaManager, ISubtitleEncoder subtitleEncoder, IDeviceManager deviceManager, IMediaSourceManager mediaSourceManager, IZipClient zipClient, IImageProcessor imageProcessor, IHttpClient httpClient) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, fileSystem, dlnaManager, subtitleEncoder, deviceManager, mediaSourceManager, zipClient)
|
||||
{
|
||||
ImageProcessor = imageProcessor;
|
||||
HttpClient = httpClient;
|
||||
|
|
|
@ -62,7 +62,7 @@ namespace MediaBrowser.Api.Playback.Progressive
|
|||
/// </summary>
|
||||
public class VideoService : BaseProgressiveStreamingService
|
||||
{
|
||||
public VideoService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, ILiveTvManager liveTvManager, IDlnaManager dlnaManager, ISubtitleEncoder subtitleEncoder, IDeviceManager deviceManager, IMediaSourceManager mediaSourceManager, IZipClient zipClient, IImageProcessor imageProcessor, IHttpClient httpClient) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, fileSystem, liveTvManager, dlnaManager, subtitleEncoder, deviceManager, mediaSourceManager, zipClient, imageProcessor, httpClient)
|
||||
public VideoService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, IDlnaManager dlnaManager, ISubtitleEncoder subtitleEncoder, IDeviceManager deviceManager, IMediaSourceManager mediaSourceManager, IZipClient zipClient, IImageProcessor imageProcessor, IHttpClient httpClient) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, fileSystem, dlnaManager, subtitleEncoder, deviceManager, mediaSourceManager, zipClient, imageProcessor, httpClient)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -86,7 +86,7 @@ namespace MediaBrowser.Api.Playback.Progressive
|
|||
return ProcessRequest(request, true);
|
||||
}
|
||||
|
||||
protected override string GetCommandLineArguments(string outputPath, string transcodingJobId, StreamState state, bool isEncoding)
|
||||
protected override string GetCommandLineArguments(string outputPath, StreamState state, bool isEncoding)
|
||||
{
|
||||
// Get the output codec name
|
||||
var videoCodec = state.OutputVideoCodec;
|
||||
|
@ -106,7 +106,7 @@ namespace MediaBrowser.Api.Playback.Progressive
|
|||
|
||||
return string.Format("{0} {1}{2} {3} {4} -map_metadata -1 -threads {5} {6}{7} -y \"{8}\"",
|
||||
inputModifier,
|
||||
GetInputArgument(transcodingJobId, state),
|
||||
GetInputArgument(state),
|
||||
keyFrame,
|
||||
GetMapArgs(state),
|
||||
GetVideoArguments(state, videoCodec),
|
||||
|
|
|
@ -71,9 +71,8 @@ namespace MediaBrowser.Api.Playback
|
|||
|
||||
public string Params { get; set; }
|
||||
public string ClientTime { get; set; }
|
||||
public string StreamId { get; set; }
|
||||
|
||||
public string TranscodingJobId { get; set; }
|
||||
public string PlaySessionId { get; set; }
|
||||
public string LiveStreamId { get; set; }
|
||||
}
|
||||
|
||||
public class VideoStreamRequest : StreamRequest
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
using MediaBrowser.Controller.LiveTv;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Model.Dlna;
|
||||
using MediaBrowser.Model.Drawing;
|
||||
using MediaBrowser.Model.Dto;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.IO;
|
||||
using MediaBrowser.Model.Logging;
|
||||
|
@ -17,7 +18,7 @@ namespace MediaBrowser.Api.Playback
|
|||
public class StreamState : IDisposable
|
||||
{
|
||||
private readonly ILogger _logger;
|
||||
private readonly ILiveTvManager _liveTvManager;
|
||||
private readonly IMediaSourceManager _mediaSourceManager;
|
||||
|
||||
public string RequestedUrl { get; set; }
|
||||
|
||||
|
@ -39,7 +40,7 @@ namespace MediaBrowser.Api.Playback
|
|||
|
||||
public string InputContainer { get; set; }
|
||||
|
||||
public List<MediaStream> AllMediaStreams { get; set; }
|
||||
public MediaSourceInfo MediaSource { get; set; }
|
||||
|
||||
public MediaStream AudioStream { get; set; }
|
||||
public MediaStream VideoStream { get; set; }
|
||||
|
@ -64,8 +65,6 @@ namespace MediaBrowser.Api.Playback
|
|||
|
||||
public List<string> PlayableStreamFileNames { get; set; }
|
||||
|
||||
public string LiveTvStreamId { get; set; }
|
||||
|
||||
public int SegmentLength = 3;
|
||||
public bool EnableGenericHlsSegmenter = false;
|
||||
public int HlsListSize
|
||||
|
@ -86,14 +85,13 @@ namespace MediaBrowser.Api.Playback
|
|||
|
||||
public List<string> SupportedAudioCodecs { get; set; }
|
||||
|
||||
public StreamState(ILiveTvManager liveTvManager, ILogger logger)
|
||||
public StreamState(IMediaSourceManager mediaSourceManager, ILogger logger)
|
||||
{
|
||||
_liveTvManager = liveTvManager;
|
||||
_mediaSourceManager = mediaSourceManager;
|
||||
_logger = logger;
|
||||
SupportedAudioCodecs = new List<string>();
|
||||
PlayableStreamFileNames = new List<string>();
|
||||
RemoteHttpHeaders = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||
AllMediaStreams = new List<MediaStream>();
|
||||
}
|
||||
|
||||
public string InputAudioSync { get; set; }
|
||||
|
@ -113,9 +111,6 @@ namespace MediaBrowser.Api.Playback
|
|||
|
||||
public long? EncodingDurationTicks { get; set; }
|
||||
|
||||
public string ItemType { get; set; }
|
||||
public string ItemId { get; set; }
|
||||
|
||||
public string GetMimeType(string outputPath)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(MimeType))
|
||||
|
@ -187,15 +182,15 @@ namespace MediaBrowser.Api.Playback
|
|||
|
||||
private async void DisposeLiveStream()
|
||||
{
|
||||
if (!string.IsNullOrEmpty(LiveTvStreamId))
|
||||
if (MediaSource.RequiresClosing && string.IsNullOrWhiteSpace(Request.LiveStreamId))
|
||||
{
|
||||
try
|
||||
{
|
||||
await _liveTvManager.CloseLiveStream(LiveTvStreamId, CancellationToken.None).ConfigureAwait(false);
|
||||
await _mediaSourceManager.CloseLiveStream(MediaSource.LiveStreamId, CancellationToken.None).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.ErrorException("Error closing live tv stream", ex);
|
||||
_logger.ErrorException("Error closing media source", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -351,6 +346,42 @@ namespace MediaBrowser.Api.Playback
|
|||
}
|
||||
}
|
||||
|
||||
public int? TargetVideoStreamCount
|
||||
{
|
||||
get
|
||||
{
|
||||
if (Request.Static)
|
||||
{
|
||||
return GetMediaStreamCount(MediaStreamType.Video, int.MaxValue);
|
||||
}
|
||||
return GetMediaStreamCount(MediaStreamType.Video, 1);
|
||||
}
|
||||
}
|
||||
|
||||
public int? TargetAudioStreamCount
|
||||
{
|
||||
get
|
||||
{
|
||||
if (Request.Static)
|
||||
{
|
||||
return GetMediaStreamCount(MediaStreamType.Audio, int.MaxValue);
|
||||
}
|
||||
return GetMediaStreamCount(MediaStreamType.Audio, 1);
|
||||
}
|
||||
}
|
||||
|
||||
private int? GetMediaStreamCount(MediaStreamType type, int limit)
|
||||
{
|
||||
var count = MediaSource.GetStreamCount(type);
|
||||
|
||||
if (count.HasValue)
|
||||
{
|
||||
count = Math.Min(count.Value, limit);
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Predicts the audio sample rate that will be in the output stream
|
||||
/// </summary>
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
using MediaBrowser.Model.Logging;
|
||||
using MediaBrowser.Common.Configuration;
|
||||
using MediaBrowser.Model.Configuration;
|
||||
using MediaBrowser.Model.Logging;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
|
@ -11,13 +13,18 @@ namespace MediaBrowser.Api.Playback
|
|||
private readonly ILogger _logger;
|
||||
private Timer _timer;
|
||||
private bool _isPaused;
|
||||
private readonly IConfigurationManager _config;
|
||||
|
||||
private readonly long _gapLengthInTicks = TimeSpan.FromMinutes(2).Ticks;
|
||||
|
||||
public TranscodingThrottler(TranscodingJob job, ILogger logger)
|
||||
public TranscodingThrottler(TranscodingJob job, ILogger logger, IConfigurationManager config)
|
||||
{
|
||||
_job = job;
|
||||
_logger = logger;
|
||||
_config = config;
|
||||
}
|
||||
|
||||
private EncodingOptions GetOptions()
|
||||
{
|
||||
return _config.GetConfiguration<EncodingOptions>("encoding");
|
||||
}
|
||||
|
||||
public void Start()
|
||||
|
@ -33,7 +40,9 @@ namespace MediaBrowser.Api.Playback
|
|||
return;
|
||||
}
|
||||
|
||||
if (IsThrottleAllowed(_job))
|
||||
var options = GetOptions();
|
||||
|
||||
if (/*options.EnableThrottling &&*/ IsThrottleAllowed(_job, options.ThrottleThresholdSeconds))
|
||||
{
|
||||
PauseTranscoding();
|
||||
}
|
||||
|
@ -79,19 +88,20 @@ namespace MediaBrowser.Api.Playback
|
|||
}
|
||||
}
|
||||
|
||||
private bool IsThrottleAllowed(TranscodingJob job)
|
||||
private bool IsThrottleAllowed(TranscodingJob job, int thresholdSeconds)
|
||||
{
|
||||
var bytesDownloaded = job.BytesDownloaded ?? 0;
|
||||
var transcodingPositionTicks = job.TranscodingPositionTicks ?? 0;
|
||||
var downloadPositionTicks = job.DownloadPositionTicks ?? 0;
|
||||
|
||||
var path = job.Path;
|
||||
var gapLengthInTicks = TimeSpan.FromSeconds(thresholdSeconds).Ticks;
|
||||
|
||||
if (downloadPositionTicks > 0 && transcodingPositionTicks > 0)
|
||||
{
|
||||
// HLS - time-based consideration
|
||||
|
||||
var targetGap = _gapLengthInTicks;
|
||||
var targetGap = gapLengthInTicks;
|
||||
var gap = transcodingPositionTicks - downloadPositionTicks;
|
||||
|
||||
if (gap < targetGap)
|
||||
|
@ -113,7 +123,7 @@ namespace MediaBrowser.Api.Playback
|
|||
var bytesTranscoded = job.BytesTranscoded ?? new FileInfo(path).Length;
|
||||
|
||||
// Estimate the bytes the transcoder should be ahead
|
||||
double gapFactor = _gapLengthInTicks;
|
||||
double gapFactor = gapLengthInTicks;
|
||||
gapFactor /= transcodingPositionTicks;
|
||||
var targetGap = bytesTranscoded * gapFactor;
|
||||
|
||||
|
|
|
@ -192,7 +192,7 @@ namespace MediaBrowser.Api.Subtitles
|
|||
{
|
||||
var item = (Video)_libraryManager.GetItemById(new Guid(request.Id));
|
||||
|
||||
var mediaSource = item.GetMediaSources(false)
|
||||
var mediaSource = _mediaSourceManager.GetStaticMediaSources(item, false, null)
|
||||
.First(i => string.Equals(i.Id, request.MediaSourceId ?? request.Id));
|
||||
|
||||
var subtitleStream = mediaSource.MediaStreams
|
||||
|
|
|
@ -142,7 +142,7 @@ namespace MediaBrowser.Api.UserLibrary
|
|||
}
|
||||
|
||||
IEnumerable<Tuple<TItemType, List<BaseItem>>> tuples;
|
||||
if (dtoOptions.Fields.Contains(ItemFields.ItemCounts) || true)
|
||||
if (dtoOptions.Fields.Contains(ItemFields.ItemCounts))
|
||||
{
|
||||
tuples = ibnItems.Select(i => new Tuple<TItemType, List<BaseItem>>(i, i.GetTaggedItems(libraryItems).ToList()));
|
||||
}
|
||||
|
@ -177,7 +177,6 @@ namespace MediaBrowser.Api.UserLibrary
|
|||
return true;
|
||||
}
|
||||
|
||||
return true;
|
||||
return options.Fields.Contains(ItemFields.ItemCounts);
|
||||
}
|
||||
|
||||
|
|
|
@ -294,6 +294,11 @@ namespace MediaBrowser.Api.UserLibrary
|
|||
|
||||
public void Post(ReportPlaybackProgress request)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(request.PlaySessionId))
|
||||
{
|
||||
ApiEntryPoint.Instance.PingTranscodingJob(AuthorizationContext.GetAuthorizationInfo(Request).DeviceId, request.PlaySessionId);
|
||||
}
|
||||
|
||||
request.SessionId = GetSession().Result.Id;
|
||||
|
||||
var task = _sessionManager.OnPlaybackProgress(request);
|
||||
|
@ -317,6 +322,11 @@ namespace MediaBrowser.Api.UserLibrary
|
|||
|
||||
public void Post(ReportPlaybackStopped request)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(request.PlaySessionId))
|
||||
{
|
||||
ApiEntryPoint.Instance.KillTranscodingJobs(AuthorizationContext.GetAuthorizationInfo(Request).DeviceId, request.PlaySessionId, s => true);
|
||||
}
|
||||
|
||||
request.SessionId = GetSession().Result.Id;
|
||||
|
||||
var task = _sessionManager.OnPlaybackStopped(request);
|
||||
|
|
|
@ -304,6 +304,14 @@ namespace MediaBrowser.Api.UserLibrary
|
|||
{
|
||||
var user = _userManager.GetUserById(request.UserId);
|
||||
|
||||
if (!request.IsPlayed.HasValue)
|
||||
{
|
||||
if (user.Configuration.HidePlayedInLatest)
|
||||
{
|
||||
request.IsPlayed = false;
|
||||
}
|
||||
}
|
||||
|
||||
var list = _userViewManager.GetLatestItems(new LatestItemsQuery
|
||||
{
|
||||
GroupItems = request.GroupItems,
|
||||
|
|
|
@ -107,30 +107,40 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager
|
|||
|
||||
private WebRequest GetRequest(HttpRequestOptions options, string method, bool enableHttpCompression)
|
||||
{
|
||||
var request = (HttpWebRequest)WebRequest.Create(options.Url);
|
||||
var request = WebRequest.Create(options.Url);
|
||||
var httpWebRequest = request as HttpWebRequest;
|
||||
|
||||
AddRequestHeaders(request, options);
|
||||
if (httpWebRequest != null)
|
||||
{
|
||||
AddRequestHeaders(httpWebRequest, options);
|
||||
|
||||
request.AutomaticDecompression = enableHttpCompression ? DecompressionMethods.Deflate : DecompressionMethods.None;
|
||||
httpWebRequest.AutomaticDecompression = enableHttpCompression ? DecompressionMethods.Deflate : DecompressionMethods.None;
|
||||
}
|
||||
|
||||
request.CachePolicy = new RequestCachePolicy(RequestCacheLevel.BypassCache);
|
||||
|
||||
if (options.EnableKeepAlive)
|
||||
if (httpWebRequest != null)
|
||||
{
|
||||
request.KeepAlive = true;
|
||||
if (options.EnableKeepAlive)
|
||||
{
|
||||
httpWebRequest.KeepAlive = true;
|
||||
}
|
||||
}
|
||||
|
||||
request.Method = method;
|
||||
request.Timeout = options.TimeoutMs;
|
||||
|
||||
if (!string.IsNullOrEmpty(options.Host))
|
||||
if (httpWebRequest != null)
|
||||
{
|
||||
request.Host = options.Host;
|
||||
}
|
||||
if (!string.IsNullOrEmpty(options.Host))
|
||||
{
|
||||
httpWebRequest.Host = options.Host;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(options.Referer))
|
||||
{
|
||||
request.Referer = options.Referer;
|
||||
if (!string.IsNullOrEmpty(options.Referer))
|
||||
{
|
||||
httpWebRequest.Referer = options.Referer;
|
||||
}
|
||||
}
|
||||
|
||||
//request.ServicePoint.BindIPEndPointDelegate = BindIPEndPointCallback;
|
||||
|
|
|
@ -6,6 +6,7 @@ using MediaBrowser.Model.Logging;
|
|||
using MediaBrowser.Model.Serialization;
|
||||
using MediaBrowser.Model.Tasks;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
@ -29,7 +30,8 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks
|
|||
/// <summary>
|
||||
/// The _task queue
|
||||
/// </summary>
|
||||
private readonly SortedDictionary<Type, TaskExecutionOptions> _taskQueue = new SortedDictionary<Type, TaskExecutionOptions>();
|
||||
private readonly ConcurrentQueue<Tuple<Type, TaskExecutionOptions>> _taskQueue =
|
||||
new ConcurrentQueue<Tuple<Type, TaskExecutionOptions>>();
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the json serializer.
|
||||
|
@ -136,25 +138,17 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks
|
|||
{
|
||||
var type = task.ScheduledTask.GetType();
|
||||
|
||||
Logger.Info("Queueing task {0}", type.Name);
|
||||
|
||||
lock (_taskQueue)
|
||||
{
|
||||
// If it's idle just execute immediately
|
||||
if (task.State == TaskState.Idle)
|
||||
{
|
||||
Execute(task, options);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_taskQueue.ContainsKey(type))
|
||||
{
|
||||
Logger.Info("Queueing task {0}", type.Name);
|
||||
_taskQueue.Add(type, options);
|
||||
}
|
||||
else
|
||||
{
|
||||
_taskQueue[type] = options;
|
||||
Logger.Info("Task already queued: {0}", type.Name);
|
||||
}
|
||||
_taskQueue.Enqueue(new Tuple<Type, TaskExecutionOptions>(type, options));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -241,15 +235,24 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks
|
|||
// Execute queued tasks
|
||||
lock (_taskQueue)
|
||||
{
|
||||
foreach (var enqueuedType in _taskQueue.ToList())
|
||||
var list = new List<Tuple<Type, TaskExecutionOptions>>();
|
||||
|
||||
Tuple<Type, TaskExecutionOptions> item;
|
||||
while (_taskQueue.TryDequeue(out item))
|
||||
{
|
||||
var scheduledTask = ScheduledTasks.First(t => t.ScheduledTask.GetType() == enqueuedType.Key);
|
||||
if (list.All(i => i.Item1 != item.Item1))
|
||||
{
|
||||
list.Add(item);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var enqueuedType in list)
|
||||
{
|
||||
var scheduledTask = ScheduledTasks.First(t => t.ScheduledTask.GetType() == enqueuedType.Item1);
|
||||
|
||||
if (scheduledTask.State == TaskState.Idle)
|
||||
{
|
||||
Execute(scheduledTask, enqueuedType.Value);
|
||||
|
||||
_taskQueue.Remove(enqueuedType.Key);
|
||||
Execute(scheduledTask, enqueuedType.Item2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,7 +5,6 @@ using System;
|
|||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Model.Users;
|
||||
|
||||
namespace MediaBrowser.Controller.Channels
|
||||
{
|
||||
|
@ -15,19 +14,9 @@ namespace MediaBrowser.Controller.Channels
|
|||
|
||||
public override bool IsVisible(User user)
|
||||
{
|
||||
if (user.Policy.BlockedChannels != null)
|
||||
if (!user.Policy.EnableAllChannels && !user.Policy.EnabledChannels.Contains(Id.ToString("N"), StringComparer.OrdinalIgnoreCase))
|
||||
{
|
||||
if (user.Policy.BlockedChannels.Contains(Id.ToString("N"), StringComparer.OrdinalIgnoreCase))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!user.Policy.EnableAllChannels && !user.Policy.EnabledChannels.Contains(Id.ToString("N"), StringComparer.OrdinalIgnoreCase))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
return base.IsVisible(user);
|
||||
|
|
|
@ -75,17 +75,23 @@ namespace MediaBrowser.Controller.Channels
|
|||
|
||||
public override IEnumerable<MediaSourceInfo> GetMediaSources(bool enablePathSubstitution)
|
||||
{
|
||||
var list = base.GetMediaSources(enablePathSubstitution).ToList();
|
||||
|
||||
var sources = ChannelManager.GetChannelItemMediaSources(Id.ToString("N"), false, CancellationToken.None)
|
||||
.Result.ToList();
|
||||
var sources = ChannelManager.GetStaticMediaSources(this, false, CancellationToken.None)
|
||||
.Result.ToList();
|
||||
|
||||
if (sources.Count > 0)
|
||||
{
|
||||
return sources;
|
||||
}
|
||||
|
||||
list.InsertRange(0, sources);
|
||||
var list = base.GetMediaSources(enablePathSubstitution).ToList();
|
||||
|
||||
foreach (var mediaSource in list)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(mediaSource.Path))
|
||||
{
|
||||
mediaSource.Type = MediaSourceType.Placeholder;
|
||||
}
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
|
|
@ -38,6 +38,7 @@ namespace MediaBrowser.Controller.Channels
|
|||
public string Id { get; set; }
|
||||
|
||||
public bool ReadAtNativeFramerate { get; set; }
|
||||
public bool SupportsDirectPlay { get; set; }
|
||||
|
||||
public ChannelMediaInfo()
|
||||
{
|
||||
|
@ -45,6 +46,7 @@ namespace MediaBrowser.Controller.Channels
|
|||
|
||||
// This is most common
|
||||
Protocol = MediaProtocol.Http;
|
||||
SupportsDirectPlay = true;
|
||||
}
|
||||
|
||||
public MediaSourceInfo ToMediaSource()
|
||||
|
@ -62,7 +64,9 @@ namespace MediaBrowser.Controller.Channels
|
|||
RunTimeTicks = RunTimeTicks,
|
||||
Name = id,
|
||||
Id = id,
|
||||
ReadAtNativeFramerate = ReadAtNativeFramerate
|
||||
ReadAtNativeFramerate = ReadAtNativeFramerate,
|
||||
SupportsDirectStream = Protocol == MediaProtocol.File || Protocol == MediaProtocol.Http,
|
||||
SupportsDirectPlay = SupportsDirectPlay
|
||||
};
|
||||
|
||||
var bitrate = (AudioBitrate ?? 0) + (VideoBitrate ?? 0);
|
||||
|
|
|
@ -90,17 +90,23 @@ namespace MediaBrowser.Controller.Channels
|
|||
|
||||
public override IEnumerable<MediaSourceInfo> GetMediaSources(bool enablePathSubstitution)
|
||||
{
|
||||
var list = base.GetMediaSources(enablePathSubstitution).ToList();
|
||||
|
||||
var sources = ChannelManager.GetChannelItemMediaSources(Id.ToString("N"), false, CancellationToken.None)
|
||||
.Result.ToList();
|
||||
var sources = ChannelManager.GetStaticMediaSources(this, false, CancellationToken.None)
|
||||
.Result.ToList();
|
||||
|
||||
if (sources.Count > 0)
|
||||
{
|
||||
return sources;
|
||||
}
|
||||
|
||||
list.InsertRange(0, sources);
|
||||
var list = base.GetMediaSources(enablePathSubstitution).ToList();
|
||||
|
||||
foreach (var mediaSource in list)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(mediaSource.Path))
|
||||
{
|
||||
mediaSource.Type = MediaSourceType.Placeholder;
|
||||
}
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
|
|
@ -112,11 +112,11 @@ namespace MediaBrowser.Controller.Channels
|
|||
/// <summary>
|
||||
/// Gets the channel item media sources.
|
||||
/// </summary>
|
||||
/// <param name="id">The identifier.</param>
|
||||
/// <param name="includeDynamicSources">if set to <c>true</c> [include dynamic sources].</param>
|
||||
/// <param name="item">The item.</param>
|
||||
/// <param name="includeCachedVersions">if set to <c>true</c> [include cached versions].</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>Task{IEnumerable{MediaSourceInfo}}.</returns>
|
||||
Task<IEnumerable<MediaSourceInfo>> GetChannelItemMediaSources(string id, bool includeDynamicSources, CancellationToken cancellationToken);
|
||||
Task<IEnumerable<MediaSourceInfo>> GetStaticMediaSources(IChannelMediaItem item, bool includeCachedVersions, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the channel folder.
|
||||
|
|
|
@ -1219,18 +1219,6 @@ namespace MediaBrowser.Controller.Entities
|
|||
|
||||
private BaseItem FindLinkedChild(LinkedChild info)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(info.ItemName))
|
||||
{
|
||||
if (string.Equals(info.ItemType, "musicgenre", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return LibraryManager.GetMusicGenre(info.ItemName);
|
||||
}
|
||||
if (string.Equals(info.ItemType, "musicartist", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return LibraryManager.GetArtist(info.ItemName);
|
||||
}
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(info.Path))
|
||||
{
|
||||
var itemByPath = LibraryManager.RootFolder.FindByPath(info.Path);
|
||||
|
@ -1243,23 +1231,6 @@ namespace MediaBrowser.Controller.Entities
|
|||
return itemByPath;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(info.ItemName) && !string.IsNullOrWhiteSpace(info.ItemType))
|
||||
{
|
||||
return LibraryManager.RootFolder.GetRecursiveChildren(i =>
|
||||
{
|
||||
if (string.Equals(i.Name, info.ItemName, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
if (string.Equals(i.GetType().Name, info.ItemType, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
|
||||
}).FirstOrDefault();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
|
|
@ -334,22 +334,9 @@ namespace MediaBrowser.Controller.Entities
|
|||
{
|
||||
if (this is ICollectionFolder && !(this is BasePluginFolder))
|
||||
{
|
||||
if (user.Policy.BlockedMediaFolders != null)
|
||||
if (!user.Policy.EnableAllFolders && !user.Policy.EnabledFolders.Contains(Id.ToString("N"), StringComparer.OrdinalIgnoreCase))
|
||||
{
|
||||
if (user.Policy.BlockedMediaFolders.Contains(Id.ToString("N"), StringComparer.OrdinalIgnoreCase) ||
|
||||
|
||||
// Backwards compatibility
|
||||
user.Policy.BlockedMediaFolders.Contains(Name, StringComparer.OrdinalIgnoreCase))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!user.Policy.EnableAllFolders && !user.Policy.EnabledFolders.Contains(Id.ToString("N"), StringComparer.OrdinalIgnoreCase))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -9,9 +9,6 @@ namespace MediaBrowser.Controller.Entities
|
|||
public string Path { get; set; }
|
||||
public LinkedChildType Type { get; set; }
|
||||
|
||||
public string ItemName { get; set; }
|
||||
public string ItemType { get; set; }
|
||||
|
||||
[IgnoreDataMember]
|
||||
public string Id { get; set; }
|
||||
|
||||
|
|
|
@ -501,7 +501,8 @@ namespace MediaBrowser.Controller.Entities
|
|||
Formats = (i.FormatName ?? string.Empty).Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).ToList(),
|
||||
Timestamp = i.Timestamp,
|
||||
Type = type,
|
||||
PlayableStreamFileNames = i.PlayableStreamFileNames.ToList()
|
||||
PlayableStreamFileNames = i.PlayableStreamFileNames.ToList(),
|
||||
SupportsDirectStream = i.VideoType == VideoType.VideoFile
|
||||
};
|
||||
|
||||
if (i.IsShortcut)
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
using MediaBrowser.Controller.Persistence;
|
||||
using MediaBrowser.Model.Dto;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.MediaInfo;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
|
@ -62,7 +63,7 @@ namespace MediaBrowser.Controller.Library
|
|||
/// <param name="enablePathSubstitution">if set to <c>true</c> [enable path substitution].</param>
|
||||
/// <param name="user">The user.</param>
|
||||
/// <returns>IEnumerable<MediaSourceInfo>.</returns>
|
||||
IEnumerable<MediaSourceInfo> GetStaticMediaSources(IHasMediaSources item, bool enablePathSubstitution, User user);
|
||||
IEnumerable<MediaSourceInfo> GetStaticMediaSources(IHasMediaSources item, bool enablePathSubstitution, User user = null);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the static media source.
|
||||
|
@ -72,5 +73,38 @@ namespace MediaBrowser.Controller.Library
|
|||
/// <param name="enablePathSubstitution">if set to <c>true</c> [enable path substitution].</param>
|
||||
/// <returns>MediaSourceInfo.</returns>
|
||||
MediaSourceInfo GetStaticMediaSource(IHasMediaSources item, string mediaSourceId, bool enablePathSubstitution);
|
||||
|
||||
/// <summary>
|
||||
/// Opens the media source.
|
||||
/// </summary>
|
||||
/// <param name="request">The request.</param>
|
||||
/// <param name="enableAutoClose">if set to <c>true</c> [enable automatic close].</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>Task<MediaSourceInfo>.</returns>
|
||||
Task<LiveStreamResponse> OpenLiveStream(LiveStreamRequest request, bool enableAutoClose, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the live stream.
|
||||
/// </summary>
|
||||
/// <param name="id">The identifier.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>Task<MediaSourceInfo>.</returns>
|
||||
Task<MediaSourceInfo> GetLiveStream(string id, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Pings the media source.
|
||||
/// </summary>
|
||||
/// <param name="id">The live stream identifier.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>Task.</returns>
|
||||
Task PingLiveStream(string id, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Closes the media source.
|
||||
/// </summary>
|
||||
/// <param name="id">The live stream identifier.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>Task.</returns>
|
||||
Task CloseLiveStream(string id, CancellationToken cancellationToken);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,5 +15,21 @@ namespace MediaBrowser.Controller.Library
|
|||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>Task<IEnumerable<MediaSourceInfo>>.</returns>
|
||||
Task<IEnumerable<MediaSourceInfo>> GetMediaSources(IHasMediaSources item, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Opens the media source.
|
||||
/// </summary>
|
||||
/// <param name="openToken">The open token.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>Task<MediaSourceInfo>.</returns>
|
||||
Task<MediaSourceInfo> OpenMediaSource(string openToken, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Closes the media source.
|
||||
/// </summary>
|
||||
/// <param name="liveStreamId">The live stream identifier.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>Task.</returns>
|
||||
Task CloseMediaSource(string liveStreamId, CancellationToken cancellationToken);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
|
||||
using System;
|
||||
|
||||
namespace MediaBrowser.Controller.LiveTv
|
||||
{
|
||||
public interface ILiveTvItem
|
||||
{
|
||||
Guid Id { get; }
|
||||
string ServiceName { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -301,5 +301,21 @@ namespace MediaBrowser.Controller.LiveTv
|
|||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>Task<QueryResult<BaseItem>>.</returns>
|
||||
Task<QueryResult<BaseItem>> GetInternalRecordings(RecordingQuery query, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the recording media sources.
|
||||
/// </summary>
|
||||
/// <param name="id">The identifier.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>Task<IEnumerable<MediaSourceInfo>>.</returns>
|
||||
Task<IEnumerable<MediaSourceInfo>> GetRecordingMediaSources(string id, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the channel media sources.
|
||||
/// </summary>
|
||||
/// <param name="id">The identifier.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>Task<IEnumerable<MediaSourceInfo>>.</returns>
|
||||
Task<IEnumerable<MediaSourceInfo>> GetChannelMediaSources(string id, CancellationToken cancellationToken);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
using System.Runtime.Serialization;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Entities.Audio;
|
||||
using MediaBrowser.Model.Configuration;
|
||||
using MediaBrowser.Model.Dto;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.Users;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
namespace MediaBrowser.Controller.LiveTv
|
||||
{
|
||||
|
@ -99,5 +101,20 @@ namespace MediaBrowser.Controller.LiveTv
|
|||
{
|
||||
return user.Policy.EnableLiveTvManagement;
|
||||
}
|
||||
|
||||
public override IEnumerable<MediaSourceInfo> GetMediaSources(bool enablePathSubstitution)
|
||||
{
|
||||
var list = base.GetMediaSources(enablePathSubstitution).ToList();
|
||||
|
||||
foreach (var mediaSource in list)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(mediaSource.Path))
|
||||
{
|
||||
mediaSource.Type = MediaSourceType.Placeholder;
|
||||
}
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -82,6 +82,15 @@ namespace MediaBrowser.Controller.LiveTv
|
|||
/// <value><c>null</c> if [has image] contains no value, <c>true</c> if [has image]; otherwise, <c>false</c>.</value>
|
||||
public bool? HasProviderImage { get; set; }
|
||||
|
||||
public override LocationType LocationType
|
||||
{
|
||||
get
|
||||
{
|
||||
// TODO: This should be removed
|
||||
return LocationType.Remote;
|
||||
}
|
||||
}
|
||||
|
||||
protected override string CreateSortName()
|
||||
{
|
||||
double number = 0;
|
||||
|
@ -127,7 +136,7 @@ namespace MediaBrowser.Controller.LiveTv
|
|||
Name = Name,
|
||||
Path = Path,
|
||||
RunTimeTicks = RunTimeTicks,
|
||||
Type = MediaSourceType.Default
|
||||
Type = MediaSourceType.Placeholder
|
||||
};
|
||||
|
||||
list.Add(info);
|
||||
|
|
|
@ -34,6 +34,12 @@ namespace MediaBrowser.Controller.LiveTv
|
|||
/// <value>The channel identifier.</value>
|
||||
public string ExternalChannelId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the original air date.
|
||||
/// </summary>
|
||||
/// <value>The original air date.</value>
|
||||
public DateTime? OriginalAirDate { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the type of the channel.
|
||||
/// </summary>
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
using System.Runtime.Serialization;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Model.Configuration;
|
||||
using MediaBrowser.Model.Dto;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using System.Linq;
|
||||
using MediaBrowser.Model.Users;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
namespace MediaBrowser.Controller.LiveTv
|
||||
{
|
||||
|
@ -97,5 +99,20 @@ namespace MediaBrowser.Controller.LiveTv
|
|||
{
|
||||
return user.Policy.EnableLiveTvManagement;
|
||||
}
|
||||
|
||||
public override IEnumerable<MediaSourceInfo> GetMediaSources(bool enablePathSubstitution)
|
||||
{
|
||||
var list = base.GetMediaSources(enablePathSubstitution).ToList();
|
||||
|
||||
foreach (var mediaSource in list)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(mediaSource.Path))
|
||||
{
|
||||
mediaSource.Type = MediaSourceType.Placeholder;
|
||||
}
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -393,12 +393,13 @@
|
|||
<Compile Include="Subtitles\SubtitleDownloadEventArgs.cs" />
|
||||
<Compile Include="Subtitles\SubtitleResponse.cs" />
|
||||
<Compile Include="Subtitles\SubtitleSearchRequest.cs" />
|
||||
<Compile Include="Sync\IHasDynamicAccess.cs" />
|
||||
<Compile Include="Sync\IServerSyncProvider.cs" />
|
||||
<Compile Include="Sync\ISyncDataProvider.cs" />
|
||||
<Compile Include="Sync\ISyncManager.cs" />
|
||||
<Compile Include="Sync\ISyncProvider.cs" />
|
||||
<Compile Include="Sync\ISyncRepository.cs" />
|
||||
<Compile Include="Sync\SendFileResult.cs" />
|
||||
<Compile Include="Sync\SyncedFileInfo.cs" />
|
||||
<Compile Include="Themes\IAppThemeManager.cs" />
|
||||
<Compile Include="Themes\InternalThemeImage.cs" />
|
||||
<Compile Include="TV\ITVSeriesManager.cs" />
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
using System.IO;
|
||||
using MediaBrowser.Model.MediaInfo;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
|
@ -47,8 +48,9 @@ namespace MediaBrowser.Controller.MediaEncoding
|
|||
/// Gets the subtitle language encoding parameter.
|
||||
/// </summary>
|
||||
/// <param name="path">The path.</param>
|
||||
/// <param name="protocol">The protocol.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>System.String.</returns>
|
||||
string GetSubtitleFileCharacterSet(string path);
|
||||
|
||||
Task<string> GetSubtitleFileCharacterSet(string path, MediaProtocol protocol, CancellationToken cancellationToken);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -41,8 +41,6 @@ namespace MediaBrowser.Controller.MediaEncoding
|
|||
streams = GetSortedStreams(streams, MediaStreamType.Subtitle, preferredLanguages)
|
||||
.ToList();
|
||||
|
||||
var full = streams.Where(s => !s.IsForced);
|
||||
|
||||
MediaStream stream = null;
|
||||
|
||||
if (mode == SubtitlePlaybackMode.None)
|
||||
|
@ -55,13 +53,13 @@ namespace MediaBrowser.Controller.MediaEncoding
|
|||
// if the audio language is not understood by the user, load their preferred subs, if there are any
|
||||
if (!ContainsOrdinal(preferredLanguages, audioTrackLanguage))
|
||||
{
|
||||
stream = full.FirstOrDefault(s => ContainsOrdinal(preferredLanguages, s.Language));
|
||||
stream = streams.Where(s => !s.IsForced).FirstOrDefault(s => ContainsOrdinal(preferredLanguages, s.Language));
|
||||
}
|
||||
}
|
||||
else if (mode == SubtitlePlaybackMode.Always)
|
||||
{
|
||||
// always load the most suitable full subtitles
|
||||
stream = full.FirstOrDefault();
|
||||
stream = streams.FirstOrDefault(s => !s.IsForced);
|
||||
}
|
||||
|
||||
// load forced subs if we have found no suitable full subtitles
|
||||
|
@ -97,6 +95,77 @@ namespace MediaBrowser.Controller.MediaEncoding
|
|||
.ThenBy(i => i.Index);
|
||||
}
|
||||
|
||||
public static void SetSubtitleStreamScores(List<MediaStream> streams,
|
||||
List<string> preferredLanguages,
|
||||
SubtitlePlaybackMode mode,
|
||||
string audioTrackLanguage)
|
||||
{
|
||||
if (mode == SubtitlePlaybackMode.None)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
streams = GetSortedStreams(streams, MediaStreamType.Subtitle, preferredLanguages)
|
||||
.ToList();
|
||||
|
||||
var filteredStreams = new List<MediaStream>();
|
||||
|
||||
if (mode == SubtitlePlaybackMode.Default)
|
||||
{
|
||||
// if the audio language is not understood by the user, load their preferred subs, if there are any
|
||||
if (!ContainsOrdinal(preferredLanguages, audioTrackLanguage))
|
||||
{
|
||||
filteredStreams = streams.Where(s => !s.IsForced && ContainsOrdinal(preferredLanguages, s.Language))
|
||||
.ToList();
|
||||
}
|
||||
}
|
||||
else if (mode == SubtitlePlaybackMode.Always)
|
||||
{
|
||||
// always load the most suitable full subtitles
|
||||
filteredStreams = streams.Where(s => !s.IsForced)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
// load forced subs if we have found no suitable full subtitles
|
||||
if (filteredStreams.Count == 0)
|
||||
{
|
||||
filteredStreams = streams
|
||||
.Where(s => s.IsForced && string.Equals(s.Language, audioTrackLanguage, StringComparison.OrdinalIgnoreCase))
|
||||
.ToList();
|
||||
}
|
||||
|
||||
foreach (var stream in filteredStreams)
|
||||
{
|
||||
stream.Score = GetSubtitleScore(stream, preferredLanguages);
|
||||
}
|
||||
}
|
||||
|
||||
private static int GetSubtitleScore(MediaStream stream, List<string> languagePreferences)
|
||||
{
|
||||
var values = new List<int>();
|
||||
|
||||
var index = languagePreferences.FindIndex(l => string.Equals(stream.Language, l, StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
values.Add(index == -1 ? 0 : 100 - index);
|
||||
|
||||
values.Add(stream.IsDefault ? 1 : 0);
|
||||
values.Add(stream.SupportsExternalStream ? 1 : 0);
|
||||
values.Add(stream.IsTextSubtitleStream ? 1 : 0);
|
||||
values.Add(stream.IsExternal ? 1 : 0);
|
||||
|
||||
values.Reverse();
|
||||
var scale = 1;
|
||||
var score = 0;
|
||||
|
||||
foreach (var value in values)
|
||||
{
|
||||
score += scale * (value + 1);
|
||||
scale *= 10;
|
||||
}
|
||||
|
||||
return score;
|
||||
}
|
||||
|
||||
private static int GetBooleanOrderBy(bool value)
|
||||
{
|
||||
return value ? 0 : 1;
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
using MediaBrowser.Common.Net;
|
||||
using MediaBrowser.Model.Logging;
|
||||
using MediaBrowser.Model.Logging;
|
||||
using MediaBrowser.Model.Net;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
@ -75,12 +74,12 @@ namespace MediaBrowser.Controller.Net
|
|||
throw new ArgumentNullException("message");
|
||||
}
|
||||
|
||||
if (message.MessageType.Equals(Name + "Start", StringComparison.OrdinalIgnoreCase))
|
||||
if (string.Equals(message.MessageType, Name + "Start", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
Start(message);
|
||||
}
|
||||
|
||||
if (message.MessageType.Equals(Name + "Stop", StringComparison.OrdinalIgnoreCase))
|
||||
if (string.Equals(message.MessageType, Name + "Stop", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
Stop(message);
|
||||
}
|
||||
|
|
|
@ -1404,24 +1404,12 @@ namespace MediaBrowser.Controller.Providers
|
|||
{
|
||||
switch (reader.Name)
|
||||
{
|
||||
case "Name":
|
||||
{
|
||||
linkedItem.ItemName = reader.ReadElementContentAsString();
|
||||
break;
|
||||
}
|
||||
|
||||
case "Path":
|
||||
{
|
||||
linkedItem.Path = reader.ReadElementContentAsString();
|
||||
break;
|
||||
}
|
||||
|
||||
case "Type":
|
||||
{
|
||||
linkedItem.ItemType = reader.ReadElementContentAsString();
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
reader.Skip();
|
||||
break;
|
||||
|
@ -1435,7 +1423,7 @@ namespace MediaBrowser.Controller.Providers
|
|||
return linkedItem;
|
||||
}
|
||||
|
||||
return string.IsNullOrWhiteSpace(linkedItem.ItemName) || string.IsNullOrWhiteSpace(linkedItem.ItemType) ? null : linkedItem;
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
|
|
18
MediaBrowser.Controller/Sync/IHasDynamicAccess.cs
Normal file
18
MediaBrowser.Controller/Sync/IHasDynamicAccess.cs
Normal file
|
@ -0,0 +1,18 @@
|
|||
using MediaBrowser.Model.Sync;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MediaBrowser.Controller.Sync
|
||||
{
|
||||
public interface IHasDynamicAccess
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the synced file information.
|
||||
/// </summary>
|
||||
/// <param name="remotePath">The remote path.</param>
|
||||
/// <param name="target">The target.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>Task<SyncedFileInfo>.</returns>
|
||||
Task<SyncedFileInfo> GetSyncedFileInfo(string remotePath, SyncTarget target, CancellationToken cancellationToken);
|
||||
}
|
||||
}
|
|
@ -18,7 +18,7 @@ namespace MediaBrowser.Controller.Sync
|
|||
/// <param name="progress">The progress.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>Task.</returns>
|
||||
Task<SendFileResult> SendFile(Stream stream, string remotePath, SyncTarget target, IProgress<double> progress, CancellationToken cancellationToken);
|
||||
Task<SyncedFileInfo> SendFile(Stream stream, string remotePath, SyncTarget target, IProgress<double> progress, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Deletes the file.
|
||||
|
@ -54,14 +54,5 @@ namespace MediaBrowser.Controller.Sync
|
|||
/// <param name="target">The target.</param>
|
||||
/// <returns>System.String.</returns>
|
||||
string GetParentDirectoryPath(string path, SyncTarget target);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the file system entries.
|
||||
/// </summary>
|
||||
/// <param name="path">The path.</param>
|
||||
/// <param name="target">The target.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>Task<List<DeviceFileInfo>>.</returns>
|
||||
Task<List<DeviceFileInfo>> GetFileSystemEntries(string path, SyncTarget target, CancellationToken cancellationToken);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,6 +14,14 @@ namespace MediaBrowser.Controller.Sync
|
|||
/// <returns>Task<List<System.String>>.</returns>
|
||||
Task<List<string>> GetServerItemIds(SyncTarget target, string serverId);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the synchronize job item ids.
|
||||
/// </summary>
|
||||
/// <param name="target">The target.</param>
|
||||
/// <param name="serverId">The server identifier.</param>
|
||||
/// <returns>Task<List<System.String>>.</returns>
|
||||
Task<List<string>> GetSyncJobItemIds(SyncTarget target, string serverId);
|
||||
|
||||
/// <summary>
|
||||
/// Adds the or update.
|
||||
/// </summary>
|
||||
|
@ -46,5 +54,13 @@ namespace MediaBrowser.Controller.Sync
|
|||
/// <param name="itemId">The item identifier.</param>
|
||||
/// <returns>Task<LocalItem>.</returns>
|
||||
Task<List<LocalItem>> GetCachedItems(SyncTarget target, string serverId, string itemId);
|
||||
/// <summary>
|
||||
/// Gets the cached items by synchronize job item identifier.
|
||||
/// </summary>
|
||||
/// <param name="target">The target.</param>
|
||||
/// <param name="serverId">The server identifier.</param>
|
||||
/// <param name="syncJobItemId">The synchronize job item identifier.</param>
|
||||
/// <returns>Task<List<LocalItem>>.</returns>
|
||||
Task<List<LocalItem>> GetCachedItemsBySyncJobItemId(SyncTarget target, string serverId, string syncJobItemId);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
using MediaBrowser.Model.MediaInfo;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace MediaBrowser.Controller.Sync
|
||||
{
|
||||
public class SendFileResult
|
||||
public class SyncedFileInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the path.
|
||||
|
@ -14,5 +15,15 @@ namespace MediaBrowser.Controller.Sync
|
|||
/// </summary>
|
||||
/// <value>The protocol.</value>
|
||||
public MediaProtocol Protocol { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets the required HTTP headers.
|
||||
/// </summary>
|
||||
/// <value>The required HTTP headers.</value>
|
||||
public Dictionary<string, string> RequiredHttpHeaders { get; set; }
|
||||
|
||||
public SyncedFileInfo()
|
||||
{
|
||||
RequiredHttpHeaders = new Dictionary<string, string>();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -124,7 +124,7 @@ namespace MediaBrowser.Dlna.Didl
|
|||
{
|
||||
if (streamInfo == null)
|
||||
{
|
||||
var sources = _user == null ? video.GetMediaSources(true).ToList() : _mediaSourceManager.GetStaticMediaSources(video, true, _user).ToList();
|
||||
var sources = _mediaSourceManager.GetStaticMediaSources(video, true, _user).ToList();
|
||||
|
||||
streamInfo = new StreamBuilder().BuildVideoItem(new VideoOptions
|
||||
{
|
||||
|
@ -158,16 +158,21 @@ namespace MediaBrowser.Dlna.Didl
|
|||
streamInfo.TranscodeSeekInfo,
|
||||
streamInfo.IsTargetAnamorphic,
|
||||
streamInfo.IsTargetCabac,
|
||||
streamInfo.TargetRefFrames);
|
||||
streamInfo.TargetRefFrames,
|
||||
streamInfo.TargetVideoStreamCount,
|
||||
streamInfo.TargetAudioStreamCount);
|
||||
|
||||
foreach (var contentFeature in contentFeatureList)
|
||||
{
|
||||
AddVideoResource(container, video, deviceId, filter, contentFeature, streamInfo);
|
||||
}
|
||||
|
||||
foreach (var subtitle in streamInfo.GetExternalSubtitles(_serverAddress, _accessToken, false))
|
||||
foreach (var subtitle in streamInfo.GetSubtitleProfiles(false, _serverAddress, _accessToken))
|
||||
{
|
||||
AddSubtitleElement(container, subtitle);
|
||||
if (subtitle.DeliveryMethod == SubtitleDeliveryMethod.External)
|
||||
{
|
||||
AddSubtitleElement(container, subtitle);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -280,7 +285,9 @@ namespace MediaBrowser.Dlna.Didl
|
|||
streamInfo.TargetTimestamp,
|
||||
streamInfo.IsTargetAnamorphic,
|
||||
streamInfo.IsTargetCabac,
|
||||
streamInfo.TargetRefFrames);
|
||||
streamInfo.TargetRefFrames,
|
||||
streamInfo.TargetVideoStreamCount,
|
||||
streamInfo.TargetAudioStreamCount);
|
||||
|
||||
var filename = url.Substring(0, url.IndexOf('?'));
|
||||
|
||||
|
@ -344,7 +351,7 @@ namespace MediaBrowser.Dlna.Didl
|
|||
|
||||
if (streamInfo == null)
|
||||
{
|
||||
var sources = _user == null ? audio.GetMediaSources(true).ToList() : _mediaSourceManager.GetStaticMediaSources(audio, true, _user).ToList();
|
||||
var sources = _mediaSourceManager.GetStaticMediaSources(audio, true, _user).ToList();
|
||||
|
||||
streamInfo = new StreamBuilder().BuildAudioItem(new AudioOptions
|
||||
{
|
||||
|
|
|
@ -470,13 +470,13 @@ namespace MediaBrowser.Dlna.PlayTo
|
|||
|
||||
var hasMediaSources = item as IHasMediaSources;
|
||||
var mediaSources = hasMediaSources != null
|
||||
? (user == null ? hasMediaSources.GetMediaSources(true) : _mediaSourceManager.GetStaticMediaSources(hasMediaSources, true, user)).ToList()
|
||||
? (_mediaSourceManager.GetStaticMediaSources(hasMediaSources, true, user)).ToList()
|
||||
: new List<MediaSourceInfo>();
|
||||
|
||||
var playlistItem = GetPlaylistItem(item, mediaSources, profile, _session.DeviceId, mediaSourceId, audioStreamIndex, subtitleStreamIndex);
|
||||
playlistItem.StreamInfo.StartPositionTicks = startPostionTicks;
|
||||
|
||||
playlistItem.StreamUrl = playlistItem.StreamInfo.ToUrl(_serverAddress, _accessToken);
|
||||
playlistItem.StreamUrl = playlistItem.StreamInfo.ToDlnaUrl(_serverAddress, _accessToken);
|
||||
|
||||
var itemXml = new DidlBuilder(profile, user, _imageProcessor, _serverAddress, _accessToken, _userDataManager, _localization, _mediaSourceManager)
|
||||
.GetItemDidl(item, null, _session.DeviceId, new Filter(), playlistItem.StreamInfo);
|
||||
|
@ -526,7 +526,9 @@ namespace MediaBrowser.Dlna.PlayTo
|
|||
streamInfo.TranscodeSeekInfo,
|
||||
streamInfo.IsTargetAnamorphic,
|
||||
streamInfo.IsTargetCabac,
|
||||
streamInfo.TargetRefFrames);
|
||||
streamInfo.TargetRefFrames,
|
||||
streamInfo.TargetVideoStreamCount,
|
||||
streamInfo.TargetAudioStreamCount);
|
||||
|
||||
return list.FirstOrDefault();
|
||||
}
|
||||
|
|
|
@ -92,7 +92,7 @@ namespace MediaBrowser.LocalMetadata
|
|||
{
|
||||
get
|
||||
{
|
||||
return "Media Browser Xml";
|
||||
return "Emby Xml";
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -186,18 +186,17 @@ namespace MediaBrowser.LocalMetadata.Images
|
|||
names.Add("movie");
|
||||
}
|
||||
|
||||
foreach (var name in names)
|
||||
{
|
||||
AddImage(files, images, imagePrefix + name, ImageType.Primary);
|
||||
}
|
||||
|
||||
var fileNameWithoutExtension = item.FileNameWithoutExtension;
|
||||
|
||||
if (!string.IsNullOrEmpty(fileNameWithoutExtension))
|
||||
{
|
||||
AddImage(files, images, fileNameWithoutExtension, ImageType.Primary);
|
||||
}
|
||||
|
||||
foreach (var name in names)
|
||||
{
|
||||
AddImage(files, images, imagePrefix + name, ImageType.Primary);
|
||||
}
|
||||
|
||||
if (!isInMixedFolder)
|
||||
{
|
||||
foreach (var name in names)
|
||||
|
|
|
@ -756,11 +756,6 @@ namespace MediaBrowser.LocalMetadata.Savers
|
|||
{
|
||||
builder.Append("<" + singularNodeName + ">");
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(link.ItemType))
|
||||
{
|
||||
builder.Append("<Type>" + SecurityElement.Escape(link.ItemType) + "</Type>");
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(link.Path))
|
||||
{
|
||||
builder.Append("<Path>" + SecurityElement.Escape((link.Path)) + "</Path>");
|
||||
|
|
|
@ -14,7 +14,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
|||
{
|
||||
public class AudioEncoder : BaseEncoder
|
||||
{
|
||||
public AudioEncoder(MediaEncoder mediaEncoder, ILogger logger, IServerConfigurationManager configurationManager, IFileSystem fileSystem, ILiveTvManager liveTvManager, IIsoManager isoManager, ILibraryManager libraryManager, IChannelManager channelManager, ISessionManager sessionManager, ISubtitleEncoder subtitleEncoder, IMediaSourceManager mediaSourceManager) : base(mediaEncoder, logger, configurationManager, fileSystem, liveTvManager, isoManager, libraryManager, channelManager, sessionManager, subtitleEncoder, mediaSourceManager)
|
||||
public AudioEncoder(MediaEncoder mediaEncoder, ILogger logger, IServerConfigurationManager configurationManager, IFileSystem fileSystem, IIsoManager isoManager, ILibraryManager libraryManager, ISessionManager sessionManager, ISubtitleEncoder subtitleEncoder, IMediaSourceManager mediaSourceManager) : base(mediaEncoder, logger, configurationManager, fileSystem, isoManager, libraryManager, sessionManager, subtitleEncoder, mediaSourceManager)
|
||||
{
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
using MediaBrowser.Common.Configuration;
|
||||
using MediaBrowser.Common.IO;
|
||||
using MediaBrowser.Controller.Channels;
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Controller.LiveTv;
|
||||
|
@ -14,6 +13,7 @@ using MediaBrowser.Model.Dto;
|
|||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.IO;
|
||||
using MediaBrowser.Model.Logging;
|
||||
using MediaBrowser.Model.MediaInfo;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
|
@ -31,10 +31,8 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
|||
protected readonly ILogger Logger;
|
||||
protected readonly IServerConfigurationManager ConfigurationManager;
|
||||
protected readonly IFileSystem FileSystem;
|
||||
protected readonly ILiveTvManager LiveTvManager;
|
||||
protected readonly IIsoManager IsoManager;
|
||||
protected readonly ILibraryManager LibraryManager;
|
||||
protected readonly IChannelManager ChannelManager;
|
||||
protected readonly ISessionManager SessionManager;
|
||||
protected readonly ISubtitleEncoder SubtitleEncoder;
|
||||
protected readonly IMediaSourceManager MediaSourceManager;
|
||||
|
@ -45,20 +43,18 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
|||
ILogger logger,
|
||||
IServerConfigurationManager configurationManager,
|
||||
IFileSystem fileSystem,
|
||||
ILiveTvManager liveTvManager,
|
||||
IIsoManager isoManager,
|
||||
ILibraryManager libraryManager,
|
||||
IChannelManager channelManager,
|
||||
ISessionManager sessionManager, ISubtitleEncoder subtitleEncoder, IMediaSourceManager mediaSourceManager)
|
||||
ISessionManager sessionManager,
|
||||
ISubtitleEncoder subtitleEncoder,
|
||||
IMediaSourceManager mediaSourceManager)
|
||||
{
|
||||
MediaEncoder = mediaEncoder;
|
||||
Logger = logger;
|
||||
ConfigurationManager = configurationManager;
|
||||
FileSystem = fileSystem;
|
||||
LiveTvManager = liveTvManager;
|
||||
IsoManager = isoManager;
|
||||
LibraryManager = libraryManager;
|
||||
ChannelManager = channelManager;
|
||||
SessionManager = sessionManager;
|
||||
SubtitleEncoder = subtitleEncoder;
|
||||
MediaSourceManager = mediaSourceManager;
|
||||
|
@ -68,7 +64,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
|||
IProgress<double> progress,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var encodingJob = await new EncodingJobFactory(Logger, LiveTvManager, LibraryManager, ChannelManager, MediaSourceManager)
|
||||
var encodingJob = await new EncodingJobFactory(Logger, LibraryManager, MediaSourceManager)
|
||||
.CreateJob(options, IsVideoEncoder, progress, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
encodingJob.OutputFilePath = GetOutputFilePath(encodingJob);
|
||||
|
@ -477,53 +473,25 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
|||
state.IsoMount = await IsoManager.Mount(state.MediaPath, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(state.MediaPath))
|
||||
if (state.MediaSource.RequiresOpening)
|
||||
{
|
||||
var checkCodecs = false;
|
||||
|
||||
if (string.Equals(state.ItemType, typeof(LiveTvChannel).Name))
|
||||
var liveStreamResponse = await MediaSourceManager.OpenLiveStream(new LiveStreamRequest
|
||||
{
|
||||
var streamInfo = await LiveTvManager.GetChannelStream(state.Options.ItemId, cancellationToken).ConfigureAwait(false);
|
||||
OpenToken = state.MediaSource.OpenToken
|
||||
|
||||
state.LiveTvStreamId = streamInfo.Id;
|
||||
}, false, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
state.MediaPath = streamInfo.Path;
|
||||
state.InputProtocol = streamInfo.Protocol;
|
||||
AttachMediaStreamInfo(state, liveStreamResponse.MediaSource, state.Options);
|
||||
|
||||
await Task.Delay(1500, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
AttachMediaStreamInfo(state, streamInfo, state.Options);
|
||||
checkCodecs = true;
|
||||
}
|
||||
|
||||
else if (string.Equals(state.ItemType, typeof(LiveTvVideoRecording).Name) ||
|
||||
string.Equals(state.ItemType, typeof(LiveTvAudioRecording).Name))
|
||||
if (state.IsVideoRequest)
|
||||
{
|
||||
var streamInfo = await LiveTvManager.GetRecordingStream(state.Options.ItemId, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
state.LiveTvStreamId = streamInfo.Id;
|
||||
|
||||
state.MediaPath = streamInfo.Path;
|
||||
state.InputProtocol = streamInfo.Protocol;
|
||||
|
||||
await Task.Delay(1500, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
AttachMediaStreamInfo(state, streamInfo, state.Options);
|
||||
checkCodecs = true;
|
||||
EncodingJobFactory.TryStreamCopy(state, state.Options);
|
||||
}
|
||||
}
|
||||
|
||||
if (state.IsVideoRequest && checkCodecs)
|
||||
{
|
||||
if (state.VideoStream != null && EncodingJobFactory.CanStreamCopyVideo(state.Options, state.VideoStream))
|
||||
{
|
||||
state.OutputVideoCodec = "copy";
|
||||
}
|
||||
|
||||
if (state.AudioStream != null && EncodingJobFactory.CanStreamCopyAudio(state.Options, state.AudioStream, state.SupportedAudioCodecs))
|
||||
{
|
||||
state.OutputAudioCodec = "copy";
|
||||
}
|
||||
}
|
||||
if (state.MediaSource.BufferMs.HasValue)
|
||||
{
|
||||
await Task.Delay(state.MediaSource.BufferMs.Value, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -531,22 +499,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
|||
MediaSourceInfo mediaSource,
|
||||
EncodingJobOptions videoRequest)
|
||||
{
|
||||
state.InputProtocol = mediaSource.Protocol;
|
||||
state.MediaPath = mediaSource.Path;
|
||||
state.RunTimeTicks = mediaSource.RunTimeTicks;
|
||||
state.RemoteHttpHeaders = mediaSource.RequiredHttpHeaders;
|
||||
state.InputBitrate = mediaSource.Bitrate;
|
||||
state.InputFileSize = mediaSource.Size;
|
||||
state.ReadInputAtNativeFramerate = mediaSource.ReadAtNativeFramerate;
|
||||
|
||||
if (state.ReadInputAtNativeFramerate)
|
||||
{
|
||||
state.OutputAudioSync = "1000";
|
||||
state.InputVideoSync = "-1";
|
||||
state.InputAudioSync = "1";
|
||||
}
|
||||
|
||||
EncodingJobFactory.AttachMediaStreamInfo(state, mediaSource.MediaStreams, videoRequest);
|
||||
EncodingJobFactory.AttachMediaStreamInfo(state, mediaSource, videoRequest);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -998,7 +951,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
|||
|
||||
if (!string.IsNullOrEmpty(state.SubtitleStream.Language))
|
||||
{
|
||||
var charenc = SubtitleEncoder.GetSubtitleFileCharacterSet(subtitlePath);
|
||||
var charenc = SubtitleEncoder.GetSubtitleFileCharacterSet(subtitlePath, state.MediaSource.Protocol, CancellationToken.None).Result;
|
||||
|
||||
if (!string.IsNullOrEmpty(charenc))
|
||||
{
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
using MediaBrowser.Controller.LiveTv;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Controller.MediaEncoding;
|
||||
using MediaBrowser.Model.Dlna;
|
||||
using MediaBrowser.Model.Drawing;
|
||||
using MediaBrowser.Model.Dto;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.IO;
|
||||
using MediaBrowser.Model.Logging;
|
||||
|
@ -26,7 +27,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
|||
|
||||
public EncodingJobOptions Options { get; set; }
|
||||
public string InputContainer { get; set; }
|
||||
public List<MediaStream> AllMediaStreams { get; set; }
|
||||
public MediaSourceInfo MediaSource { get; set; }
|
||||
public MediaStream AudioStream { get; set; }
|
||||
public MediaStream VideoStream { get; set; }
|
||||
public MediaStream SubtitleStream { get; set; }
|
||||
|
@ -76,12 +77,12 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
|||
}
|
||||
|
||||
private readonly ILogger _logger;
|
||||
private readonly ILiveTvManager _liveTvManager;
|
||||
private readonly IMediaSourceManager _mediaSourceManager;
|
||||
|
||||
public EncodingJob(ILogger logger, ILiveTvManager liveTvManager)
|
||||
public EncodingJob(ILogger logger, IMediaSourceManager mediaSourceManager)
|
||||
{
|
||||
_logger = logger;
|
||||
_liveTvManager = liveTvManager;
|
||||
_mediaSourceManager = mediaSourceManager;
|
||||
Id = Guid.NewGuid().ToString("N");
|
||||
|
||||
RemoteHttpHeaders = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||
|
@ -89,7 +90,6 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
|||
SupportedAudioCodecs = new List<string>();
|
||||
PlayableStreamFileNames = new List<string>();
|
||||
RemoteHttpHeaders = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||
AllMediaStreams = new List<MediaStream>();
|
||||
TaskCompletionSource = new TaskCompletionSource<bool>();
|
||||
}
|
||||
|
||||
|
@ -136,15 +136,15 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
|||
|
||||
private async void DisposeLiveStream()
|
||||
{
|
||||
if (!string.IsNullOrEmpty(LiveTvStreamId))
|
||||
if (MediaSource.RequiresClosing)
|
||||
{
|
||||
try
|
||||
{
|
||||
await _liveTvManager.CloseLiveStream(LiveTvStreamId, CancellationToken.None).ConfigureAwait(false);
|
||||
await _mediaSourceManager.CloseLiveStream(MediaSource.LiveStreamId, CancellationToken.None).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.ErrorException("Error closing live tv stream", ex);
|
||||
_logger.ErrorException("Error closing media source", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -394,6 +394,42 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
|||
}
|
||||
}
|
||||
|
||||
public int? TargetVideoStreamCount
|
||||
{
|
||||
get
|
||||
{
|
||||
if (Options.Static)
|
||||
{
|
||||
return GetMediaStreamCount(MediaStreamType.Video, int.MaxValue);
|
||||
}
|
||||
return GetMediaStreamCount(MediaStreamType.Video, 1);
|
||||
}
|
||||
}
|
||||
|
||||
public int? TargetAudioStreamCount
|
||||
{
|
||||
get
|
||||
{
|
||||
if (Options.Static)
|
||||
{
|
||||
return GetMediaStreamCount(MediaStreamType.Audio, int.MaxValue);
|
||||
}
|
||||
return GetMediaStreamCount(MediaStreamType.Audio, 1);
|
||||
}
|
||||
}
|
||||
|
||||
private int? GetMediaStreamCount(MediaStreamType type, int limit)
|
||||
{
|
||||
var count = MediaSource.GetStreamCount(type);
|
||||
|
||||
if (count.HasValue)
|
||||
{
|
||||
count = Math.Min(count.Value, limit);
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
public void ReportTranscodingProgress(TimeSpan? transcodingPosition, float? framerate, double? percentComplete, long? bytesTranscoded)
|
||||
{
|
||||
var ticks = transcodingPosition.HasValue ? transcodingPosition.Value.Ticks : (long?)null;
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
using MediaBrowser.Controller.Channels;
|
||||
using System.IO;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Controller.LiveTv;
|
||||
using MediaBrowser.Controller.MediaEncoding;
|
||||
using MediaBrowser.Model.Dlna;
|
||||
using MediaBrowser.Model.Dto;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.Logging;
|
||||
using MediaBrowser.Model.MediaInfo;
|
||||
|
@ -19,19 +20,15 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
|||
public class EncodingJobFactory
|
||||
{
|
||||
private readonly ILogger _logger;
|
||||
private readonly ILiveTvManager _liveTvManager;
|
||||
private readonly ILibraryManager _libraryManager;
|
||||
private readonly IChannelManager _channelManager;
|
||||
private readonly IMediaSourceManager _mediaSourceManager;
|
||||
|
||||
protected static readonly CultureInfo UsCulture = new CultureInfo("en-US");
|
||||
|
||||
public EncodingJobFactory(ILogger logger, ILiveTvManager liveTvManager, ILibraryManager libraryManager, IChannelManager channelManager, IMediaSourceManager mediaSourceManager)
|
||||
public EncodingJobFactory(ILogger logger, ILibraryManager libraryManager, IMediaSourceManager mediaSourceManager)
|
||||
{
|
||||
_logger = logger;
|
||||
_liveTvManager = liveTvManager;
|
||||
_libraryManager = libraryManager;
|
||||
_channelManager = channelManager;
|
||||
_mediaSourceManager = mediaSourceManager;
|
||||
}
|
||||
|
||||
|
@ -44,7 +41,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
|||
request.AudioCodec = InferAudioCodec(request.OutputContainer);
|
||||
}
|
||||
|
||||
var state = new EncodingJob(_logger, _liveTvManager)
|
||||
var state = new EncodingJob(_logger, _mediaSourceManager)
|
||||
{
|
||||
Options = options,
|
||||
IsVideoRequest = isVideoRequest,
|
||||
|
@ -58,106 +55,17 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
|||
}
|
||||
|
||||
var item = _libraryManager.GetItemById(request.ItemId);
|
||||
|
||||
List<MediaStream> mediaStreams = null;
|
||||
|
||||
state.ItemType = item.GetType().Name;
|
||||
|
||||
if (item is ILiveTvRecording)
|
||||
{
|
||||
var recording = await _liveTvManager.GetInternalRecording(request.ItemId, cancellationToken).ConfigureAwait(false);
|
||||
state.IsInputVideo = string.Equals(item.MediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
state.VideoType = VideoType.VideoFile;
|
||||
state.IsInputVideo = string.Equals(recording.MediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase);
|
||||
var mediaSources = await _mediaSourceManager.GetPlayackMediaSources(request.ItemId, false, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
var path = recording.RecordingInfo.Path;
|
||||
var mediaUrl = recording.RecordingInfo.Url;
|
||||
var mediaSource = string.IsNullOrEmpty(request.MediaSourceId)
|
||||
? mediaSources.First()
|
||||
: mediaSources.First(i => string.Equals(i.Id, request.MediaSourceId));
|
||||
|
||||
var source = string.IsNullOrEmpty(request.MediaSourceId)
|
||||
? recording.GetMediaSources(false).First()
|
||||
: _mediaSourceManager.GetStaticMediaSource(recording, request.MediaSourceId, false);
|
||||
|
||||
mediaStreams = source.MediaStreams;
|
||||
|
||||
// Just to prevent this from being null and causing other methods to fail
|
||||
state.MediaPath = string.Empty;
|
||||
|
||||
if (!string.IsNullOrEmpty(path))
|
||||
{
|
||||
state.MediaPath = path;
|
||||
state.InputProtocol = MediaProtocol.File;
|
||||
}
|
||||
else if (!string.IsNullOrEmpty(mediaUrl))
|
||||
{
|
||||
state.MediaPath = mediaUrl;
|
||||
state.InputProtocol = MediaProtocol.Http;
|
||||
}
|
||||
|
||||
state.RunTimeTicks = recording.RunTimeTicks;
|
||||
state.DeInterlace = true;
|
||||
state.OutputAudioSync = "1000";
|
||||
state.InputVideoSync = "-1";
|
||||
state.InputAudioSync = "1";
|
||||
state.InputContainer = recording.Container;
|
||||
state.ReadInputAtNativeFramerate = source.ReadAtNativeFramerate;
|
||||
}
|
||||
else if (item is LiveTvChannel)
|
||||
{
|
||||
var channel = _liveTvManager.GetInternalChannel(request.ItemId);
|
||||
|
||||
state.VideoType = VideoType.VideoFile;
|
||||
state.IsInputVideo = string.Equals(channel.MediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase);
|
||||
mediaStreams = new List<MediaStream>();
|
||||
|
||||
state.DeInterlace = true;
|
||||
|
||||
// Just to prevent this from being null and causing other methods to fail
|
||||
state.MediaPath = string.Empty;
|
||||
}
|
||||
else
|
||||
{
|
||||
var mediaSources = await _mediaSourceManager.GetPlayackMediaSources(request.ItemId, false, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
var mediaSource = string.IsNullOrEmpty(request.MediaSourceId)
|
||||
? mediaSources.First()
|
||||
: mediaSources.First(i => string.Equals(i.Id, request.MediaSourceId));
|
||||
|
||||
mediaStreams = mediaSource.MediaStreams;
|
||||
|
||||
state.MediaPath = mediaSource.Path;
|
||||
state.InputProtocol = mediaSource.Protocol;
|
||||
state.InputContainer = mediaSource.Container;
|
||||
state.InputFileSize = mediaSource.Size;
|
||||
state.InputBitrate = mediaSource.Bitrate;
|
||||
state.ReadInputAtNativeFramerate = mediaSource.ReadAtNativeFramerate;
|
||||
state.RunTimeTicks = mediaSource.RunTimeTicks;
|
||||
state.RemoteHttpHeaders = mediaSource.RequiredHttpHeaders;
|
||||
|
||||
var video = item as Video;
|
||||
|
||||
if (video != null)
|
||||
{
|
||||
state.IsInputVideo = true;
|
||||
|
||||
if (mediaSource.VideoType.HasValue)
|
||||
{
|
||||
state.VideoType = mediaSource.VideoType.Value;
|
||||
}
|
||||
|
||||
state.IsoType = mediaSource.IsoType;
|
||||
|
||||
state.PlayableStreamFileNames = mediaSource.PlayableStreamFileNames.ToList();
|
||||
|
||||
if (mediaSource.Timestamp.HasValue)
|
||||
{
|
||||
state.InputTimestamp = mediaSource.Timestamp.Value;
|
||||
}
|
||||
}
|
||||
|
||||
state.RunTimeTicks = mediaSource.RunTimeTicks;
|
||||
}
|
||||
|
||||
AttachMediaStreamInfo(state, mediaStreams, request);
|
||||
AttachMediaStreamInfo(state, mediaSource, options);
|
||||
|
||||
state.OutputAudioBitrate = GetAudioBitrateParam(request, state.AudioStream);
|
||||
state.OutputAudioSampleRate = request.AudioSampleRate;
|
||||
|
@ -185,26 +93,73 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
|||
|
||||
ApplyDeviceProfileSettings(state);
|
||||
|
||||
if (isVideoRequest)
|
||||
{
|
||||
if (state.VideoStream != null && CanStreamCopyVideo(request, state.VideoStream))
|
||||
{
|
||||
state.OutputVideoCodec = "copy";
|
||||
}
|
||||
|
||||
if (state.AudioStream != null && CanStreamCopyAudio(request, state.AudioStream, state.SupportedAudioCodecs))
|
||||
{
|
||||
state.OutputAudioCodec = "copy";
|
||||
}
|
||||
}
|
||||
TryStreamCopy(state, request);
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
internal static void AttachMediaStreamInfo(EncodingJob state,
|
||||
List<MediaStream> mediaStreams,
|
||||
internal static void TryStreamCopy(EncodingJob state,
|
||||
EncodingJobOptions videoRequest)
|
||||
{
|
||||
if (state.IsVideoRequest)
|
||||
{
|
||||
if (state.VideoStream != null && CanStreamCopyVideo(videoRequest, state.VideoStream))
|
||||
{
|
||||
state.OutputVideoCodec = "copy";
|
||||
}
|
||||
|
||||
if (state.AudioStream != null && CanStreamCopyAudio(videoRequest, state.AudioStream, state.SupportedAudioCodecs))
|
||||
{
|
||||
state.OutputAudioCodec = "copy";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal static void AttachMediaStreamInfo(EncodingJob state,
|
||||
MediaSourceInfo mediaSource,
|
||||
EncodingJobOptions videoRequest)
|
||||
{
|
||||
state.MediaPath = mediaSource.Path;
|
||||
state.InputProtocol = mediaSource.Protocol;
|
||||
state.InputContainer = mediaSource.Container;
|
||||
state.InputFileSize = mediaSource.Size;
|
||||
state.InputBitrate = mediaSource.Bitrate;
|
||||
state.ReadInputAtNativeFramerate = mediaSource.ReadAtNativeFramerate;
|
||||
state.RunTimeTicks = mediaSource.RunTimeTicks;
|
||||
state.RemoteHttpHeaders = mediaSource.RequiredHttpHeaders;
|
||||
|
||||
if (mediaSource.VideoType.HasValue)
|
||||
{
|
||||
state.VideoType = mediaSource.VideoType.Value;
|
||||
}
|
||||
|
||||
state.IsoType = mediaSource.IsoType;
|
||||
|
||||
state.PlayableStreamFileNames = mediaSource.PlayableStreamFileNames.ToList();
|
||||
|
||||
if (mediaSource.Timestamp.HasValue)
|
||||
{
|
||||
state.InputTimestamp = mediaSource.Timestamp.Value;
|
||||
}
|
||||
|
||||
state.InputProtocol = mediaSource.Protocol;
|
||||
state.MediaPath = mediaSource.Path;
|
||||
state.RunTimeTicks = mediaSource.RunTimeTicks;
|
||||
state.RemoteHttpHeaders = mediaSource.RequiredHttpHeaders;
|
||||
state.InputBitrate = mediaSource.Bitrate;
|
||||
state.InputFileSize = mediaSource.Size;
|
||||
state.ReadInputAtNativeFramerate = mediaSource.ReadAtNativeFramerate;
|
||||
|
||||
if (state.ReadInputAtNativeFramerate ||
|
||||
mediaSource.Protocol == MediaProtocol.File && string.Equals(mediaSource.Container, "wtv", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
state.OutputAudioSync = "1000";
|
||||
state.InputVideoSync = "-1";
|
||||
state.InputAudioSync = "1";
|
||||
}
|
||||
|
||||
var mediaStreams = mediaSource.MediaStreams;
|
||||
|
||||
if (videoRequest != null)
|
||||
{
|
||||
if (string.IsNullOrEmpty(videoRequest.VideoCodec))
|
||||
|
@ -233,7 +188,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
|||
state.AudioStream = GetMediaStream(mediaStreams, null, MediaStreamType.Audio, true);
|
||||
}
|
||||
|
||||
state.AllMediaStreams = mediaStreams;
|
||||
state.MediaSource = mediaSource;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -771,7 +726,9 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
|||
state.TargetTimestamp,
|
||||
state.IsTargetAnamorphic,
|
||||
state.IsTargetCabac,
|
||||
state.TargetRefFrames);
|
||||
state.TargetRefFrames,
|
||||
state.TargetVideoStreamCount,
|
||||
state.TargetAudioStreamCount);
|
||||
|
||||
if (mediaProfile != null)
|
||||
{
|
||||
|
|
|
@ -577,10 +577,8 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
|||
_logger,
|
||||
ConfigurationManager,
|
||||
FileSystem,
|
||||
LiveTvManager,
|
||||
IsoManager,
|
||||
LibraryManager,
|
||||
ChannelManager,
|
||||
SessionManager,
|
||||
SubtitleEncoder(),
|
||||
MediaSourceManager())
|
||||
|
@ -599,10 +597,8 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
|||
_logger,
|
||||
ConfigurationManager,
|
||||
FileSystem,
|
||||
LiveTvManager,
|
||||
IsoManager,
|
||||
LibraryManager,
|
||||
ChannelManager,
|
||||
SessionManager,
|
||||
SubtitleEncoder(),
|
||||
MediaSourceManager())
|
||||
|
|
|
@ -15,7 +15,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
|||
{
|
||||
public class VideoEncoder : BaseEncoder
|
||||
{
|
||||
public VideoEncoder(MediaEncoder mediaEncoder, ILogger logger, IServerConfigurationManager configurationManager, IFileSystem fileSystem, ILiveTvManager liveTvManager, IIsoManager isoManager, ILibraryManager libraryManager, IChannelManager channelManager, ISessionManager sessionManager, ISubtitleEncoder subtitleEncoder, IMediaSourceManager mediaSourceManager) : base(mediaEncoder, logger, configurationManager, fileSystem, liveTvManager, isoManager, libraryManager, channelManager, sessionManager, subtitleEncoder, mediaSourceManager)
|
||||
public VideoEncoder(MediaEncoder mediaEncoder, ILogger logger, IServerConfigurationManager configurationManager, IFileSystem fileSystem, IIsoManager isoManager, ILibraryManager libraryManager, ISessionManager sessionManager, ISubtitleEncoder subtitleEncoder, IMediaSourceManager mediaSourceManager) : base(mediaEncoder, logger, configurationManager, fileSystem, isoManager, libraryManager, sessionManager, subtitleEncoder, mediaSourceManager)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -73,10 +73,13 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
|||
args;
|
||||
}
|
||||
|
||||
var keyFrameArg = string.Format(" -force_key_frames expr:gte(t,n_forced*{0})",
|
||||
5.ToString(UsCulture));
|
||||
if (state.Options.Context == EncodingContext.Streaming)
|
||||
{
|
||||
var keyFrameArg = string.Format(" -force_key_frames expr:gte(t,n_forced*{0})",
|
||||
5.ToString(UsCulture));
|
||||
|
||||
args += keyFrameArg;
|
||||
args += keyFrameArg;
|
||||
}
|
||||
|
||||
var hasGraphicalSubs = state.SubtitleStream != null && !state.SubtitleStream.IsTextSubtitleStream;
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
using MediaBrowser.Common.Configuration;
|
||||
using MediaBrowser.Common.Extensions;
|
||||
using MediaBrowser.Common.IO;
|
||||
using MediaBrowser.Common.Net;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Controller.MediaEncoding;
|
||||
|
@ -29,8 +30,10 @@ namespace MediaBrowser.MediaEncoding.Subtitles
|
|||
private readonly IFileSystem _fileSystem;
|
||||
private readonly IMediaEncoder _mediaEncoder;
|
||||
private readonly IJsonSerializer _json;
|
||||
private readonly IHttpClient _httpClient;
|
||||
private readonly IMediaSourceManager _mediaSourceManager;
|
||||
|
||||
public SubtitleEncoder(ILibraryManager libraryManager, ILogger logger, IApplicationPaths appPaths, IFileSystem fileSystem, IMediaEncoder mediaEncoder, IJsonSerializer json)
|
||||
public SubtitleEncoder(ILibraryManager libraryManager, ILogger logger, IApplicationPaths appPaths, IFileSystem fileSystem, IMediaEncoder mediaEncoder, IJsonSerializer json, IHttpClient httpClient, IMediaSourceManager mediaSourceManager)
|
||||
{
|
||||
_libraryManager = libraryManager;
|
||||
_logger = logger;
|
||||
|
@ -38,6 +41,8 @@ namespace MediaBrowser.MediaEncoding.Subtitles
|
|||
_fileSystem = fileSystem;
|
||||
_mediaEncoder = mediaEncoder;
|
||||
_json = json;
|
||||
_httpClient = httpClient;
|
||||
_mediaSourceManager = mediaSourceManager;
|
||||
}
|
||||
|
||||
private string SubtitleCachePath
|
||||
|
@ -127,9 +132,9 @@ namespace MediaBrowser.MediaEncoding.Subtitles
|
|||
int subtitleStreamIndex,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var item = (Video)_libraryManager.GetItemById(new Guid(itemId));
|
||||
var mediaSources = await _mediaSourceManager.GetPlayackMediaSources(itemId, false, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
var mediaSource = item.GetMediaSources(false)
|
||||
var mediaSource = mediaSources
|
||||
.First(i => string.Equals(i.Id, mediaSourceId));
|
||||
|
||||
var subtitleStream = mediaSource.MediaStreams
|
||||
|
@ -149,20 +154,20 @@ namespace MediaBrowser.MediaEncoding.Subtitles
|
|||
|
||||
var fileInfo = await GetReadableFile(mediaSource.Path, inputFiles, mediaSource.Protocol, subtitleStream, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
var stream = await GetSubtitleStream(fileInfo.Item1, fileInfo.Item3).ConfigureAwait(false);
|
||||
var stream = await GetSubtitleStream(fileInfo.Item1, fileInfo.Item2, fileInfo.Item4, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
return new Tuple<Stream, string>(stream, fileInfo.Item2);
|
||||
return new Tuple<Stream, string>(stream, fileInfo.Item3);
|
||||
}
|
||||
|
||||
private async Task<Stream> GetSubtitleStream(string path, bool requiresCharset)
|
||||
private async Task<Stream> GetSubtitleStream(string path, MediaProtocol protocol, bool requiresCharset, CancellationToken cancellationToken)
|
||||
{
|
||||
if (requiresCharset)
|
||||
{
|
||||
var charset = GetSubtitleFileCharacterSet(path);
|
||||
var charset = await GetSubtitleFileCharacterSet(path, protocol, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (!string.IsNullOrEmpty(charset))
|
||||
{
|
||||
using (var fs = _fileSystem.GetFileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, true))
|
||||
using (var fs = await GetStream(path, protocol, cancellationToken).ConfigureAwait(false))
|
||||
{
|
||||
using (var reader = new StreamReader(fs, GetEncoding(charset)))
|
||||
{
|
||||
|
@ -196,7 +201,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
|
|||
}
|
||||
}
|
||||
|
||||
private async Task<Tuple<string, string, bool>> GetReadableFile(string mediaPath,
|
||||
private async Task<Tuple<string, MediaProtocol, string, bool>> GetReadableFile(string mediaPath,
|
||||
string[] inputFiles,
|
||||
MediaProtocol protocol,
|
||||
MediaStream subtitleStream,
|
||||
|
@ -228,12 +233,12 @@ namespace MediaBrowser.MediaEncoding.Subtitles
|
|||
}
|
||||
|
||||
// Extract
|
||||
var outputPath = GetSubtitleCachePath(mediaPath, subtitleStream.Index, "." + outputFormat);
|
||||
var outputPath = GetSubtitleCachePath(mediaPath, protocol, subtitleStream.Index, "." + outputFormat);
|
||||
|
||||
await ExtractTextSubtitle(inputFiles, protocol, subtitleStream.Index, outputCodec, outputPath, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
return new Tuple<string, string, bool>(outputPath, outputFormat, false);
|
||||
return new Tuple<string, MediaProtocol, string, bool>(outputPath, MediaProtocol.File, outputFormat, false);
|
||||
}
|
||||
|
||||
var currentFormat = (Path.GetExtension(subtitleStream.Path) ?? subtitleStream.Codec)
|
||||
|
@ -242,14 +247,14 @@ namespace MediaBrowser.MediaEncoding.Subtitles
|
|||
if (GetReader(currentFormat, false) == null)
|
||||
{
|
||||
// Convert
|
||||
var outputPath = GetSubtitleCachePath(mediaPath, subtitleStream.Index, ".srt");
|
||||
var outputPath = GetSubtitleCachePath(mediaPath, protocol, subtitleStream.Index, ".srt");
|
||||
|
||||
await ConvertTextSubtitleToSrt(subtitleStream.Path, outputPath, cancellationToken).ConfigureAwait(false);
|
||||
await ConvertTextSubtitleToSrt(subtitleStream.Path, protocol, outputPath, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
return new Tuple<string, string, bool>(outputPath, "srt", true);
|
||||
return new Tuple<string, MediaProtocol, string, bool>(outputPath, MediaProtocol.File, "srt", true);
|
||||
}
|
||||
|
||||
return new Tuple<string, string, bool>(subtitleStream.Path, currentFormat, true);
|
||||
return new Tuple<string, MediaProtocol, string, bool>(subtitleStream.Path, protocol, currentFormat, true);
|
||||
}
|
||||
|
||||
private async Task<SubtitleTrackInfo> GetTrackInfo(Stream stream,
|
||||
|
@ -336,10 +341,11 @@ namespace MediaBrowser.MediaEncoding.Subtitles
|
|||
/// Converts the text subtitle to SRT.
|
||||
/// </summary>
|
||||
/// <param name="inputPath">The input path.</param>
|
||||
/// <param name="inputProtocol">The input protocol.</param>
|
||||
/// <param name="outputPath">The output path.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>Task.</returns>
|
||||
public async Task ConvertTextSubtitleToSrt(string inputPath, string outputPath, CancellationToken cancellationToken)
|
||||
private async Task ConvertTextSubtitleToSrt(string inputPath, MediaProtocol inputProtocol, string outputPath, CancellationToken cancellationToken)
|
||||
{
|
||||
var semaphore = GetLock(outputPath);
|
||||
|
||||
|
@ -349,7 +355,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
|
|||
{
|
||||
if (!File.Exists(outputPath))
|
||||
{
|
||||
await ConvertTextSubtitleToSrtInternal(inputPath, outputPath).ConfigureAwait(false);
|
||||
await ConvertTextSubtitleToSrtInternal(inputPath, inputProtocol, outputPath, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
finally
|
||||
|
@ -362,13 +368,17 @@ namespace MediaBrowser.MediaEncoding.Subtitles
|
|||
/// Converts the text subtitle to SRT internal.
|
||||
/// </summary>
|
||||
/// <param name="inputPath">The input path.</param>
|
||||
/// <param name="inputProtocol">The input protocol.</param>
|
||||
/// <param name="outputPath">The output path.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>Task.</returns>
|
||||
/// <exception cref="System.ArgumentNullException">inputPath
|
||||
/// <exception cref="System.ArgumentNullException">
|
||||
/// inputPath
|
||||
/// or
|
||||
/// outputPath</exception>
|
||||
/// outputPath
|
||||
/// </exception>
|
||||
/// <exception cref="System.ApplicationException"></exception>
|
||||
private async Task ConvertTextSubtitleToSrtInternal(string inputPath, string outputPath)
|
||||
private async Task ConvertTextSubtitleToSrtInternal(string inputPath, MediaProtocol inputProtocol, string outputPath, CancellationToken cancellationToken)
|
||||
{
|
||||
if (string.IsNullOrEmpty(inputPath))
|
||||
{
|
||||
|
@ -382,7 +392,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
|
|||
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(outputPath));
|
||||
|
||||
var encodingParam = GetSubtitleFileCharacterSet(inputPath);
|
||||
var encodingParam = await GetSubtitleFileCharacterSet(inputPath, inputProtocol, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (!string.IsNullOrEmpty(encodingParam))
|
||||
{
|
||||
|
@ -688,32 +698,41 @@ namespace MediaBrowser.MediaEncoding.Subtitles
|
|||
}
|
||||
}
|
||||
|
||||
private string GetSubtitleCachePath(string mediaPath, int subtitleStreamIndex, string outputSubtitleExtension)
|
||||
private string GetSubtitleCachePath(string mediaPath, MediaProtocol protocol, int subtitleStreamIndex, string outputSubtitleExtension)
|
||||
{
|
||||
var ticksParam = string.Empty;
|
||||
if (protocol == MediaProtocol.File)
|
||||
{
|
||||
var ticksParam = string.Empty;
|
||||
|
||||
var date = _fileSystem.GetLastWriteTimeUtc(mediaPath);
|
||||
var date = _fileSystem.GetLastWriteTimeUtc(mediaPath);
|
||||
|
||||
var filename = (mediaPath + "_" + subtitleStreamIndex.ToString(CultureInfo.InvariantCulture) + "_" + date.Ticks.ToString(CultureInfo.InvariantCulture) + ticksParam).GetMD5() + outputSubtitleExtension;
|
||||
var filename = (mediaPath + "_" + subtitleStreamIndex.ToString(CultureInfo.InvariantCulture) + "_" + date.Ticks.ToString(CultureInfo.InvariantCulture) + ticksParam).GetMD5() + outputSubtitleExtension;
|
||||
|
||||
var prefix = filename.Substring(0, 1);
|
||||
var prefix = filename.Substring(0, 1);
|
||||
|
||||
return Path.Combine(SubtitleCachePath, prefix, filename);
|
||||
return Path.Combine(SubtitleCachePath, prefix, filename);
|
||||
}
|
||||
else
|
||||
{
|
||||
var filename = (mediaPath + "_" + subtitleStreamIndex.ToString(CultureInfo.InvariantCulture)).GetMD5() + outputSubtitleExtension;
|
||||
|
||||
var prefix = filename.Substring(0, 1);
|
||||
|
||||
return Path.Combine(SubtitleCachePath, prefix, filename);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the subtitle language encoding param.
|
||||
/// </summary>
|
||||
/// <param name="path">The path.</param>
|
||||
/// <returns>System.String.</returns>
|
||||
public string GetSubtitleFileCharacterSet(string path)
|
||||
public async Task<string> GetSubtitleFileCharacterSet(string path, MediaProtocol protocol, CancellationToken cancellationToken)
|
||||
{
|
||||
if (GetFileEncoding(path).Equals(Encoding.UTF8))
|
||||
if (protocol == MediaProtocol.File)
|
||||
{
|
||||
return string.Empty;
|
||||
if (GetFileEncoding(path).Equals(Encoding.UTF8))
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
var charset = DetectCharset(path);
|
||||
var charset = await DetectCharset(path, protocol, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(charset))
|
||||
{
|
||||
|
@ -769,11 +788,11 @@ namespace MediaBrowser.MediaEncoding.Subtitles
|
|||
}
|
||||
}
|
||||
|
||||
private string DetectCharset(string path)
|
||||
private async Task<string> DetectCharset(string path, MediaProtocol protocol, CancellationToken cancellationToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
using (var file = _fileSystem.GetFileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
|
||||
using (var file = await GetStream(path, protocol, cancellationToken).ConfigureAwait(false))
|
||||
{
|
||||
var detector = new CharsetDetector();
|
||||
detector.Feed(file);
|
||||
|
@ -819,5 +838,19 @@ namespace MediaBrowser.MediaEncoding.Subtitles
|
|||
// It's ok - anything aside from utf is ok since that's what we're looking for
|
||||
return Encoding.Default;
|
||||
}
|
||||
|
||||
private async Task<Stream> GetStream(string path, MediaProtocol protocol, CancellationToken cancellationToken)
|
||||
{
|
||||
if (protocol == MediaProtocol.Http)
|
||||
{
|
||||
return await _httpClient.Get(path, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
if (protocol == MediaProtocol.File)
|
||||
{
|
||||
return _fileSystem.GetFileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
|
||||
}
|
||||
|
||||
throw new ArgumentOutOfRangeException("protocol");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -413,6 +413,9 @@
|
|||
<Compile Include="..\MediaBrowser.Model\Dlna\StreamInfo.cs">
|
||||
<Link>Dlna\StreamInfo.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\MediaBrowser.Model\Dlna\StreamInfoSorter.cs">
|
||||
<Link>Dlna\StreamInfoSorter.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\MediaBrowser.Model\Dlna\SubtitleDeliveryMethod.cs">
|
||||
<Link>Dlna\SubtitleDeliveryMethod.cs</Link>
|
||||
</Compile>
|
||||
|
@ -800,12 +803,21 @@
|
|||
<Compile Include="..\MediaBrowser.Model\MediaInfo\IBlurayExaminer.cs">
|
||||
<Link>MediaInfo\IBlurayExaminer.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\MediaBrowser.Model\MediaInfo\LiveMediaInfoResult.cs">
|
||||
<Link>MediaInfo\LiveMediaInfoResult.cs</Link>
|
||||
<Compile Include="..\MediaBrowser.Model\MediaInfo\LiveStreamRequest.cs">
|
||||
<Link>MediaInfo\LiveStreamRequest.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\MediaBrowser.Model\MediaInfo\LiveStreamResponse.cs">
|
||||
<Link>MediaInfo\LiveStreamResponse.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\MediaBrowser.Model\MediaInfo\MediaProtocol.cs">
|
||||
<Link>MediaInfo\MediaProtocol.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\MediaBrowser.Model\MediaInfo\PlaybackInfoRequest.cs">
|
||||
<Link>MediaInfo\PlaybackInfoRequest.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\MediaBrowser.Model\MediaInfo\PlaybackInfoResponse.cs">
|
||||
<Link>MediaInfo\PlaybackInfoResponse.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\MediaBrowser.Model\MediaInfo\SubtitleFormat.cs">
|
||||
<Link>MediaInfo\SubtitleFormat.cs</Link>
|
||||
</Compile>
|
||||
|
|
|
@ -378,6 +378,9 @@
|
|||
<Compile Include="..\MediaBrowser.Model\Dlna\StreamInfo.cs">
|
||||
<Link>Dlna\StreamInfo.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\MediaBrowser.Model\Dlna\StreamInfoSorter.cs">
|
||||
<Link>Dlna\StreamInfoSorter.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\MediaBrowser.Model\Dlna\SubtitleDeliveryMethod.cs">
|
||||
<Link>Dlna\SubtitleDeliveryMethod.cs</Link>
|
||||
</Compile>
|
||||
|
@ -756,12 +759,21 @@
|
|||
<Compile Include="..\MediaBrowser.Model\MediaInfo\IBlurayExaminer.cs">
|
||||
<Link>MediaInfo\IBlurayExaminer.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\MediaBrowser.Model\MediaInfo\LiveMediaInfoResult.cs">
|
||||
<Link>MediaInfo\LiveMediaInfoResult.cs</Link>
|
||||
<Compile Include="..\MediaBrowser.Model\MediaInfo\LiveStreamRequest.cs">
|
||||
<Link>MediaInfo\LiveStreamRequest.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\MediaBrowser.Model\MediaInfo\LiveStreamResponse.cs">
|
||||
<Link>MediaInfo\LiveStreamResponse.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\MediaBrowser.Model\MediaInfo\MediaProtocol.cs">
|
||||
<Link>MediaInfo\MediaProtocol.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\MediaBrowser.Model\MediaInfo\PlaybackInfoRequest.cs">
|
||||
<Link>MediaInfo\PlaybackInfoRequest.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\MediaBrowser.Model\MediaInfo\PlaybackInfoResponse.cs">
|
||||
<Link>MediaInfo\PlaybackInfoResponse.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\MediaBrowser.Model\MediaInfo\SubtitleFormat.cs">
|
||||
<Link>MediaInfo\SubtitleFormat.cs</Link>
|
||||
</Compile>
|
||||
|
|
|
@ -248,10 +248,9 @@ namespace MediaBrowser.Model.ApiClient
|
|||
/// <summary>
|
||||
/// Gets the playback information.
|
||||
/// </summary>
|
||||
/// <param name="itemId">The item identifier.</param>
|
||||
/// <param name="userId">The user identifier.</param>
|
||||
/// <param name="request">The request.</param>
|
||||
/// <returns>Task<LiveMediaInfoResult>.</returns>
|
||||
Task<LiveMediaInfoResult> GetPlaybackInfo(string itemId, string userId);
|
||||
Task<PlaybackInfoResponse> GetPlaybackInfo(PlaybackInfoRequest request);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the users async.
|
||||
|
@ -1486,5 +1485,12 @@ namespace MediaBrowser.Model.ApiClient
|
|||
/// <param name="query">The query.</param>
|
||||
/// <returns>Task<List<RecommendationDto>>.</returns>
|
||||
Task<List<RecommendationDto>> GetMovieRecommendations(MovieRecommendationQuery query);
|
||||
/// <summary>
|
||||
/// Opens the live stream.
|
||||
/// </summary>
|
||||
/// <param name="request">The request.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>Task<LiveStreamResponse>.</returns>
|
||||
Task<LiveStreamResponse> OpenLiveStream(LiveStreamRequest request, CancellationToken cancellationToken);
|
||||
}
|
||||
}
|
|
@ -172,5 +172,11 @@ namespace MediaBrowser.Model.ApiClient
|
|||
/// <param name="rememberCredentials">if set to <c>true</c> [remember credentials].</param>
|
||||
/// <returns>Task.</returns>
|
||||
Task AuthenticateOffline(UserDto user, string password, bool rememberCredentials);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the offline users.
|
||||
/// </summary>
|
||||
/// <returns>Task<List<UserDto>>.</returns>
|
||||
Task<List<UserDto>> GetOfflineUsers();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
using MediaBrowser.Model.Extensions;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace MediaBrowser.Model.ApiClient
|
||||
{
|
||||
|
@ -24,7 +23,12 @@ namespace MediaBrowser.Model.ApiClient
|
|||
throw new ArgumentNullException("server");
|
||||
}
|
||||
|
||||
var list = Servers.ToList();
|
||||
// Clone the existing list of servers
|
||||
var list = new List<ServerInfo>();
|
||||
foreach (ServerInfo serverInfo in Servers)
|
||||
{
|
||||
list.Add(serverInfo);
|
||||
}
|
||||
|
||||
var index = FindIndex(list, server.Id);
|
||||
|
||||
|
@ -32,8 +36,11 @@ namespace MediaBrowser.Model.ApiClient
|
|||
{
|
||||
var existing = list[index];
|
||||
|
||||
// Merge the data
|
||||
existing.DateLastAccessed = new[] { existing.DateLastAccessed, server.DateLastAccessed }.Max();
|
||||
// Take the most recent DateLastAccessed
|
||||
if (server.DateLastAccessed > existing.DateLastAccessed)
|
||||
{
|
||||
existing.DateLastAccessed = server.DateLastAccessed;
|
||||
}
|
||||
|
||||
existing.UserLinkType = server.UserLinkType;
|
||||
|
||||
|
@ -64,7 +71,11 @@ namespace MediaBrowser.Model.ApiClient
|
|||
}
|
||||
if (server.WakeOnLanInfos != null && server.WakeOnLanInfos.Count > 0)
|
||||
{
|
||||
existing.WakeOnLanInfos = server.WakeOnLanInfos.ToList();
|
||||
existing.WakeOnLanInfos = new List<WakeOnLanInfo>();
|
||||
foreach (WakeOnLanInfo info in server.WakeOnLanInfos)
|
||||
{
|
||||
existing.WakeOnLanInfos.Add(info);
|
||||
}
|
||||
}
|
||||
if (server.LastConnectionMode.HasValue)
|
||||
{
|
||||
|
|
|
@ -3,7 +3,6 @@ using MediaBrowser.Model.Extensions;
|
|||
using MediaBrowser.Model.System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace MediaBrowser.Model.ApiClient
|
||||
{
|
||||
|
@ -83,7 +82,12 @@ namespace MediaBrowser.Model.ApiClient
|
|||
throw new ArgumentNullException("user");
|
||||
}
|
||||
|
||||
var list = Users.ToList();
|
||||
// Clone the existing list of users
|
||||
var list = new List<ServerUserInfo>();
|
||||
foreach (ServerUserInfo serverUserInfo in Users)
|
||||
{
|
||||
list.Add(serverUserInfo);
|
||||
}
|
||||
|
||||
var index = FindIndex(list, user.Id);
|
||||
|
||||
|
|
|
@ -8,12 +8,16 @@ namespace MediaBrowser.Model.Configuration
|
|||
public double DownMixAudioBoost { get; set; }
|
||||
public string H264Encoder { get; set; }
|
||||
public bool EnableDebugLogging { get; set; }
|
||||
public bool EnableThrottling { get; set; }
|
||||
public int ThrottleThresholdSeconds { get; set; }
|
||||
|
||||
public EncodingOptions()
|
||||
{
|
||||
H264Encoder = "libx264";
|
||||
DownMixAudioBoost = 2;
|
||||
EncodingQuality = EncodingQuality.Auto;
|
||||
EnableThrottling = true;
|
||||
ThrottleThresholdSeconds = 120;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -200,7 +200,7 @@ namespace MediaBrowser.Model.Configuration
|
|||
public PeopleMetadataOptions PeopleMetadataOptions { get; set; }
|
||||
public bool FindInternetTrailers { get; set; }
|
||||
|
||||
public string[] InsecureApps8 { get; set; }
|
||||
public string[] InsecureApps9 { get; set; }
|
||||
|
||||
public bool SaveMetadataHidden { get; set; }
|
||||
|
||||
|
@ -257,7 +257,7 @@ namespace MediaBrowser.Model.Configuration
|
|||
|
||||
PeopleMetadataOptions = new PeopleMetadataOptions();
|
||||
|
||||
InsecureApps8 = new[]
|
||||
InsecureApps9 = new[]
|
||||
{
|
||||
"Chromecast",
|
||||
"iOS",
|
||||
|
@ -266,7 +266,6 @@ namespace MediaBrowser.Model.Configuration
|
|||
"Media Portal",
|
||||
"iPad",
|
||||
"iPhone",
|
||||
"Roku",
|
||||
"Windows Phone"
|
||||
};
|
||||
|
||||
|
|
|
@ -6,12 +6,6 @@ namespace MediaBrowser.Model.Configuration
|
|||
/// </summary>
|
||||
public class UserConfiguration
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether this instance is administrator.
|
||||
/// </summary>
|
||||
/// <value><c>true</c> if this instance is administrator; otherwise, <c>false</c>.</value>
|
||||
public bool IsAdministrator { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the audio language preference.
|
||||
/// </summary>
|
||||
|
@ -53,13 +47,14 @@ namespace MediaBrowser.Model.Configuration
|
|||
|
||||
public string[] LatestItemsExcludes { get; set; }
|
||||
|
||||
public bool HasMigratedToPolicy { get; set; }
|
||||
public bool HidePlayedInLatest { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="UserConfiguration" /> class.
|
||||
/// </summary>
|
||||
public UserConfiguration()
|
||||
{
|
||||
HidePlayedInLatest = true;
|
||||
PlayDefaultAudioTrack = true;
|
||||
|
||||
LatestItemsExcludes = new string[] { };
|
||||
|
|
|
@ -20,7 +20,9 @@ namespace MediaBrowser.Model.Dlna
|
|||
TransportStreamTimestamp? timestamp,
|
||||
bool? isAnamorphic,
|
||||
bool? isCabac,
|
||||
int? refFrames)
|
||||
int? refFrames,
|
||||
int? numVideoStreams,
|
||||
int? numAudioStreams)
|
||||
{
|
||||
switch (condition.Property)
|
||||
{
|
||||
|
@ -56,6 +58,10 @@ namespace MediaBrowser.Model.Dlna
|
|||
return IsConditionSatisfied(condition, width);
|
||||
case ProfileConditionValue.RefFrames:
|
||||
return IsConditionSatisfied(condition, refFrames);
|
||||
case ProfileConditionValue.NumAudioStreams:
|
||||
return IsConditionSatisfied(condition, numAudioStreams);
|
||||
case ProfileConditionValue.NumVideoStreams:
|
||||
return IsConditionSatisfied(condition, numVideoStreams);
|
||||
case ProfileConditionValue.VideoTimestamp:
|
||||
return IsConditionSatisfied(condition, timestamp);
|
||||
default:
|
||||
|
@ -92,7 +98,8 @@ namespace MediaBrowser.Model.Dlna
|
|||
public bool IsVideoAudioConditionSatisfied(ProfileCondition condition,
|
||||
int? audioChannels,
|
||||
int? audioBitrate,
|
||||
string audioProfile)
|
||||
string audioProfile,
|
||||
bool? isSecondaryTrack)
|
||||
{
|
||||
switch (condition.Property)
|
||||
{
|
||||
|
@ -102,6 +109,8 @@ namespace MediaBrowser.Model.Dlna
|
|||
return IsConditionSatisfied(condition, audioBitrate);
|
||||
case ProfileConditionValue.AudioChannels:
|
||||
return IsConditionSatisfied(condition, audioChannels);
|
||||
case ProfileConditionValue.IsSecondaryAudio:
|
||||
return IsConditionSatisfied(condition, isSecondaryTrack);
|
||||
default:
|
||||
throw new ArgumentException("Unexpected condition on audio file: " + condition.Property);
|
||||
}
|
||||
|
|
|
@ -117,7 +117,9 @@ namespace MediaBrowser.Model.Dlna
|
|||
TranscodeSeekInfo transcodeSeekInfo,
|
||||
bool? isAnamorphic,
|
||||
bool? isCabac,
|
||||
int? refFrames)
|
||||
int? refFrames,
|
||||
int? numVideoStreams,
|
||||
int? numAudioStreams)
|
||||
{
|
||||
// first bit means Time based seek supported, second byte range seek supported (not sure about the order now), so 01 = only byte seek, 10 = time based, 11 = both, 00 = none
|
||||
string orgOp = ";DLNA.ORG_OP=" + DlnaMaps.GetOrgOpValue(runtimeTicks.HasValue, isDirectStream, transcodeSeekInfo);
|
||||
|
@ -158,7 +160,9 @@ namespace MediaBrowser.Model.Dlna
|
|||
timestamp,
|
||||
isAnamorphic,
|
||||
isCabac,
|
||||
refFrames);
|
||||
refFrames,
|
||||
numVideoStreams,
|
||||
numAudioStreams);
|
||||
|
||||
List<string> orgPnValues = new List<string>();
|
||||
|
||||
|
|
|
@ -281,7 +281,9 @@ namespace MediaBrowser.Model.Dlna
|
|||
TransportStreamTimestamp timestamp,
|
||||
bool? isAnamorphic,
|
||||
bool? isCabac,
|
||||
int? refFrames)
|
||||
int? refFrames,
|
||||
int? numVideoStreams,
|
||||
int? numAudioStreams)
|
||||
{
|
||||
container = StringHelper.TrimStart((container ?? string.Empty), '.');
|
||||
|
||||
|
@ -315,7 +317,7 @@ namespace MediaBrowser.Model.Dlna
|
|||
var anyOff = false;
|
||||
foreach (ProfileCondition c in i.Conditions)
|
||||
{
|
||||
if (!conditionProcessor.IsVideoConditionSatisfied(c, audioBitrate, audioChannels, width, height, bitDepth, videoBitrate, videoProfile, videoLevel, videoFramerate, packetLength, timestamp, isAnamorphic, isCabac, refFrames))
|
||||
if (!conditionProcessor.IsVideoConditionSatisfied(c, audioBitrate, audioChannels, width, height, bitDepth, videoBitrate, videoProfile, videoLevel, videoFramerate, packetLength, timestamp, isAnamorphic, isCabac, refFrames, numVideoStreams, numAudioStreams))
|
||||
{
|
||||
anyOff = true;
|
||||
break;
|
||||
|
|
|
@ -17,6 +17,9 @@
|
|||
VideoTimestamp = 12,
|
||||
IsAnamorphic = 13,
|
||||
RefFrames = 14,
|
||||
IsCabac = 15
|
||||
IsCabac = 15,
|
||||
NumAudioStreams = 16,
|
||||
NumVideoStreams = 17,
|
||||
IsSecondaryAudio
|
||||
}
|
||||
}
|
|
@ -89,38 +89,14 @@ namespace MediaBrowser.Model.Dlna
|
|||
|
||||
private StreamInfo GetOptimalStream(List<StreamInfo> streams)
|
||||
{
|
||||
// Grab the first one that can be direct streamed
|
||||
// If that doesn't produce anything, just take the first
|
||||
foreach (StreamInfo i in streams)
|
||||
{
|
||||
if (i.PlayMethod == PlayMethod.DirectPlay && i.MediaSource.Protocol == MediaProtocol.File)
|
||||
{
|
||||
return i;
|
||||
}
|
||||
}
|
||||
foreach (StreamInfo i in streams)
|
||||
{
|
||||
if (i.PlayMethod == PlayMethod.DirectPlay)
|
||||
{
|
||||
return i;
|
||||
}
|
||||
}
|
||||
foreach (StreamInfo i in streams)
|
||||
{
|
||||
if (i.PlayMethod == PlayMethod.DirectStream)
|
||||
{
|
||||
return i;
|
||||
}
|
||||
}
|
||||
streams = StreamInfoSorter.SortMediaSources(streams);
|
||||
|
||||
foreach (StreamInfo stream in streams)
|
||||
{
|
||||
return stream;
|
||||
}
|
||||
|
||||
PlaybackException error = new PlaybackException();
|
||||
error.ErrorCode = PlaybackErrorCode.NoCompatibleStream;
|
||||
throw error;
|
||||
return null;
|
||||
}
|
||||
|
||||
private StreamInfo BuildAudioItem(MediaSourceInfo item, AudioOptions options)
|
||||
|
@ -221,7 +197,7 @@ namespace MediaBrowser.Model.Dlna
|
|||
playlistItem.EstimateContentLength = transcodingProfile.EstimateContentLength;
|
||||
playlistItem.Container = transcodingProfile.Container;
|
||||
playlistItem.AudioCodec = transcodingProfile.AudioCodec;
|
||||
playlistItem.Protocol = transcodingProfile.Protocol;
|
||||
playlistItem.SubProtocol = transcodingProfile.Protocol;
|
||||
|
||||
List<CodecProfile> audioCodecProfiles = new List<CodecProfile>();
|
||||
foreach (CodecProfile i in options.Profile.CodecProfiles)
|
||||
|
@ -263,6 +239,16 @@ namespace MediaBrowser.Model.Dlna
|
|||
return playlistItem;
|
||||
}
|
||||
|
||||
private int? GetBitrateForDirectPlayCheck(MediaSourceInfo item, AudioOptions options)
|
||||
{
|
||||
if (item.Protocol == MediaProtocol.File)
|
||||
{
|
||||
return options.Profile.MaxStaticBitrate;
|
||||
}
|
||||
|
||||
return options.GetMaxBitrate();
|
||||
}
|
||||
|
||||
private List<PlayMethod> GetAudioDirectPlayMethods(MediaSourceInfo item, MediaStream audioStream, AudioOptions options)
|
||||
{
|
||||
DirectPlayProfile directPlayProfile = null;
|
||||
|
@ -287,7 +273,7 @@ namespace MediaBrowser.Model.Dlna
|
|||
|
||||
// The profile describes what the device supports
|
||||
// If device requirements are satisfied then allow both direct stream and direct play
|
||||
if (IsAudioEligibleForDirectPlay(item, options.Profile.MaxStaticBitrate))
|
||||
if (IsAudioEligibleForDirectPlay(item, GetBitrateForDirectPlayCheck(item, options)))
|
||||
{
|
||||
playMethods.Add(PlayMethod.DirectPlay);
|
||||
}
|
||||
|
@ -296,6 +282,49 @@ namespace MediaBrowser.Model.Dlna
|
|||
return playMethods;
|
||||
}
|
||||
|
||||
private int? GetDefaultSubtitleStreamIndex(MediaSourceInfo item, SubtitleProfile[] subtitleProfiles)
|
||||
{
|
||||
int highestScore = -1;
|
||||
|
||||
foreach (MediaStream stream in item.MediaStreams)
|
||||
{
|
||||
if (stream.Type == MediaStreamType.Subtitle && stream.Score.HasValue)
|
||||
{
|
||||
if (stream.Score.Value > highestScore)
|
||||
{
|
||||
highestScore = stream.Score.Value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
List<MediaStream> topStreams = new List<MediaStream>();
|
||||
foreach (MediaStream stream in item.MediaStreams)
|
||||
{
|
||||
if (stream.Type == MediaStreamType.Subtitle && stream.Score.HasValue && stream.Score.Value == highestScore)
|
||||
{
|
||||
topStreams.Add(stream);
|
||||
}
|
||||
}
|
||||
|
||||
// If multiple streams have an equal score, try to pick the most efficient one
|
||||
if (topStreams.Count > 1)
|
||||
{
|
||||
foreach (MediaStream stream in topStreams)
|
||||
{
|
||||
foreach (SubtitleProfile profile in subtitleProfiles)
|
||||
{
|
||||
if (profile.Method == SubtitleDeliveryMethod.External && StringHelper.EqualsIgnoreCase(profile.Format, stream.Codec))
|
||||
{
|
||||
return stream.Index;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If no optimization panned out, just use the original default
|
||||
return item.DefaultSubtitleStreamIndex;
|
||||
}
|
||||
|
||||
private StreamInfo BuildVideoItem(MediaSourceInfo item, VideoOptions options)
|
||||
{
|
||||
StreamInfo playlistItem = new StreamInfo
|
||||
|
@ -308,16 +337,20 @@ namespace MediaBrowser.Model.Dlna
|
|||
DeviceProfile = options.Profile
|
||||
};
|
||||
|
||||
playlistItem.SubtitleStreamIndex = options.SubtitleStreamIndex ?? item.DefaultSubtitleStreamIndex;
|
||||
playlistItem.SubtitleStreamIndex = options.SubtitleStreamIndex ?? GetDefaultSubtitleStreamIndex(item, options.Profile.SubtitleProfiles);
|
||||
MediaStream subtitleStream = playlistItem.SubtitleStreamIndex.HasValue ? item.GetMediaStream(MediaStreamType.Subtitle, playlistItem.SubtitleStreamIndex.Value) : null;
|
||||
|
||||
MediaStream audioStream = item.GetDefaultAudioStream(options.AudioStreamIndex ?? item.DefaultAudioStreamIndex);
|
||||
int? audioStreamIndex = audioStream == null ? (int?)null : audioStream.Index;
|
||||
int? audioStreamIndex = null;
|
||||
if (audioStream != null)
|
||||
{
|
||||
audioStreamIndex = audioStream.Index;
|
||||
}
|
||||
|
||||
MediaStream videoStream = item.VideoStream;
|
||||
|
||||
// TODO: This doesn't accout for situation of device being able to handle media bitrate, but wifi connection not fast enough
|
||||
bool isEligibleForDirectPlay = IsEligibleForDirectPlay(item, options.Profile.MaxStaticBitrate, subtitleStream, options);
|
||||
bool isEligibleForDirectPlay = IsEligibleForDirectPlay(item, GetBitrateForDirectPlayCheck(item, options), subtitleStream, options);
|
||||
bool isEligibleForDirectStream = IsEligibleForDirectPlay(item, options.GetMaxBitrate(), subtitleStream, options);
|
||||
|
||||
if (isEligibleForDirectPlay || isEligibleForDirectStream)
|
||||
|
@ -374,7 +407,7 @@ namespace MediaBrowser.Model.Dlna
|
|||
playlistItem.TranscodeSeekInfo = transcodingProfile.TranscodeSeekInfo;
|
||||
playlistItem.AudioCodec = transcodingProfile.AudioCodec.Split(',')[0];
|
||||
playlistItem.VideoCodec = transcodingProfile.VideoCodec;
|
||||
playlistItem.Protocol = transcodingProfile.Protocol;
|
||||
playlistItem.SubProtocol = transcodingProfile.Protocol;
|
||||
playlistItem.AudioStreamIndex = audioStreamIndex;
|
||||
|
||||
List<ProfileCondition> videoTranscodingConditions = new List<ProfileCondition>();
|
||||
|
@ -509,10 +542,13 @@ namespace MediaBrowser.Model.Dlna
|
|||
int? packetLength = videoStream == null ? null : videoStream.PacketLength;
|
||||
int? refFrames = videoStream == null ? null : videoStream.RefFrames;
|
||||
|
||||
int? numAudioStreams = mediaSource.GetStreamCount(MediaStreamType.Audio);
|
||||
int? numVideoStreams = mediaSource.GetStreamCount(MediaStreamType.Video);
|
||||
|
||||
// Check container conditions
|
||||
foreach (ProfileCondition i in conditions)
|
||||
{
|
||||
if (!conditionProcessor.IsVideoConditionSatisfied(i, audioBitrate, audioChannels, width, height, bitDepth, videoBitrate, videoProfile, videoLevel, videoFramerate, packetLength, timestamp, isAnamorphic, isCabac, refFrames))
|
||||
if (!conditionProcessor.IsVideoConditionSatisfied(i, audioBitrate, audioChannels, width, height, bitDepth, videoBitrate, videoProfile, videoLevel, videoFramerate, packetLength, timestamp, isAnamorphic, isCabac, refFrames, numVideoStreams, numAudioStreams))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
@ -539,7 +575,7 @@ namespace MediaBrowser.Model.Dlna
|
|||
|
||||
foreach (ProfileCondition i in conditions)
|
||||
{
|
||||
if (!conditionProcessor.IsVideoConditionSatisfied(i, audioBitrate, audioChannels, width, height, bitDepth, videoBitrate, videoProfile, videoLevel, videoFramerate, packetLength, timestamp, isAnamorphic, isCabac, refFrames))
|
||||
if (!conditionProcessor.IsVideoConditionSatisfied(i, audioBitrate, audioChannels, width, height, bitDepth, videoBitrate, videoProfile, videoLevel, videoFramerate, packetLength, timestamp, isAnamorphic, isCabac, refFrames, numVideoStreams, numAudioStreams))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
@ -568,7 +604,8 @@ namespace MediaBrowser.Model.Dlna
|
|||
|
||||
foreach (ProfileCondition i in conditions)
|
||||
{
|
||||
if (!conditionProcessor.IsVideoAudioConditionSatisfied(i, audioChannels, audioBitrate, audioProfile))
|
||||
bool? isSecondaryAudio = audioStream == null ? null : mediaSource.IsSecondaryAudio(audioStream);
|
||||
if (!conditionProcessor.IsVideoAudioConditionSatisfied(i, audioChannels, audioBitrate, audioProfile, isSecondaryAudio))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
@ -628,8 +665,20 @@ namespace MediaBrowser.Model.Dlna
|
|||
// Look for an external profile that matches the stream type (text/graphical)
|
||||
foreach (SubtitleProfile profile in subtitleProfiles)
|
||||
{
|
||||
bool requiresConversion = !StringHelper.EqualsIgnoreCase(subtitleStream.Codec, profile.Format);
|
||||
|
||||
if (!profile.SupportsLanguage(subtitleStream.Language))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (profile.Method == SubtitleDeliveryMethod.External && subtitleStream.IsTextSubtitleStream == MediaStream.IsTextFormat(profile.Format))
|
||||
{
|
||||
if (!requiresConversion)
|
||||
{
|
||||
return profile;
|
||||
}
|
||||
|
||||
if (subtitleStream.SupportsExternalStream)
|
||||
{
|
||||
return profile;
|
||||
|
@ -645,8 +694,20 @@ namespace MediaBrowser.Model.Dlna
|
|||
|
||||
foreach (SubtitleProfile profile in subtitleProfiles)
|
||||
{
|
||||
bool requiresConversion = !StringHelper.EqualsIgnoreCase(subtitleStream.Codec, profile.Format);
|
||||
|
||||
if (!profile.SupportsLanguage(subtitleStream.Language))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (profile.Method == SubtitleDeliveryMethod.Embed && subtitleStream.IsTextSubtitleStream == MediaStream.IsTextFormat(profile.Format))
|
||||
{
|
||||
if (!requiresConversion)
|
||||
{
|
||||
return profile;
|
||||
}
|
||||
|
||||
return profile;
|
||||
}
|
||||
}
|
||||
|
@ -756,6 +817,9 @@ namespace MediaBrowser.Model.Dlna
|
|||
case ProfileConditionValue.AudioProfile:
|
||||
case ProfileConditionValue.Has64BitOffsets:
|
||||
case ProfileConditionValue.PacketLength:
|
||||
case ProfileConditionValue.NumAudioStreams:
|
||||
case ProfileConditionValue.NumVideoStreams:
|
||||
case ProfileConditionValue.IsSecondaryAudio:
|
||||
case ProfileConditionValue.VideoTimestamp:
|
||||
{
|
||||
// Not supported yet
|
||||
|
|
|
@ -24,7 +24,7 @@ namespace MediaBrowser.Model.Dlna
|
|||
|
||||
public string Container { get; set; }
|
||||
|
||||
public string Protocol { get; set; }
|
||||
public string SubProtocol { get; set; }
|
||||
|
||||
public long StartPositionTicks { get; set; }
|
||||
|
||||
|
@ -69,7 +69,7 @@ namespace MediaBrowser.Model.Dlna
|
|||
public SubtitleDeliveryMethod SubtitleDeliveryMethod { get; set; }
|
||||
public string SubtitleFormat { get; set; }
|
||||
|
||||
public LiveMediaInfoResult PlaybackInfo { get; set; }
|
||||
public string PlaySessionId { get; set; }
|
||||
|
||||
public string MediaSourceId
|
||||
{
|
||||
|
@ -81,18 +81,14 @@ namespace MediaBrowser.Model.Dlna
|
|||
|
||||
public bool IsDirectStream
|
||||
{
|
||||
get {
|
||||
get
|
||||
{
|
||||
return PlayMethod == PlayMethod.DirectStream ||
|
||||
PlayMethod == PlayMethod.DirectPlay;
|
||||
}
|
||||
}
|
||||
|
||||
public string ToUrl(string baseUrl, string accessToken)
|
||||
{
|
||||
return ToDlnaUrl(baseUrl, accessToken);
|
||||
}
|
||||
|
||||
public string ToDlnaUrl(string baseUrl, string accessToken)
|
||||
{
|
||||
if (PlayMethod == PlayMethod.DirectPlay)
|
||||
{
|
||||
|
@ -104,7 +100,56 @@ namespace MediaBrowser.Model.Dlna
|
|||
throw new ArgumentNullException(baseUrl);
|
||||
}
|
||||
|
||||
string dlnaCommand = BuildDlnaParam(this);
|
||||
List<string> list = new List<string>();
|
||||
foreach (NameValuePair pair in BuildParams(this, accessToken))
|
||||
{
|
||||
if (string.IsNullOrEmpty(pair.Value))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Try to keep the url clean by omitting defaults
|
||||
if (StringHelper.EqualsIgnoreCase(pair.Name, "StartTimeTicks") &&
|
||||
StringHelper.EqualsIgnoreCase(pair.Value, "0"))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (StringHelper.EqualsIgnoreCase(pair.Name, "SubtitleStreamIndex") &&
|
||||
StringHelper.EqualsIgnoreCase(pair.Value, "-1"))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (StringHelper.EqualsIgnoreCase(pair.Name, "Static") &&
|
||||
StringHelper.EqualsIgnoreCase(pair.Value, "false"))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
list.Add(string.Format("{0}={1}", pair.Name, pair.Value));
|
||||
}
|
||||
|
||||
string queryString = string.Join("&", list.ToArray());
|
||||
|
||||
return GetUrl(baseUrl, queryString);
|
||||
}
|
||||
|
||||
public string ToDlnaUrl(string baseUrl, string accessToken)
|
||||
{
|
||||
if (PlayMethod == PlayMethod.DirectPlay)
|
||||
{
|
||||
return MediaSource.Path;
|
||||
}
|
||||
|
||||
string dlnaCommand = BuildDlnaParam(this, accessToken);
|
||||
return GetUrl(baseUrl, dlnaCommand);
|
||||
}
|
||||
|
||||
private string GetUrl(string baseUrl, string queryString)
|
||||
{
|
||||
if (string.IsNullOrEmpty(baseUrl))
|
||||
{
|
||||
throw new ArgumentNullException(baseUrl);
|
||||
}
|
||||
|
||||
string extension = string.IsNullOrEmpty(Container) ? string.Empty : "." + Container;
|
||||
|
||||
|
@ -112,102 +157,107 @@ namespace MediaBrowser.Model.Dlna
|
|||
|
||||
if (MediaType == DlnaProfileType.Audio)
|
||||
{
|
||||
return string.Format("{0}/audio/{1}/stream{2}?{3}", baseUrl, ItemId, extension, dlnaCommand);
|
||||
return string.Format("{0}/audio/{1}/stream{2}?{3}", baseUrl, ItemId, extension, queryString);
|
||||
}
|
||||
|
||||
if (StringHelper.EqualsIgnoreCase(Protocol, "hls"))
|
||||
if (StringHelper.EqualsIgnoreCase(SubProtocol, "hls"))
|
||||
{
|
||||
return string.Format("{0}/videos/{1}/master.m3u8?{2}", baseUrl, ItemId, dlnaCommand);
|
||||
return string.Format("{0}/videos/{1}/master.m3u8?{2}", baseUrl, ItemId, queryString);
|
||||
}
|
||||
|
||||
return string.Format("{0}/videos/{1}/stream{2}?{3}", baseUrl, ItemId, extension, dlnaCommand);
|
||||
return string.Format("{0}/videos/{1}/stream{2}?{3}", baseUrl, ItemId, extension, queryString);
|
||||
}
|
||||
|
||||
private static string BuildDlnaParam(StreamInfo item)
|
||||
private static string BuildDlnaParam(StreamInfo item, string accessToken)
|
||||
{
|
||||
List<string> list = new List<string>
|
||||
List<string> list = new List<string>();
|
||||
|
||||
foreach (NameValuePair pair in BuildParams(item, accessToken))
|
||||
{
|
||||
item.DeviceProfileId ?? string.Empty,
|
||||
item.DeviceId ?? string.Empty,
|
||||
item.MediaSourceId ?? string.Empty,
|
||||
(item.IsDirectStream).ToString().ToLower(),
|
||||
item.VideoCodec ?? string.Empty,
|
||||
item.AudioCodec ?? string.Empty,
|
||||
item.AudioStreamIndex.HasValue ? StringHelper.ToStringCultureInvariant(item.AudioStreamIndex.Value) : string.Empty,
|
||||
item.SubtitleStreamIndex.HasValue && item.SubtitleDeliveryMethod != SubtitleDeliveryMethod.External ? StringHelper.ToStringCultureInvariant(item.SubtitleStreamIndex.Value) : string.Empty,
|
||||
item.VideoBitrate.HasValue ? StringHelper.ToStringCultureInvariant(item.VideoBitrate.Value) : string.Empty,
|
||||
item.AudioBitrate.HasValue ? StringHelper.ToStringCultureInvariant(item.AudioBitrate.Value) : string.Empty,
|
||||
item.MaxAudioChannels.HasValue ? StringHelper.ToStringCultureInvariant(item.MaxAudioChannels.Value) : string.Empty,
|
||||
item.MaxFramerate.HasValue ? StringHelper.ToStringCultureInvariant(item.MaxFramerate.Value) : string.Empty,
|
||||
item.MaxWidth.HasValue ? StringHelper.ToStringCultureInvariant(item.MaxWidth.Value) : string.Empty,
|
||||
item.MaxHeight.HasValue ? StringHelper.ToStringCultureInvariant(item.MaxHeight.Value) : string.Empty,
|
||||
StringHelper.ToStringCultureInvariant(item.StartPositionTicks),
|
||||
item.VideoLevel.HasValue ? StringHelper.ToStringCultureInvariant(item.VideoLevel.Value) : string.Empty
|
||||
};
|
||||
|
||||
list.Add(item.IsDirectStream ? string.Empty : DateTime.UtcNow.Ticks.ToString(CultureInfo.InvariantCulture));
|
||||
list.Add(item.MaxRefFrames.HasValue ? StringHelper.ToStringCultureInvariant(item.MaxRefFrames.Value) : string.Empty);
|
||||
list.Add(item.MaxVideoBitDepth.HasValue ? StringHelper.ToStringCultureInvariant(item.MaxVideoBitDepth.Value) : string.Empty);
|
||||
list.Add(item.VideoProfile ?? string.Empty);
|
||||
list.Add(item.Cabac.HasValue ? item.Cabac.Value.ToString() : string.Empty);
|
||||
|
||||
string streamId = item.PlaybackInfo == null ? null : item.PlaybackInfo.StreamId;
|
||||
list.Add(streamId ?? string.Empty);
|
||||
list.Add(pair.Value);
|
||||
}
|
||||
|
||||
return string.Format("Params={0}", string.Join(";", list.ToArray()));
|
||||
}
|
||||
|
||||
public List<SubtitleStreamInfo> GetExternalSubtitles(bool includeSelectedTrackOnly)
|
||||
private static List<NameValuePair> BuildParams(StreamInfo item, string accessToken)
|
||||
{
|
||||
List<SubtitleStreamInfo> list = new List<SubtitleStreamInfo>();
|
||||
List<NameValuePair> list = new List<NameValuePair>();
|
||||
|
||||
// First add the selected track
|
||||
if (SubtitleStreamIndex.HasValue)
|
||||
list.Add(new NameValuePair("DeviceProfileId", item.DeviceProfileId ?? string.Empty));
|
||||
list.Add(new NameValuePair("DeviceId", item.DeviceId ?? string.Empty));
|
||||
list.Add(new NameValuePair("MediaSourceId", item.MediaSourceId ?? string.Empty));
|
||||
list.Add(new NameValuePair("Static", (item.IsDirectStream).ToString().ToLower()));
|
||||
list.Add(new NameValuePair("VideoCodec", item.VideoCodec ?? string.Empty));
|
||||
list.Add(new NameValuePair("AudioCodec", item.AudioCodec ?? string.Empty));
|
||||
list.Add(new NameValuePair("AudioStreamIndex", item.AudioStreamIndex.HasValue ? StringHelper.ToStringCultureInvariant(item.AudioStreamIndex.Value) : string.Empty));
|
||||
list.Add(new NameValuePair("SubtitleStreamIndex", item.SubtitleStreamIndex.HasValue && item.SubtitleDeliveryMethod != SubtitleDeliveryMethod.External ? StringHelper.ToStringCultureInvariant(item.SubtitleStreamIndex.Value) : string.Empty));
|
||||
list.Add(new NameValuePair("VideoBitrate", item.VideoBitrate.HasValue ? StringHelper.ToStringCultureInvariant(item.VideoBitrate.Value) : string.Empty));
|
||||
list.Add(new NameValuePair("AudioBitrate", item.AudioBitrate.HasValue ? StringHelper.ToStringCultureInvariant(item.AudioBitrate.Value) : string.Empty));
|
||||
list.Add(new NameValuePair("MaxAudioChannels", item.MaxAudioChannels.HasValue ? StringHelper.ToStringCultureInvariant(item.MaxAudioChannels.Value) : string.Empty));
|
||||
list.Add(new NameValuePair("MaxFramerate", item.MaxFramerate.HasValue ? StringHelper.ToStringCultureInvariant(item.MaxFramerate.Value) : string.Empty));
|
||||
list.Add(new NameValuePair("MaxWidth", item.MaxWidth.HasValue ? StringHelper.ToStringCultureInvariant(item.MaxWidth.Value) : string.Empty));
|
||||
list.Add(new NameValuePair("MaxHeight", item.MaxHeight.HasValue ? StringHelper.ToStringCultureInvariant(item.MaxHeight.Value) : string.Empty));
|
||||
|
||||
if (StringHelper.EqualsIgnoreCase(item.SubProtocol, "hls"))
|
||||
{
|
||||
foreach (MediaStream stream in MediaSource.MediaStreams)
|
||||
{
|
||||
if (stream.Type == MediaStreamType.Subtitle && stream.Index == SubtitleStreamIndex.Value)
|
||||
{
|
||||
SubtitleStreamInfo info = GetSubtitleStreamInfo(stream);
|
||||
|
||||
if (info != null)
|
||||
{
|
||||
list.Add(info);
|
||||
}
|
||||
}
|
||||
}
|
||||
list.Add(new NameValuePair("StartTimeTicks", string.Empty));
|
||||
}
|
||||
else
|
||||
{
|
||||
list.Add(new NameValuePair("StartTimeTicks", StringHelper.ToStringCultureInvariant(item.StartPositionTicks)));
|
||||
}
|
||||
|
||||
if (!includeSelectedTrackOnly)
|
||||
{
|
||||
foreach (MediaStream stream in MediaSource.MediaStreams)
|
||||
{
|
||||
if (stream.Type == MediaStreamType.Subtitle && (!SubtitleStreamIndex.HasValue || stream.Index != SubtitleStreamIndex.Value))
|
||||
{
|
||||
SubtitleStreamInfo info = GetSubtitleStreamInfo(stream);
|
||||
list.Add(new NameValuePair("Level", item.VideoLevel.HasValue ? StringHelper.ToStringCultureInvariant(item.VideoLevel.Value) : string.Empty));
|
||||
|
||||
if (info != null)
|
||||
{
|
||||
list.Add(info);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
list.Add(new NameValuePair("ClientTime", item.IsDirectStream ? string.Empty : DateTime.UtcNow.Ticks.ToString(CultureInfo.InvariantCulture)));
|
||||
list.Add(new NameValuePair("MaxRefFrames", item.MaxRefFrames.HasValue ? StringHelper.ToStringCultureInvariant(item.MaxRefFrames.Value) : string.Empty));
|
||||
list.Add(new NameValuePair("MaxVideoBitDepth", item.MaxVideoBitDepth.HasValue ? StringHelper.ToStringCultureInvariant(item.MaxVideoBitDepth.Value) : string.Empty));
|
||||
list.Add(new NameValuePair("Profile", item.VideoProfile ?? string.Empty));
|
||||
list.Add(new NameValuePair("Cabac", item.Cabac.HasValue ? item.Cabac.Value.ToString() : string.Empty));
|
||||
|
||||
list.Add(new NameValuePair("PlaySessionId", item.PlaySessionId ?? string.Empty));
|
||||
list.Add(new NameValuePair("api_key", accessToken ?? string.Empty));
|
||||
|
||||
string liveStreamId = item.MediaSource == null ? null : item.MediaSource.LiveStreamId;
|
||||
list.Add(new NameValuePair("LiveStreamId", liveStreamId ?? string.Empty));
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
public List<SubtitleStreamInfo> GetExternalSubtitles(string baseUrl, string accessToken, bool includeSelectedTrackOnly)
|
||||
public List<SubtitleStreamInfo> GetExternalSubtitles(bool includeSelectedTrackOnly, string baseUrl, string accessToken)
|
||||
{
|
||||
if (string.IsNullOrEmpty(baseUrl))
|
||||
return GetExternalSubtitles(includeSelectedTrackOnly, false, baseUrl, accessToken);
|
||||
}
|
||||
|
||||
public List<SubtitleStreamInfo> GetExternalSubtitles(bool includeSelectedTrackOnly, bool enableAllProfiles, string baseUrl, string accessToken)
|
||||
{
|
||||
List<SubtitleStreamInfo> list = GetSubtitleProfiles(includeSelectedTrackOnly, enableAllProfiles, baseUrl, accessToken);
|
||||
List<SubtitleStreamInfo> newList = new List<SubtitleStreamInfo>();
|
||||
|
||||
// First add the selected track
|
||||
foreach (SubtitleStreamInfo stream in list)
|
||||
{
|
||||
throw new ArgumentNullException(baseUrl);
|
||||
if (stream.DeliveryMethod == SubtitleDeliveryMethod.External)
|
||||
{
|
||||
newList.Add(stream);
|
||||
}
|
||||
}
|
||||
|
||||
return newList;
|
||||
}
|
||||
|
||||
public List<SubtitleStreamInfo> GetSubtitleProfiles(bool includeSelectedTrackOnly, string baseUrl, string accessToken)
|
||||
{
|
||||
return GetSubtitleProfiles(includeSelectedTrackOnly, false, baseUrl, accessToken);
|
||||
}
|
||||
|
||||
public List<SubtitleStreamInfo> GetSubtitleProfiles(bool includeSelectedTrackOnly, bool enableAllProfiles, string baseUrl, string accessToken)
|
||||
{
|
||||
List<SubtitleStreamInfo> list = new List<SubtitleStreamInfo>();
|
||||
|
||||
// HLS will preserve timestamps so we can just grab the full subtitle stream
|
||||
long startPositionTicks = StringHelper.EqualsIgnoreCase(Protocol, "hls")
|
||||
long startPositionTicks = StringHelper.EqualsIgnoreCase(SubProtocol, "hls")
|
||||
? 0
|
||||
: StartPositionTicks;
|
||||
|
||||
|
@ -218,12 +268,7 @@ namespace MediaBrowser.Model.Dlna
|
|||
{
|
||||
if (stream.Type == MediaStreamType.Subtitle && stream.Index == SubtitleStreamIndex.Value)
|
||||
{
|
||||
SubtitleStreamInfo info = GetSubtitleStreamInfo(stream, baseUrl, accessToken, startPositionTicks);
|
||||
|
||||
if (info != null)
|
||||
{
|
||||
list.Add(info);
|
||||
}
|
||||
AddSubtitleProfiles(list, stream, enableAllProfiles, baseUrl, accessToken, startPositionTicks);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -234,12 +279,7 @@ namespace MediaBrowser.Model.Dlna
|
|||
{
|
||||
if (stream.Type == MediaStreamType.Subtitle && (!SubtitleStreamIndex.HasValue || stream.Index != SubtitleStreamIndex.Value))
|
||||
{
|
||||
SubtitleStreamInfo info = GetSubtitleStreamInfo(stream, baseUrl, accessToken, startPositionTicks);
|
||||
|
||||
if (info != null)
|
||||
{
|
||||
list.Add(info);
|
||||
}
|
||||
AddSubtitleProfiles(list, stream, enableAllProfiles, baseUrl, accessToken, startPositionTicks);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -247,41 +287,60 @@ namespace MediaBrowser.Model.Dlna
|
|||
return list;
|
||||
}
|
||||
|
||||
private SubtitleStreamInfo GetSubtitleStreamInfo(MediaStream stream, string baseUrl, string accessToken, long startPositionTicks)
|
||||
private void AddSubtitleProfiles(List<SubtitleStreamInfo> list, MediaStream stream, bool enableAllProfiles, string baseUrl, string accessToken, long startPositionTicks)
|
||||
{
|
||||
SubtitleStreamInfo info = GetSubtitleStreamInfo(stream);
|
||||
|
||||
if (info != null)
|
||||
if (enableAllProfiles)
|
||||
{
|
||||
info.Url = string.Format("{0}/Videos/{1}/{2}/Subtitles/{3}/{4}/Stream.{5}",
|
||||
baseUrl,
|
||||
ItemId,
|
||||
MediaSourceId,
|
||||
StringHelper.ToStringCultureInvariant(stream.Index),
|
||||
StringHelper.ToStringCultureInvariant(startPositionTicks),
|
||||
SubtitleFormat);
|
||||
}
|
||||
foreach (SubtitleProfile profile in DeviceProfile.SubtitleProfiles)
|
||||
{
|
||||
SubtitleStreamInfo info = GetSubtitleStreamInfo(stream, baseUrl, accessToken, startPositionTicks, new[] { profile });
|
||||
|
||||
return info;
|
||||
list.Add(info);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
SubtitleStreamInfo info = GetSubtitleStreamInfo(stream, baseUrl, accessToken, startPositionTicks, DeviceProfile.SubtitleProfiles);
|
||||
|
||||
list.Add(info);
|
||||
}
|
||||
}
|
||||
|
||||
private SubtitleStreamInfo GetSubtitleStreamInfo(MediaStream stream)
|
||||
private SubtitleStreamInfo GetSubtitleStreamInfo(MediaStream stream, string baseUrl, string accessToken, long startPositionTicks, SubtitleProfile[] subtitleProfiles)
|
||||
{
|
||||
SubtitleProfile subtitleProfile = StreamBuilder.GetSubtitleProfile(stream, DeviceProfile.SubtitleProfiles, Context);
|
||||
|
||||
if (subtitleProfile.Method != SubtitleDeliveryMethod.External)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return new SubtitleStreamInfo
|
||||
SubtitleProfile subtitleProfile = StreamBuilder.GetSubtitleProfile(stream, subtitleProfiles, Context);
|
||||
SubtitleStreamInfo info = new SubtitleStreamInfo
|
||||
{
|
||||
IsForced = stream.IsForced,
|
||||
Language = stream.Language,
|
||||
Name = stream.Language ?? "Unknown",
|
||||
Format = SubtitleFormat,
|
||||
Index = stream.Index
|
||||
Format = subtitleProfile.Format,
|
||||
Index = stream.Index,
|
||||
DeliveryMethod = subtitleProfile.Method
|
||||
};
|
||||
|
||||
if (info.DeliveryMethod == SubtitleDeliveryMethod.External)
|
||||
{
|
||||
if (MediaSource.Protocol == MediaProtocol.File || !StringHelper.EqualsIgnoreCase(stream.Codec, subtitleProfile.Format))
|
||||
{
|
||||
info.Url = string.Format("{0}/Videos/{1}/{2}/Subtitles/{3}/{4}/Stream.{5}",
|
||||
baseUrl,
|
||||
ItemId,
|
||||
MediaSourceId,
|
||||
StringHelper.ToStringCultureInvariant(stream.Index),
|
||||
StringHelper.ToStringCultureInvariant(startPositionTicks),
|
||||
subtitleProfile.Format);
|
||||
|
||||
info.IsExternalUrl = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
info.Url = stream.Path;
|
||||
info.IsExternalUrl = true;
|
||||
}
|
||||
}
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -613,6 +672,42 @@ namespace MediaBrowser.Model.Dlna
|
|||
}
|
||||
}
|
||||
|
||||
public int? TargetVideoStreamCount
|
||||
{
|
||||
get
|
||||
{
|
||||
if (IsDirectStream)
|
||||
{
|
||||
return GetMediaStreamCount(MediaStreamType.Video, int.MaxValue);
|
||||
}
|
||||
return GetMediaStreamCount(MediaStreamType.Video, 1);
|
||||
}
|
||||
}
|
||||
|
||||
public int? TargetAudioStreamCount
|
||||
{
|
||||
get
|
||||
{
|
||||
if (IsDirectStream)
|
||||
{
|
||||
return GetMediaStreamCount(MediaStreamType.Audio, int.MaxValue);
|
||||
}
|
||||
return GetMediaStreamCount(MediaStreamType.Audio, 1);
|
||||
}
|
||||
}
|
||||
|
||||
private int? GetMediaStreamCount(MediaStreamType type, int limit)
|
||||
{
|
||||
var count = MediaSource.GetStreamCount(type);
|
||||
|
||||
if (count.HasValue)
|
||||
{
|
||||
count = Math.Min(count.Value, limit);
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
public List<MediaStream> GetSelectableAudioStreams()
|
||||
{
|
||||
return GetSelectableStreams(MediaStreamType.Audio);
|
||||
|
|
47
MediaBrowser.Model/Dlna/StreamInfoSorter.cs
Normal file
47
MediaBrowser.Model/Dlna/StreamInfoSorter.cs
Normal file
|
@ -0,0 +1,47 @@
|
|||
using MediaBrowser.Model.MediaInfo;
|
||||
using MediaBrowser.Model.Session;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace MediaBrowser.Model.Dlna
|
||||
{
|
||||
public class StreamInfoSorter
|
||||
{
|
||||
public static List<StreamInfo> SortMediaSources(List<StreamInfo> streams)
|
||||
{
|
||||
return streams.OrderBy(i =>
|
||||
{
|
||||
// Nothing beats direct playing a file
|
||||
if (i.PlayMethod == PlayMethod.DirectPlay && i.MediaSource.Protocol == MediaProtocol.File)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 1;
|
||||
|
||||
}).ThenBy(i =>
|
||||
{
|
||||
switch (i.PlayMethod)
|
||||
{
|
||||
// Let's assume direct streaming a file is just as desirable as direct playing a remote url
|
||||
case PlayMethod.DirectStream:
|
||||
case PlayMethod.DirectPlay:
|
||||
return 0;
|
||||
default:
|
||||
return 1;
|
||||
}
|
||||
|
||||
}).ThenBy(i =>
|
||||
{
|
||||
switch (i.MediaSource.Protocol)
|
||||
{
|
||||
case MediaProtocol.File:
|
||||
return 0;
|
||||
default:
|
||||
return 1;
|
||||
}
|
||||
|
||||
}).ToList();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,4 +1,6 @@
|
|||
using System.Xml.Serialization;
|
||||
using MediaBrowser.Model.Extensions;
|
||||
using System.Collections.Generic;
|
||||
using System.Xml.Serialization;
|
||||
|
||||
namespace MediaBrowser.Model.Dlna
|
||||
{
|
||||
|
@ -13,5 +15,28 @@ namespace MediaBrowser.Model.Dlna
|
|||
[XmlAttribute("didlMode")]
|
||||
public string DidlMode { get; set; }
|
||||
|
||||
[XmlAttribute("language")]
|
||||
public string Language { get; set; }
|
||||
|
||||
public List<string> GetLanguages()
|
||||
{
|
||||
List<string> list = new List<string>();
|
||||
foreach (string i in (Language ?? string.Empty).Split(','))
|
||||
{
|
||||
if (!string.IsNullOrEmpty(i)) list.Add(i);
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
public bool SupportsLanguage(string language)
|
||||
{
|
||||
if (string.IsNullOrEmpty(language))
|
||||
{
|
||||
language = "und";
|
||||
}
|
||||
|
||||
List<string> languages = GetLanguages();
|
||||
return languages.Count == 0 || ListHelper.ContainsIgnoreCase(languages, language);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -8,5 +8,7 @@ namespace MediaBrowser.Model.Dlna
|
|||
public bool IsForced { get; set; }
|
||||
public string Format { get; set; }
|
||||
public int Index { get; set; }
|
||||
public SubtitleDeliveryMethod DeliveryMethod { get; set; }
|
||||
public bool IsExternalUrl { get; set; }
|
||||
}
|
||||
}
|
|
@ -24,6 +24,13 @@ namespace MediaBrowser.Model.Dto
|
|||
public bool ReadAtNativeFramerate { get; set; }
|
||||
public bool SupportsTranscoding { get; set; }
|
||||
public bool SupportsDirectStream { get; set; }
|
||||
public bool SupportsDirectPlay { get; set; }
|
||||
|
||||
public bool RequiresOpening { get; set; }
|
||||
public string OpenToken { get; set; }
|
||||
public bool RequiresClosing { get; set; }
|
||||
public string LiveStreamId { get; set; }
|
||||
public int? BufferMs { get; set; }
|
||||
|
||||
public VideoType? VideoType { get; set; }
|
||||
|
||||
|
@ -41,6 +48,10 @@ namespace MediaBrowser.Model.Dto
|
|||
public TransportStreamTimestamp? Timestamp { get; set; }
|
||||
public Dictionary<string, string> RequiredHttpHeaders { get; set; }
|
||||
|
||||
public string TranscodingUrl { get; set; }
|
||||
public string TranscodingSubProtocol { get; set; }
|
||||
public string TranscodingContainer { get; set; }
|
||||
|
||||
public MediaSourceInfo()
|
||||
{
|
||||
Formats = new List<string>();
|
||||
|
@ -49,6 +60,7 @@ namespace MediaBrowser.Model.Dto
|
|||
PlayableStreamFileNames = new List<string>();
|
||||
SupportsTranscoding = true;
|
||||
SupportsDirectStream = true;
|
||||
SupportsDirectPlay = true;
|
||||
}
|
||||
|
||||
public int? DefaultAudioStreamIndex { get; set; }
|
||||
|
@ -123,5 +135,40 @@ namespace MediaBrowser.Model.Dto
|
|||
|
||||
return null;
|
||||
}
|
||||
|
||||
public int? GetStreamCount(MediaStreamType type)
|
||||
{
|
||||
int numMatches = 0;
|
||||
int numStreams = 0;
|
||||
|
||||
foreach (MediaStream i in MediaStreams)
|
||||
{
|
||||
numStreams++;
|
||||
if (i.Type == type)
|
||||
{
|
||||
numMatches++;
|
||||
}
|
||||
}
|
||||
|
||||
if (numStreams == 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return numMatches;
|
||||
}
|
||||
|
||||
public bool? IsSecondaryAudio(MediaStream stream)
|
||||
{
|
||||
foreach (MediaStream currentStream in MediaStreams)
|
||||
{
|
||||
if (currentStream.Type == MediaStreamType.Audio)
|
||||
{
|
||||
return currentStream.Index != stream.Index;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,6 @@ namespace MediaBrowser.Model.Dto
|
|||
{
|
||||
Default = 0,
|
||||
Grouping = 1,
|
||||
Cache = 2
|
||||
Placeholder = 2
|
||||
}
|
||||
}
|
|
@ -3,6 +3,17 @@ namespace MediaBrowser.Model.Dto
|
|||
{
|
||||
public class NameValuePair
|
||||
{
|
||||
public NameValuePair()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public NameValuePair(string name, string value)
|
||||
{
|
||||
Name = name;
|
||||
Value = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the name.
|
||||
/// </summary>
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
using MediaBrowser.Model.Extensions;
|
||||
using MediaBrowser.Model.Dlna;
|
||||
using MediaBrowser.Model.Extensions;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace MediaBrowser.Model.Entities
|
||||
|
@ -129,18 +130,45 @@ namespace MediaBrowser.Model.Entities
|
|||
/// <value>The index.</value>
|
||||
public int Index { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the score.
|
||||
/// </summary>
|
||||
/// <value>The score.</value>
|
||||
public int? Score { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether this instance is external.
|
||||
/// </summary>
|
||||
/// <value><c>true</c> if this instance is external; otherwise, <c>false</c>.</value>
|
||||
public bool IsExternal { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the method.
|
||||
/// </summary>
|
||||
/// <value>The method.</value>
|
||||
public SubtitleDeliveryMethod? DeliveryMethod { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets the delivery URL.
|
||||
/// </summary>
|
||||
/// <value>The delivery URL.</value>
|
||||
public string DeliveryUrl { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether this instance is external URL.
|
||||
/// </summary>
|
||||
/// <value><c>null</c> if [is external URL] contains no value, <c>true</c> if [is external URL]; otherwise, <c>false</c>.</value>
|
||||
public bool? IsExternalUrl { get; set; }
|
||||
|
||||
public bool IsTextSubtitleStream
|
||||
{
|
||||
get
|
||||
{
|
||||
if (Type != MediaStreamType.Subtitle) return false;
|
||||
|
||||
if (string.IsNullOrEmpty(Codec) && !IsExternal)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return IsTextFormat(Codec);
|
||||
}
|
||||
}
|
||||
|
@ -168,6 +196,12 @@ namespace MediaBrowser.Model.Entities
|
|||
/// <value>The filename.</value>
|
||||
public string Path { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the external identifier.
|
||||
/// </summary>
|
||||
/// <value>The external identifier.</value>
|
||||
public string ExternalId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the pixel format.
|
||||
/// </summary>
|
||||
|
|
|
@ -53,6 +53,12 @@ namespace MediaBrowser.Model.LiveTv
|
|||
/// <remarks>If set to null, all programs will be returned</remarks>
|
||||
public bool? IsMovie { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether this instance is sports.
|
||||
/// </summary>
|
||||
/// <value><c>null</c> if [is sports] contains no value, <c>true</c> if [is sports]; otherwise, <c>false</c>.</value>
|
||||
public bool? IsSports { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Skips over a given number of items within the results. Use for paging.
|
||||
/// </summary>
|
||||
|
|
|
@ -31,5 +31,10 @@
|
|||
/// </summary>
|
||||
/// <value><c>null</c> if [is movie] contains no value, <c>true</c> if [is movie]; otherwise, <c>false</c>.</value>
|
||||
public bool? IsMovie { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether this instance is sports.
|
||||
/// </summary>
|
||||
/// <value><c>null</c> if [is sports] contains no value, <c>true</c> if [is sports]; otherwise, <c>false</c>.</value>
|
||||
public bool? IsSports { get; set; }
|
||||
}
|
||||
}
|
|
@ -126,6 +126,7 @@
|
|||
<Compile Include="Devices\DevicesOptions.cs" />
|
||||
<Compile Include="Dlna\EncodingContext.cs" />
|
||||
<Compile Include="Dlna\ILocalPlayer.cs" />
|
||||
<Compile Include="Dlna\StreamInfoSorter.cs" />
|
||||
<Compile Include="Dlna\NullLocalPlayer.cs" />
|
||||
<Compile Include="Dlna\PlaybackErrorCode.cs" />
|
||||
<Compile Include="Dlna\PlaybackException.cs" />
|
||||
|
@ -140,7 +141,10 @@
|
|||
<Compile Include="Dto\MetadataEditorInfo.cs" />
|
||||
<Compile Include="Dto\NameIdPair.cs" />
|
||||
<Compile Include="Dto\NameValuePair.cs" />
|
||||
<Compile Include="MediaInfo\LiveMediaInfoResult.cs" />
|
||||
<Compile Include="MediaInfo\LiveStreamRequest.cs" />
|
||||
<Compile Include="MediaInfo\LiveStreamResponse.cs" />
|
||||
<Compile Include="MediaInfo\PlaybackInfoRequest.cs" />
|
||||
<Compile Include="MediaInfo\PlaybackInfoResponse.cs" />
|
||||
<Compile Include="Dto\MediaSourceType.cs" />
|
||||
<Compile Include="Configuration\DynamicDayOfWeek.cs" />
|
||||
<Compile Include="Entities\ExtraType.cs" />
|
||||
|
|
36
MediaBrowser.Model/MediaInfo/LiveStreamRequest.cs
Normal file
36
MediaBrowser.Model/MediaInfo/LiveStreamRequest.cs
Normal file
|
@ -0,0 +1,36 @@
|
|||
using MediaBrowser.Model.Dlna;
|
||||
|
||||
namespace MediaBrowser.Model.MediaInfo
|
||||
{
|
||||
public class LiveStreamRequest
|
||||
{
|
||||
public string OpenToken { get; set; }
|
||||
public string UserId { get; set; }
|
||||
public string PlaySessionId { get; set; }
|
||||
public int? MaxStreamingBitrate { get; set; }
|
||||
public long? StartTimeTicks { get; set; }
|
||||
public int? AudioStreamIndex { get; set; }
|
||||
public int? SubtitleStreamIndex { get; set; }
|
||||
public string ItemId { get; set; }
|
||||
public DeviceProfile DeviceProfile { get; set; }
|
||||
|
||||
public LiveStreamRequest()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public LiveStreamRequest(AudioOptions options)
|
||||
{
|
||||
MaxStreamingBitrate = options.MaxBitrate;
|
||||
ItemId = options.ItemId;
|
||||
DeviceProfile = options.Profile;
|
||||
|
||||
VideoOptions videoOptions = options as VideoOptions;
|
||||
if (videoOptions != null)
|
||||
{
|
||||
AudioStreamIndex = videoOptions.AudioStreamIndex;
|
||||
SubtitleStreamIndex = videoOptions.SubtitleStreamIndex;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
9
MediaBrowser.Model/MediaInfo/LiveStreamResponse.cs
Normal file
9
MediaBrowser.Model/MediaInfo/LiveStreamResponse.cs
Normal file
|
@ -0,0 +1,9 @@
|
|||
using MediaBrowser.Model.Dto;
|
||||
|
||||
namespace MediaBrowser.Model.MediaInfo
|
||||
{
|
||||
public class LiveStreamResponse
|
||||
{
|
||||
public MediaSourceInfo MediaSource { get; set; }
|
||||
}
|
||||
}
|
25
MediaBrowser.Model/MediaInfo/PlaybackInfoRequest.cs
Normal file
25
MediaBrowser.Model/MediaInfo/PlaybackInfoRequest.cs
Normal file
|
@ -0,0 +1,25 @@
|
|||
using MediaBrowser.Model.Dlna;
|
||||
|
||||
namespace MediaBrowser.Model.MediaInfo
|
||||
{
|
||||
public class PlaybackInfoRequest
|
||||
{
|
||||
public string Id { get; set; }
|
||||
|
||||
public string UserId { get; set; }
|
||||
|
||||
public int? MaxStreamingBitrate { get; set; }
|
||||
|
||||
public long? StartTimeTicks { get; set; }
|
||||
|
||||
public int? AudioStreamIndex { get; set; }
|
||||
|
||||
public int? SubtitleStreamIndex { get; set; }
|
||||
|
||||
public string MediaSourceId { get; set; }
|
||||
|
||||
public string LiveStreamId { get; set; }
|
||||
|
||||
public DeviceProfile DeviceProfile { get; set; }
|
||||
}
|
||||
}
|
|
@ -4,7 +4,7 @@ using System.Collections.Generic;
|
|||
|
||||
namespace MediaBrowser.Model.MediaInfo
|
||||
{
|
||||
public class LiveMediaInfoResult
|
||||
public class PlaybackInfoResponse
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the media sources.
|
||||
|
@ -13,10 +13,10 @@ namespace MediaBrowser.Model.MediaInfo
|
|||
public List<MediaSourceInfo> MediaSources { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the stream identifier.
|
||||
/// Gets or sets the play session identifier.
|
||||
/// </summary>
|
||||
/// <value>The stream identifier.</value>
|
||||
public string StreamId { get; set; }
|
||||
/// <value>The play session identifier.</value>
|
||||
public string PlaySessionId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the error code.
|
||||
|
@ -24,7 +24,7 @@ namespace MediaBrowser.Model.MediaInfo
|
|||
/// <value>The error code.</value>
|
||||
public PlaybackErrorCode? ErrorCode { get; set; }
|
||||
|
||||
public LiveMediaInfoResult()
|
||||
public PlaybackInfoResponse()
|
||||
{
|
||||
MediaSources = new List<MediaSourceInfo>();
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
using MediaBrowser.Model.Configuration;
|
||||
using MediaBrowser.Model.Extensions;
|
||||
using MediaBrowser.Model.Users;
|
||||
|
||||
namespace MediaBrowser.Model.Notifications
|
||||
{
|
||||
|
@ -106,7 +107,7 @@ namespace MediaBrowser.Model.Notifications
|
|||
!ListHelper.ContainsIgnoreCase(opt.DisabledMonitorUsers, userId);
|
||||
}
|
||||
|
||||
public bool IsEnabledToSendToUser(string type, string userId, UserConfiguration userConfig)
|
||||
public bool IsEnabledToSendToUser(string type, string userId, UserPolicy userPolicy)
|
||||
{
|
||||
NotificationOption opt = GetOptions(type);
|
||||
|
||||
|
@ -117,7 +118,7 @@ namespace MediaBrowser.Model.Notifications
|
|||
return true;
|
||||
}
|
||||
|
||||
if (opt.SendToUserMode == SendToUserType.Admins && userConfig.IsAdministrator)
|
||||
if (opt.SendToUserMode == SendToUserType.Admins && userPolicy.IsAdministrator)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -281,6 +281,13 @@ namespace MediaBrowser.Model.Querying
|
|||
public int? ImageTypeLimit { get; set; }
|
||||
public ImageType[] EnableImageTypes { get; set; }
|
||||
|
||||
[Obsolete]
|
||||
public string[] Artists { get; set; }
|
||||
[Obsolete]
|
||||
public string[] Studios { get; set; }
|
||||
[Obsolete]
|
||||
public string Person { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ItemQuery" /> class.
|
||||
/// </summary>
|
||||
|
@ -299,6 +306,9 @@ namespace MediaBrowser.Model.Querying
|
|||
|
||||
VideoTypes = new VideoType[] { };
|
||||
|
||||
Artists = new string[] { };
|
||||
Studios = new string[] { };
|
||||
|
||||
Genres = new string[] { };
|
||||
StudioIds = new string[] { };
|
||||
IncludeItemTypes = new string[] { };
|
||||
|
|
|
@ -43,6 +43,7 @@ namespace MediaBrowser.Model.Querying
|
|||
/// The premiere date
|
||||
/// </summary>
|
||||
public const string PremiereDate = "PremiereDate";
|
||||
public const string StartDate = "StartDate";
|
||||
/// <summary>
|
||||
/// The sort name
|
||||
/// </summary>
|
||||
|
|
|
@ -78,5 +78,15 @@ namespace MediaBrowser.Model.Session
|
|||
/// </summary>
|
||||
/// <value>The play method.</value>
|
||||
public PlayMethod PlayMethod { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets the live stream identifier.
|
||||
/// </summary>
|
||||
/// <value>The live stream identifier.</value>
|
||||
public string LiveStreamId { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets the play session identifier.
|
||||
/// </summary>
|
||||
/// <value>The play session identifier.</value>
|
||||
public string PlaySessionId { get; set; }
|
||||
}
|
||||
}
|
|
@ -36,5 +36,15 @@ namespace MediaBrowser.Model.Session
|
|||
/// </summary>
|
||||
/// <value>The position ticks.</value>
|
||||
public long? PositionTicks { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets the live stream identifier.
|
||||
/// </summary>
|
||||
/// <value>The live stream identifier.</value>
|
||||
public string LiveStreamId { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets the play session identifier.
|
||||
/// </summary>
|
||||
/// <value>The play session identifier.</value>
|
||||
public string PlaySessionId { get; set; }
|
||||
}
|
||||
}
|
|
@ -31,13 +31,24 @@ namespace MediaBrowser.Model.Sync
|
|||
/// <value>The item identifier.</value>
|
||||
public string ItemId { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets the synchronize job item identifier.
|
||||
/// </summary>
|
||||
/// <value>The synchronize job item identifier.</value>
|
||||
public string SyncJobItemId { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets the user ids with access.
|
||||
/// </summary>
|
||||
/// <value>The user ids with access.</value>
|
||||
public List<string> UserIdsWithAccess { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets the additional files.
|
||||
/// </summary>
|
||||
/// <value>The additional files.</value>
|
||||
public List<string> AdditionalFiles { get; set; }
|
||||
|
||||
public LocalItem()
|
||||
{
|
||||
AdditionalFiles = new List<string>();
|
||||
UserIdsWithAccess = new List<string>();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@ namespace MediaBrowser.Model.Sync
|
|||
{
|
||||
public List<string> LocalItemIds { get; set; }
|
||||
public List<string> OfflineUserIds { get; set; }
|
||||
public List<string> SyncJobItemIds { get; set; }
|
||||
|
||||
public string TargetId { get; set; }
|
||||
|
||||
|
|
|
@ -32,8 +32,6 @@ namespace MediaBrowser.Model.Users
|
|||
public bool EnableUserPreferenceAccess { get; set; }
|
||||
public AccessSchedule[] AccessSchedules { get; set; }
|
||||
public UnratedItem[] BlockUnratedItems { get; set; }
|
||||
public string[] BlockedMediaFolders { get; set; }
|
||||
public string[] BlockedChannels { get; set; }
|
||||
public bool EnableRemoteControlOfOtherUsers { get; set; }
|
||||
public bool EnableSharedDeviceControl { get; set; }
|
||||
|
||||
|
@ -63,6 +61,7 @@ namespace MediaBrowser.Model.Users
|
|||
|
||||
public UserPolicy()
|
||||
{
|
||||
EnableSync = true;
|
||||
EnableLiveTvManagement = true;
|
||||
EnableMediaPlayback = true;
|
||||
EnableLiveTvAccess = true;
|
||||
|
|
|
@ -96,7 +96,7 @@ namespace MediaBrowser.Providers.MediaInfo
|
|||
var album = item.Parent as MusicAlbum;
|
||||
|
||||
var filename = item.Album ?? string.Empty;
|
||||
filename += item.Artists.FirstOrDefault() ?? string.Empty;
|
||||
filename += string.Join(",", item.Artists.ToArray());
|
||||
filename += album == null ? item.Id.ToString("N") + "_primary" + item.DateModified.Ticks : album.Id.ToString("N") + album.DateModified.Ticks + "_primary";
|
||||
|
||||
filename = filename.GetMD5() + ".jpg";
|
||||
|
|
|
@ -6,15 +6,14 @@ using MediaBrowser.Controller.Entities.Movies;
|
|||
using MediaBrowser.Controller.Entities.TV;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Controller.Subtitles;
|
||||
using MediaBrowser.Model.Configuration;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.Logging;
|
||||
using MediaBrowser.Model.Providers;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Model.Providers;
|
||||
|
||||
namespace MediaBrowser.Providers.MediaInfo
|
||||
{
|
||||
|
@ -23,14 +22,16 @@ namespace MediaBrowser.Providers.MediaInfo
|
|||
private readonly ILibraryManager _libraryManager;
|
||||
private readonly IServerConfigurationManager _config;
|
||||
private readonly ISubtitleManager _subtitleManager;
|
||||
private readonly IMediaSourceManager _mediaSourceManager;
|
||||
private readonly ILogger _logger;
|
||||
|
||||
public SubtitleScheduledTask(ILibraryManager libraryManager, IServerConfigurationManager config, ISubtitleManager subtitleManager, ILogger logger)
|
||||
public SubtitleScheduledTask(ILibraryManager libraryManager, IServerConfigurationManager config, ISubtitleManager subtitleManager, ILogger logger, IMediaSourceManager mediaSourceManager)
|
||||
{
|
||||
_libraryManager = libraryManager;
|
||||
_config = config;
|
||||
_subtitleManager = subtitleManager;
|
||||
_logger = logger;
|
||||
_mediaSourceManager = mediaSourceManager;
|
||||
}
|
||||
|
||||
public string Name
|
||||
|
@ -107,7 +108,7 @@ namespace MediaBrowser.Providers.MediaInfo
|
|||
(options.DownloadMovieSubtitles &&
|
||||
video is Movie))
|
||||
{
|
||||
var mediaStreams = video.GetMediaSources(false).First().MediaStreams;
|
||||
var mediaStreams = _mediaSourceManager.GetStaticMediaSources(video, false).First().MediaStreams;
|
||||
|
||||
var downloadedLanguages = await new SubtitleDownloader(_logger,
|
||||
_subtitleManager)
|
||||
|
|
|
@ -169,29 +169,32 @@ namespace MediaBrowser.Server.Implementations.Channels
|
|||
|
||||
foreach (var item in result.Items)
|
||||
{
|
||||
var channelItem = (IChannelItem)item;
|
||||
var channelItem = item as IChannelMediaItem;
|
||||
|
||||
var channelFeatures = _manager.GetChannelFeatures(channelItem.ChannelId);
|
||||
|
||||
if (channelFeatures.SupportsContentDownloading)
|
||||
if (channelItem != null)
|
||||
{
|
||||
if (options.DownloadingChannels.Contains(channelItem.ChannelId))
|
||||
var channelFeatures = _manager.GetChannelFeatures(channelItem.ChannelId);
|
||||
|
||||
if (channelFeatures.SupportsContentDownloading)
|
||||
{
|
||||
try
|
||||
if (options.DownloadingChannels.Contains(channelItem.ChannelId))
|
||||
{
|
||||
await DownloadChannelItem(item, options, cancellationToken, path);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
break;
|
||||
}
|
||||
catch (ChannelDownloadException)
|
||||
{
|
||||
// Logged at lower levels
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.ErrorException("Error downloading channel content for {0}", ex, item.Name);
|
||||
try
|
||||
{
|
||||
await DownloadChannelItem(channelItem, options, cancellationToken, path);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
break;
|
||||
}
|
||||
catch (ChannelDownloadException)
|
||||
{
|
||||
// Logged at lower levels
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.ErrorException("Error downloading channel content for {0}", ex, item.Name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -210,13 +213,13 @@ namespace MediaBrowser.Server.Implementations.Channels
|
|||
return channelOptions.DownloadSizeLimit;
|
||||
}
|
||||
|
||||
private async Task DownloadChannelItem(BaseItem item,
|
||||
private async Task DownloadChannelItem(IChannelMediaItem item,
|
||||
ChannelOptions channelOptions,
|
||||
CancellationToken cancellationToken,
|
||||
string path)
|
||||
{
|
||||
var itemId = item.Id.ToString("N");
|
||||
var sources = await _manager.GetChannelItemMediaSources(itemId, false, cancellationToken)
|
||||
var sources = await _manager.GetStaticMediaSources(item, true, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
var cachedVersions = sources.Where(i => i.Protocol == MediaProtocol.File).ToList();
|
||||
|
@ -237,11 +240,9 @@ namespace MediaBrowser.Server.Implementations.Channels
|
|||
}
|
||||
}
|
||||
|
||||
var channelItem = (IChannelMediaItem)item;
|
||||
var destination = Path.Combine(path, item.ChannelId, itemId);
|
||||
|
||||
var destination = Path.Combine(path, channelItem.ChannelId, itemId);
|
||||
|
||||
await _manager.DownloadChannelItem(channelItem, destination, new Progress<double>(), cancellationToken)
|
||||
await _manager.DownloadChannelItem(item, destination, new Progress<double>(), cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
await RefreshMediaSourceItem(destination, cancellationToken).ConfigureAwait(false);
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue