Merging in latest dev

pull/702/head
T. Adams 10 years ago
commit abf12569ba

@ -132,7 +132,7 @@ namespace MediaBrowser.Api
/// Called when [transcode beginning]. /// Called when [transcode beginning].
/// </summary> /// </summary>
/// <param name="path">The path.</param> /// <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="transcodingJobId">The transcoding job identifier.</param>
/// <param name="type">The type.</param> /// <param name="type">The type.</param>
/// <param name="process">The process.</param> /// <param name="process">The process.</param>
@ -141,7 +141,7 @@ namespace MediaBrowser.Api
/// <param name="cancellationTokenSource">The cancellation token source.</param> /// <param name="cancellationTokenSource">The cancellation token source.</param>
/// <returns>TranscodingJob.</returns> /// <returns>TranscodingJob.</returns>
public TranscodingJob OnTranscodeBeginning(string path, public TranscodingJob OnTranscodeBeginning(string path,
string streamId, string playSessionId,
string transcodingJobId, string transcodingJobId,
TranscodingJobType type, TranscodingJobType type,
Process process, Process process,
@ -160,7 +160,7 @@ namespace MediaBrowser.Api
DeviceId = deviceId, DeviceId = deviceId,
CancellationTokenSource = cancellationTokenSource, CancellationTokenSource = cancellationTokenSource,
Id = transcodingJobId, Id = transcodingJobId,
StreamId = streamId PlaySessionId = playSessionId
}; };
_activeTranscodingJobs.Add(job); _activeTranscodingJobs.Add(job);
@ -187,7 +187,7 @@ namespace MediaBrowser.Api
if (!string.IsNullOrWhiteSpace(deviceId)) if (!string.IsNullOrWhiteSpace(deviceId))
{ {
var audioCodec = state.ActualOutputVideoCodec; var audioCodec = state.ActualOutputAudioCodec;
var videoCodec = state.ActualOutputVideoCodec; var videoCodec = state.ActualOutputVideoCodec;
_sessionManager.ReportTranscodingInfo(deviceId, new TranscodingInfo _sessionManager.ReportTranscodingInfo(deviceId, new TranscodingInfo
@ -286,27 +286,65 @@ namespace MediaBrowser.Api
job.DisposeKillTimer(); job.DisposeKillTimer();
} }
public void OnTranscodeEndRequest(TranscodingJob job) public void OnTranscodeEndRequest(TranscodingJob job)
{ {
job.ActiveRequestCount--; job.ActiveRequestCount--;
if (job.ActiveRequestCount == 0) if (job.ActiveRequestCount == 0)
{ {
// TODO: Lower this hls timeout PingTimer(job, true);
var timerDuration = job.Type == TranscodingJobType.Progressive ? }
1000 : }
14400000; 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 =>
{ {
job.KillTimer = new Timer(OnTranscodeKillTimerStopped, job, timerDuration, Timeout.Infinite); if (string.Equals(deviceId, j.DeviceId, StringComparison.OrdinalIgnoreCase))
} {
else 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.Change(timerDuration, Timeout.Infinite); job.KillTimer = new Timer(OnTranscodeKillTimerStopped, job, timerDuration, Timeout.Infinite);
} }
} }
else
{
job.KillTimer.Change(timerDuration, Timeout.Infinite);
}
} }
/// <summary> /// <summary>
@ -324,10 +362,10 @@ namespace MediaBrowser.Api
/// Kills the single transcoding job. /// Kills the single transcoding job.
/// </summary> /// </summary>
/// <param name="deviceId">The device id.</param> /// <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> /// <param name="deleteFiles">The delete files.</param>
/// <returns>Task.</returns> /// <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)) if (string.IsNullOrEmpty(deviceId))
{ {
@ -338,7 +376,7 @@ namespace MediaBrowser.Api
{ {
if (string.Equals(deviceId, j.DeviceId, StringComparison.OrdinalIgnoreCase)) 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; return false;
@ -539,10 +577,10 @@ namespace MediaBrowser.Api
public class TranscodingJob public class TranscodingJob
{ {
/// <summary> /// <summary>
/// Gets or sets the stream identifier. /// Gets or sets the play session identifier.
/// </summary> /// </summary>
/// <value>The stream identifier.</value> /// <value>The play session identifier.</value>
public string StreamId { get; set; } public string PlaySessionId { get; set; }
/// <summary> /// <summary>
/// Gets or sets the path. /// Gets or sets the path.
/// </summary> /// </summary>

@ -123,7 +123,7 @@ namespace MediaBrowser.Api
public void Post(AutoSetMetadataOptions request) public void Post(AutoSetMetadataOptions request)
{ {
_configurationManager.DisableMetadataService("Media Browser Xml"); _configurationManager.DisableMetadataService("Emby Xml");
_configurationManager.SaveConfiguration(); _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")] [ApiMember(Name = "IsMovie", Description = "Optional filter for movies.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET,POST")]
public bool? IsMovie { get; set; } 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")] [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; } 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")] [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; } 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")] [ApiMember(Name = "IsMovie", Description = "Optional filter for movies.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")]
public bool? IsMovie { get; set; } public bool? IsMovie { get; set; }
} }
@ -422,6 +428,7 @@ namespace MediaBrowser.Api.LiveTv
query.SortBy = (request.SortBy ?? String.Empty).Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); query.SortBy = (request.SortBy ?? String.Empty).Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
query.SortOrder = request.SortOrder; query.SortOrder = request.SortOrder;
query.IsMovie = request.IsMovie; query.IsMovie = request.IsMovie;
query.IsSports = request.IsSports;
query.Genres = (request.Genres ?? String.Empty).Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); query.Genres = (request.Genres ?? String.Empty).Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
var result = await _liveTvManager.GetPrograms(query, CancellationToken.None).ConfigureAwait(false); var result = await _liveTvManager.GetPrograms(query, CancellationToken.None).ConfigureAwait(false);
@ -437,7 +444,8 @@ namespace MediaBrowser.Api.LiveTv
IsAiring = request.IsAiring, IsAiring = request.IsAiring,
Limit = request.Limit, Limit = request.Limit,
HasAired = request.HasAired, HasAired = request.HasAired,
IsMovie = request.IsMovie IsMovie = request.IsMovie,
IsSports = request.IsSports
}; };
var result = await _liveTvManager.GetRecommendedPrograms(query, CancellationToken.None).ConfigureAwait(false); var result = await _liveTvManager.GetRecommendedPrograms(query, CancellationToken.None).ConfigureAwait(false);

@ -1,12 +1,10 @@
using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Extensions;
using MediaBrowser.Common.IO; using MediaBrowser.Common.IO;
using MediaBrowser.Controller.Channels;
using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Devices; using MediaBrowser.Controller.Devices;
using MediaBrowser.Controller.Dlna; using MediaBrowser.Controller.Dlna;
using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.LiveTv;
using MediaBrowser.Controller.MediaEncoding; using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Dlna;
@ -65,7 +63,6 @@ namespace MediaBrowser.Api.Playback
protected IFileSystem FileSystem { get; private set; } protected IFileSystem FileSystem { get; private set; }
protected ILiveTvManager LiveTvManager { get; private set; }
protected IDlnaManager DlnaManager { get; private set; } protected IDlnaManager DlnaManager { get; private set; }
protected IDeviceManager DeviceManager { get; private set; } protected IDeviceManager DeviceManager { get; private set; }
protected ISubtitleEncoder SubtitleEncoder { get; private set; } protected ISubtitleEncoder SubtitleEncoder { get; private set; }
@ -75,14 +72,13 @@ namespace MediaBrowser.Api.Playback
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="BaseStreamingService" /> class. /// Initializes a new instance of the <see cref="BaseStreamingService" /> class.
/// </summary> /// </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; ZipClient = zipClient;
MediaSourceManager = mediaSourceManager; MediaSourceManager = mediaSourceManager;
DeviceManager = deviceManager; DeviceManager = deviceManager;
SubtitleEncoder = subtitleEncoder; SubtitleEncoder = subtitleEncoder;
DlnaManager = dlnaManager; DlnaManager = dlnaManager;
LiveTvManager = liveTvManager;
FileSystem = fileSystem; FileSystem = fileSystem;
ServerConfigurationManager = serverConfig; ServerConfigurationManager = serverConfig;
UserManager = userManager; UserManager = userManager;
@ -95,11 +91,10 @@ namespace MediaBrowser.Api.Playback
/// Gets the command line arguments. /// Gets the command line arguments.
/// </summary> /// </summary>
/// <param name="outputPath">The output path.</param> /// <param name="outputPath">The output path.</param>
/// <param name="transcodingJobId">The transcoding job identifier.</param>
/// <param name="state">The state.</param> /// <param name="state">The state.</param>
/// <param name="isEncoding">if set to <c>true</c> [is encoding].</param> /// <param name="isEncoding">if set to <c>true</c> [is encoding].</param>
/// <returns>System.String.</returns> /// <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> /// <summary>
/// Gets the type of the transcoding job. /// Gets the type of the transcoding job.
@ -128,10 +123,10 @@ namespace MediaBrowser.Api.Playback
var outputFileExtension = GetOutputFileExtension(state); 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.DeviceId ?? string.Empty);
data += "-" + (state.Request.StreamId ?? string.Empty); data += "-" + (state.Request.PlaySessionId ?? string.Empty);
data += "-" + (state.Request.ClientTime ?? string.Empty); data += "-" + (state.Request.ClientTime ?? string.Empty);
var dataHash = data.GetMD5().ToString("N"); var dataHash = data.GetMD5().ToString("N");
@ -704,7 +699,7 @@ namespace MediaBrowser.Api.Playback
if (!string.IsNullOrEmpty(state.SubtitleStream.Language)) 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)) if (!string.IsNullOrEmpty(charenc))
{ {
@ -719,8 +714,10 @@ namespace MediaBrowser.Api.Playback
seconds.ToString(UsCulture)); seconds.ToString(UsCulture));
} }
var mediaPath = state.MediaPath ?? string.Empty;
return string.Format("subtitles='{0}:si={1}',setpts=PTS -{2}/TB", return string.Format("subtitles='{0}:si={1}',setpts=PTS -{2}/TB",
state.MediaPath.Replace('\\', '/').Replace(":/", "\\:/"), mediaPath.Replace('\\', '/').Replace(":/", "\\:/"),
state.InternalSubtitleStreamOffset.ToString(UsCulture), state.InternalSubtitleStreamOffset.ToString(UsCulture),
seconds.ToString(UsCulture)); seconds.ToString(UsCulture));
} }
@ -895,12 +892,11 @@ namespace MediaBrowser.Api.Playback
/// <summary> /// <summary>
/// Gets the input argument. /// Gets the input argument.
/// </summary> /// </summary>
/// <param name="transcodingJobId">The transcoding job identifier.</param>
/// <param name="state">The state.</param> /// <param name="state">The state.</param>
/// <returns>System.String.</returns> /// <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) if (state.SubtitleStream != null)
{ {
@ -913,27 +909,18 @@ namespace MediaBrowser.Api.Playback
return arg; 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 protocol = state.InputProtocol;
var mediaPath = state.MediaPath ?? string.Empty;
var inputPath = new[] { state.MediaPath }; var inputPath = new[] { mediaPath };
if (state.IsInputVideo) if (state.IsInputVideo)
{ {
if (!(state.VideoType == VideoType.Iso && state.IsoMount == null)) 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); state.IsoMount = await IsoManager.Mount(state.MediaPath, cancellationTokenSource.Token).ConfigureAwait(false);
} }
if (string.IsNullOrEmpty(state.MediaPath)) if (state.MediaSource.RequiresOpening)
{ {
var checkCodecs = false; var liveStreamResponse = await MediaSourceManager.OpenLiveStream(new LiveStreamRequest
if (string.Equals(state.ItemType, typeof(LiveTvChannel).Name))
{ {
var streamInfo = await LiveTvManager.GetChannelStream(state.Request.Id, cancellationTokenSource.Token).ConfigureAwait(false); OpenToken = state.MediaSource.OpenToken
state.LiveTvStreamId = streamInfo.Id;
state.MediaPath = streamInfo.Path; }, false, cancellationTokenSource.Token).ConfigureAwait(false);
state.InputProtocol = streamInfo.Protocol;
await Task.Delay(1500, cancellationTokenSource.Token).ConfigureAwait(false); AttachMediaSourceInfo(state, liveStreamResponse.MediaSource, state.VideoRequest, state.RequestedUrl);
AttachMediaStreamInfo(state, streamInfo, state.VideoRequest, state.RequestedUrl);
checkCodecs = true;
}
else if (string.Equals(state.ItemType, typeof(LiveTvVideoRecording).Name) || if (state.VideoRequest != null)
string.Equals(state.ItemType, typeof(LiveTvAudioRecording).Name))
{ {
var streamInfo = await LiveTvManager.GetRecordingStream(state.Request.Id, cancellationTokenSource.Token).ConfigureAwait(false); TryStreamCopy(state, state.VideoRequest);
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;
} }
}
var videoRequest = state.VideoRequest; if (state.MediaSource.BufferMs.HasValue)
{
if (videoRequest != null && checkCodecs) await Task.Delay(state.MediaSource.BufferMs.Value, cancellationTokenSource.Token).ConfigureAwait(false);
{
if (state.VideoStream != null && CanStreamCopyVideo(videoRequest, state.VideoStream))
{
state.OutputVideoCodec = "copy";
}
if (state.AudioStream != null && CanStreamCopyAudio(videoRequest, state.AudioStream, state.SupportedAudioCodecs))
{
state.OutputAudioCodec = "copy";
}
}
} }
} }
@ -1017,7 +974,7 @@ namespace MediaBrowser.Api.Playback
await AcquireResources(state, cancellationTokenSource).ConfigureAwait(false); await AcquireResources(state, cancellationTokenSource).ConfigureAwait(false);
var transcodingId = Guid.NewGuid().ToString("N"); var transcodingId = Guid.NewGuid().ToString("N");
var commandLineArgs = GetCommandLineArguments(outputPath, transcodingId, state, true); var commandLineArgs = GetCommandLineArguments(outputPath, state, true);
if (ApiEntryPoint.Instance.GetEncodingOptions().EnableDebugLogging) if (ApiEntryPoint.Instance.GetEncodingOptions().EnableDebugLogging)
{ {
@ -1052,7 +1009,7 @@ namespace MediaBrowser.Api.Playback
} }
var transcodingJob = ApiEntryPoint.Instance.OnTranscodeBeginning(outputPath, var transcodingJob = ApiEntryPoint.Instance.OnTranscodeBeginning(outputPath,
state.Request.StreamId, state.Request.PlaySessionId,
transcodingId, transcodingId,
TranscodingJobType, TranscodingJobType,
process, process,
@ -1123,7 +1080,7 @@ namespace MediaBrowser.Api.Playback
{ {
if (state.RunTimeTicks.Value >= TimeSpan.FromMinutes(5).Ticks && state.IsInputVideo) 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(); state.TranscodingThrottler.Start();
} }
} }
@ -1554,7 +1511,11 @@ namespace MediaBrowser.Api.Playback
} }
else if (i == 21) 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); request.AudioCodec = InferAudioCodec(url);
} }
var state = new StreamState(LiveTvManager, Logger) var state = new StreamState(MediaSourceManager, Logger)
{ {
Request = request, Request = request,
RequestedUrl = url RequestedUrl = url
@ -1658,109 +1619,28 @@ namespace MediaBrowser.Api.Playback
var item = LibraryManager.GetItemById(request.Id); 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; var archivable = item as IArchivable;
state.IsInputArchive = archivable != null && archivable.IsArchive; state.IsInputArchive = archivable != null && archivable.IsArchive;
if (item is ILiveTvRecording) MediaSourceInfo mediaSource = null;
{ if (string.IsNullOrWhiteSpace(request.LiveStreamId))
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); var mediaSources = await MediaSourceManager.GetPlayackMediaSources(request.Id, false, cancellationToken).ConfigureAwait(false);
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 mediaSource = string.IsNullOrEmpty(request.MediaSourceId)
state.MediaPath = string.Empty; ? mediaSources.First()
: mediaSources.First(i => string.Equals(i.Id, request.MediaSourceId));
} }
else else
{ {
var mediaSources = await MediaSourceManager.GetPlayackMediaSources(request.Id, false, cancellationToken).ConfigureAwait(false); mediaSource = await MediaSourceManager.GetLiveStream(request.LiveStreamId, 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;
}
}
} }
var videoRequest = request as VideoStreamRequest; var videoRequest = request as VideoStreamRequest;
AttachMediaStreamInfo(state, mediaStreams, videoRequest, url); AttachMediaSourceInfo(state, mediaSource, videoRequest, url);
var container = Path.GetExtension(state.RequestedUrl); var container = Path.GetExtension(state.RequestedUrl);
@ -1801,15 +1681,7 @@ namespace MediaBrowser.Api.Playback
if (videoRequest != null) if (videoRequest != null)
{ {
if (state.VideoStream != null && CanStreamCopyVideo(videoRequest, state.VideoStream)) TryStreamCopy(state, videoRequest);
{
state.OutputVideoCodec = "copy";
}
if (state.AudioStream != null && CanStreamCopyAudio(videoRequest, state.AudioStream, state.SupportedAudioCodecs))
{
state.OutputAudioCodec = "copy";
}
} }
state.OutputFilePath = GetOutputFilePath(state); state.OutputFilePath = GetOutputFilePath(state);
@ -1817,11 +1689,47 @@ namespace MediaBrowser.Api.Playback
return state; 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, MediaSourceInfo mediaSource,
VideoStreamRequest videoRequest, VideoStreamRequest videoRequest,
string requestedUrl) 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.InputProtocol = mediaSource.Protocol;
state.MediaPath = mediaSource.Path; state.MediaPath = mediaSource.Path;
state.RunTimeTicks = mediaSource.RunTimeTicks; state.RunTimeTicks = mediaSource.RunTimeTicks;
@ -1830,21 +1738,16 @@ namespace MediaBrowser.Api.Playback
state.InputFileSize = mediaSource.Size; state.InputFileSize = mediaSource.Size;
state.ReadInputAtNativeFramerate = mediaSource.ReadAtNativeFramerate; 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.OutputAudioSync = "1000";
state.InputVideoSync = "-1"; state.InputVideoSync = "-1";
state.InputAudioSync = "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 (videoRequest != null)
{ {
if (string.IsNullOrEmpty(videoRequest.VideoCodec)) if (string.IsNullOrEmpty(videoRequest.VideoCodec))
@ -1873,7 +1776,7 @@ namespace MediaBrowser.Api.Playback
state.AudioStream = GetMediaStream(mediaStreams, null, MediaStreamType.Audio, true); state.AudioStream = GetMediaStream(mediaStreams, null, MediaStreamType.Audio, true);
} }
state.AllMediaStreams = mediaStreams; state.MediaSource = mediaSource;
} }
private bool CanStreamCopyVideo(VideoStreamRequest request, MediaStream videoStream) private bool CanStreamCopyVideo(VideoStreamRequest request, MediaStream videoStream)
@ -2109,7 +2012,6 @@ namespace MediaBrowser.Api.Playback
} }
var audioCodec = state.ActualOutputAudioCodec; var audioCodec = state.ActualOutputAudioCodec;
var videoCodec = state.ActualOutputVideoCodec; var videoCodec = state.ActualOutputVideoCodec;
var mediaProfile = state.VideoRequest == null ? var mediaProfile = state.VideoRequest == null ?
@ -2130,7 +2032,9 @@ namespace MediaBrowser.Api.Playback
state.TargetTimestamp, state.TargetTimestamp,
state.IsTargetAnamorphic, state.IsTargetAnamorphic,
state.IsTargetCabac, state.IsTargetCabac,
state.TargetRefFrames); state.TargetRefFrames,
state.TargetVideoStreamCount,
state.TargetAudioStreamCount);
if (mediaProfile != null) if (mediaProfile != null)
{ {
@ -2215,7 +2119,9 @@ namespace MediaBrowser.Api.Playback
state.TranscodeSeekInfo, state.TranscodeSeekInfo,
state.IsTargetAnamorphic, state.IsTargetAnamorphic,
state.IsTargetCabac, state.IsTargetCabac,
state.TargetRefFrames state.TargetRefFrames,
state.TargetVideoStreamCount,
state.TargetAudioStreamCount
).FirstOrDefault() ?? string.Empty; ).FirstOrDefault() ?? string.Empty;
} }

@ -5,7 +5,6 @@ using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Devices; using MediaBrowser.Controller.Devices;
using MediaBrowser.Controller.Dlna; using MediaBrowser.Controller.Dlna;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.LiveTv;
using MediaBrowser.Controller.MediaEncoding; using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Net;
using MediaBrowser.Model.IO; using MediaBrowser.Model.IO;
@ -54,7 +53,7 @@ namespace MediaBrowser.Api.Playback.Dash
public class MpegDashService : BaseHlsService 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; NetworkManager = networkManager;
} }
@ -160,7 +159,7 @@ namespace MediaBrowser.Api.Playback.Dash
// If the playlist doesn't already exist, startup ffmpeg // If the playlist doesn't already exist, startup ffmpeg
try try
{ {
ApiEntryPoint.Instance.KillTranscodingJobs(request.DeviceId, request.StreamId, p => false); ApiEntryPoint.Instance.KillTranscodingJobs(request.DeviceId, request.PlaySessionId, p => false);
if (currentTranscodingIndex.HasValue) if (currentTranscodingIndex.HasValue)
{ {
@ -447,7 +446,7 @@ namespace MediaBrowser.Api.Playback.Dash
return args; 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 // 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/ // 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}\"", 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, inputModifier,
GetInputArgument(transcodingJobId, state), GetInputArgument(state),
threads, threads,
GetMapArgs(state), GetMapArgs(state),
GetVideoArguments(state), GetVideoArguments(state),
@ -518,25 +517,14 @@ namespace MediaBrowser.Api.Playback.Dash
private async Task WaitForSegment(string playlist, string segment, CancellationToken cancellationToken) private async Task WaitForSegment(string playlist, string segment, CancellationToken cancellationToken)
{ {
var tmpPath = playlist + ".tmp";
var segmentFilename = Path.GetFileName(segment); var segmentFilename = Path.GetFileName(segment);
Logger.Debug("Waiting for {0} in {1}", segmentFilename, playlist); Logger.Debug("Waiting for {0} in {1}", segmentFilename, playlist);
while (true) 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 // 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)) using (var reader = new StreamReader(fileStream))
{ {

@ -3,7 +3,6 @@ using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Devices; using MediaBrowser.Controller.Devices;
using MediaBrowser.Controller.Dlna; using MediaBrowser.Controller.Dlna;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.LiveTv;
using MediaBrowser.Controller.MediaEncoding; using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.Model.Extensions; using MediaBrowser.Model.Extensions;
using MediaBrowser.Model.IO; using MediaBrowser.Model.IO;
@ -22,7 +21,7 @@ namespace MediaBrowser.Api.Playback.Hls
/// </summary> /// </summary>
public abstract class BaseHlsService : BaseStreamingService 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) while (true)
{ {
// Need to use FileShare.ReadWrite because we're reading the file at the same time it's being written // 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)) 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; 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}\"", 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, itsOffset,
inputModifier, inputModifier,
GetInputArgument(transcodingJobId, state), GetInputArgument(state),
threads, threads,
GetMapArgs(state), GetMapArgs(state),
GetVideoArguments(state), GetVideoArguments(state),

@ -4,7 +4,6 @@ using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Devices; using MediaBrowser.Controller.Devices;
using MediaBrowser.Controller.Dlna; using MediaBrowser.Controller.Dlna;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.LiveTv;
using MediaBrowser.Controller.MediaEncoding; using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Net;
using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Dlna;
@ -62,7 +61,7 @@ namespace MediaBrowser.Api.Playback.Hls
public class DynamicHlsService : BaseHlsService 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; NetworkManager = networkManager;
} }
@ -135,7 +134,7 @@ namespace MediaBrowser.Api.Playback.Hls
// If the playlist doesn't already exist, startup ffmpeg // If the playlist doesn't already exist, startup ffmpeg
try try
{ {
ApiEntryPoint.Instance.KillTranscodingJobs(request.DeviceId, request.StreamId, p => false); ApiEntryPoint.Instance.KillTranscodingJobs(request.DeviceId, request.PlaySessionId, p => false);
if (currentTranscodingIndex.HasValue) if (currentTranscodingIndex.HasValue)
{ {
@ -300,7 +299,7 @@ namespace MediaBrowser.Api.Playback.Hls
var segmentFilename = Path.GetFileName(segmentPath); 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)) using (var reader = new StreamReader(fileStream))
{ {
@ -414,7 +413,8 @@ namespace MediaBrowser.Api.Playback.Hls
var request = (GetMasterHlsVideoStream)state.Request; var request = (GetMasterHlsVideoStream)state.Request;
var subtitleStreams = state.AllMediaStreams var subtitleStreams = state.MediaSource
.MediaStreams
.Where(i => i.IsTextSubtitleStream) .Where(i => i.IsTextSubtitleStream)
.ToList(); .ToList();
@ -684,7 +684,7 @@ namespace MediaBrowser.Api.Playback.Hls
return args; 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); 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"; 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, inputModifier,
GetInputArgument(transcodingJobId, state), GetInputArgument(state),
threads, threads,
GetMapArgs(state), GetMapArgs(state),
GetVideoArguments(state), GetVideoArguments(state),
@ -711,9 +711,9 @@ namespace MediaBrowser.Api.Playback.Hls
).Trim(); ).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, inputModifier,
GetInputArgument(transcodingJobId, state), GetInputArgument(state),
threads, threads,
GetMapArgs(state), GetMapArgs(state),
GetVideoArguments(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")] [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; } public string DeviceId { get; set; }
[ApiMember(Name = "StreamId", Description = "The stream id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "DELETE")] [ApiMember(Name = "PlaySessionId", Description = "The play session id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "DELETE")]
public string StreamId { get; set; } public string PlaySessionId { get; set; }
} }
/// <summary> /// <summary>
@ -95,7 +95,7 @@ namespace MediaBrowser.Api.Playback.Hls
public void Delete(StopEncodingProcess request) public void Delete(StopEncodingProcess request)
{ {
ApiEntryPoint.Instance.KillTranscodingJobs(request.DeviceId, request.StreamId, path => true); ApiEntryPoint.Instance.KillTranscodingJobs(request.DeviceId, request.PlaySessionId, path => true);
} }
/// <summary> /// <summary>

@ -42,7 +42,7 @@ namespace MediaBrowser.Api.Playback.Hls
/// </summary> /// </summary>
public class VideoHlsService : BaseHlsService 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.Controller.Net;
using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Dlna;
using MediaBrowser.Model.Dto; using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.MediaInfo; using MediaBrowser.Model.MediaInfo;
using MediaBrowser.Model.Session;
using ServiceStack; using ServiceStack;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
@ -13,7 +17,7 @@ using System.Threading.Tasks;
namespace MediaBrowser.Api.Playback namespace MediaBrowser.Api.Playback
{ {
[Route("/Items/{Id}/MediaInfo", "GET", Summary = "Gets live playback media info for an item")] [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")] [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
public string Id { get; set; } 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")] [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")] [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
public string Id { get; set; } public string Id { get; set; }
@ -32,45 +36,338 @@ namespace MediaBrowser.Api.Playback
public string UserId { get; set; } 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] [Authenticated]
public class MediaInfoService : BaseApiService public class MediaInfoService : BaseApiService
{ {
private readonly IMediaSourceManager _mediaSourceManager; 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; _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 authInfo = AuthorizationContext.GetAuthorizationInfo(Request);
var result = new LiveMediaInfoResult();
var result = await _mediaSourceManager.OpenLiveStream(request, false, CancellationToken.None).ConfigureAwait(false);
try var profile = request.DeviceProfile;
if (profile == null)
{ {
mediaSources = await _mediaSourceManager.GetPlayackMediaSources(id, userId, true, CancellationToken.None).ConfigureAwait(false); var caps = _deviceManager.GetCapabilities(authInfo.DeviceId);
if (caps != null)
{
profile = caps.DeviceProfile;
}
} }
catch (PlaybackException ex)
if (profile != null)
{ {
mediaSources = new List<MediaSourceInfo>(); var item = _libraryManager.GetItemById(request.ItemId);
result.ErrorCode = ex.ErrorCode;
}
result.MediaSources = mediaSources.ToList(); SetDeviceSpecificData(item, result.MediaSource, profile, authInfo, request.MaxStreamingBitrate,
result.StreamId = Guid.NewGuid().ToString("N"); 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); 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> /// </summary>
public class AudioService : BaseProgressiveStreamingService 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); 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>(); 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}\"", return string.Format("{0} {1} -threads {2}{3} {4} -id3v2_version 3 -write_id3v1 1 -y \"{5}\"",
inputModifier, inputModifier,
GetInputArgument(transcodingJobId, state), GetInputArgument(state),
threads, threads,
vn, vn,
string.Join(" ", audioTranscodeParams.ToArray()), string.Join(" ", audioTranscodeParams.ToArray()),

@ -27,7 +27,7 @@ namespace MediaBrowser.Api.Playback.Progressive
protected readonly IImageProcessor ImageProcessor; protected readonly IImageProcessor ImageProcessor;
protected readonly IHttpClient HttpClient; 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; ImageProcessor = imageProcessor;
HttpClient = httpClient; HttpClient = httpClient;

@ -62,7 +62,7 @@ namespace MediaBrowser.Api.Playback.Progressive
/// </summary> /// </summary>
public class VideoService : BaseProgressiveStreamingService 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); 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 // Get the output codec name
var videoCodec = state.OutputVideoCodec; 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}\"", return string.Format("{0} {1}{2} {3} {4} -map_metadata -1 -threads {5} {6}{7} -y \"{8}\"",
inputModifier, inputModifier,
GetInputArgument(transcodingJobId, state), GetInputArgument(state),
keyFrame, keyFrame,
GetMapArgs(state), GetMapArgs(state),
GetVideoArguments(state, videoCodec), GetVideoArguments(state, videoCodec),

@ -71,9 +71,8 @@ namespace MediaBrowser.Api.Playback
public string Params { get; set; } public string Params { get; set; }
public string ClientTime { get; set; } public string ClientTime { get; set; }
public string StreamId { get; set; } public string PlaySessionId { get; set; }
public string LiveStreamId { get; set; }
public string TranscodingJobId { get; set; }
} }
public class VideoStreamRequest : StreamRequest public class VideoStreamRequest : StreamRequest

@ -1,6 +1,7 @@
using MediaBrowser.Controller.LiveTv; using MediaBrowser.Controller.Library;
using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Dlna;
using MediaBrowser.Model.Drawing; using MediaBrowser.Model.Drawing;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities; using MediaBrowser.Model.Entities;
using MediaBrowser.Model.IO; using MediaBrowser.Model.IO;
using MediaBrowser.Model.Logging; using MediaBrowser.Model.Logging;
@ -17,7 +18,7 @@ namespace MediaBrowser.Api.Playback
public class StreamState : IDisposable public class StreamState : IDisposable
{ {
private readonly ILogger _logger; private readonly ILogger _logger;
private readonly ILiveTvManager _liveTvManager; private readonly IMediaSourceManager _mediaSourceManager;
public string RequestedUrl { get; set; } public string RequestedUrl { get; set; }
@ -39,7 +40,7 @@ namespace MediaBrowser.Api.Playback
public string InputContainer { 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 AudioStream { get; set; }
public MediaStream VideoStream { get; set; } public MediaStream VideoStream { get; set; }
@ -64,8 +65,6 @@ namespace MediaBrowser.Api.Playback
public List<string> PlayableStreamFileNames { get; set; } public List<string> PlayableStreamFileNames { get; set; }
public string LiveTvStreamId { get; set; }
public int SegmentLength = 3; public int SegmentLength = 3;
public bool EnableGenericHlsSegmenter = false; public bool EnableGenericHlsSegmenter = false;
public int HlsListSize public int HlsListSize
@ -86,14 +85,13 @@ namespace MediaBrowser.Api.Playback
public List<string> SupportedAudioCodecs { get; set; } public List<string> SupportedAudioCodecs { get; set; }
public StreamState(ILiveTvManager liveTvManager, ILogger logger) public StreamState(IMediaSourceManager mediaSourceManager, ILogger logger)
{ {
_liveTvManager = liveTvManager; _mediaSourceManager = mediaSourceManager;
_logger = logger; _logger = logger;
SupportedAudioCodecs = new List<string>(); SupportedAudioCodecs = new List<string>();
PlayableStreamFileNames = new List<string>(); PlayableStreamFileNames = new List<string>();
RemoteHttpHeaders = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); RemoteHttpHeaders = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
AllMediaStreams = new List<MediaStream>();
} }
public string InputAudioSync { get; set; } public string InputAudioSync { get; set; }
@ -113,9 +111,6 @@ namespace MediaBrowser.Api.Playback
public long? EncodingDurationTicks { get; set; } public long? EncodingDurationTicks { get; set; }
public string ItemType { get; set; }
public string ItemId { get; set; }
public string GetMimeType(string outputPath) public string GetMimeType(string outputPath)
{ {
if (!string.IsNullOrEmpty(MimeType)) if (!string.IsNullOrEmpty(MimeType))
@ -187,15 +182,15 @@ namespace MediaBrowser.Api.Playback
private async void DisposeLiveStream() private async void DisposeLiveStream()
{ {
if (!string.IsNullOrEmpty(LiveTvStreamId)) if (MediaSource.RequiresClosing && string.IsNullOrWhiteSpace(Request.LiveStreamId))
{ {
try try
{ {
await _liveTvManager.CloseLiveStream(LiveTvStreamId, CancellationToken.None).ConfigureAwait(false); await _mediaSourceManager.CloseLiveStream(MediaSource.LiveStreamId, CancellationToken.None).ConfigureAwait(false);
} }
catch (Exception ex) 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> /// <summary>
/// Predicts the audio sample rate that will be in the output stream /// Predicts the audio sample rate that will be in the output stream
/// </summary> /// </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;
using System.IO; using System.IO;
using System.Threading; using System.Threading;
@ -11,13 +13,18 @@ namespace MediaBrowser.Api.Playback
private readonly ILogger _logger; private readonly ILogger _logger;
private Timer _timer; private Timer _timer;
private bool _isPaused; private bool _isPaused;
private readonly IConfigurationManager _config;
private readonly long _gapLengthInTicks = TimeSpan.FromMinutes(2).Ticks; public TranscodingThrottler(TranscodingJob job, ILogger logger, IConfigurationManager config)
public TranscodingThrottler(TranscodingJob job, ILogger logger)
{ {
_job = job; _job = job;
_logger = logger; _logger = logger;
_config = config;
}
private EncodingOptions GetOptions()
{
return _config.GetConfiguration<EncodingOptions>("encoding");
} }
public void Start() public void Start()
@ -33,7 +40,9 @@ namespace MediaBrowser.Api.Playback
return; return;
} }
if (IsThrottleAllowed(_job)) var options = GetOptions();
if (/*options.EnableThrottling &&*/ IsThrottleAllowed(_job, options.ThrottleThresholdSeconds))
{ {
PauseTranscoding(); 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 bytesDownloaded = job.BytesDownloaded ?? 0;
var transcodingPositionTicks = job.TranscodingPositionTicks ?? 0; var transcodingPositionTicks = job.TranscodingPositionTicks ?? 0;
var downloadPositionTicks = job.DownloadPositionTicks ?? 0; var downloadPositionTicks = job.DownloadPositionTicks ?? 0;
var path = job.Path; var path = job.Path;
var gapLengthInTicks = TimeSpan.FromSeconds(thresholdSeconds).Ticks;
if (downloadPositionTicks > 0 && transcodingPositionTicks > 0) if (downloadPositionTicks > 0 && transcodingPositionTicks > 0)
{ {
// HLS - time-based consideration // HLS - time-based consideration
var targetGap = _gapLengthInTicks; var targetGap = gapLengthInTicks;
var gap = transcodingPositionTicks - downloadPositionTicks; var gap = transcodingPositionTicks - downloadPositionTicks;
if (gap < targetGap) if (gap < targetGap)
@ -113,7 +123,7 @@ namespace MediaBrowser.Api.Playback
var bytesTranscoded = job.BytesTranscoded ?? new FileInfo(path).Length; var bytesTranscoded = job.BytesTranscoded ?? new FileInfo(path).Length;
// Estimate the bytes the transcoder should be ahead // Estimate the bytes the transcoder should be ahead
double gapFactor = _gapLengthInTicks; double gapFactor = gapLengthInTicks;
gapFactor /= transcodingPositionTicks; gapFactor /= transcodingPositionTicks;
var targetGap = bytesTranscoded * gapFactor; var targetGap = bytesTranscoded * gapFactor;

@ -192,7 +192,7 @@ namespace MediaBrowser.Api.Subtitles
{ {
var item = (Video)_libraryManager.GetItemById(new Guid(request.Id)); 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)); .First(i => string.Equals(i.Id, request.MediaSourceId ?? request.Id));
var subtitleStream = mediaSource.MediaStreams var subtitleStream = mediaSource.MediaStreams

@ -142,7 +142,7 @@ namespace MediaBrowser.Api.UserLibrary
} }
IEnumerable<Tuple<TItemType, List<BaseItem>>> tuples; 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())); 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 true;
return options.Fields.Contains(ItemFields.ItemCounts); return options.Fields.Contains(ItemFields.ItemCounts);
} }

@ -294,6 +294,11 @@ namespace MediaBrowser.Api.UserLibrary
public void Post(ReportPlaybackProgress request) public void Post(ReportPlaybackProgress request)
{ {
if (!string.IsNullOrWhiteSpace(request.PlaySessionId))
{
ApiEntryPoint.Instance.PingTranscodingJob(AuthorizationContext.GetAuthorizationInfo(Request).DeviceId, request.PlaySessionId);
}
request.SessionId = GetSession().Result.Id; request.SessionId = GetSession().Result.Id;
var task = _sessionManager.OnPlaybackProgress(request); var task = _sessionManager.OnPlaybackProgress(request);
@ -317,6 +322,11 @@ namespace MediaBrowser.Api.UserLibrary
public void Post(ReportPlaybackStopped request) 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; request.SessionId = GetSession().Result.Id;
var task = _sessionManager.OnPlaybackStopped(request); var task = _sessionManager.OnPlaybackStopped(request);

@ -304,6 +304,14 @@ namespace MediaBrowser.Api.UserLibrary
{ {
var user = _userManager.GetUserById(request.UserId); var user = _userManager.GetUserById(request.UserId);
if (!request.IsPlayed.HasValue)
{
if (user.Configuration.HidePlayedInLatest)
{
request.IsPlayed = false;
}
}
var list = _userViewManager.GetLatestItems(new LatestItemsQuery var list = _userViewManager.GetLatestItems(new LatestItemsQuery
{ {
GroupItems = request.GroupItems, GroupItems = request.GroupItems,

@ -107,30 +107,40 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager
private WebRequest GetRequest(HttpRequestOptions options, string method, bool enableHttpCompression) 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); 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.Method = method;
request.Timeout = options.TimeoutMs; 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)) if (!string.IsNullOrEmpty(options.Referer))
{ {
request.Referer = options.Referer; httpWebRequest.Referer = options.Referer;
}
} }
//request.ServicePoint.BindIPEndPointDelegate = BindIPEndPointCallback; //request.ServicePoint.BindIPEndPointDelegate = BindIPEndPointCallback;

@ -6,6 +6,7 @@ using MediaBrowser.Model.Logging;
using MediaBrowser.Model.Serialization; using MediaBrowser.Model.Serialization;
using MediaBrowser.Model.Tasks; using MediaBrowser.Model.Tasks;
using System; using System;
using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -29,7 +30,8 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks
/// <summary> /// <summary>
/// The _task queue /// The _task queue
/// </summary> /// </summary>
private readonly SortedDictionary<Type, TaskExecutionOptions> _taskQueue = new SortedDictionary<Type, TaskExecutionOptions>(); private readonly ConcurrentQueue<Tuple<Type, TaskExecutionOptions>> _taskQueue =
new ConcurrentQueue<Tuple<Type, TaskExecutionOptions>>();
/// <summary> /// <summary>
/// Gets or sets the json serializer. /// Gets or sets the json serializer.
@ -136,25 +138,17 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks
{ {
var type = task.ScheduledTask.GetType(); var type = task.ScheduledTask.GetType();
Logger.Info("Queueing task {0}", type.Name);
lock (_taskQueue) lock (_taskQueue)
{ {
// If it's idle just execute immediately
if (task.State == TaskState.Idle) if (task.State == TaskState.Idle)
{ {
Execute(task, options); Execute(task, options);
return; return;
} }
if (!_taskQueue.ContainsKey(type)) _taskQueue.Enqueue(new Tuple<Type, TaskExecutionOptions>(type, options));
{
Logger.Info("Queueing task {0}", type.Name);
_taskQueue.Add(type, options);
}
else
{
_taskQueue[type] = options;
Logger.Info("Task already queued: {0}", type.Name);
}
} }
} }
@ -241,15 +235,24 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks
// Execute queued tasks // Execute queued tasks
lock (_taskQueue) 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) if (scheduledTask.State == TaskState.Idle)
{ {
Execute(scheduledTask, enqueuedType.Value); Execute(scheduledTask, enqueuedType.Item2);
_taskQueue.Remove(enqueuedType.Key);
} }
} }
} }

@ -5,7 +5,6 @@ using System;
using System.Linq; using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using MediaBrowser.Model.Users;
namespace MediaBrowser.Controller.Channels namespace MediaBrowser.Controller.Channels
{ {
@ -15,19 +14,9 @@ namespace MediaBrowser.Controller.Channels
public override bool IsVisible(User user) 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;
{
return false;
}
}
else
{
if (!user.Policy.EnableAllChannels && !user.Policy.EnabledChannels.Contains(Id.ToString("N"), StringComparer.OrdinalIgnoreCase))
{
return false;
}
} }
return base.IsVisible(user); return base.IsVisible(user);

@ -75,17 +75,23 @@ namespace MediaBrowser.Controller.Channels
public override IEnumerable<MediaSourceInfo> GetMediaSources(bool enablePathSubstitution) public override IEnumerable<MediaSourceInfo> GetMediaSources(bool enablePathSubstitution)
{ {
var list = base.GetMediaSources(enablePathSubstitution).ToList(); var sources = ChannelManager.GetStaticMediaSources(this, false, CancellationToken.None)
.Result.ToList();
var sources = ChannelManager.GetChannelItemMediaSources(Id.ToString("N"), false, CancellationToken.None)
.Result.ToList();
if (sources.Count > 0) if (sources.Count > 0)
{ {
return sources; 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; return list;
} }

@ -38,6 +38,7 @@ namespace MediaBrowser.Controller.Channels
public string Id { get; set; } public string Id { get; set; }
public bool ReadAtNativeFramerate { get; set; } public bool ReadAtNativeFramerate { get; set; }
public bool SupportsDirectPlay { get; set; }
public ChannelMediaInfo() public ChannelMediaInfo()
{ {
@ -45,6 +46,7 @@ namespace MediaBrowser.Controller.Channels
// This is most common // This is most common
Protocol = MediaProtocol.Http; Protocol = MediaProtocol.Http;
SupportsDirectPlay = true;
} }
public MediaSourceInfo ToMediaSource() public MediaSourceInfo ToMediaSource()
@ -62,7 +64,9 @@ namespace MediaBrowser.Controller.Channels
RunTimeTicks = RunTimeTicks, RunTimeTicks = RunTimeTicks,
Name = id, Name = id,
Id = id, Id = id,
ReadAtNativeFramerate = ReadAtNativeFramerate ReadAtNativeFramerate = ReadAtNativeFramerate,
SupportsDirectStream = Protocol == MediaProtocol.File || Protocol == MediaProtocol.Http,
SupportsDirectPlay = SupportsDirectPlay
}; };
var bitrate = (AudioBitrate ?? 0) + (VideoBitrate ?? 0); var bitrate = (AudioBitrate ?? 0) + (VideoBitrate ?? 0);

@ -90,17 +90,23 @@ namespace MediaBrowser.Controller.Channels
public override IEnumerable<MediaSourceInfo> GetMediaSources(bool enablePathSubstitution) public override IEnumerable<MediaSourceInfo> GetMediaSources(bool enablePathSubstitution)
{ {
var list = base.GetMediaSources(enablePathSubstitution).ToList(); var sources = ChannelManager.GetStaticMediaSources(this, false, CancellationToken.None)
.Result.ToList();
var sources = ChannelManager.GetChannelItemMediaSources(Id.ToString("N"), false, CancellationToken.None)
.Result.ToList();
if (sources.Count > 0) if (sources.Count > 0)
{ {
return sources; 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; return list;
} }

@ -112,11 +112,11 @@ namespace MediaBrowser.Controller.Channels
/// <summary> /// <summary>
/// Gets the channel item media sources. /// Gets the channel item media sources.
/// </summary> /// </summary>
/// <param name="id">The identifier.</param> /// <param name="item">The item.</param>
/// <param name="includeDynamicSources">if set to <c>true</c> [include dynamic sources].</param> /// <param name="includeCachedVersions">if set to <c>true</c> [include cached versions].</param>
/// <param name="cancellationToken">The cancellation token.</param> /// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task{IEnumerable{MediaSourceInfo}}.</returns> /// <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> /// <summary>
/// Gets the channel folder. /// Gets the channel folder.

@ -1219,18 +1219,6 @@ namespace MediaBrowser.Controller.Entities
private BaseItem FindLinkedChild(LinkedChild info) 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)) if (!string.IsNullOrEmpty(info.Path))
{ {
var itemByPath = LibraryManager.RootFolder.FindByPath(info.Path); var itemByPath = LibraryManager.RootFolder.FindByPath(info.Path);
@ -1243,23 +1231,6 @@ namespace MediaBrowser.Controller.Entities
return itemByPath; 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; return null;
} }

@ -334,22 +334,9 @@ namespace MediaBrowser.Controller.Entities
{ {
if (this is ICollectionFolder && !(this is BasePluginFolder)) 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) || return false;
// 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;
}
} }
} }

@ -9,9 +9,6 @@ namespace MediaBrowser.Controller.Entities
public string Path { get; set; } public string Path { get; set; }
public LinkedChildType Type { get; set; } public LinkedChildType Type { get; set; }
public string ItemName { get; set; }
public string ItemType { get; set; }
[IgnoreDataMember] [IgnoreDataMember]
public string Id { get; set; } public string Id { get; set; }

@ -501,7 +501,8 @@ namespace MediaBrowser.Controller.Entities
Formats = (i.FormatName ?? string.Empty).Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).ToList(), Formats = (i.FormatName ?? string.Empty).Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).ToList(),
Timestamp = i.Timestamp, Timestamp = i.Timestamp,
Type = type, Type = type,
PlayableStreamFileNames = i.PlayableStreamFileNames.ToList() PlayableStreamFileNames = i.PlayableStreamFileNames.ToList(),
SupportsDirectStream = i.VideoType == VideoType.VideoFile
}; };
if (i.IsShortcut) if (i.IsShortcut)

@ -2,6 +2,7 @@
using MediaBrowser.Controller.Persistence; using MediaBrowser.Controller.Persistence;
using MediaBrowser.Model.Dto; using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities; using MediaBrowser.Model.Entities;
using MediaBrowser.Model.MediaInfo;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading; using System.Threading;
@ -62,8 +63,8 @@ namespace MediaBrowser.Controller.Library
/// <param name="enablePathSubstitution">if set to <c>true</c> [enable path substitution].</param> /// <param name="enablePathSubstitution">if set to <c>true</c> [enable path substitution].</param>
/// <param name="user">The user.</param> /// <param name="user">The user.</param>
/// <returns>IEnumerable&lt;MediaSourceInfo&gt;.</returns> /// <returns>IEnumerable&lt;MediaSourceInfo&gt;.</returns>
IEnumerable<MediaSourceInfo> GetStaticMediaSources(IHasMediaSources item, bool enablePathSubstitution, User user); IEnumerable<MediaSourceInfo> GetStaticMediaSources(IHasMediaSources item, bool enablePathSubstitution, User user = null);
/// <summary> /// <summary>
/// Gets the static media source. /// Gets the static media source.
/// </summary> /// </summary>
@ -72,5 +73,38 @@ namespace MediaBrowser.Controller.Library
/// <param name="enablePathSubstitution">if set to <c>true</c> [enable path substitution].</param> /// <param name="enablePathSubstitution">if set to <c>true</c> [enable path substitution].</param>
/// <returns>MediaSourceInfo.</returns> /// <returns>MediaSourceInfo.</returns>
MediaSourceInfo GetStaticMediaSource(IHasMediaSources item, string mediaSourceId, bool enablePathSubstitution); 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&lt;MediaSourceInfo&gt;.</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&lt;MediaSourceInfo&gt;.</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> /// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task&lt;IEnumerable&lt;MediaSourceInfo&gt;&gt;.</returns> /// <returns>Task&lt;IEnumerable&lt;MediaSourceInfo&gt;&gt;.</returns>
Task<IEnumerable<MediaSourceInfo>> GetMediaSources(IHasMediaSources item, CancellationToken cancellationToken); 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&lt;MediaSourceInfo&gt;.</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 namespace MediaBrowser.Controller.LiveTv
{ {
public interface ILiveTvItem public interface ILiveTvItem
{ {
Guid Id { get; }
string ServiceName { get; set; } string ServiceName { get; set; }
} }
} }

@ -301,5 +301,21 @@ namespace MediaBrowser.Controller.LiveTv
/// <param name="cancellationToken">The cancellation token.</param> /// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task&lt;QueryResult&lt;BaseItem&gt;&gt;.</returns> /// <returns>Task&lt;QueryResult&lt;BaseItem&gt;&gt;.</returns>
Task<QueryResult<BaseItem>> GetInternalRecordings(RecordingQuery query, CancellationToken cancellationToken); 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&lt;IEnumerable&lt;MediaSourceInfo&gt;&gt;.</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&lt;IEnumerable&lt;MediaSourceInfo&gt;&gt;.</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.Controller.Entities.Audio;
using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities; using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Users; using MediaBrowser.Model.Users;
using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Runtime.Serialization;
namespace MediaBrowser.Controller.LiveTv namespace MediaBrowser.Controller.LiveTv
{ {
@ -99,5 +101,20 @@ namespace MediaBrowser.Controller.LiveTv
{ {
return user.Policy.EnableLiveTvManagement; 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> /// <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 bool? HasProviderImage { get; set; }
public override LocationType LocationType
{
get
{
// TODO: This should be removed
return LocationType.Remote;
}
}
protected override string CreateSortName() protected override string CreateSortName()
{ {
double number = 0; double number = 0;
@ -127,7 +136,7 @@ namespace MediaBrowser.Controller.LiveTv
Name = Name, Name = Name,
Path = Path, Path = Path,
RunTimeTicks = RunTimeTicks, RunTimeTicks = RunTimeTicks,
Type = MediaSourceType.Default Type = MediaSourceType.Placeholder
}; };
list.Add(info); list.Add(info);

@ -34,6 +34,12 @@ namespace MediaBrowser.Controller.LiveTv
/// <value>The channel identifier.</value> /// <value>The channel identifier.</value>
public string ExternalChannelId { get; set; } 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> /// <summary>
/// Gets or sets the type of the channel. /// Gets or sets the type of the channel.
/// </summary> /// </summary>

@ -1,9 +1,11 @@
using System.Runtime.Serialization; using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities; using MediaBrowser.Model.Entities;
using System.Linq;
using MediaBrowser.Model.Users; using MediaBrowser.Model.Users;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
namespace MediaBrowser.Controller.LiveTv namespace MediaBrowser.Controller.LiveTv
{ {
@ -97,5 +99,20 @@ namespace MediaBrowser.Controller.LiveTv
{ {
return user.Policy.EnableLiveTvManagement; 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\SubtitleDownloadEventArgs.cs" />
<Compile Include="Subtitles\SubtitleResponse.cs" /> <Compile Include="Subtitles\SubtitleResponse.cs" />
<Compile Include="Subtitles\SubtitleSearchRequest.cs" /> <Compile Include="Subtitles\SubtitleSearchRequest.cs" />
<Compile Include="Sync\IHasDynamicAccess.cs" />
<Compile Include="Sync\IServerSyncProvider.cs" /> <Compile Include="Sync\IServerSyncProvider.cs" />
<Compile Include="Sync\ISyncDataProvider.cs" /> <Compile Include="Sync\ISyncDataProvider.cs" />
<Compile Include="Sync\ISyncManager.cs" /> <Compile Include="Sync\ISyncManager.cs" />
<Compile Include="Sync\ISyncProvider.cs" /> <Compile Include="Sync\ISyncProvider.cs" />
<Compile Include="Sync\ISyncRepository.cs" /> <Compile Include="Sync\ISyncRepository.cs" />
<Compile Include="Sync\SendFileResult.cs" /> <Compile Include="Sync\SyncedFileInfo.cs" />
<Compile Include="Themes\IAppThemeManager.cs" /> <Compile Include="Themes\IAppThemeManager.cs" />
<Compile Include="Themes\InternalThemeImage.cs" /> <Compile Include="Themes\InternalThemeImage.cs" />
<Compile Include="TV\ITVSeriesManager.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;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -47,8 +48,9 @@ namespace MediaBrowser.Controller.MediaEncoding
/// Gets the subtitle language encoding parameter. /// Gets the subtitle language encoding parameter.
/// </summary> /// </summary>
/// <param name="path">The path.</param> /// <param name="path">The path.</param>
/// <param name="protocol">The protocol.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>System.String.</returns> /// <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) streams = GetSortedStreams(streams, MediaStreamType.Subtitle, preferredLanguages)
.ToList(); .ToList();
var full = streams.Where(s => !s.IsForced);
MediaStream stream = null; MediaStream stream = null;
if (mode == SubtitlePlaybackMode.None) 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 the audio language is not understood by the user, load their preferred subs, if there are any
if (!ContainsOrdinal(preferredLanguages, audioTrackLanguage)) 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) else if (mode == SubtitlePlaybackMode.Always)
{ {
// always load the most suitable full subtitles // 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 // load forced subs if we have found no suitable full subtitles
@ -97,6 +95,77 @@ namespace MediaBrowser.Controller.MediaEncoding
.ThenBy(i => i.Index); .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) private static int GetBooleanOrderBy(bool value)
{ {
return value ? 0 : 1; 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 MediaBrowser.Model.Net;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
@ -75,12 +74,12 @@ namespace MediaBrowser.Controller.Net
throw new ArgumentNullException("message"); throw new ArgumentNullException("message");
} }
if (message.MessageType.Equals(Name + "Start", StringComparison.OrdinalIgnoreCase)) if (string.Equals(message.MessageType, Name + "Start", StringComparison.OrdinalIgnoreCase))
{ {
Start(message); Start(message);
} }
if (message.MessageType.Equals(Name + "Stop", StringComparison.OrdinalIgnoreCase)) if (string.Equals(message.MessageType, Name + "Stop", StringComparison.OrdinalIgnoreCase))
{ {
Stop(message); Stop(message);
} }

@ -1404,24 +1404,12 @@ namespace MediaBrowser.Controller.Providers
{ {
switch (reader.Name) switch (reader.Name)
{ {
case "Name":
{
linkedItem.ItemName = reader.ReadElementContentAsString();
break;
}
case "Path": case "Path":
{ {
linkedItem.Path = reader.ReadElementContentAsString(); linkedItem.Path = reader.ReadElementContentAsString();
break; break;
} }
case "Type":
{
linkedItem.ItemType = reader.ReadElementContentAsString();
break;
}
default: default:
reader.Skip(); reader.Skip();
break; break;
@ -1435,7 +1423,7 @@ namespace MediaBrowser.Controller.Providers
return linkedItem; return linkedItem;
} }
return string.IsNullOrWhiteSpace(linkedItem.ItemName) || string.IsNullOrWhiteSpace(linkedItem.ItemType) ? null : linkedItem; return null;
} }

@ -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&lt;SyncedFileInfo&gt;.</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="progress">The progress.</param>
/// <param name="cancellationToken">The cancellation token.</param> /// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task.</returns> /// <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> /// <summary>
/// Deletes the file. /// Deletes the file.
@ -54,14 +54,5 @@ namespace MediaBrowser.Controller.Sync
/// <param name="target">The target.</param> /// <param name="target">The target.</param>
/// <returns>System.String.</returns> /// <returns>System.String.</returns>
string GetParentDirectoryPath(string path, SyncTarget target); 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&lt;List&lt;DeviceFileInfo&gt;&gt;.</returns>
Task<List<DeviceFileInfo>> GetFileSystemEntries(string path, SyncTarget target, CancellationToken cancellationToken);
} }
} }

@ -14,6 +14,14 @@ namespace MediaBrowser.Controller.Sync
/// <returns>Task&lt;List&lt;System.String&gt;&gt;.</returns> /// <returns>Task&lt;List&lt;System.String&gt;&gt;.</returns>
Task<List<string>> GetServerItemIds(SyncTarget target, string serverId); 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&lt;List&lt;System.String&gt;&gt;.</returns>
Task<List<string>> GetSyncJobItemIds(SyncTarget target, string serverId);
/// <summary> /// <summary>
/// Adds the or update. /// Adds the or update.
/// </summary> /// </summary>
@ -46,5 +54,13 @@ namespace MediaBrowser.Controller.Sync
/// <param name="itemId">The item identifier.</param> /// <param name="itemId">The item identifier.</param>
/// <returns>Task&lt;LocalItem&gt;.</returns> /// <returns>Task&lt;LocalItem&gt;.</returns>
Task<List<LocalItem>> GetCachedItems(SyncTarget target, string serverId, string itemId); 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&lt;List&lt;LocalItem&gt;&gt;.</returns>
Task<List<LocalItem>> GetCachedItemsBySyncJobItemId(SyncTarget target, string serverId, string syncJobItemId);
} }
} }

@ -1,8 +1,9 @@
using MediaBrowser.Model.MediaInfo; using MediaBrowser.Model.MediaInfo;
using System.Collections.Generic;
namespace MediaBrowser.Controller.Sync namespace MediaBrowser.Controller.Sync
{ {
public class SendFileResult public class SyncedFileInfo
{ {
/// <summary> /// <summary>
/// Gets or sets the path. /// Gets or sets the path.
@ -14,5 +15,15 @@ namespace MediaBrowser.Controller.Sync
/// </summary> /// </summary>
/// <value>The protocol.</value> /// <value>The protocol.</value>
public MediaProtocol Protocol { get; set; } 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) 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 streamInfo = new StreamBuilder().BuildVideoItem(new VideoOptions
{ {
@ -158,16 +158,21 @@ namespace MediaBrowser.Dlna.Didl
streamInfo.TranscodeSeekInfo, streamInfo.TranscodeSeekInfo,
streamInfo.IsTargetAnamorphic, streamInfo.IsTargetAnamorphic,
streamInfo.IsTargetCabac, streamInfo.IsTargetCabac,
streamInfo.TargetRefFrames); streamInfo.TargetRefFrames,
streamInfo.TargetVideoStreamCount,
streamInfo.TargetAudioStreamCount);
foreach (var contentFeature in contentFeatureList) foreach (var contentFeature in contentFeatureList)
{ {
AddVideoResource(container, video, deviceId, filter, contentFeature, streamInfo); 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.TargetTimestamp,
streamInfo.IsTargetAnamorphic, streamInfo.IsTargetAnamorphic,
streamInfo.IsTargetCabac, streamInfo.IsTargetCabac,
streamInfo.TargetRefFrames); streamInfo.TargetRefFrames,
streamInfo.TargetVideoStreamCount,
streamInfo.TargetAudioStreamCount);
var filename = url.Substring(0, url.IndexOf('?')); var filename = url.Substring(0, url.IndexOf('?'));
@ -344,7 +351,7 @@ namespace MediaBrowser.Dlna.Didl
if (streamInfo == null) 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 streamInfo = new StreamBuilder().BuildAudioItem(new AudioOptions
{ {

@ -467,16 +467,16 @@ namespace MediaBrowser.Dlna.PlayTo
var profile = _dlnaManager.GetProfile(deviceInfo.ToDeviceIdentification()) ?? var profile = _dlnaManager.GetProfile(deviceInfo.ToDeviceIdentification()) ??
_dlnaManager.GetDefaultProfile(); _dlnaManager.GetDefaultProfile();
var hasMediaSources = item as IHasMediaSources; var hasMediaSources = item as IHasMediaSources;
var mediaSources = hasMediaSources != null var mediaSources = hasMediaSources != null
? (user == null ? hasMediaSources.GetMediaSources(true) : _mediaSourceManager.GetStaticMediaSources(hasMediaSources, true, user)).ToList() ? (_mediaSourceManager.GetStaticMediaSources(hasMediaSources, true, user)).ToList()
: new List<MediaSourceInfo>(); : new List<MediaSourceInfo>();
var playlistItem = GetPlaylistItem(item, mediaSources, profile, _session.DeviceId, mediaSourceId, audioStreamIndex, subtitleStreamIndex); var playlistItem = GetPlaylistItem(item, mediaSources, profile, _session.DeviceId, mediaSourceId, audioStreamIndex, subtitleStreamIndex);
playlistItem.StreamInfo.StartPositionTicks = startPostionTicks; 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) var itemXml = new DidlBuilder(profile, user, _imageProcessor, _serverAddress, _accessToken, _userDataManager, _localization, _mediaSourceManager)
.GetItemDidl(item, null, _session.DeviceId, new Filter(), playlistItem.StreamInfo); .GetItemDidl(item, null, _session.DeviceId, new Filter(), playlistItem.StreamInfo);
@ -526,7 +526,9 @@ namespace MediaBrowser.Dlna.PlayTo
streamInfo.TranscodeSeekInfo, streamInfo.TranscodeSeekInfo,
streamInfo.IsTargetAnamorphic, streamInfo.IsTargetAnamorphic,
streamInfo.IsTargetCabac, streamInfo.IsTargetCabac,
streamInfo.TargetRefFrames); streamInfo.TargetRefFrames,
streamInfo.TargetVideoStreamCount,
streamInfo.TargetAudioStreamCount);
return list.FirstOrDefault(); return list.FirstOrDefault();
} }

@ -92,7 +92,7 @@ namespace MediaBrowser.LocalMetadata
{ {
get get
{ {
return "Media Browser Xml"; return "Emby Xml";
} }
} }

@ -186,18 +186,17 @@ namespace MediaBrowser.LocalMetadata.Images
names.Add("movie"); names.Add("movie");
} }
foreach (var name in names)
{
AddImage(files, images, imagePrefix + name, ImageType.Primary);
}
var fileNameWithoutExtension = item.FileNameWithoutExtension; var fileNameWithoutExtension = item.FileNameWithoutExtension;
if (!string.IsNullOrEmpty(fileNameWithoutExtension)) if (!string.IsNullOrEmpty(fileNameWithoutExtension))
{ {
AddImage(files, images, fileNameWithoutExtension, ImageType.Primary); AddImage(files, images, fileNameWithoutExtension, ImageType.Primary);
} }
foreach (var name in names)
{
AddImage(files, images, imagePrefix + name, ImageType.Primary);
}
if (!isInMixedFolder) if (!isInMixedFolder)
{ {
foreach (var name in names) foreach (var name in names)

@ -756,11 +756,6 @@ namespace MediaBrowser.LocalMetadata.Savers
{ {
builder.Append("<" + singularNodeName + ">"); builder.Append("<" + singularNodeName + ">");
if (!string.IsNullOrWhiteSpace(link.ItemType))
{
builder.Append("<Type>" + SecurityElement.Escape(link.ItemType) + "</Type>");
}
if (!string.IsNullOrWhiteSpace(link.Path)) if (!string.IsNullOrWhiteSpace(link.Path))
{ {
builder.Append("<Path>" + SecurityElement.Escape((link.Path)) + "</Path>"); builder.Append("<Path>" + SecurityElement.Escape((link.Path)) + "</Path>");

@ -14,7 +14,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
{ {
public class AudioEncoder : BaseEncoder 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.Configuration;
using MediaBrowser.Common.IO; using MediaBrowser.Common.IO;
using MediaBrowser.Controller.Channels;
using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.LiveTv; using MediaBrowser.Controller.LiveTv;
@ -14,6 +13,7 @@ using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities; using MediaBrowser.Model.Entities;
using MediaBrowser.Model.IO; using MediaBrowser.Model.IO;
using MediaBrowser.Model.Logging; using MediaBrowser.Model.Logging;
using MediaBrowser.Model.MediaInfo;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
@ -31,10 +31,8 @@ namespace MediaBrowser.MediaEncoding.Encoder
protected readonly ILogger Logger; protected readonly ILogger Logger;
protected readonly IServerConfigurationManager ConfigurationManager; protected readonly IServerConfigurationManager ConfigurationManager;
protected readonly IFileSystem FileSystem; protected readonly IFileSystem FileSystem;
protected readonly ILiveTvManager LiveTvManager;
protected readonly IIsoManager IsoManager; protected readonly IIsoManager IsoManager;
protected readonly ILibraryManager LibraryManager; protected readonly ILibraryManager LibraryManager;
protected readonly IChannelManager ChannelManager;
protected readonly ISessionManager SessionManager; protected readonly ISessionManager SessionManager;
protected readonly ISubtitleEncoder SubtitleEncoder; protected readonly ISubtitleEncoder SubtitleEncoder;
protected readonly IMediaSourceManager MediaSourceManager; protected readonly IMediaSourceManager MediaSourceManager;
@ -45,20 +43,18 @@ namespace MediaBrowser.MediaEncoding.Encoder
ILogger logger, ILogger logger,
IServerConfigurationManager configurationManager, IServerConfigurationManager configurationManager,
IFileSystem fileSystem, IFileSystem fileSystem,
ILiveTvManager liveTvManager,
IIsoManager isoManager, IIsoManager isoManager,
ILibraryManager libraryManager, ILibraryManager libraryManager,
IChannelManager channelManager, ISessionManager sessionManager,
ISessionManager sessionManager, ISubtitleEncoder subtitleEncoder, IMediaSourceManager mediaSourceManager) ISubtitleEncoder subtitleEncoder,
IMediaSourceManager mediaSourceManager)
{ {
MediaEncoder = mediaEncoder; MediaEncoder = mediaEncoder;
Logger = logger; Logger = logger;
ConfigurationManager = configurationManager; ConfigurationManager = configurationManager;
FileSystem = fileSystem; FileSystem = fileSystem;
LiveTvManager = liveTvManager;
IsoManager = isoManager; IsoManager = isoManager;
LibraryManager = libraryManager; LibraryManager = libraryManager;
ChannelManager = channelManager;
SessionManager = sessionManager; SessionManager = sessionManager;
SubtitleEncoder = subtitleEncoder; SubtitleEncoder = subtitleEncoder;
MediaSourceManager = mediaSourceManager; MediaSourceManager = mediaSourceManager;
@ -68,7 +64,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
IProgress<double> progress, IProgress<double> progress,
CancellationToken cancellationToken) 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); .CreateJob(options, IsVideoEncoder, progress, cancellationToken).ConfigureAwait(false);
encodingJob.OutputFilePath = GetOutputFilePath(encodingJob); encodingJob.OutputFilePath = GetOutputFilePath(encodingJob);
@ -477,53 +473,25 @@ namespace MediaBrowser.MediaEncoding.Encoder
state.IsoMount = await IsoManager.Mount(state.MediaPath, cancellationToken).ConfigureAwait(false); state.IsoMount = await IsoManager.Mount(state.MediaPath, cancellationToken).ConfigureAwait(false);
} }
if (string.IsNullOrEmpty(state.MediaPath)) if (state.MediaSource.RequiresOpening)
{ {
var checkCodecs = false; var liveStreamResponse = await MediaSourceManager.OpenLiveStream(new LiveStreamRequest
if (string.Equals(state.ItemType, typeof(LiveTvChannel).Name))
{ {
var streamInfo = await LiveTvManager.GetChannelStream(state.Options.ItemId, cancellationToken).ConfigureAwait(false); OpenToken = state.MediaSource.OpenToken
state.LiveTvStreamId = streamInfo.Id;
state.MediaPath = streamInfo.Path;
state.InputProtocol = streamInfo.Protocol;
await Task.Delay(1500, cancellationToken).ConfigureAwait(false); }, false, cancellationToken).ConfigureAwait(false);
AttachMediaStreamInfo(state, streamInfo, state.Options); AttachMediaStreamInfo(state, liveStreamResponse.MediaSource, state.Options);
checkCodecs = true;
}
else if (string.Equals(state.ItemType, typeof(LiveTvVideoRecording).Name) || if (state.IsVideoRequest)
string.Equals(state.ItemType, typeof(LiveTvAudioRecording).Name))
{ {
var streamInfo = await LiveTvManager.GetRecordingStream(state.Options.ItemId, cancellationToken).ConfigureAwait(false); EncodingJobFactory.TryStreamCopy(state, state.Options);
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;
} }
}
if (state.IsVideoRequest && checkCodecs) if (state.MediaSource.BufferMs.HasValue)
{ {
if (state.VideoStream != null && EncodingJobFactory.CanStreamCopyVideo(state.Options, state.VideoStream)) await Task.Delay(state.MediaSource.BufferMs.Value, cancellationToken).ConfigureAwait(false);
{
state.OutputVideoCodec = "copy";
}
if (state.AudioStream != null && EncodingJobFactory.CanStreamCopyAudio(state.Options, state.AudioStream, state.SupportedAudioCodecs))
{
state.OutputAudioCodec = "copy";
}
}
} }
} }
@ -531,22 +499,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
MediaSourceInfo mediaSource, MediaSourceInfo mediaSource,
EncodingJobOptions videoRequest) EncodingJobOptions videoRequest)
{ {
state.InputProtocol = mediaSource.Protocol; EncodingJobFactory.AttachMediaStreamInfo(state, mediaSource, videoRequest);
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);
} }
/// <summary> /// <summary>
@ -998,7 +951,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
if (!string.IsNullOrEmpty(state.SubtitleStream.Language)) 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)) if (!string.IsNullOrEmpty(charenc))
{ {

@ -1,7 +1,8 @@
using MediaBrowser.Controller.LiveTv; using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.MediaEncoding; using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Dlna;
using MediaBrowser.Model.Drawing; using MediaBrowser.Model.Drawing;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities; using MediaBrowser.Model.Entities;
using MediaBrowser.Model.IO; using MediaBrowser.Model.IO;
using MediaBrowser.Model.Logging; using MediaBrowser.Model.Logging;
@ -26,7 +27,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
public EncodingJobOptions Options { get; set; } public EncodingJobOptions Options { get; set; }
public string InputContainer { 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 AudioStream { get; set; }
public MediaStream VideoStream { get; set; } public MediaStream VideoStream { get; set; }
public MediaStream SubtitleStream { get; set; } public MediaStream SubtitleStream { get; set; }
@ -76,12 +77,12 @@ namespace MediaBrowser.MediaEncoding.Encoder
} }
private readonly ILogger _logger; 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; _logger = logger;
_liveTvManager = liveTvManager; _mediaSourceManager = mediaSourceManager;
Id = Guid.NewGuid().ToString("N"); Id = Guid.NewGuid().ToString("N");
RemoteHttpHeaders = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); RemoteHttpHeaders = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
@ -89,7 +90,6 @@ namespace MediaBrowser.MediaEncoding.Encoder
SupportedAudioCodecs = new List<string>(); SupportedAudioCodecs = new List<string>();
PlayableStreamFileNames = new List<string>(); PlayableStreamFileNames = new List<string>();
RemoteHttpHeaders = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); RemoteHttpHeaders = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
AllMediaStreams = new List<MediaStream>();
TaskCompletionSource = new TaskCompletionSource<bool>(); TaskCompletionSource = new TaskCompletionSource<bool>();
} }
@ -136,15 +136,15 @@ namespace MediaBrowser.MediaEncoding.Encoder
private async void DisposeLiveStream() private async void DisposeLiveStream()
{ {
if (!string.IsNullOrEmpty(LiveTvStreamId)) if (MediaSource.RequiresClosing)
{ {
try try
{ {
await _liveTvManager.CloseLiveStream(LiveTvStreamId, CancellationToken.None).ConfigureAwait(false); await _mediaSourceManager.CloseLiveStream(MediaSource.LiveStreamId, CancellationToken.None).ConfigureAwait(false);
} }
catch (Exception ex) 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) public void ReportTranscodingProgress(TimeSpan? transcodingPosition, float? framerate, double? percentComplete, long? bytesTranscoded)
{ {
var ticks = transcodingPosition.HasValue ? transcodingPosition.Value.Ticks : (long?)null; 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.Entities;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.LiveTv; using MediaBrowser.Controller.LiveTv;
using MediaBrowser.Controller.MediaEncoding; using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Dlna;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities; using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Logging; using MediaBrowser.Model.Logging;
using MediaBrowser.Model.MediaInfo; using MediaBrowser.Model.MediaInfo;
@ -19,19 +20,15 @@ namespace MediaBrowser.MediaEncoding.Encoder
public class EncodingJobFactory public class EncodingJobFactory
{ {
private readonly ILogger _logger; private readonly ILogger _logger;
private readonly ILiveTvManager _liveTvManager;
private readonly ILibraryManager _libraryManager; private readonly ILibraryManager _libraryManager;
private readonly IChannelManager _channelManager;
private readonly IMediaSourceManager _mediaSourceManager; private readonly IMediaSourceManager _mediaSourceManager;
protected static readonly CultureInfo UsCulture = new CultureInfo("en-US"); 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; _logger = logger;
_liveTvManager = liveTvManager;
_libraryManager = libraryManager; _libraryManager = libraryManager;
_channelManager = channelManager;
_mediaSourceManager = mediaSourceManager; _mediaSourceManager = mediaSourceManager;
} }
@ -42,9 +39,9 @@ namespace MediaBrowser.MediaEncoding.Encoder
if (string.IsNullOrEmpty(request.AudioCodec)) if (string.IsNullOrEmpty(request.AudioCodec))
{ {
request.AudioCodec = InferAudioCodec(request.OutputContainer); request.AudioCodec = InferAudioCodec(request.OutputContainer);
} }
var state = new EncodingJob(_logger, _liveTvManager) var state = new EncodingJob(_logger, _mediaSourceManager)
{ {
Options = options, Options = options,
IsVideoRequest = isVideoRequest, IsVideoRequest = isVideoRequest,
@ -58,106 +55,17 @@ namespace MediaBrowser.MediaEncoding.Encoder
} }
var item = _libraryManager.GetItemById(request.ItemId); var item = _libraryManager.GetItemById(request.ItemId);
List<MediaStream> mediaStreams = null;
state.ItemType = item.GetType().Name; state.ItemType = item.GetType().Name;
if (item is ILiveTvRecording) state.IsInputVideo = string.Equals(item.MediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase);
{
var recording = await _liveTvManager.GetInternalRecording(request.ItemId, 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.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) var mediaSources = await _mediaSourceManager.GetPlayackMediaSources(request.ItemId, false, cancellationToken).ConfigureAwait(false);
? 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; var mediaSource = string.IsNullOrEmpty(request.MediaSourceId)
? mediaSources.First()
: mediaSources.First(i => string.Equals(i.Id, request.MediaSourceId));
state.PlayableStreamFileNames = mediaSource.PlayableStreamFileNames.ToList(); AttachMediaStreamInfo(state, mediaSource, options);
if (mediaSource.Timestamp.HasValue)
{
state.InputTimestamp = mediaSource.Timestamp.Value;
}
}
state.RunTimeTicks = mediaSource.RunTimeTicks;
}
AttachMediaStreamInfo(state, mediaStreams, request);
state.OutputAudioBitrate = GetAudioBitrateParam(request, state.AudioStream); state.OutputAudioBitrate = GetAudioBitrateParam(request, state.AudioStream);
state.OutputAudioSampleRate = request.AudioSampleRate; state.OutputAudioSampleRate = request.AudioSampleRate;
@ -185,26 +93,73 @@ namespace MediaBrowser.MediaEncoding.Encoder
ApplyDeviceProfileSettings(state); ApplyDeviceProfileSettings(state);
if (isVideoRequest) TryStreamCopy(state, request);
return state;
}
internal static void TryStreamCopy(EncodingJob state,
EncodingJobOptions videoRequest)
{
if (state.IsVideoRequest)
{ {
if (state.VideoStream != null && CanStreamCopyVideo(request, state.VideoStream)) if (state.VideoStream != null && CanStreamCopyVideo(videoRequest, state.VideoStream))
{ {
state.OutputVideoCodec = "copy"; state.OutputVideoCodec = "copy";
} }
if (state.AudioStream != null && CanStreamCopyAudio(request, state.AudioStream, state.SupportedAudioCodecs)) if (state.AudioStream != null && CanStreamCopyAudio(videoRequest, state.AudioStream, state.SupportedAudioCodecs))
{ {
state.OutputAudioCodec = "copy"; state.OutputAudioCodec = "copy";
} }
} }
return state;
} }
internal static void AttachMediaStreamInfo(EncodingJob state, internal static void AttachMediaStreamInfo(EncodingJob state,
List<MediaStream> mediaStreams, MediaSourceInfo mediaSource,
EncodingJobOptions videoRequest) 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 (videoRequest != null)
{ {
if (string.IsNullOrEmpty(videoRequest.VideoCodec)) if (string.IsNullOrEmpty(videoRequest.VideoCodec))
@ -233,7 +188,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
state.AudioStream = GetMediaStream(mediaStreams, null, MediaStreamType.Audio, true); state.AudioStream = GetMediaStream(mediaStreams, null, MediaStreamType.Audio, true);
} }
state.AllMediaStreams = mediaStreams; state.MediaSource = mediaSource;
} }
/// <summary> /// <summary>
@ -771,7 +726,9 @@ namespace MediaBrowser.MediaEncoding.Encoder
state.TargetTimestamp, state.TargetTimestamp,
state.IsTargetAnamorphic, state.IsTargetAnamorphic,
state.IsTargetCabac, state.IsTargetCabac,
state.TargetRefFrames); state.TargetRefFrames,
state.TargetVideoStreamCount,
state.TargetAudioStreamCount);
if (mediaProfile != null) if (mediaProfile != null)
{ {

@ -577,10 +577,8 @@ namespace MediaBrowser.MediaEncoding.Encoder
_logger, _logger,
ConfigurationManager, ConfigurationManager,
FileSystem, FileSystem,
LiveTvManager,
IsoManager, IsoManager,
LibraryManager, LibraryManager,
ChannelManager,
SessionManager, SessionManager,
SubtitleEncoder(), SubtitleEncoder(),
MediaSourceManager()) MediaSourceManager())
@ -599,10 +597,8 @@ namespace MediaBrowser.MediaEncoding.Encoder
_logger, _logger,
ConfigurationManager, ConfigurationManager,
FileSystem, FileSystem,
LiveTvManager,
IsoManager, IsoManager,
LibraryManager, LibraryManager,
ChannelManager,
SessionManager, SessionManager,
SubtitleEncoder(), SubtitleEncoder(),
MediaSourceManager()) MediaSourceManager())

@ -15,7 +15,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
{ {
public class VideoEncoder : BaseEncoder 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; args;
} }
var keyFrameArg = string.Format(" -force_key_frames expr:gte(t,n_forced*{0})", if (state.Options.Context == EncodingContext.Streaming)
5.ToString(UsCulture)); {
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; var hasGraphicalSubs = state.SubtitleStream != null && !state.SubtitleStream.IsTextSubtitleStream;

@ -1,6 +1,7 @@
using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Extensions;
using MediaBrowser.Common.IO; using MediaBrowser.Common.IO;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.MediaEncoding; using MediaBrowser.Controller.MediaEncoding;
@ -29,8 +30,10 @@ namespace MediaBrowser.MediaEncoding.Subtitles
private readonly IFileSystem _fileSystem; private readonly IFileSystem _fileSystem;
private readonly IMediaEncoder _mediaEncoder; private readonly IMediaEncoder _mediaEncoder;
private readonly IJsonSerializer _json; 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; _libraryManager = libraryManager;
_logger = logger; _logger = logger;
@ -38,6 +41,8 @@ namespace MediaBrowser.MediaEncoding.Subtitles
_fileSystem = fileSystem; _fileSystem = fileSystem;
_mediaEncoder = mediaEncoder; _mediaEncoder = mediaEncoder;
_json = json; _json = json;
_httpClient = httpClient;
_mediaSourceManager = mediaSourceManager;
} }
private string SubtitleCachePath private string SubtitleCachePath
@ -127,9 +132,9 @@ namespace MediaBrowser.MediaEncoding.Subtitles
int subtitleStreamIndex, int subtitleStreamIndex,
CancellationToken cancellationToken) 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)); .First(i => string.Equals(i.Id, mediaSourceId));
var subtitleStream = mediaSource.MediaStreams 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 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) if (requiresCharset)
{ {
var charset = GetSubtitleFileCharacterSet(path); var charset = await GetSubtitleFileCharacterSet(path, protocol, cancellationToken).ConfigureAwait(false);
if (!string.IsNullOrEmpty(charset)) 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))) 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, string[] inputFiles,
MediaProtocol protocol, MediaProtocol protocol,
MediaStream subtitleStream, MediaStream subtitleStream,
@ -228,12 +233,12 @@ namespace MediaBrowser.MediaEncoding.Subtitles
} }
// Extract // 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) await ExtractTextSubtitle(inputFiles, protocol, subtitleStream.Index, outputCodec, outputPath, cancellationToken)
.ConfigureAwait(false); .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) var currentFormat = (Path.GetExtension(subtitleStream.Path) ?? subtitleStream.Codec)
@ -242,14 +247,14 @@ namespace MediaBrowser.MediaEncoding.Subtitles
if (GetReader(currentFormat, false) == null) if (GetReader(currentFormat, false) == null)
{ {
// Convert // 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, private async Task<SubtitleTrackInfo> GetTrackInfo(Stream stream,
@ -336,10 +341,11 @@ namespace MediaBrowser.MediaEncoding.Subtitles
/// Converts the text subtitle to SRT. /// Converts the text subtitle to SRT.
/// </summary> /// </summary>
/// <param name="inputPath">The input path.</param> /// <param name="inputPath">The input path.</param>
/// <param name="inputProtocol">The input protocol.</param>
/// <param name="outputPath">The output path.</param> /// <param name="outputPath">The output path.</param>
/// <param name="cancellationToken">The cancellation token.</param> /// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task.</returns> /// <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); var semaphore = GetLock(outputPath);
@ -349,7 +355,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
{ {
if (!File.Exists(outputPath)) if (!File.Exists(outputPath))
{ {
await ConvertTextSubtitleToSrtInternal(inputPath, outputPath).ConfigureAwait(false); await ConvertTextSubtitleToSrtInternal(inputPath, inputProtocol, outputPath, cancellationToken).ConfigureAwait(false);
} }
} }
finally finally
@ -362,13 +368,17 @@ namespace MediaBrowser.MediaEncoding.Subtitles
/// Converts the text subtitle to SRT internal. /// Converts the text subtitle to SRT internal.
/// </summary> /// </summary>
/// <param name="inputPath">The input path.</param> /// <param name="inputPath">The input path.</param>
/// <param name="inputProtocol">The input protocol.</param>
/// <param name="outputPath">The output path.</param> /// <param name="outputPath">The output path.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task.</returns> /// <returns>Task.</returns>
/// <exception cref="System.ArgumentNullException">inputPath /// <exception cref="System.ArgumentNullException">
/// inputPath
/// or /// or
/// outputPath</exception> /// outputPath
/// </exception>
/// <exception cref="System.ApplicationException"></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)) if (string.IsNullOrEmpty(inputPath))
{ {
@ -382,7 +392,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
Directory.CreateDirectory(Path.GetDirectoryName(outputPath)); Directory.CreateDirectory(Path.GetDirectoryName(outputPath));
var encodingParam = GetSubtitleFileCharacterSet(inputPath); var encodingParam = await GetSubtitleFileCharacterSet(inputPath, inputProtocol, cancellationToken).ConfigureAwait(false);
if (!string.IsNullOrEmpty(encodingParam)) 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);
return Path.Combine(SubtitleCachePath, prefix, filename);
}
else
{
var filename = (mediaPath + "_" + subtitleStreamIndex.ToString(CultureInfo.InvariantCulture)).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);
}
} }
/// <summary> public async Task<string> GetSubtitleFileCharacterSet(string path, MediaProtocol protocol, CancellationToken cancellationToken)
/// Gets the subtitle language encoding param.
/// </summary>
/// <param name="path">The path.</param>
/// <returns>System.String.</returns>
public string GetSubtitleFileCharacterSet(string path)
{ {
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)) 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 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(); var detector = new CharsetDetector();
detector.Feed(file); 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 // It's ok - anything aside from utf is ok since that's what we're looking for
return Encoding.Default; 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"> <Compile Include="..\MediaBrowser.Model\Dlna\StreamInfo.cs">
<Link>Dlna\StreamInfo.cs</Link> <Link>Dlna\StreamInfo.cs</Link>
</Compile> </Compile>
<Compile Include="..\MediaBrowser.Model\Dlna\StreamInfoSorter.cs">
<Link>Dlna\StreamInfoSorter.cs</Link>
</Compile>
<Compile Include="..\MediaBrowser.Model\Dlna\SubtitleDeliveryMethod.cs"> <Compile Include="..\MediaBrowser.Model\Dlna\SubtitleDeliveryMethod.cs">
<Link>Dlna\SubtitleDeliveryMethod.cs</Link> <Link>Dlna\SubtitleDeliveryMethod.cs</Link>
</Compile> </Compile>
@ -800,12 +803,21 @@
<Compile Include="..\MediaBrowser.Model\MediaInfo\IBlurayExaminer.cs"> <Compile Include="..\MediaBrowser.Model\MediaInfo\IBlurayExaminer.cs">
<Link>MediaInfo\IBlurayExaminer.cs</Link> <Link>MediaInfo\IBlurayExaminer.cs</Link>
</Compile> </Compile>
<Compile Include="..\MediaBrowser.Model\MediaInfo\LiveMediaInfoResult.cs"> <Compile Include="..\MediaBrowser.Model\MediaInfo\LiveStreamRequest.cs">
<Link>MediaInfo\LiveMediaInfoResult.cs</Link> <Link>MediaInfo\LiveStreamRequest.cs</Link>
</Compile>
<Compile Include="..\MediaBrowser.Model\MediaInfo\LiveStreamResponse.cs">
<Link>MediaInfo\LiveStreamResponse.cs</Link>
</Compile> </Compile>
<Compile Include="..\MediaBrowser.Model\MediaInfo\MediaProtocol.cs"> <Compile Include="..\MediaBrowser.Model\MediaInfo\MediaProtocol.cs">
<Link>MediaInfo\MediaProtocol.cs</Link> <Link>MediaInfo\MediaProtocol.cs</Link>
</Compile> </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"> <Compile Include="..\MediaBrowser.Model\MediaInfo\SubtitleFormat.cs">
<Link>MediaInfo\SubtitleFormat.cs</Link> <Link>MediaInfo\SubtitleFormat.cs</Link>
</Compile> </Compile>

@ -378,6 +378,9 @@
<Compile Include="..\MediaBrowser.Model\Dlna\StreamInfo.cs"> <Compile Include="..\MediaBrowser.Model\Dlna\StreamInfo.cs">
<Link>Dlna\StreamInfo.cs</Link> <Link>Dlna\StreamInfo.cs</Link>
</Compile> </Compile>
<Compile Include="..\MediaBrowser.Model\Dlna\StreamInfoSorter.cs">
<Link>Dlna\StreamInfoSorter.cs</Link>
</Compile>
<Compile Include="..\MediaBrowser.Model\Dlna\SubtitleDeliveryMethod.cs"> <Compile Include="..\MediaBrowser.Model\Dlna\SubtitleDeliveryMethod.cs">
<Link>Dlna\SubtitleDeliveryMethod.cs</Link> <Link>Dlna\SubtitleDeliveryMethod.cs</Link>
</Compile> </Compile>
@ -756,12 +759,21 @@
<Compile Include="..\MediaBrowser.Model\MediaInfo\IBlurayExaminer.cs"> <Compile Include="..\MediaBrowser.Model\MediaInfo\IBlurayExaminer.cs">
<Link>MediaInfo\IBlurayExaminer.cs</Link> <Link>MediaInfo\IBlurayExaminer.cs</Link>
</Compile> </Compile>
<Compile Include="..\MediaBrowser.Model\MediaInfo\LiveMediaInfoResult.cs"> <Compile Include="..\MediaBrowser.Model\MediaInfo\LiveStreamRequest.cs">
<Link>MediaInfo\LiveMediaInfoResult.cs</Link> <Link>MediaInfo\LiveStreamRequest.cs</Link>
</Compile>
<Compile Include="..\MediaBrowser.Model\MediaInfo\LiveStreamResponse.cs">
<Link>MediaInfo\LiveStreamResponse.cs</Link>
</Compile> </Compile>
<Compile Include="..\MediaBrowser.Model\MediaInfo\MediaProtocol.cs"> <Compile Include="..\MediaBrowser.Model\MediaInfo\MediaProtocol.cs">
<Link>MediaInfo\MediaProtocol.cs</Link> <Link>MediaInfo\MediaProtocol.cs</Link>
</Compile> </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"> <Compile Include="..\MediaBrowser.Model\MediaInfo\SubtitleFormat.cs">
<Link>MediaInfo\SubtitleFormat.cs</Link> <Link>MediaInfo\SubtitleFormat.cs</Link>
</Compile> </Compile>

@ -248,10 +248,9 @@ namespace MediaBrowser.Model.ApiClient
/// <summary> /// <summary>
/// Gets the playback information. /// Gets the playback information.
/// </summary> /// </summary>
/// <param name="itemId">The item identifier.</param> /// <param name="request">The request.</param>
/// <param name="userId">The user identifier.</param>
/// <returns>Task&lt;LiveMediaInfoResult&gt;.</returns> /// <returns>Task&lt;LiveMediaInfoResult&gt;.</returns>
Task<LiveMediaInfoResult> GetPlaybackInfo(string itemId, string userId); Task<PlaybackInfoResponse> GetPlaybackInfo(PlaybackInfoRequest request);
/// <summary> /// <summary>
/// Gets the users async. /// Gets the users async.
@ -1486,5 +1485,12 @@ namespace MediaBrowser.Model.ApiClient
/// <param name="query">The query.</param> /// <param name="query">The query.</param>
/// <returns>Task&lt;List&lt;RecommendationDto&gt;&gt;.</returns> /// <returns>Task&lt;List&lt;RecommendationDto&gt;&gt;.</returns>
Task<List<RecommendationDto>> GetMovieRecommendations(MovieRecommendationQuery query); 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&lt;LiveStreamResponse&gt;.</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> /// <param name="rememberCredentials">if set to <c>true</c> [remember credentials].</param>
/// <returns>Task.</returns> /// <returns>Task.</returns>
Task AuthenticateOffline(UserDto user, string password, bool rememberCredentials); Task AuthenticateOffline(UserDto user, string password, bool rememberCredentials);
/// <summary>
/// Gets the offline users.
/// </summary>
/// <returns>Task&lt;List&lt;UserDto&gt;&gt;.</returns>
Task<List<UserDto>> GetOfflineUsers();
} }
} }

@ -1,7 +1,6 @@
using MediaBrowser.Model.Extensions; using MediaBrowser.Model.Extensions;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
namespace MediaBrowser.Model.ApiClient namespace MediaBrowser.Model.ApiClient
{ {
@ -24,7 +23,12 @@ namespace MediaBrowser.Model.ApiClient
throw new ArgumentNullException("server"); 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); var index = FindIndex(list, server.Id);
@ -32,8 +36,11 @@ namespace MediaBrowser.Model.ApiClient
{ {
var existing = list[index]; var existing = list[index];
// Merge the data // Take the most recent DateLastAccessed
existing.DateLastAccessed = new[] { existing.DateLastAccessed, server.DateLastAccessed }.Max(); if (server.DateLastAccessed > existing.DateLastAccessed)
{
existing.DateLastAccessed = server.DateLastAccessed;
}
existing.UserLinkType = server.UserLinkType; existing.UserLinkType = server.UserLinkType;
@ -64,7 +71,11 @@ namespace MediaBrowser.Model.ApiClient
} }
if (server.WakeOnLanInfos != null && server.WakeOnLanInfos.Count > 0) 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) if (server.LastConnectionMode.HasValue)
{ {

@ -3,7 +3,6 @@ using MediaBrowser.Model.Extensions;
using MediaBrowser.Model.System; using MediaBrowser.Model.System;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
namespace MediaBrowser.Model.ApiClient namespace MediaBrowser.Model.ApiClient
{ {
@ -83,7 +82,12 @@ namespace MediaBrowser.Model.ApiClient
throw new ArgumentNullException("user"); 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); var index = FindIndex(list, user.Id);

@ -8,12 +8,16 @@ namespace MediaBrowser.Model.Configuration
public double DownMixAudioBoost { get; set; } public double DownMixAudioBoost { get; set; }
public string H264Encoder { get; set; } public string H264Encoder { get; set; }
public bool EnableDebugLogging { get; set; } public bool EnableDebugLogging { get; set; }
public bool EnableThrottling { get; set; }
public int ThrottleThresholdSeconds { get; set; }
public EncodingOptions() public EncodingOptions()
{ {
H264Encoder = "libx264"; H264Encoder = "libx264";
DownMixAudioBoost = 2; DownMixAudioBoost = 2;
EncodingQuality = EncodingQuality.Auto; EncodingQuality = EncodingQuality.Auto;
EnableThrottling = true;
ThrottleThresholdSeconds = 120;
} }
} }
} }

@ -200,7 +200,7 @@ namespace MediaBrowser.Model.Configuration
public PeopleMetadataOptions PeopleMetadataOptions { get; set; } public PeopleMetadataOptions PeopleMetadataOptions { get; set; }
public bool FindInternetTrailers { get; set; } public bool FindInternetTrailers { get; set; }
public string[] InsecureApps8 { get; set; } public string[] InsecureApps9 { get; set; }
public bool SaveMetadataHidden { get; set; } public bool SaveMetadataHidden { get; set; }
@ -257,7 +257,7 @@ namespace MediaBrowser.Model.Configuration
PeopleMetadataOptions = new PeopleMetadataOptions(); PeopleMetadataOptions = new PeopleMetadataOptions();
InsecureApps8 = new[] InsecureApps9 = new[]
{ {
"Chromecast", "Chromecast",
"iOS", "iOS",
@ -266,7 +266,6 @@ namespace MediaBrowser.Model.Configuration
"Media Portal", "Media Portal",
"iPad", "iPad",
"iPhone", "iPhone",
"Roku",
"Windows Phone" "Windows Phone"
}; };

@ -6,12 +6,6 @@ namespace MediaBrowser.Model.Configuration
/// </summary> /// </summary>
public class UserConfiguration 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> /// <summary>
/// Gets or sets the audio language preference. /// Gets or sets the audio language preference.
/// </summary> /// </summary>
@ -53,13 +47,14 @@ namespace MediaBrowser.Model.Configuration
public string[] LatestItemsExcludes { get; set; } public string[] LatestItemsExcludes { get; set; }
public bool HasMigratedToPolicy { get; set; } public bool HidePlayedInLatest { get; set; }
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="UserConfiguration" /> class. /// Initializes a new instance of the <see cref="UserConfiguration" /> class.
/// </summary> /// </summary>
public UserConfiguration() public UserConfiguration()
{ {
HidePlayedInLatest = true;
PlayDefaultAudioTrack = true; PlayDefaultAudioTrack = true;
LatestItemsExcludes = new string[] { }; LatestItemsExcludes = new string[] { };

@ -20,7 +20,9 @@ namespace MediaBrowser.Model.Dlna
TransportStreamTimestamp? timestamp, TransportStreamTimestamp? timestamp,
bool? isAnamorphic, bool? isAnamorphic,
bool? isCabac, bool? isCabac,
int? refFrames) int? refFrames,
int? numVideoStreams,
int? numAudioStreams)
{ {
switch (condition.Property) switch (condition.Property)
{ {
@ -56,6 +58,10 @@ namespace MediaBrowser.Model.Dlna
return IsConditionSatisfied(condition, width); return IsConditionSatisfied(condition, width);
case ProfileConditionValue.RefFrames: case ProfileConditionValue.RefFrames:
return IsConditionSatisfied(condition, refFrames); return IsConditionSatisfied(condition, refFrames);
case ProfileConditionValue.NumAudioStreams:
return IsConditionSatisfied(condition, numAudioStreams);
case ProfileConditionValue.NumVideoStreams:
return IsConditionSatisfied(condition, numVideoStreams);
case ProfileConditionValue.VideoTimestamp: case ProfileConditionValue.VideoTimestamp:
return IsConditionSatisfied(condition, timestamp); return IsConditionSatisfied(condition, timestamp);
default: default:
@ -92,7 +98,8 @@ namespace MediaBrowser.Model.Dlna
public bool IsVideoAudioConditionSatisfied(ProfileCondition condition, public bool IsVideoAudioConditionSatisfied(ProfileCondition condition,
int? audioChannels, int? audioChannels,
int? audioBitrate, int? audioBitrate,
string audioProfile) string audioProfile,
bool? isSecondaryTrack)
{ {
switch (condition.Property) switch (condition.Property)
{ {
@ -102,6 +109,8 @@ namespace MediaBrowser.Model.Dlna
return IsConditionSatisfied(condition, audioBitrate); return IsConditionSatisfied(condition, audioBitrate);
case ProfileConditionValue.AudioChannels: case ProfileConditionValue.AudioChannels:
return IsConditionSatisfied(condition, audioChannels); return IsConditionSatisfied(condition, audioChannels);
case ProfileConditionValue.IsSecondaryAudio:
return IsConditionSatisfied(condition, isSecondaryTrack);
default: default:
throw new ArgumentException("Unexpected condition on audio file: " + condition.Property); throw new ArgumentException("Unexpected condition on audio file: " + condition.Property);
} }

@ -117,7 +117,9 @@ namespace MediaBrowser.Model.Dlna
TranscodeSeekInfo transcodeSeekInfo, TranscodeSeekInfo transcodeSeekInfo,
bool? isAnamorphic, bool? isAnamorphic,
bool? isCabac, 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 // 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); string orgOp = ";DLNA.ORG_OP=" + DlnaMaps.GetOrgOpValue(runtimeTicks.HasValue, isDirectStream, transcodeSeekInfo);
@ -158,7 +160,9 @@ namespace MediaBrowser.Model.Dlna
timestamp, timestamp,
isAnamorphic, isAnamorphic,
isCabac, isCabac,
refFrames); refFrames,
numVideoStreams,
numAudioStreams);
List<string> orgPnValues = new List<string>(); List<string> orgPnValues = new List<string>();

@ -281,7 +281,9 @@ namespace MediaBrowser.Model.Dlna
TransportStreamTimestamp timestamp, TransportStreamTimestamp timestamp,
bool? isAnamorphic, bool? isAnamorphic,
bool? isCabac, bool? isCabac,
int? refFrames) int? refFrames,
int? numVideoStreams,
int? numAudioStreams)
{ {
container = StringHelper.TrimStart((container ?? string.Empty), '.'); container = StringHelper.TrimStart((container ?? string.Empty), '.');
@ -315,7 +317,7 @@ namespace MediaBrowser.Model.Dlna
var anyOff = false; var anyOff = false;
foreach (ProfileCondition c in i.Conditions) 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; anyOff = true;
break; break;

@ -17,6 +17,9 @@
VideoTimestamp = 12, VideoTimestamp = 12,
IsAnamorphic = 13, IsAnamorphic = 13,
RefFrames = 14, 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) private StreamInfo GetOptimalStream(List<StreamInfo> streams)
{ {
// Grab the first one that can be direct streamed streams = StreamInfoSorter.SortMediaSources(streams);
// 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;
}
}
foreach (StreamInfo stream in streams) foreach (StreamInfo stream in streams)
{ {
return stream; return stream;
} }
PlaybackException error = new PlaybackException(); return null;
error.ErrorCode = PlaybackErrorCode.NoCompatibleStream;
throw error;
} }
private StreamInfo BuildAudioItem(MediaSourceInfo item, AudioOptions options) private StreamInfo BuildAudioItem(MediaSourceInfo item, AudioOptions options)
@ -221,7 +197,7 @@ namespace MediaBrowser.Model.Dlna
playlistItem.EstimateContentLength = transcodingProfile.EstimateContentLength; playlistItem.EstimateContentLength = transcodingProfile.EstimateContentLength;
playlistItem.Container = transcodingProfile.Container; playlistItem.Container = transcodingProfile.Container;
playlistItem.AudioCodec = transcodingProfile.AudioCodec; playlistItem.AudioCodec = transcodingProfile.AudioCodec;
playlistItem.Protocol = transcodingProfile.Protocol; playlistItem.SubProtocol = transcodingProfile.Protocol;
List<CodecProfile> audioCodecProfiles = new List<CodecProfile>(); List<CodecProfile> audioCodecProfiles = new List<CodecProfile>();
foreach (CodecProfile i in options.Profile.CodecProfiles) foreach (CodecProfile i in options.Profile.CodecProfiles)
@ -263,6 +239,16 @@ namespace MediaBrowser.Model.Dlna
return playlistItem; 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) private List<PlayMethod> GetAudioDirectPlayMethods(MediaSourceInfo item, MediaStream audioStream, AudioOptions options)
{ {
DirectPlayProfile directPlayProfile = null; DirectPlayProfile directPlayProfile = null;
@ -287,7 +273,7 @@ namespace MediaBrowser.Model.Dlna
// The profile describes what the device supports // The profile describes what the device supports
// If device requirements are satisfied then allow both direct stream and direct play // 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); playMethods.Add(PlayMethod.DirectPlay);
} }
@ -296,6 +282,49 @@ namespace MediaBrowser.Model.Dlna
return playMethods; 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) private StreamInfo BuildVideoItem(MediaSourceInfo item, VideoOptions options)
{ {
StreamInfo playlistItem = new StreamInfo StreamInfo playlistItem = new StreamInfo
@ -308,16 +337,20 @@ namespace MediaBrowser.Model.Dlna
DeviceProfile = options.Profile 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 subtitleStream = playlistItem.SubtitleStreamIndex.HasValue ? item.GetMediaStream(MediaStreamType.Subtitle, playlistItem.SubtitleStreamIndex.Value) : null;
MediaStream audioStream = item.GetDefaultAudioStream(options.AudioStreamIndex ?? item.DefaultAudioStreamIndex); 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; 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 // 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); bool isEligibleForDirectStream = IsEligibleForDirectPlay(item, options.GetMaxBitrate(), subtitleStream, options);
if (isEligibleForDirectPlay || isEligibleForDirectStream) if (isEligibleForDirectPlay || isEligibleForDirectStream)
@ -374,7 +407,7 @@ namespace MediaBrowser.Model.Dlna
playlistItem.TranscodeSeekInfo = transcodingProfile.TranscodeSeekInfo; playlistItem.TranscodeSeekInfo = transcodingProfile.TranscodeSeekInfo;
playlistItem.AudioCodec = transcodingProfile.AudioCodec.Split(',')[0]; playlistItem.AudioCodec = transcodingProfile.AudioCodec.Split(',')[0];
playlistItem.VideoCodec = transcodingProfile.VideoCodec; playlistItem.VideoCodec = transcodingProfile.VideoCodec;
playlistItem.Protocol = transcodingProfile.Protocol; playlistItem.SubProtocol = transcodingProfile.Protocol;
playlistItem.AudioStreamIndex = audioStreamIndex; playlistItem.AudioStreamIndex = audioStreamIndex;
List<ProfileCondition> videoTranscodingConditions = new List<ProfileCondition>(); List<ProfileCondition> videoTranscodingConditions = new List<ProfileCondition>();
@ -509,10 +542,13 @@ namespace MediaBrowser.Model.Dlna
int? packetLength = videoStream == null ? null : videoStream.PacketLength; int? packetLength = videoStream == null ? null : videoStream.PacketLength;
int? refFrames = videoStream == null ? null : videoStream.RefFrames; int? refFrames = videoStream == null ? null : videoStream.RefFrames;
int? numAudioStreams = mediaSource.GetStreamCount(MediaStreamType.Audio);
int? numVideoStreams = mediaSource.GetStreamCount(MediaStreamType.Video);
// Check container conditions // Check container conditions
foreach (ProfileCondition i in 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; return null;
} }
@ -539,7 +575,7 @@ namespace MediaBrowser.Model.Dlna
foreach (ProfileCondition i in 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; return null;
} }
@ -568,7 +604,8 @@ namespace MediaBrowser.Model.Dlna
foreach (ProfileCondition i in conditions) 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; return null;
} }
@ -628,8 +665,20 @@ namespace MediaBrowser.Model.Dlna
// Look for an external profile that matches the stream type (text/graphical) // Look for an external profile that matches the stream type (text/graphical)
foreach (SubtitleProfile profile in subtitleProfiles) 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 (profile.Method == SubtitleDeliveryMethod.External && subtitleStream.IsTextSubtitleStream == MediaStream.IsTextFormat(profile.Format))
{ {
if (!requiresConversion)
{
return profile;
}
if (subtitleStream.SupportsExternalStream) if (subtitleStream.SupportsExternalStream)
{ {
return profile; return profile;
@ -645,8 +694,20 @@ namespace MediaBrowser.Model.Dlna
foreach (SubtitleProfile profile in subtitleProfiles) 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 (profile.Method == SubtitleDeliveryMethod.Embed && subtitleStream.IsTextSubtitleStream == MediaStream.IsTextFormat(profile.Format))
{ {
if (!requiresConversion)
{
return profile;
}
return profile; return profile;
} }
} }
@ -756,6 +817,9 @@ namespace MediaBrowser.Model.Dlna
case ProfileConditionValue.AudioProfile: case ProfileConditionValue.AudioProfile:
case ProfileConditionValue.Has64BitOffsets: case ProfileConditionValue.Has64BitOffsets:
case ProfileConditionValue.PacketLength: case ProfileConditionValue.PacketLength:
case ProfileConditionValue.NumAudioStreams:
case ProfileConditionValue.NumVideoStreams:
case ProfileConditionValue.IsSecondaryAudio:
case ProfileConditionValue.VideoTimestamp: case ProfileConditionValue.VideoTimestamp:
{ {
// Not supported yet // Not supported yet

@ -24,7 +24,7 @@ namespace MediaBrowser.Model.Dlna
public string Container { get; set; } public string Container { get; set; }
public string Protocol { get; set; } public string SubProtocol { get; set; }
public long StartPositionTicks { get; set; } public long StartPositionTicks { get; set; }
@ -51,7 +51,7 @@ namespace MediaBrowser.Model.Dlna
public int? MaxVideoBitDepth { get; set; } public int? MaxVideoBitDepth { get; set; }
public int? MaxRefFrames { get; set; } public int? MaxRefFrames { get; set; }
public float? MaxFramerate { get; set; } public float? MaxFramerate { get; set; }
public DeviceProfile DeviceProfile { get; set; } public DeviceProfile DeviceProfile { get; set; }
@ -69,7 +69,7 @@ namespace MediaBrowser.Model.Dlna
public SubtitleDeliveryMethod SubtitleDeliveryMethod { get; set; } public SubtitleDeliveryMethod SubtitleDeliveryMethod { get; set; }
public string SubtitleFormat { get; set; } public string SubtitleFormat { get; set; }
public LiveMediaInfoResult PlaybackInfo { get; set; } public string PlaySessionId { get; set; }
public string MediaSourceId public string MediaSourceId
{ {
@ -81,7 +81,8 @@ namespace MediaBrowser.Model.Dlna
public bool IsDirectStream public bool IsDirectStream
{ {
get { get
{
return PlayMethod == PlayMethod.DirectStream || return PlayMethod == PlayMethod.DirectStream ||
PlayMethod == PlayMethod.DirectPlay; PlayMethod == PlayMethod.DirectPlay;
} }
@ -89,7 +90,47 @@ namespace MediaBrowser.Model.Dlna
public string ToUrl(string baseUrl, string accessToken) public string ToUrl(string baseUrl, string accessToken)
{ {
return ToDlnaUrl(baseUrl, accessToken); if (PlayMethod == PlayMethod.DirectPlay)
{
return MediaSource.Path;
}
if (string.IsNullOrEmpty(baseUrl))
{
throw new ArgumentNullException(baseUrl);
}
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) public string ToDlnaUrl(string baseUrl, string accessToken)
@ -99,115 +140,124 @@ namespace MediaBrowser.Model.Dlna
return MediaSource.Path; return MediaSource.Path;
} }
string dlnaCommand = BuildDlnaParam(this, accessToken);
return GetUrl(baseUrl, dlnaCommand);
}
private string GetUrl(string baseUrl, string queryString)
{
if (string.IsNullOrEmpty(baseUrl)) if (string.IsNullOrEmpty(baseUrl))
{ {
throw new ArgumentNullException(baseUrl); throw new ArgumentNullException(baseUrl);
} }
string dlnaCommand = BuildDlnaParam(this);
string extension = string.IsNullOrEmpty(Container) ? string.Empty : "." + Container; string extension = string.IsNullOrEmpty(Container) ? string.Empty : "." + Container;
baseUrl = baseUrl.TrimEnd('/'); baseUrl = baseUrl.TrimEnd('/');
if (MediaType == DlnaProfileType.Audio) 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>();
{
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)); foreach (NameValuePair pair in BuildParams(item, accessToken))
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(pair.Value);
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);
return string.Format("Params={0}", string.Join(";", list.ToArray())); 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 list.Add(new NameValuePair("DeviceProfileId", item.DeviceProfileId ?? string.Empty));
if (SubtitleStreamIndex.HasValue) list.Add(new NameValuePair("DeviceId", item.DeviceId ?? string.Empty));
{ list.Add(new NameValuePair("MediaSourceId", item.MediaSourceId ?? string.Empty));
foreach (MediaStream stream in MediaSource.MediaStreams) list.Add(new NameValuePair("Static", (item.IsDirectStream).ToString().ToLower()));
{ list.Add(new NameValuePair("VideoCodec", item.VideoCodec ?? string.Empty));
if (stream.Type == MediaStreamType.Subtitle && stream.Index == SubtitleStreamIndex.Value) list.Add(new NameValuePair("AudioCodec", item.AudioCodec ?? string.Empty));
{ list.Add(new NameValuePair("AudioStreamIndex", item.AudioStreamIndex.HasValue ? StringHelper.ToStringCultureInvariant(item.AudioStreamIndex.Value) : string.Empty));
SubtitleStreamInfo info = GetSubtitleStreamInfo(stream); 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 (info != null) if (StringHelper.EqualsIgnoreCase(item.SubProtocol, "hls"))
{ {
list.Add(info); list.Add(new NameValuePair("StartTimeTicks", string.Empty));
}
}
}
} }
else
if (!includeSelectedTrackOnly)
{ {
foreach (MediaStream stream in MediaSource.MediaStreams) list.Add(new NameValuePair("StartTimeTicks", StringHelper.ToStringCultureInvariant(item.StartPositionTicks)));
{
if (stream.Type == MediaStreamType.Subtitle && (!SubtitleStreamIndex.HasValue || stream.Index != SubtitleStreamIndex.Value))
{
SubtitleStreamInfo info = GetSubtitleStreamInfo(stream);
if (info != null)
{
list.Add(info);
}
}
}
} }
list.Add(new NameValuePair("Level", item.VideoLevel.HasValue ? StringHelper.ToStringCultureInvariant(item.VideoLevel.Value) : string.Empty));
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; 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>(); List<SubtitleStreamInfo> list = new List<SubtitleStreamInfo>();
// HLS will preserve timestamps so we can just grab the full subtitle stream // 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 ? 0
: StartPositionTicks; : StartPositionTicks;
@ -218,12 +268,7 @@ namespace MediaBrowser.Model.Dlna
{ {
if (stream.Type == MediaStreamType.Subtitle && stream.Index == SubtitleStreamIndex.Value) if (stream.Type == MediaStreamType.Subtitle && stream.Index == SubtitleStreamIndex.Value)
{ {
SubtitleStreamInfo info = GetSubtitleStreamInfo(stream, baseUrl, accessToken, startPositionTicks); AddSubtitleProfiles(list, stream, enableAllProfiles, baseUrl, accessToken, startPositionTicks);
if (info != null)
{
list.Add(info);
}
} }
} }
} }
@ -234,54 +279,68 @@ namespace MediaBrowser.Model.Dlna
{ {
if (stream.Type == MediaStreamType.Subtitle && (!SubtitleStreamIndex.HasValue || stream.Index != SubtitleStreamIndex.Value)) if (stream.Type == MediaStreamType.Subtitle && (!SubtitleStreamIndex.HasValue || stream.Index != SubtitleStreamIndex.Value))
{ {
SubtitleStreamInfo info = GetSubtitleStreamInfo(stream, baseUrl, accessToken, startPositionTicks); AddSubtitleProfiles(list, stream, enableAllProfiles, baseUrl, accessToken, startPositionTicks);
if (info != null)
{
list.Add(info);
}
} }
} }
} }
return list; 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 (enableAllProfiles)
if (info != null)
{ {
info.Url = string.Format("{0}/Videos/{1}/{2}/Subtitles/{3}/{4}/Stream.{5}", foreach (SubtitleProfile profile in DeviceProfile.SubtitleProfiles)
baseUrl, {
ItemId, SubtitleStreamInfo info = GetSubtitleStreamInfo(stream, baseUrl, accessToken, startPositionTicks, new[] { profile });
MediaSourceId,
StringHelper.ToStringCultureInvariant(stream.Index), list.Add(info);
StringHelper.ToStringCultureInvariant(startPositionTicks), }
SubtitleFormat);
} }
else
{
SubtitleStreamInfo info = GetSubtitleStreamInfo(stream, baseUrl, accessToken, startPositionTicks, DeviceProfile.SubtitleProfiles);
return info; 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); SubtitleProfile subtitleProfile = StreamBuilder.GetSubtitleProfile(stream, subtitleProfiles, Context);
SubtitleStreamInfo info = new SubtitleStreamInfo
if (subtitleProfile.Method != SubtitleDeliveryMethod.External)
{
return null;
}
return new SubtitleStreamInfo
{ {
IsForced = stream.IsForced, IsForced = stream.IsForced,
Language = stream.Language, Language = stream.Language,
Name = stream.Language ?? "Unknown", Name = stream.Language ?? "Unknown",
Format = SubtitleFormat, Format = subtitleProfile.Format,
Index = stream.Index 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> /// <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() public List<MediaStream> GetSelectableAudioStreams()
{ {
return GetSelectableStreams(MediaStreamType.Audio); return GetSelectableStreams(MediaStreamType.Audio);

@ -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 namespace MediaBrowser.Model.Dlna
{ {
@ -13,5 +15,28 @@ namespace MediaBrowser.Model.Dlna
[XmlAttribute("didlMode")] [XmlAttribute("didlMode")]
public string DidlMode { get; set; } 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 bool IsForced { get; set; }
public string Format { get; set; } public string Format { get; set; }
public int Index { 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 ReadAtNativeFramerate { get; set; }
public bool SupportsTranscoding { get; set; } public bool SupportsTranscoding { get; set; }
public bool SupportsDirectStream { 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; } public VideoType? VideoType { get; set; }
@ -41,6 +48,10 @@ namespace MediaBrowser.Model.Dto
public TransportStreamTimestamp? Timestamp { get; set; } public TransportStreamTimestamp? Timestamp { get; set; }
public Dictionary<string, string> RequiredHttpHeaders { 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() public MediaSourceInfo()
{ {
Formats = new List<string>(); Formats = new List<string>();
@ -49,6 +60,7 @@ namespace MediaBrowser.Model.Dto
PlayableStreamFileNames = new List<string>(); PlayableStreamFileNames = new List<string>();
SupportsTranscoding = true; SupportsTranscoding = true;
SupportsDirectStream = true; SupportsDirectStream = true;
SupportsDirectPlay = true;
} }
public int? DefaultAudioStreamIndex { get; set; } public int? DefaultAudioStreamIndex { get; set; }
@ -123,5 +135,40 @@ namespace MediaBrowser.Model.Dto
return null; 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, Default = 0,
Grouping = 1, Grouping = 1,
Cache = 2 Placeholder = 2
} }
} }

@ -3,6 +3,17 @@ namespace MediaBrowser.Model.Dto
{ {
public class NameValuePair public class NameValuePair
{ {
public NameValuePair()
{
}
public NameValuePair(string name, string value)
{
Name = name;
Value = value;
}
/// <summary> /// <summary>
/// Gets or sets the name. /// Gets or sets the name.
/// </summary> /// </summary>

@ -1,4 +1,5 @@
using MediaBrowser.Model.Extensions; using MediaBrowser.Model.Dlna;
using MediaBrowser.Model.Extensions;
using System.Diagnostics; using System.Diagnostics;
namespace MediaBrowser.Model.Entities namespace MediaBrowser.Model.Entities
@ -129,18 +130,45 @@ namespace MediaBrowser.Model.Entities
/// <value>The index.</value> /// <value>The index.</value>
public int Index { get; set; } public int Index { get; set; }
/// <summary>
/// Gets or sets the score.
/// </summary>
/// <value>The score.</value>
public int? Score { get; set; }
/// <summary> /// <summary>
/// Gets or sets a value indicating whether this instance is external. /// Gets or sets a value indicating whether this instance is external.
/// </summary> /// </summary>
/// <value><c>true</c> if this instance is external; otherwise, <c>false</c>.</value> /// <value><c>true</c> if this instance is external; otherwise, <c>false</c>.</value>
public bool IsExternal { get; set; } 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 public bool IsTextSubtitleStream
{ {
get get
{ {
if (Type != MediaStreamType.Subtitle) return false; if (Type != MediaStreamType.Subtitle) return false;
if (string.IsNullOrEmpty(Codec) && !IsExternal)
{
return false;
}
return IsTextFormat(Codec); return IsTextFormat(Codec);
} }
} }
@ -168,6 +196,12 @@ namespace MediaBrowser.Model.Entities
/// <value>The filename.</value> /// <value>The filename.</value>
public string Path { get; set; } public string Path { get; set; }
/// <summary>
/// Gets or sets the external identifier.
/// </summary>
/// <value>The external identifier.</value>
public string ExternalId { get; set; }
/// <summary> /// <summary>
/// Gets or sets the pixel format. /// Gets or sets the pixel format.
/// </summary> /// </summary>

@ -53,6 +53,12 @@ namespace MediaBrowser.Model.LiveTv
/// <remarks>If set to null, all programs will be returned</remarks> /// <remarks>If set to null, all programs will be returned</remarks>
public bool? IsMovie { get; set; } 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> /// <summary>
/// Skips over a given number of items within the results. Use for paging. /// Skips over a given number of items within the results. Use for paging.
/// </summary> /// </summary>

@ -31,5 +31,10 @@
/// </summary> /// </summary>
/// <value><c>null</c> if [is movie] contains no value, <c>true</c> if [is movie]; otherwise, <c>false</c>.</value> /// <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; } 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="Devices\DevicesOptions.cs" />
<Compile Include="Dlna\EncodingContext.cs" /> <Compile Include="Dlna\EncodingContext.cs" />
<Compile Include="Dlna\ILocalPlayer.cs" /> <Compile Include="Dlna\ILocalPlayer.cs" />
<Compile Include="Dlna\StreamInfoSorter.cs" />
<Compile Include="Dlna\NullLocalPlayer.cs" /> <Compile Include="Dlna\NullLocalPlayer.cs" />
<Compile Include="Dlna\PlaybackErrorCode.cs" /> <Compile Include="Dlna\PlaybackErrorCode.cs" />
<Compile Include="Dlna\PlaybackException.cs" /> <Compile Include="Dlna\PlaybackException.cs" />
@ -140,7 +141,10 @@
<Compile Include="Dto\MetadataEditorInfo.cs" /> <Compile Include="Dto\MetadataEditorInfo.cs" />
<Compile Include="Dto\NameIdPair.cs" /> <Compile Include="Dto\NameIdPair.cs" />
<Compile Include="Dto\NameValuePair.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="Dto\MediaSourceType.cs" />
<Compile Include="Configuration\DynamicDayOfWeek.cs" /> <Compile Include="Configuration\DynamicDayOfWeek.cs" />
<Compile Include="Entities\ExtraType.cs" /> <Compile Include="Entities\ExtraType.cs" />

@ -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;
}
}
}
}

@ -0,0 +1,9 @@
using MediaBrowser.Model.Dto;
namespace MediaBrowser.Model.MediaInfo
{
public class LiveStreamResponse
{
public MediaSourceInfo MediaSource { get; set; }
}
}

@ -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 namespace MediaBrowser.Model.MediaInfo
{ {
public class LiveMediaInfoResult public class PlaybackInfoResponse
{ {
/// <summary> /// <summary>
/// Gets or sets the media sources. /// Gets or sets the media sources.
@ -13,10 +13,10 @@ namespace MediaBrowser.Model.MediaInfo
public List<MediaSourceInfo> MediaSources { get; set; } public List<MediaSourceInfo> MediaSources { get; set; }
/// <summary> /// <summary>
/// Gets or sets the stream identifier. /// Gets or sets the play session identifier.
/// </summary> /// </summary>
/// <value>The stream identifier.</value> /// <value>The play session identifier.</value>
public string StreamId { get; set; } public string PlaySessionId { get; set; }
/// <summary> /// <summary>
/// Gets or sets the error code. /// Gets or sets the error code.
@ -24,7 +24,7 @@ namespace MediaBrowser.Model.MediaInfo
/// <value>The error code.</value> /// <value>The error code.</value>
public PlaybackErrorCode? ErrorCode { get; set; } public PlaybackErrorCode? ErrorCode { get; set; }
public LiveMediaInfoResult() public PlaybackInfoResponse()
{ {
MediaSources = new List<MediaSourceInfo>(); MediaSources = new List<MediaSourceInfo>();
} }

@ -1,5 +1,6 @@
using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Extensions; using MediaBrowser.Model.Extensions;
using MediaBrowser.Model.Users;
namespace MediaBrowser.Model.Notifications namespace MediaBrowser.Model.Notifications
{ {
@ -106,7 +107,7 @@ namespace MediaBrowser.Model.Notifications
!ListHelper.ContainsIgnoreCase(opt.DisabledMonitorUsers, userId); !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); NotificationOption opt = GetOptions(type);
@ -117,7 +118,7 @@ namespace MediaBrowser.Model.Notifications
return true; return true;
} }
if (opt.SendToUserMode == SendToUserType.Admins && userConfig.IsAdministrator) if (opt.SendToUserMode == SendToUserType.Admins && userPolicy.IsAdministrator)
{ {
return true; return true;
} }

@ -281,6 +281,13 @@ namespace MediaBrowser.Model.Querying
public int? ImageTypeLimit { get; set; } public int? ImageTypeLimit { get; set; }
public ImageType[] EnableImageTypes { 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> /// <summary>
/// Initializes a new instance of the <see cref="ItemQuery" /> class. /// Initializes a new instance of the <see cref="ItemQuery" /> class.
/// </summary> /// </summary>
@ -299,6 +306,9 @@ namespace MediaBrowser.Model.Querying
VideoTypes = new VideoType[] { }; VideoTypes = new VideoType[] { };
Artists = new string[] { };
Studios = new string[] { };
Genres = new string[] { }; Genres = new string[] { };
StudioIds = new string[] { }; StudioIds = new string[] { };
IncludeItemTypes = new string[] { }; IncludeItemTypes = new string[] { };

@ -43,6 +43,7 @@ namespace MediaBrowser.Model.Querying
/// The premiere date /// The premiere date
/// </summary> /// </summary>
public const string PremiereDate = "PremiereDate"; public const string PremiereDate = "PremiereDate";
public const string StartDate = "StartDate";
/// <summary> /// <summary>
/// The sort name /// The sort name
/// </summary> /// </summary>

@ -78,5 +78,15 @@ namespace MediaBrowser.Model.Session
/// </summary> /// </summary>
/// <value>The play method.</value> /// <value>The play method.</value>
public PlayMethod PlayMethod { get; set; } 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> /// </summary>
/// <value>The position ticks.</value> /// <value>The position ticks.</value>
public long? PositionTicks { get; set; } 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> /// <value>The item identifier.</value>
public string ItemId { get; set; } public string ItemId { get; set; }
/// <summary> /// <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. /// Gets or sets the user ids with access.
/// </summary> /// </summary>
/// <value>The user ids with access.</value> /// <value>The user ids with access.</value>
public List<string> UserIdsWithAccess { get; set; } 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() public LocalItem()
{ {
AdditionalFiles = new List<string>();
UserIdsWithAccess = new List<string>(); UserIdsWithAccess = new List<string>();
} }
} }

@ -6,6 +6,7 @@ namespace MediaBrowser.Model.Sync
{ {
public List<string> LocalItemIds { get; set; } public List<string> LocalItemIds { get; set; }
public List<string> OfflineUserIds { get; set; } public List<string> OfflineUserIds { get; set; }
public List<string> SyncJobItemIds { get; set; }
public string TargetId { get; set; } public string TargetId { get; set; }

@ -32,8 +32,6 @@ namespace MediaBrowser.Model.Users
public bool EnableUserPreferenceAccess { get; set; } public bool EnableUserPreferenceAccess { get; set; }
public AccessSchedule[] AccessSchedules { get; set; } public AccessSchedule[] AccessSchedules { get; set; }
public UnratedItem[] BlockUnratedItems { get; set; } public UnratedItem[] BlockUnratedItems { get; set; }
public string[] BlockedMediaFolders { get; set; }
public string[] BlockedChannels { get; set; }
public bool EnableRemoteControlOfOtherUsers { get; set; } public bool EnableRemoteControlOfOtherUsers { get; set; }
public bool EnableSharedDeviceControl { get; set; } public bool EnableSharedDeviceControl { get; set; }
@ -63,6 +61,7 @@ namespace MediaBrowser.Model.Users
public UserPolicy() public UserPolicy()
{ {
EnableSync = true;
EnableLiveTvManagement = true; EnableLiveTvManagement = true;
EnableMediaPlayback = true; EnableMediaPlayback = true;
EnableLiveTvAccess = true; EnableLiveTvAccess = true;

@ -96,7 +96,7 @@ namespace MediaBrowser.Providers.MediaInfo
var album = item.Parent as MusicAlbum; var album = item.Parent as MusicAlbum;
var filename = item.Album ?? string.Empty; 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 += album == null ? item.Id.ToString("N") + "_primary" + item.DateModified.Ticks : album.Id.ToString("N") + album.DateModified.Ticks + "_primary";
filename = filename.GetMD5() + ".jpg"; filename = filename.GetMD5() + ".jpg";

@ -6,15 +6,14 @@ using MediaBrowser.Controller.Entities.Movies;
using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Subtitles; using MediaBrowser.Controller.Subtitles;
using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Entities; using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Logging; using MediaBrowser.Model.Logging;
using MediaBrowser.Model.Providers;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using MediaBrowser.Model.Providers;
namespace MediaBrowser.Providers.MediaInfo namespace MediaBrowser.Providers.MediaInfo
{ {
@ -23,14 +22,16 @@ namespace MediaBrowser.Providers.MediaInfo
private readonly ILibraryManager _libraryManager; private readonly ILibraryManager _libraryManager;
private readonly IServerConfigurationManager _config; private readonly IServerConfigurationManager _config;
private readonly ISubtitleManager _subtitleManager; private readonly ISubtitleManager _subtitleManager;
private readonly IMediaSourceManager _mediaSourceManager;
private readonly ILogger _logger; 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; _libraryManager = libraryManager;
_config = config; _config = config;
_subtitleManager = subtitleManager; _subtitleManager = subtitleManager;
_logger = logger; _logger = logger;
_mediaSourceManager = mediaSourceManager;
} }
public string Name public string Name
@ -107,7 +108,7 @@ namespace MediaBrowser.Providers.MediaInfo
(options.DownloadMovieSubtitles && (options.DownloadMovieSubtitles &&
video is Movie)) video is Movie))
{ {
var mediaStreams = video.GetMediaSources(false).First().MediaStreams; var mediaStreams = _mediaSourceManager.GetStaticMediaSources(video, false).First().MediaStreams;
var downloadedLanguages = await new SubtitleDownloader(_logger, var downloadedLanguages = await new SubtitleDownloader(_logger,
_subtitleManager) _subtitleManager)

@ -169,29 +169,32 @@ namespace MediaBrowser.Server.Implementations.Channels
foreach (var item in result.Items) foreach (var item in result.Items)
{ {
var channelItem = (IChannelItem)item; var channelItem = item as IChannelMediaItem;
var channelFeatures = _manager.GetChannelFeatures(channelItem.ChannelId); if (channelItem != null)
if (channelFeatures.SupportsContentDownloading)
{ {
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); try
} {
catch (OperationCanceledException) await DownloadChannelItem(channelItem, options, cancellationToken, path);
{ }
break; catch (OperationCanceledException)
} {
catch (ChannelDownloadException) break;
{ }
// Logged at lower levels catch (ChannelDownloadException)
} {
catch (Exception ex) // Logged at lower levels
{ }
_logger.ErrorException("Error downloading channel content for {0}", ex, item.Name); 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; return channelOptions.DownloadSizeLimit;
} }
private async Task DownloadChannelItem(BaseItem item, private async Task DownloadChannelItem(IChannelMediaItem item,
ChannelOptions channelOptions, ChannelOptions channelOptions,
CancellationToken cancellationToken, CancellationToken cancellationToken,
string path) string path)
{ {
var itemId = item.Id.ToString("N"); var itemId = item.Id.ToString("N");
var sources = await _manager.GetChannelItemMediaSources(itemId, false, cancellationToken) var sources = await _manager.GetStaticMediaSources(item, true, cancellationToken)
.ConfigureAwait(false); .ConfigureAwait(false);
var cachedVersions = sources.Where(i => i.Protocol == MediaProtocol.File).ToList(); 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); .ConfigureAwait(false);
await RefreshMediaSourceItem(destination, 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…
Cancel
Save