Merge pull request #1058 from MediaBrowser/dev

3.0.5569.0
This commit is contained in:
Luke 2015-03-31 15:41:12 -04:00
commit be74de0236
192 changed files with 4124 additions and 1538 deletions

View File

@ -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
@ -274,23 +274,29 @@ namespace MediaBrowser.Api
return null; return null;
} }
job.ActiveRequestCount++; OnTranscodeBeginRequest(job);
job.DisposeKillTimer();
return job; return job;
} }
} }
public void OnTranscodeBeginRequest(TranscodingJob job)
{
job.ActiveRequestCount++;
job.DisposeKillTimer();
}
public void OnTranscodeEndRequest(TranscodingJob job) public void OnTranscodeEndRequest(TranscodingJob job)
{ {
job.ActiveRequestCount--; job.ActiveRequestCount--;
if (job.ActiveRequestCount == 0) if (job.ActiveRequestCount == 0)
{ {
if (job.Type == TranscodingJobType.Progressive) // TODO: Lower this hls timeout
{ var timerDuration = job.Type == TranscodingJobType.Progressive ?
const int timerDuration = 1000; 1000 :
7200000;
if (job.KillTimer == null) if (job.KillTimer == null)
{ {
@ -302,7 +308,6 @@ namespace MediaBrowser.Api
} }
} }
} }
}
/// <summary> /// <summary>
/// Called when [transcode kill timer stopped]. /// Called when [transcode kill timer stopped].
@ -319,10 +324,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))
{ {
@ -333,7 +338,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;
@ -534,10 +539,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>

View File

@ -73,6 +73,17 @@ namespace MediaBrowser.Api
return ResultFactory.GetOptimizedResultUsingCache(Request, cacheKey, lastDateModified, cacheDuration, factoryFn); return ResultFactory.GetOptimizedResultUsingCache(Request, cacheKey, lastDateModified, cacheDuration, factoryFn);
} }
/// <summary>
/// Infers the server address from the url
/// </summary>
/// <returns></returns>
protected string GetServerAddress()
{
var index = Request.AbsoluteUri.IndexOf(Request.PathInfo, StringComparison.OrdinalIgnoreCase);
return Request.AbsoluteUri.Substring(0, index);
}
protected void AssertCanUpdateUser(IUserManager userManager, string userId) protected void AssertCanUpdateUser(IUserManager userManager, string userId)
{ {
var auth = AuthorizationContext.GetAuthorizationInfo(Request); var auth = AuthorizationContext.GetAuthorizationInfo(Request);

View File

@ -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; }, false, cancellationTokenSource.Token).ConfigureAwait(false);
state.MediaPath = streamInfo.Path; AttachMediaSourceInfo(state, liveStreamResponse.MediaSource, state.VideoRequest, state.RequestedUrl);
state.InputProtocol = streamInfo.Protocol;
await Task.Delay(1500, cancellationTokenSource.Token).ConfigureAwait(false); if (state.VideoRequest != null)
AttachMediaStreamInfo(state, streamInfo, state.VideoRequest, state.RequestedUrl);
checkCodecs = true;
}
else if (string.Equals(state.ItemType, typeof(LiveTvVideoRecording).Name) ||
string.Equals(state.ItemType, typeof(LiveTvAudioRecording).Name))
{ {
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 (videoRequest != null && checkCodecs)
{
if (state.VideoStream != null && CanStreamCopyVideo(videoRequest, state.VideoStream))
{
state.OutputVideoCodec = "copy";
}
if (state.AudioStream != null && CanStreamCopyAudio(videoRequest, state.AudioStream, state.SupportedAudioCodecs))
{
state.OutputAudioCodec = "copy";
} }
} }
if (state.MediaSource.BufferMs.HasValue)
{
await Task.Delay(state.MediaSource.BufferMs.Value, cancellationTokenSource.Token).ConfigureAwait(false);
} }
} }
@ -1017,7 +974,7 @@ namespace MediaBrowser.Api.Playback
await AcquireResources(state, cancellationTokenSource).ConfigureAwait(false); 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);
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.Id, false, cancellationToken).ConfigureAwait(false); var mediaSources = await MediaSourceManager.GetPlayackMediaSources(request.Id, false, cancellationToken).ConfigureAwait(false);
var mediaSource = string.IsNullOrEmpty(request.MediaSourceId) mediaSource = string.IsNullOrEmpty(request.MediaSourceId)
? mediaSources.First() ? mediaSources.First()
: mediaSources.First(i => string.Equals(i.Id, request.MediaSourceId)); : 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;
} }
else
state.IsoType = mediaSource.IsoType;
state.PlayableStreamFileNames = mediaSource.PlayableStreamFileNames.ToList();
if (mediaSource.Timestamp.HasValue)
{ {
state.InputTimestamp = mediaSource.Timestamp.Value; mediaSource = await MediaSourceManager.GetLiveStream(request.LiveStreamId, cancellationToken).ConfigureAwait(false);
}
}
} }
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);
@ -1800,6 +1680,16 @@ namespace MediaBrowser.Api.Playback
ApplyDeviceProfileSettings(state); ApplyDeviceProfileSettings(state);
if (videoRequest != null) if (videoRequest != null)
{
TryStreamCopy(state, videoRequest);
}
state.OutputFilePath = GetOutputFilePath(state);
return state;
}
private void TryStreamCopy(StreamState state, VideoStreamRequest videoRequest)
{ {
if (state.VideoStream != null && CanStreamCopyVideo(videoRequest, state.VideoStream)) if (state.VideoStream != null && CanStreamCopyVideo(videoRequest, state.VideoStream))
{ {
@ -1812,16 +1702,34 @@ namespace MediaBrowser.Api.Playback
} }
} }
state.OutputFilePath = GetOutputFilePath(state); private void AttachMediaSourceInfo(StreamState state,
return state;
}
private void AttachMediaStreamInfo(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;
} }

View File

@ -54,7 +54,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 +160,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 +447,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 +461,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),

View File

@ -22,7 +22,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)
{ {
} }
@ -212,7 +212,7 @@ namespace MediaBrowser.Api.Playback.Hls
} }
} }
protected override string GetCommandLineArguments(string outputPath, string transcodingJobId, StreamState state, bool isEncoding) protected override string GetCommandLineArguments(string outputPath, StreamState state, bool isEncoding)
{ {
var hlsVideoRequest = state.VideoRequest as GetHlsVideoStream; var hlsVideoRequest = state.VideoRequest as GetHlsVideoStream;
@ -240,7 +240,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),

View File

@ -62,7 +62,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;
} }
@ -114,7 +114,7 @@ namespace MediaBrowser.Api.Playback.Hls
if (File.Exists(segmentPath)) if (File.Exists(segmentPath))
{ {
job = ApiEntryPoint.Instance.GetTranscodingJob(playlistPath, TranscodingJobType); job = ApiEntryPoint.Instance.OnTranscodeBeginRequest(playlistPath, TranscodingJobType);
return await GetSegmentResult(playlistPath, segmentPath, requestedIndex, segmentLength, job, cancellationToken).ConfigureAwait(false); return await GetSegmentResult(playlistPath, segmentPath, requestedIndex, segmentLength, job, cancellationToken).ConfigureAwait(false);
} }
@ -123,7 +123,7 @@ namespace MediaBrowser.Api.Playback.Hls
{ {
if (File.Exists(segmentPath)) if (File.Exists(segmentPath))
{ {
job = ApiEntryPoint.Instance.GetTranscodingJob(playlistPath, TranscodingJobType); job = ApiEntryPoint.Instance.OnTranscodeBeginRequest(playlistPath, TranscodingJobType);
return await GetSegmentResult(playlistPath, segmentPath, requestedIndex, segmentLength, job, cancellationToken).ConfigureAwait(false); return await GetSegmentResult(playlistPath, segmentPath, requestedIndex, segmentLength, job, cancellationToken).ConfigureAwait(false);
} }
else else
@ -135,7 +135,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)
{ {
@ -145,6 +145,7 @@ namespace MediaBrowser.Api.Playback.Hls
request.StartTimeTicks = GetSeekPositionTicks(state, requestedIndex); request.StartTimeTicks = GetSeekPositionTicks(state, requestedIndex);
job = await StartFfMpeg(state, playlistPath, cancellationTokenSource).ConfigureAwait(false); job = await StartFfMpeg(state, playlistPath, cancellationTokenSource).ConfigureAwait(false);
ApiEntryPoint.Instance.OnTranscodeBeginRequest(job);
} }
catch catch
{ {
@ -168,7 +169,7 @@ namespace MediaBrowser.Api.Playback.Hls
} }
Logger.Info("returning {0}", segmentPath); Logger.Info("returning {0}", segmentPath);
job = job ?? ApiEntryPoint.Instance.GetTranscodingJob(playlistPath, TranscodingJobType); job = job ?? ApiEntryPoint.Instance.OnTranscodeBeginRequest(playlistPath, TranscodingJobType);
return await GetSegmentResult(playlistPath, segmentPath, requestedIndex, segmentLength, job, cancellationToken).ConfigureAwait(false); return await GetSegmentResult(playlistPath, segmentPath, requestedIndex, segmentLength, job, cancellationToken).ConfigureAwait(false);
} }
@ -368,8 +369,8 @@ namespace MediaBrowser.Api.Playback.Hls
if (transcodingJob != null) if (transcodingJob != null)
{ {
transcodingJob.DownloadPositionTicks = Math.Max(transcodingJob.DownloadPositionTicks ?? segmentEndingPositionTicks, segmentEndingPositionTicks); transcodingJob.DownloadPositionTicks = Math.Max(transcodingJob.DownloadPositionTicks ?? segmentEndingPositionTicks, segmentEndingPositionTicks);
ApiEntryPoint.Instance.OnTranscodeEndRequest(transcodingJob);
} }
} }
}); });
} }
@ -413,7 +414,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();
@ -683,7 +685,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);
@ -696,9 +698,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} -copyts -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),
@ -710,9 +712,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} -copyts -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),

View File

@ -1,6 +1,10 @@
using MediaBrowser.Controller; using MediaBrowser.Controller;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Net;
using ServiceStack; using ServiceStack;
using System;
using System.IO; using System.IO;
using System.Linq;
namespace MediaBrowser.Api.Playback.Hls namespace MediaBrowser.Api.Playback.Hls
{ {
@ -32,6 +36,8 @@ namespace MediaBrowser.Api.Playback.Hls
[Api(Description = "Gets an Http live streaming segment file. Internal use only.")] [Api(Description = "Gets an Http live streaming segment file. Internal use only.")]
public class GetHlsPlaylist public class GetHlsPlaylist
{ {
// TODO: Deprecate with new iOS app
/// <summary> /// <summary>
/// Gets or sets the id. /// Gets or sets the id.
/// </summary> /// </summary>
@ -48,31 +54,66 @@ 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>
/// Class GetHlsVideoSegment
/// </summary>
[Route("/Videos/{Id}/hls/{PlaylistId}/{SegmentId}.ts", "GET")]
[Api(Description = "Gets an Http live streaming segment file. Internal use only.")]
public class GetHlsVideoSegment : VideoStreamRequest
{
public string PlaylistId { get; set; }
/// <summary>
/// Gets or sets the segment id.
/// </summary>
/// <value>The segment id.</value>
public string SegmentId { get; set; }
} }
public class HlsSegmentService : BaseApiService public class HlsSegmentService : BaseApiService
{ {
private readonly IServerApplicationPaths _appPaths; private readonly IServerApplicationPaths _appPaths;
private readonly IServerConfigurationManager _config;
public HlsSegmentService(IServerApplicationPaths appPaths) public HlsSegmentService(IServerApplicationPaths appPaths, IServerConfigurationManager config)
{ {
_appPaths = appPaths; _appPaths = appPaths;
_config = config;
} }
public object Get(GetHlsPlaylist request) public object Get(GetHlsPlaylist request)
{ {
var file = request.PlaylistId + Path.GetExtension(Request.PathInfo); var file = request.PlaylistId + Path.GetExtension(Request.PathInfo);
file = Path.Combine(_appPaths.TranscodingTempPath, file); file = Path.Combine(_appPaths.TranscodingTempPath, file);
return ResultFactory.GetStaticFileResult(Request, file, FileShare.ReadWrite); return GetFileResult(file, file);
} }
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>
/// Gets the specified request.
/// </summary>
/// <param name="request">The request.</param>
/// <returns>System.Object.</returns>
public object Get(GetHlsVideoSegment request)
{
var file = request.SegmentId + Path.GetExtension(Request.PathInfo);
file = Path.Combine(_config.ApplicationPaths.TranscodingTempPath, file);
var normalizedPlaylistId = request.PlaylistId.Replace("-low", string.Empty);
var playlistPath = Directory.EnumerateFiles(_config.ApplicationPaths.TranscodingTempPath, "*")
.FirstOrDefault(i => string.Equals(Path.GetExtension(i), ".m3u8", StringComparison.OrdinalIgnoreCase) && i.IndexOf(normalizedPlaylistId, StringComparison.OrdinalIgnoreCase) != -1);
return GetFileResult(file, playlistPath);
} }
/// <summary> /// <summary>
@ -82,11 +123,29 @@ namespace MediaBrowser.Api.Playback.Hls
/// <returns>System.Object.</returns> /// <returns>System.Object.</returns>
public object Get(GetHlsAudioSegment request) public object Get(GetHlsAudioSegment request)
{ {
// TODO: Deprecate with new iOS app
var file = request.SegmentId + Path.GetExtension(Request.PathInfo); var file = request.SegmentId + Path.GetExtension(Request.PathInfo);
file = Path.Combine(_appPaths.TranscodingTempPath, file); file = Path.Combine(_appPaths.TranscodingTempPath, file);
return ResultFactory.GetStaticFileResult(Request, file, FileShare.ReadWrite); return ResultFactory.GetStaticFileResult(Request, file, FileShare.ReadWrite);
} }
private object GetFileResult(string path, string playlistPath)
{
var transcodingJob = ApiEntryPoint.Instance.OnTranscodeBeginRequest(playlistPath, TranscodingJobType.Hls);
return ResultFactory.GetStaticFileResult(Request, new StaticFileResultOptions
{
Path = path,
FileShare = FileShare.ReadWrite,
OnComplete = () =>
{
if (transcodingJob != null)
{
ApiEntryPoint.Instance.OnTranscodeEndRequest(transcodingJob);
}
}
});
}
} }
} }

View File

@ -19,6 +19,8 @@ namespace MediaBrowser.Api.Playback.Hls
[Api(Description = "Gets a video stream using HTTP live streaming.")] [Api(Description = "Gets a video stream using HTTP live streaming.")]
public class GetHlsVideoStream : VideoStreamRequest public class GetHlsVideoStream : VideoStreamRequest
{ {
// TODO: Deprecate with new iOS app
[ApiMember(Name = "BaselineStreamAudioBitRate", Description = "Optional. Specify the audio bitrate for the baseline stream.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] [ApiMember(Name = "BaselineStreamAudioBitRate", Description = "Optional. Specify the audio bitrate for the baseline stream.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
public int? BaselineStreamAudioBitRate { get; set; } public int? BaselineStreamAudioBitRate { get; set; }
@ -35,45 +37,15 @@ namespace MediaBrowser.Api.Playback.Hls
{ {
} }
/// <summary>
/// Class GetHlsVideoSegment
/// </summary>
[Route("/Videos/{Id}/hls/{PlaylistId}/{SegmentId}.ts", "GET")]
[Api(Description = "Gets an Http live streaming segment file. Internal use only.")]
public class GetHlsVideoSegment : VideoStreamRequest
{
public string PlaylistId { get; set; }
/// <summary>
/// Gets or sets the segment id.
/// </summary>
/// <value>The segment id.</value>
public string SegmentId { get; set; }
}
/// <summary> /// <summary>
/// Class VideoHlsService /// Class VideoHlsService
/// </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)
{ {
} }
/// <summary>
/// Gets the specified request.
/// </summary>
/// <param name="request">The request.</param>
/// <returns>System.Object.</returns>
public object Get(GetHlsVideoSegment request)
{
var file = request.SegmentId + Path.GetExtension(Request.PathInfo);
file = Path.Combine(ServerConfigurationManager.ApplicationPaths.TranscodingTempPath, file);
return ResultFactory.GetStaticFileResult(Request, file);
}
/// <summary> /// <summary>
/// Gets the specified request. /// Gets the specified request.
/// </summary> /// </summary>

View File

@ -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,31 +36,121 @@ 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)
{
var authInfo = AuthorizationContext.GetAuthorizationInfo(Request);
var result = await _mediaSourceManager.OpenLiveStream(request, false, CancellationToken.None).ConfigureAwait(false);
var profile = request.DeviceProfile;
if (profile == null)
{
var caps = _deviceManager.GetCapabilities(authInfo.DeviceId);
if (caps != null)
{
profile = caps.DeviceProfile;
}
}
if (profile != null)
{
var item = _libraryManager.GetItemById(request.ItemId);
SetDeviceSpecificData(item, result.MediaSource, profile, authInfo, request.MaxStreamingBitrate,
request.StartTimeTicks ?? 0, result.MediaSource.Id, request.AudioStreamIndex,
request.SubtitleStreamIndex);
}
else
{
if (!string.IsNullOrWhiteSpace(result.MediaSource.TranscodingUrl))
{
result.MediaSource.TranscodingUrl += "&LiveStreamId=" + result.MediaSource.LiveStreamId;
}
}
return ToOptimizedResult(result);
}
public void Post(CloseMediaSource request)
{
var task = _mediaSourceManager.CloseLiveStream(request.LiveStreamId, CancellationToken.None);
Task.WaitAll(task);
}
public async Task<object> Post(GetPostedPlaybackInfo request)
{
var info = await GetPlaybackInfo(request.Id, request.UserId, request.MediaSourceId, request.LiveStreamId).ConfigureAwait(false);
var authInfo = AuthorizationContext.GetAuthorizationInfo(Request);
var profile = request.DeviceProfile;
if (profile == null)
{
var caps = _deviceManager.GetCapabilities(authInfo.DeviceId);
if (caps != null)
{
profile = caps.DeviceProfile;
}
}
if (profile != null)
{
var mediaSourceId = request.MediaSourceId;
SetDeviceSpecificData(request.Id, info, profile, authInfo, request.MaxStreamingBitrate, request.StartTimeTicks ?? 0, mediaSourceId, request.AudioStreamIndex, request.SubtitleStreamIndex);
}
return ToOptimizedResult(info);
}
private async Task<PlaybackInfoResponse> GetPlaybackInfo(string id, string userId, string mediaSourceId = null, string liveStreamId = null)
{
var result = new PlaybackInfoResponse();
if (string.IsNullOrWhiteSpace(liveStreamId))
{ {
IEnumerable<MediaSourceInfo> mediaSources; IEnumerable<MediaSourceInfo> mediaSources;
var result = new LiveMediaInfoResult();
try try
{ {
mediaSources = await _mediaSourceManager.GetPlayackMediaSources(id, userId, true, CancellationToken.None).ConfigureAwait(false); mediaSources = await _mediaSourceManager.GetPlayackMediaSources(id, userId, true, CancellationToken.None).ConfigureAwait(false);
@ -68,9 +162,211 @@ namespace MediaBrowser.Api.Playback
} }
result.MediaSources = mediaSources.ToList(); result.MediaSources = mediaSources.ToList();
result.StreamId = Guid.NewGuid().ToString("N");
return ToOptimizedResult(result); 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);
}
SortMediaSources(result);
}
private void SetDeviceSpecificData(BaseItem item,
MediaSourceInfo mediaSource,
DeviceProfile profile,
AuthorizationInfo auth,
int? maxBitrate,
long startTimeTicks,
string mediaSourceId,
int? audioStreamIndex,
int? subtitleStreamIndex)
{
var streamBuilder = new StreamBuilder();
var baseUrl = GetServerAddress();
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, baseUrl, 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, baseUrl, 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.PlayMethod == PlayMethod.Transcode)
{
streamInfo.StartPositionTicks = startTimeTicks;
mediaSource.TranscodingUrl = streamInfo.ToUrl(baseUrl, auth.Token);
mediaSource.TranscodingContainer = streamInfo.Container;
mediaSource.TranscodingSubProtocol = streamInfo.SubProtocol;
}
if (streamInfo != null)
{
SetDeviceSpecificSubtitleInfo(streamInfo, mediaSource, baseUrl, auth.Token);
}
}
}
private void SetDeviceSpecificSubtitleInfo(StreamInfo info, MediaSourceInfo mediaSource, string baseUrl, string accessToken)
{
var profiles = info.GetSubtitleProfiles(false, baseUrl, 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;
}
}
}
}
}
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();
} }
} }
} }

View File

@ -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()),

View File

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

View File

@ -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),

View File

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

View File

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

View File

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

View File

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

View File

@ -55,22 +55,30 @@ namespace MediaBrowser.Api.UserLibrary
/// <returns>Task{ItemsResult}.</returns> /// <returns>Task{ItemsResult}.</returns>
protected ItemsResult GetResult(GetItemsByName request) protected ItemsResult GetResult(GetItemsByName request)
{ {
var dtoOptions = GetDtoOptions(request);
User user = null; User user = null;
BaseItem parentItem; BaseItem parentItem;
List<BaseItem> libraryItems; List<BaseItem> libraryItems = null;
if (request.UserId.HasValue) if (request.UserId.HasValue)
{ {
user = UserManager.GetUserById(request.UserId.Value); user = UserManager.GetUserById(request.UserId.Value);
parentItem = string.IsNullOrEmpty(request.ParentId) ? user.RootFolder : LibraryManager.GetItemById(request.ParentId); parentItem = string.IsNullOrEmpty(request.ParentId) ? user.RootFolder : LibraryManager.GetItemById(request.ParentId);
libraryItems = user.RootFolder.GetRecursiveChildren(user).ToList();
if (RequiresLibraryItems(request, dtoOptions))
{
libraryItems = user.RootFolder.GetRecursiveChildren(user).ToList();
}
} }
else else
{ {
parentItem = string.IsNullOrEmpty(request.ParentId) ? LibraryManager.RootFolder : LibraryManager.GetItemById(request.ParentId); parentItem = string.IsNullOrEmpty(request.ParentId) ? LibraryManager.RootFolder : LibraryManager.GetItemById(request.ParentId);
if (RequiresLibraryItems(request, dtoOptions))
{
libraryItems = LibraryManager.RootFolder.GetRecursiveChildren().ToList(); libraryItems = LibraryManager.RootFolder.GetRecursiveChildren().ToList();
} }
}
IEnumerable<BaseItem> items; IEnumerable<BaseItem> items;
@ -133,17 +141,46 @@ namespace MediaBrowser.Api.UserLibrary
} }
var tuples = ibnItems.Select(i => new Tuple<TItemType, List<BaseItem>>(i, i.GetTaggedItems(libraryItems).ToList())); IEnumerable<Tuple<TItemType, List<BaseItem>>> tuples;
if (dtoOptions.Fields.Contains(ItemFields.ItemCounts) || true)
{
tuples = ibnItems.Select(i => new Tuple<TItemType, List<BaseItem>>(i, i.GetTaggedItems(libraryItems).ToList()));
}
else
{
tuples = ibnItems.Select(i => new Tuple<TItemType, List<BaseItem>>(i, new List<BaseItem>()));
}
var dtoOptions = GetDtoOptions(request); var dtos = tuples.Select(i => DtoService.GetItemByNameDto(i.Item1, dtoOptions, i.Item2, user));
var dtos = tuples.Select(i => GetDto(i.Item1, user, dtoOptions, i.Item2));
result.Items = dtos.Where(i => i != null).ToArray(); result.Items = dtos.Where(i => i != null).ToArray();
return result; return result;
} }
private bool RequiresLibraryItems(GetItemsByName request, DtoOptions options)
{
var filters = request.GetFilters().ToList();
if (filters.Contains(ItemFilter.IsPlayed))
{
return true;
}
if (filters.Contains(ItemFilter.IsUnplayed))
{
return true;
}
if (request.IsPlayed.HasValue)
{
return true;
}
return true;
return options.Fields.Contains(ItemFields.ItemCounts);
}
private IEnumerable<TItemType> FilterByLibraryItems(GetItemsByName request, IEnumerable<TItemType> items, User user, IEnumerable<BaseItem> libraryItems) private IEnumerable<TItemType> FilterByLibraryItems(GetItemsByName request, IEnumerable<TItemType> items, User user, IEnumerable<BaseItem> libraryItems)
{ {
var filters = request.GetFilters().ToList(); var filters = request.GetFilters().ToList();
@ -340,21 +377,6 @@ namespace MediaBrowser.Api.UserLibrary
/// <param name="items">The items.</param> /// <param name="items">The items.</param>
/// <returns>IEnumerable{Task{`0}}.</returns> /// <returns>IEnumerable{Task{`0}}.</returns>
protected abstract IEnumerable<TItemType> GetAllItems(GetItemsByName request, IEnumerable<BaseItem> items); protected abstract IEnumerable<TItemType> GetAllItems(GetItemsByName request, IEnumerable<BaseItem> items);
/// <summary>
/// Gets the dto.
/// </summary>
/// <param name="item">The item.</param>
/// <param name="user">The user.</param>
/// <param name="options">The options.</param>
/// <param name="libraryItems">The library items.</param>
/// <returns>Task{DtoBaseItem}.</returns>
private BaseItemDto GetDto(TItemType item, User user, DtoOptions options, List<BaseItem> libraryItems)
{
var dto = DtoService.GetItemByNameDto(item, options, libraryItems, user);
return dto;
}
} }
/// <summary> /// <summary>

View File

@ -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,

View File

@ -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 (httpWebRequest != null)
{
if (options.EnableKeepAlive) if (options.EnableKeepAlive)
{ {
request.KeepAlive = true; httpWebRequest.KeepAlive = true;
}
} }
request.Method = method; request.Method = method;
request.Timeout = options.TimeoutMs; request.Timeout = options.TimeoutMs;
if (httpWebRequest != null)
{
if (!string.IsNullOrEmpty(options.Host)) if (!string.IsNullOrEmpty(options.Host))
{ {
request.Host = 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;

View File

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

View File

@ -75,9 +75,7 @@ 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)
var sources = ChannelManager.GetChannelItemMediaSources(Id.ToString("N"), false, CancellationToken.None)
.Result.ToList(); .Result.ToList();
if (sources.Count > 0) if (sources.Count > 0)
@ -85,7 +83,15 @@ namespace MediaBrowser.Controller.Channels
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;
} }

View File

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

View File

@ -90,9 +90,7 @@ 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)
var sources = ChannelManager.GetChannelItemMediaSources(Id.ToString("N"), false, CancellationToken.None)
.Result.ToList(); .Result.ToList();
if (sources.Count > 0) if (sources.Count > 0)
@ -100,7 +98,15 @@ namespace MediaBrowser.Controller.Channels
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;
} }

View File

@ -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.

View File

@ -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)

View File

@ -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;
@ -64,6 +65,14 @@ namespace MediaBrowser.Controller.Library
/// <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);
/// <summary>
/// Gets the static media sources.
/// </summary>
/// <param name="item">The item.</param>
/// <param name="enablePathSubstitution">if set to <c>true</c> [enable path substitution].</param>
/// <returns>IEnumerable&lt;MediaSourceInfo&gt;.</returns>
IEnumerable<MediaSourceInfo> GetStaticMediaSources(IHasMediaSources item, bool enablePathSubstitution);
/// <summary> /// <summary>
/// Gets the static media source. /// Gets the static media source.
/// </summary> /// </summary>
@ -72,5 +81,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);
} }
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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" />

View File

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

View File

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

View File

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

View File

@ -0,0 +1,18 @@
using MediaBrowser.Model.Sync;
using System.Threading;
using System.Threading.Tasks;
namespace MediaBrowser.Controller.Sync
{
public interface IHasDynamicAccess
{
/// <summary>
/// Gets the synced file information.
/// </summary>
/// <param name="remotePath">The remote path.</param>
/// <param name="target">The target.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task&lt;SyncedFileInfo&gt;.</returns>
Task<SyncedFileInfo> GetSyncedFileInfo(string remotePath, SyncTarget target, CancellationToken cancellationToken);
}
}

View File

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

View File

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

View File

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

View File

@ -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 = _user == null ? _mediaSourceManager.GetStaticMediaSources(video, true).ToList() : _mediaSourceManager.GetStaticMediaSources(video, true, _user).ToList();
streamInfo = new StreamBuilder().BuildVideoItem(new VideoOptions streamInfo = new StreamBuilder().BuildVideoItem(new VideoOptions
{ {
@ -158,18 +158,23 @@ 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))
{
if (subtitle.DeliveryMethod == SubtitleDeliveryMethod.External)
{ {
AddSubtitleElement(container, subtitle); AddSubtitleElement(container, subtitle);
} }
} }
}
private void AddSubtitleElement(XmlElement container, SubtitleStreamInfo info) private void AddSubtitleElement(XmlElement container, SubtitleStreamInfo info)
{ {
@ -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 = _user == null ? _mediaSourceManager.GetStaticMediaSources(audio, true).ToList() : _mediaSourceManager.GetStaticMediaSources(audio, true, _user).ToList();
streamInfo = new StreamBuilder().BuildAudioItem(new AudioOptions streamInfo = new StreamBuilder().BuildAudioItem(new AudioOptions
{ {

View File

@ -470,13 +470,13 @@ namespace MediaBrowser.Dlna.PlayTo
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() ? (user == null ? _mediaSourceManager.GetStaticMediaSources(hasMediaSources, true) : _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();
} }

View File

@ -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)

View File

@ -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)
{ {
} }

View File

@ -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; }, false, cancellationToken).ConfigureAwait(false);
state.MediaPath = streamInfo.Path; AttachMediaStreamInfo(state, liveStreamResponse.MediaSource, state.Options);
state.InputProtocol = streamInfo.Protocol;
await Task.Delay(1500, cancellationToken).ConfigureAwait(false); if (state.IsVideoRequest)
AttachMediaStreamInfo(state, streamInfo, state.Options);
checkCodecs = true;
}
else if (string.Equals(state.ItemType, typeof(LiveTvVideoRecording).Name) ||
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.VideoStream != null && EncodingJobFactory.CanStreamCopyVideo(state.Options, state.VideoStream))
{
state.OutputVideoCodec = "copy";
}
if (state.AudioStream != null && EncodingJobFactory.CanStreamCopyAudio(state.Options, state.AudioStream, state.SupportedAudioCodecs))
{
state.OutputAudioCodec = "copy";
} }
} }
if (state.MediaSource.BufferMs.HasValue)
{
await Task.Delay(state.MediaSource.BufferMs.Value, cancellationToken).ConfigureAwait(false);
} }
} }
@ -531,22 +499,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
MediaSourceInfo mediaSource, 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))
{ {

View File

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

View File

@ -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;
} }
@ -44,7 +41,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
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 mediaSources = await _mediaSourceManager.GetPlayackMediaSources(request.ItemId, false, cancellationToken).ConfigureAwait(false);
var mediaSource = string.IsNullOrEmpty(request.MediaSourceId) var mediaSource = string.IsNullOrEmpty(request.MediaSourceId)
? mediaSources.First() ? mediaSources.First()
: mediaSources.First(i => string.Equals(i.Id, request.MediaSourceId)); : mediaSources.First(i => string.Equals(i.Id, request.MediaSourceId));
mediaStreams = mediaSource.MediaStreams; AttachMediaStreamInfo(state, mediaSource, options);
state.MediaPath = mediaSource.Path;
state.InputProtocol = mediaSource.Protocol;
state.InputContainer = mediaSource.Container;
state.InputFileSize = mediaSource.Size;
state.InputBitrate = mediaSource.Bitrate;
state.ReadInputAtNativeFramerate = mediaSource.ReadAtNativeFramerate;
state.RunTimeTicks = mediaSource.RunTimeTicks;
state.RemoteHttpHeaders = mediaSource.RequiredHttpHeaders;
var video = item as Video;
if (video != null)
{
state.IsInputVideo = true;
if (mediaSource.VideoType.HasValue)
{
state.VideoType = mediaSource.VideoType.Value;
}
state.IsoType = mediaSource.IsoType;
state.PlayableStreamFileNames = mediaSource.PlayableStreamFileNames.ToList();
if (mediaSource.Timestamp.HasValue)
{
state.InputTimestamp = mediaSource.Timestamp.Value;
}
}
state.RunTimeTicks = mediaSource.RunTimeTicks;
}
AttachMediaStreamInfo(state, mediaStreams, request);
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);
{
if (state.VideoStream != null && CanStreamCopyVideo(request, state.VideoStream))
{
state.OutputVideoCodec = "copy";
}
if (state.AudioStream != null && CanStreamCopyAudio(request, state.AudioStream, state.SupportedAudioCodecs))
{
state.OutputAudioCodec = "copy";
}
}
return state; return state;
} }
internal static void AttachMediaStreamInfo(EncodingJob state, internal static void TryStreamCopy(EncodingJob state,
List<MediaStream> mediaStreams,
EncodingJobOptions videoRequest) EncodingJobOptions videoRequest)
{ {
if (state.IsVideoRequest)
{
if (state.VideoStream != null && CanStreamCopyVideo(videoRequest, state.VideoStream))
{
state.OutputVideoCodec = "copy";
}
if (state.AudioStream != null && CanStreamCopyAudio(videoRequest, state.AudioStream, state.SupportedAudioCodecs))
{
state.OutputAudioCodec = "copy";
}
}
}
internal static void AttachMediaStreamInfo(EncodingJob state,
MediaSourceInfo mediaSource,
EncodingJobOptions videoRequest)
{
state.MediaPath = mediaSource.Path;
state.InputProtocol = mediaSource.Protocol;
state.InputContainer = mediaSource.Container;
state.InputFileSize = mediaSource.Size;
state.InputBitrate = mediaSource.Bitrate;
state.ReadInputAtNativeFramerate = mediaSource.ReadAtNativeFramerate;
state.RunTimeTicks = mediaSource.RunTimeTicks;
state.RemoteHttpHeaders = mediaSource.RequiredHttpHeaders;
if (mediaSource.VideoType.HasValue)
{
state.VideoType = mediaSource.VideoType.Value;
}
state.IsoType = mediaSource.IsoType;
state.PlayableStreamFileNames = mediaSource.PlayableStreamFileNames.ToList();
if (mediaSource.Timestamp.HasValue)
{
state.InputTimestamp = mediaSource.Timestamp.Value;
}
state.InputProtocol = mediaSource.Protocol;
state.MediaPath = mediaSource.Path;
state.RunTimeTicks = mediaSource.RunTimeTicks;
state.RemoteHttpHeaders = mediaSource.RequiredHttpHeaders;
state.InputBitrate = mediaSource.Bitrate;
state.InputFileSize = mediaSource.Size;
state.ReadInputAtNativeFramerate = mediaSource.ReadAtNativeFramerate;
if (state.ReadInputAtNativeFramerate ||
mediaSource.Protocol == MediaProtocol.File && string.Equals(mediaSource.Container, "wtv", StringComparison.OrdinalIgnoreCase))
{
state.OutputAudioSync = "1000";
state.InputVideoSync = "-1";
state.InputAudioSync = "1";
}
var mediaStreams = mediaSource.MediaStreams;
if (videoRequest != null) if (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)
{ {

View File

@ -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())

View File

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

View File

@ -43,10 +43,11 @@ namespace MediaBrowser.MediaEncoding.Subtitles
subEvent.StartPositionTicks = GetTicks(sections[headers["Start"]]); subEvent.StartPositionTicks = GetTicks(sections[headers["Start"]]);
subEvent.EndPositionTicks = GetTicks(sections[headers["End"]]); subEvent.EndPositionTicks = GetTicks(sections[headers["End"]]);
//RemoteNativeFormatting(subEvent);
subEvent.Text = string.Join(",", sections.Skip(headers["Text"])); subEvent.Text = string.Join(",", sections.Skip(headers["Text"]));
subEvent.Text = subEvent.Text.Replace(@"\N", ParserValues.NewLine, StringComparison.OrdinalIgnoreCase); RemoteNativeFormatting(subEvent);
subEvent.Text = subEvent.Text.Replace("\\n", ParserValues.NewLine, StringComparison.OrdinalIgnoreCase);
subEvent.Text = Regex.Replace(subEvent.Text, @"\{(\\[\w]+\(?([\w\d]+,?)+\)?)+\}", string.Empty, RegexOptions.IgnoreCase); subEvent.Text = Regex.Replace(subEvent.Text, @"\{(\\[\w]+\(?([\w\d]+,?)+\)?)+\}", string.Empty, RegexOptions.IgnoreCase);
trackInfo.TrackEvents.Add(subEvent); trackInfo.TrackEvents.Add(subEvent);

View File

@ -148,8 +148,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
public static string GetFormattedText(string text) public static string GetFormattedText(string text)
{ {
text = text.Replace("\\n", ParserValues.NewLine, StringComparison.OrdinalIgnoreCase) text = text.Replace("\\n", ParserValues.NewLine, StringComparison.OrdinalIgnoreCase);
.Replace("\\n", ParserValues.NewLine, StringComparison.OrdinalIgnoreCase);
bool italic = false; bool italic = false;

View File

@ -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,7 +698,9 @@ namespace MediaBrowser.MediaEncoding.Subtitles
} }
} }
private string GetSubtitleCachePath(string mediaPath, int subtitleStreamIndex, string outputSubtitleExtension) private string GetSubtitleCachePath(string mediaPath, MediaProtocol protocol, int subtitleStreamIndex, string outputSubtitleExtension)
{
if (protocol == MediaProtocol.File)
{ {
var ticksParam = string.Empty; var ticksParam = string.Empty;
@ -700,20 +712,27 @@ namespace MediaBrowser.MediaEncoding.Subtitles
return Path.Combine(SubtitleCachePath, prefix, filename); return Path.Combine(SubtitleCachePath, prefix, filename);
} }
else
{
var filename = (mediaPath + "_" + subtitleStreamIndex.ToString(CultureInfo.InvariantCulture)).GetMD5() + outputSubtitleExtension;
/// <summary> var prefix = filename.Substring(0, 1);
/// Gets the subtitle language encoding param.
/// </summary> return Path.Combine(SubtitleCachePath, prefix, filename);
/// <param name="path">The path.</param> }
/// <returns>System.String.</returns> }
public string GetSubtitleFileCharacterSet(string path)
public async Task<string> GetSubtitleFileCharacterSet(string path, MediaProtocol protocol, CancellationToken cancellationToken)
{
if (protocol == MediaProtocol.File)
{ {
if (GetFileEncoding(path).Equals(Encoding.UTF8)) if (GetFileEncoding(path).Equals(Encoding.UTF8))
{ {
return string.Empty; 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");
}
} }
} }

View File

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

View File

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

View File

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

View File

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

View File

@ -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)
{ {

View File

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

View File

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

View File

@ -54,6 +54,7 @@ namespace MediaBrowser.Model.Configuration
public string[] LatestItemsExcludes { get; set; } public string[] LatestItemsExcludes { get; set; }
public bool HasMigratedToPolicy { 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.

View File

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

View File

@ -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>();

View File

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

View File

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

View File

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

View File

@ -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; }
@ -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 PlaybackInfoResponse PlaybackInfo { get; set; }
public string MediaSourceId public string MediaSourceId
{ {
@ -81,18 +81,14 @@ 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;
} }
} }
public string ToUrl(string baseUrl, string accessToken) public string ToUrl(string baseUrl, string accessToken)
{
return ToDlnaUrl(baseUrl, accessToken);
}
public string ToDlnaUrl(string baseUrl, string accessToken)
{ {
if (PlayMethod == PlayMethod.DirectPlay) if (PlayMethod == PlayMethod.DirectPlay)
{ {
@ -104,7 +100,56 @@ namespace MediaBrowser.Model.Dlna
throw new ArgumentNullException(baseUrl); throw new ArgumentNullException(baseUrl);
} }
string dlnaCommand = BuildDlnaParam(this); List<string> list = new List<string>();
foreach (NameValuePair pair in BuildParams(this, accessToken))
{
if (string.IsNullOrEmpty(pair.Value))
{
continue;
}
// Try to keep the url clean by omitting defaults
if (StringHelper.EqualsIgnoreCase(pair.Name, "StartTimeTicks") &&
StringHelper.EqualsIgnoreCase(pair.Value, "0"))
{
continue;
}
if (StringHelper.EqualsIgnoreCase(pair.Name, "SubtitleStreamIndex") &&
StringHelper.EqualsIgnoreCase(pair.Value, "-1"))
{
continue;
}
if (StringHelper.EqualsIgnoreCase(pair.Name, "Static") &&
StringHelper.EqualsIgnoreCase(pair.Value, "false"))
{
continue;
}
list.Add(string.Format("{0}={1}", pair.Name, pair.Value));
}
string queryString = string.Join("&", list.ToArray());
return GetUrl(baseUrl, queryString);
}
public string ToDlnaUrl(string baseUrl, string accessToken)
{
if (PlayMethod == PlayMethod.DirectPlay)
{
return MediaSource.Path;
}
string dlnaCommand = BuildDlnaParam(this, accessToken);
return GetUrl(baseUrl, dlnaCommand);
}
private string GetUrl(string baseUrl, string queryString)
{
if (string.IsNullOrEmpty(baseUrl))
{
throw new ArgumentNullException(baseUrl);
}
string extension = string.IsNullOrEmpty(Container) ? string.Empty : "." + Container; string extension = string.IsNullOrEmpty(Container) ? string.Empty : "." + Container;
@ -112,102 +157,94 @@ namespace MediaBrowser.Model.Dlna
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>();
foreach (NameValuePair pair in BuildParams(item, accessToken))
{ {
item.DeviceProfileId ?? string.Empty, list.Add(pair.Value);
item.DeviceId ?? string.Empty, }
item.MediaSourceId ?? string.Empty,
(item.IsDirectStream).ToString().ToLower(),
item.VideoCodec ?? string.Empty,
item.AudioCodec ?? string.Empty,
item.AudioStreamIndex.HasValue ? StringHelper.ToStringCultureInvariant(item.AudioStreamIndex.Value) : string.Empty,
item.SubtitleStreamIndex.HasValue && item.SubtitleDeliveryMethod != SubtitleDeliveryMethod.External ? StringHelper.ToStringCultureInvariant(item.SubtitleStreamIndex.Value) : string.Empty,
item.VideoBitrate.HasValue ? StringHelper.ToStringCultureInvariant(item.VideoBitrate.Value) : string.Empty,
item.AudioBitrate.HasValue ? StringHelper.ToStringCultureInvariant(item.AudioBitrate.Value) : string.Empty,
item.MaxAudioChannels.HasValue ? StringHelper.ToStringCultureInvariant(item.MaxAudioChannels.Value) : string.Empty,
item.MaxFramerate.HasValue ? StringHelper.ToStringCultureInvariant(item.MaxFramerate.Value) : string.Empty,
item.MaxWidth.HasValue ? StringHelper.ToStringCultureInvariant(item.MaxWidth.Value) : string.Empty,
item.MaxHeight.HasValue ? StringHelper.ToStringCultureInvariant(item.MaxHeight.Value) : string.Empty,
StringHelper.ToStringCultureInvariant(item.StartPositionTicks),
item.VideoLevel.HasValue ? StringHelper.ToStringCultureInvariant(item.VideoLevel.Value) : string.Empty
};
list.Add(item.IsDirectStream ? string.Empty : DateTime.UtcNow.Ticks.ToString(CultureInfo.InvariantCulture));
list.Add(item.MaxRefFrames.HasValue ? StringHelper.ToStringCultureInvariant(item.MaxRefFrames.Value) : string.Empty);
list.Add(item.MaxVideoBitDepth.HasValue ? StringHelper.ToStringCultureInvariant(item.MaxVideoBitDepth.Value) : string.Empty);
list.Add(item.VideoProfile ?? string.Empty);
list.Add(item.Cabac.HasValue ? item.Cabac.Value.ToString() : string.Empty);
string streamId = item.PlaybackInfo == null ? null : item.PlaybackInfo.StreamId;
list.Add(streamId ?? string.Empty);
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));
list.Add(new NameValuePair("StartTimeTicks", StringHelper.ToStringCultureInvariant(item.StartPositionTicks)));
list.Add(new NameValuePair("Level", item.VideoLevel.HasValue ? StringHelper.ToStringCultureInvariant(item.VideoLevel.Value) : string.Empty));
if (info != null) 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(info); 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));
}
}
if (!includeSelectedTrackOnly) string playSessionId = item.PlaybackInfo == null ? null : item.PlaybackInfo.PlaySessionId;
{ list.Add(new NameValuePair("PlaySessionId", playSessionId ?? string.Empty));
foreach (MediaStream stream in MediaSource.MediaStreams) list.Add(new NameValuePair("api_key", accessToken ?? string.Empty));
{
if (stream.Type == MediaStreamType.Subtitle && (!SubtitleStreamIndex.HasValue || stream.Index != SubtitleStreamIndex.Value))
{
SubtitleStreamInfo info = GetSubtitleStreamInfo(stream);
if (info != null) string liveStreamId = item.MediaSource == null ? null : item.MediaSource.LiveStreamId;
{ list.Add(new NameValuePair("LiveStreamId", liveStreamId ?? string.Empty));
list.Add(info);
}
}
}
}
return list; return list;
} }
public List<SubtitleStreamInfo> GetExternalSubtitles(string baseUrl, string accessToken, bool includeSelectedTrackOnly) public List<SubtitleStreamInfo> GetExternalSubtitles(bool includeSelectedTrackOnly, bool enableAllProfiles, string baseUrl, string accessToken)
{ {
if (string.IsNullOrEmpty(baseUrl)) 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 +255,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,12 +266,7 @@ 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);
}
} }
} }
} }
@ -247,11 +274,41 @@ namespace MediaBrowser.Model.Dlna
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)
{
foreach (SubtitleProfile profile in DeviceProfile.SubtitleProfiles)
{
SubtitleStreamInfo info = GetSubtitleStreamInfo(stream, baseUrl, accessToken, startPositionTicks, new[] { profile });
if (info != null) list.Add(info);
}
}
else
{
SubtitleStreamInfo info = GetSubtitleStreamInfo(stream, baseUrl, accessToken, startPositionTicks, DeviceProfile.SubtitleProfiles);
list.Add(info);
}
}
private SubtitleStreamInfo GetSubtitleStreamInfo(MediaStream stream, string baseUrl, string accessToken, long startPositionTicks, SubtitleProfile[] subtitleProfiles)
{
SubtitleProfile subtitleProfile = StreamBuilder.GetSubtitleProfile(stream, subtitleProfiles, Context);
SubtitleStreamInfo info = new SubtitleStreamInfo
{
IsForced = stream.IsForced,
Language = stream.Language,
Name = stream.Language ?? "Unknown",
Format = subtitleProfile.Format,
Index = stream.Index,
DeliveryMethod = subtitleProfile.Method
};
if (info.DeliveryMethod == SubtitleDeliveryMethod.External)
{
if (MediaSource.Protocol == MediaProtocol.File || !StringHelper.EqualsIgnoreCase(stream.Codec, subtitleProfile.Format))
{ {
info.Url = string.Format("{0}/Videos/{1}/{2}/Subtitles/{3}/{4}/Stream.{5}", info.Url = string.Format("{0}/Videos/{1}/{2}/Subtitles/{3}/{4}/Stream.{5}",
baseUrl, baseUrl,
@ -259,31 +316,17 @@ namespace MediaBrowser.Model.Dlna
MediaSourceId, MediaSourceId,
StringHelper.ToStringCultureInvariant(stream.Index), StringHelper.ToStringCultureInvariant(stream.Index),
StringHelper.ToStringCultureInvariant(startPositionTicks), StringHelper.ToStringCultureInvariant(startPositionTicks),
SubtitleFormat); subtitleProfile.Format);
}
else
{
info.Url = stream.Path;
}
} }
return info; return info;
} }
private SubtitleStreamInfo GetSubtitleStreamInfo(MediaStream stream)
{
SubtitleProfile subtitleProfile = StreamBuilder.GetSubtitleProfile(stream, DeviceProfile.SubtitleProfiles, Context);
if (subtitleProfile.Method != SubtitleDeliveryMethod.External)
{
return null;
}
return new SubtitleStreamInfo
{
IsForced = stream.IsForced,
Language = stream.Language,
Name = stream.Language ?? "Unknown",
Format = SubtitleFormat,
Index = stream.Index
};
}
/// <summary> /// <summary>
/// Returns the audio stream that will be used /// Returns the audio stream that will be used
/// </summary> /// </summary>
@ -613,6 +656,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);

View File

@ -0,0 +1,47 @@
using MediaBrowser.Model.MediaInfo;
using MediaBrowser.Model.Session;
using System.Collections.Generic;
using System.Linq;
namespace MediaBrowser.Model.Dlna
{
public class StreamInfoSorter
{
public static List<StreamInfo> SortMediaSources(List<StreamInfo> streams)
{
return streams.OrderBy(i =>
{
// Nothing beats direct playing a file
if (i.PlayMethod == PlayMethod.DirectPlay && i.MediaSource.Protocol == MediaProtocol.File)
{
return 0;
}
return 1;
}).ThenBy(i =>
{
switch (i.PlayMethod)
{
// Let's assume direct streaming a file is just as desirable as direct playing a remote url
case PlayMethod.DirectStream:
case PlayMethod.DirectPlay:
return 0;
default:
return 1;
}
}).ThenBy(i =>
{
switch (i.MediaSource.Protocol)
{
case MediaProtocol.File:
return 0;
default:
return 1;
}
}).ToList();
}
}
}

View File

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

View File

@ -8,5 +8,6 @@ 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; }
} }
} }

View File

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

View File

@ -4,6 +4,6 @@ namespace MediaBrowser.Model.Dto
{ {
Default = 0, Default = 0,
Grouping = 1, Grouping = 1,
Cache = 2 Placeholder = 2
} }
} }

View File

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

View File

@ -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,40 @@ 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; }
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 +191,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>

View File

@ -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" />

View File

@ -0,0 +1,35 @@
using MediaBrowser.Model.Dlna;
namespace MediaBrowser.Model.MediaInfo
{
public class LiveStreamRequest
{
public string OpenToken { 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 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;
}
}
}
}

View File

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

View File

@ -0,0 +1,25 @@
using MediaBrowser.Model.Dlna;
namespace MediaBrowser.Model.MediaInfo
{
public class PlaybackInfoRequest
{
public string Id { get; set; }
public string UserId { get; set; }
public int? MaxStreamingBitrate { get; set; }
public long? StartTimeTicks { get; set; }
public int? AudioStreamIndex { get; set; }
public int? SubtitleStreamIndex { get; set; }
public string MediaSourceId { get; set; }
public string LiveStreamId { get; set; }
public DeviceProfile DeviceProfile { get; set; }
}
}

View File

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

View File

@ -95,6 +95,11 @@
/// </summary> /// </summary>
IndexOptions, IndexOptions,
/// <summary>
/// The item counts
/// </summary>
ItemCounts,
/// <summary> /// <summary>
/// The keywords /// The keywords
/// </summary> /// </summary>

View File

@ -78,5 +78,10 @@ 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; }
} }
} }

View File

@ -36,5 +36,10 @@ 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; }
} }
} }

View File

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

View File

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

View File

@ -1,4 +1,5 @@
using MediaBrowser.Model.Dto; using MediaBrowser.Model.Dto;
using System;
using System.Collections.Generic; using System.Collections.Generic;
namespace MediaBrowser.Model.Sync namespace MediaBrowser.Model.Sync
@ -16,6 +17,16 @@ namespace MediaBrowser.Model.Sync
/// <value>The synchronize job identifier.</value> /// <value>The synchronize job identifier.</value>
public string SyncJobId { get; set; } public string SyncJobId { get; set; }
/// <summary> /// <summary>
/// Gets or sets the name of the synchronize job.
/// </summary>
/// <value>The name of the synchronize job.</value>
public string SyncJobName { get; set; }
/// <summary>
/// Gets or sets the synchronize job date created.
/// </summary>
/// <value>The synchronize job date created.</value>
public DateTime SyncJobDateCreated { get; set; }
/// <summary>
/// Gets or sets the synchronize job item identifier. /// Gets or sets the synchronize job item identifier.
/// </summary> /// </summary>
/// <value>The synchronize job item identifier.</value> /// <value>The synchronize job item identifier.</value>

View File

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

View File

@ -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)

View File

@ -169,7 +169,7 @@ namespace MediaBrowser.Server.Implementations.Channels
foreach (var item in result.Items) foreach (var item in result.Items)
{ {
var channelItem = (IChannelItem)item; var channelItem = (IChannelMediaItem)item;
var channelFeatures = _manager.GetChannelFeatures(channelItem.ChannelId); var channelFeatures = _manager.GetChannelFeatures(channelItem.ChannelId);
@ -179,7 +179,7 @@ namespace MediaBrowser.Server.Implementations.Channels
{ {
try try
{ {
await DownloadChannelItem(item, options, cancellationToken, path); await DownloadChannelItem(channelItem, options, cancellationToken, path);
} }
catch (OperationCanceledException) catch (OperationCanceledException)
{ {
@ -210,13 +210,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 +237,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(item, destination, new Progress<double>(), cancellationToken)
await _manager.DownloadChannelItem(channelItem, destination, new Progress<double>(), cancellationToken)
.ConfigureAwait(false); .ConfigureAwait(false);
await RefreshMediaSourceItem(destination, cancellationToken).ConfigureAwait(false); await RefreshMediaSourceItem(destination, cancellationToken).ConfigureAwait(false);

View File

@ -0,0 +1,43 @@
using MediaBrowser.Controller.Channels;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
using MediaBrowser.Model.Dto;
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
namespace MediaBrowser.Server.Implementations.Channels
{
public class ChannelDynamicMediaSourceProvider : IMediaSourceProvider
{
private readonly ChannelManager _channelManager;
public ChannelDynamicMediaSourceProvider(IChannelManager channelManager)
{
_channelManager = (ChannelManager)channelManager;
}
public Task<IEnumerable<MediaSourceInfo>> GetMediaSources(IHasMediaSources item, CancellationToken cancellationToken)
{
var channelItem = item as IChannelMediaItem;
if (channelItem != null)
{
return _channelManager.GetDynamicMediaSources(channelItem, cancellationToken);
}
return Task.FromResult<IEnumerable<MediaSourceInfo>>(new List<MediaSourceInfo>());
}
public Task<MediaSourceInfo> OpenMediaSource(string openToken, CancellationToken cancellationToken)
{
throw new NotImplementedException();
}
public Task CloseMediaSource(string liveStreamId, CancellationToken cancellationToken)
{
throw new NotImplementedException();
}
}
}

View File

@ -2,7 +2,6 @@
using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Providers; using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities; using MediaBrowser.Model.Entities;
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading; using System.Threading;

View File

@ -241,10 +241,25 @@ namespace MediaBrowser.Server.Implementations.Channels
return item; return item;
} }
public async Task<IEnumerable<MediaSourceInfo>> GetChannelItemMediaSources(string id, bool includeDynamicSources, CancellationToken cancellationToken) public async Task<IEnumerable<MediaSourceInfo>> GetStaticMediaSources(IChannelMediaItem item, bool includeCachedVersions, CancellationToken cancellationToken)
{ {
var item = (IChannelMediaItem)_libraryManager.GetItemById(id); IEnumerable<ChannelMediaInfo> results = item.ChannelMediaSources;
var sources = SortMediaInfoResults(results)
.Select(i => GetMediaSource(item, i))
.ToList();
if (includeCachedVersions)
{
var cachedVersions = GetCachedChannelItemMediaSources(item);
sources.InsertRange(0, cachedVersions);
}
return sources.Where(IsValidMediaSource);
}
public async Task<IEnumerable<MediaSourceInfo>> GetDynamicMediaSources(IChannelMediaItem item, CancellationToken cancellationToken)
{
var channel = GetChannel(item.ChannelId); var channel = GetChannel(item.ChannelId);
var channelPlugin = GetChannelProvider(channel); var channelPlugin = GetChannelProvider(channel);
@ -252,24 +267,25 @@ namespace MediaBrowser.Server.Implementations.Channels
IEnumerable<ChannelMediaInfo> results; IEnumerable<ChannelMediaInfo> results;
if (requiresCallback != null && includeDynamicSources) if (requiresCallback != null)
{ {
results = await GetChannelItemMediaSourcesInternal(requiresCallback, item.ExternalId, cancellationToken) results = await GetChannelItemMediaSourcesInternal(requiresCallback, item.ExternalId, cancellationToken)
.ConfigureAwait(false); .ConfigureAwait(false);
} }
else else
{ {
results = item.ChannelMediaSources; results = new List<ChannelMediaInfo>();
} }
var sources = SortMediaInfoResults(results).Select(i => GetMediaSource(item, i)) var list = SortMediaInfoResults(results)
.Select(i => GetMediaSource(item, i))
.Where(IsValidMediaSource)
.ToList(); .ToList();
var cachedVersions = GetCachedChannelItemMediaSources(item); var cachedVersions = GetCachedChannelItemMediaSources(item);
list.InsertRange(0, cachedVersions);
sources.InsertRange(0, cachedVersions); return list;
return sources.Where(IsValidMediaSource);
} }
private readonly ConcurrentDictionary<string, Tuple<DateTime, List<ChannelMediaInfo>>> _channelItemMediaInfo = private readonly ConcurrentDictionary<string, Tuple<DateTime, List<ChannelMediaInfo>>> _channelItemMediaInfo =
@ -297,14 +313,7 @@ namespace MediaBrowser.Server.Implementations.Channels
return list; return list;
} }
public IEnumerable<MediaSourceInfo> GetCachedChannelItemMediaSources(string id) private IEnumerable<MediaSourceInfo> GetCachedChannelItemMediaSources(IChannelMediaItem item)
{
var item = (IChannelMediaItem)_libraryManager.GetItemById(id);
return GetCachedChannelItemMediaSources(item);
}
public IEnumerable<MediaSourceInfo> GetCachedChannelItemMediaSources(IChannelMediaItem item)
{ {
var filenamePrefix = item.Id.ToString("N"); var filenamePrefix = item.Id.ToString("N");
var parentPath = Path.Combine(ChannelDownloadPath, item.ChannelId); var parentPath = Path.Combine(ChannelDownloadPath, item.ChannelId);
@ -339,7 +348,6 @@ namespace MediaBrowser.Server.Implementations.Channels
if (source != null) if (source != null)
{ {
source.Type = MediaSourceType.Cache;
return new[] { source }; return new[] { source };
} }
} }
@ -1408,8 +1416,7 @@ namespace MediaBrowser.Server.Implementations.Channels
public async Task DownloadChannelItem(IChannelMediaItem item, string destination, public async Task DownloadChannelItem(IChannelMediaItem item, string destination,
IProgress<double> progress, CancellationToken cancellationToken) IProgress<double> progress, CancellationToken cancellationToken)
{ {
var itemId = item.Id.ToString("N"); var sources = await GetDynamicMediaSources(item, cancellationToken)
var sources = await GetChannelItemMediaSources(itemId, true, cancellationToken)
.ConfigureAwait(false); .ConfigureAwait(false);
var list = sources.Where(i => i.Protocol == MediaProtocol.Http).ToList(); var list = sources.Where(i => i.Protocol == MediaProtocol.Http).ToList();

View File

@ -233,25 +233,12 @@ namespace MediaBrowser.Server.Implementations.Drawing
await semaphore.WaitAsync().ConfigureAwait(false); await semaphore.WaitAsync().ConfigureAwait(false);
// Check again in case of lock contention
try
{
if (File.Exists(cacheFilePath))
{
semaphore.Release();
return cacheFilePath;
}
}
catch
{
semaphore.Release();
throw;
}
try try
{ {
CheckDisposed(); CheckDisposed();
if (!File.Exists(cacheFilePath))
{
var newWidth = Convert.ToInt32(newSize.Width); var newWidth = Convert.ToInt32(newSize.Width);
var newHeight = Convert.ToInt32(newSize.Height); var newHeight = Convert.ToInt32(newSize.Height);
@ -268,8 +255,6 @@ namespace MediaBrowser.Server.Implementations.Drawing
originalImage.CurrentImage.CompressionQuality = quality; originalImage.CurrentImage.CompressionQuality = quality;
originalImage.SaveImage(cacheFilePath); originalImage.SaveImage(cacheFilePath);
return cacheFilePath;
} }
} }
else else
@ -286,8 +271,7 @@ namespace MediaBrowser.Server.Implementations.Drawing
wand.CurrentImage.CompressionQuality = quality; wand.CurrentImage.CompressionQuality = quality;
wand.SaveImage(cacheFilePath); wand.SaveImage(cacheFilePath);
}
return cacheFilePath;
} }
} }
} }
@ -296,6 +280,8 @@ namespace MediaBrowser.Server.Implementations.Drawing
{ {
semaphore.Release(); semaphore.Release();
} }
return cacheFilePath;
} }
private ImageFormat GetOutputFormat(ImageFormat requestedFormat) private ImageFormat GetOutputFormat(ImageFormat requestedFormat)

View File

@ -96,6 +96,8 @@ namespace MediaBrowser.Server.Implementations.Dto
var byName = item as IItemByName; var byName = item as IItemByName;
if (byName != null && !(item is LiveTvChannel)) if (byName != null && !(item is LiveTvChannel))
{
//if (options.Fields.Contains(ItemFields.ItemCounts))
{ {
var itemFilter = byName.GetItemFilter(); var itemFilter = byName.GetItemFilter();
@ -105,6 +107,7 @@ namespace MediaBrowser.Server.Implementations.Dto
SetItemByNameInfo(item, dto, libraryItems.ToList(), user); SetItemByNameInfo(item, dto, libraryItems.ToList(), user);
} }
}
FillSyncInfo(dto, item, itemIdsWithSyncJobs, options, user); FillSyncInfo(dto, item, itemIdsWithSyncJobs, options, user);
@ -121,6 +124,8 @@ namespace MediaBrowser.Server.Implementations.Dto
var byName = item as IItemByName; var byName = item as IItemByName;
if (byName != null && !(item is LiveTvChannel)) if (byName != null && !(item is LiveTvChannel))
{
//if (options.Fields.Contains(ItemFields.ItemCounts))
{ {
var itemFilter = byName.GetItemFilter(); var itemFilter = byName.GetItemFilter();
@ -129,6 +134,7 @@ namespace MediaBrowser.Server.Implementations.Dto
_libraryManager.RootFolder.GetRecursiveChildren(itemFilter); _libraryManager.RootFolder.GetRecursiveChildren(itemFilter);
SetItemByNameInfo(item, dto, libraryItems.ToList(), user); SetItemByNameInfo(item, dto, libraryItems.ToList(), user);
}
FillSyncInfo(dto, item, options, user); FillSyncInfo(dto, item, options, user);
return dto; return dto;
@ -255,7 +261,7 @@ namespace MediaBrowser.Server.Implementations.Dto
{ {
if (user == null) if (user == null)
{ {
dto.MediaSources = hasMediaSources.GetMediaSources(true).ToList(); dto.MediaSources = _mediaSourceManager().GetStaticMediaSources(hasMediaSources, true).ToList();
} }
else else
{ {
@ -311,7 +317,11 @@ namespace MediaBrowser.Server.Implementations.Dto
{ {
var dto = GetBaseItemDtoInternal(item, options, user); var dto = GetBaseItemDtoInternal(item, options, user);
//if (options.Fields.Contains(ItemFields.ItemCounts))
{
SetItemByNameInfo(item, dto, taggedItems, user); SetItemByNameInfo(item, dto, taggedItems, user);
}
FillSyncInfo(dto, item, options, user); FillSyncInfo(dto, item, options, user);
return dto; return dto;
@ -1270,7 +1280,7 @@ namespace MediaBrowser.Server.Implementations.Dto
} }
else else
{ {
mediaStreams = iHasMediaSources.GetMediaSources(true).First().MediaStreams; mediaStreams = _mediaSourceManager().GetStaticMediaSources(iHasMediaSources, true).First().MediaStreams;
} }
dto.MediaStreams = mediaStreams; dto.MediaStreams = mediaStreams;
@ -1443,7 +1453,7 @@ namespace MediaBrowser.Server.Implementations.Dto
var tvChannel = item as LiveTvChannel; var tvChannel = item as LiveTvChannel;
if (tvChannel != null) if (tvChannel != null)
{ {
dto.MediaSources = tvChannel.GetMediaSources(true).ToList(); dto.MediaSources = _mediaSourceManager().GetStaticMediaSources(tvChannel, true).ToList();
} }
var channelItem = item as IChannelItem; var channelItem = item as IChannelItem;

View File

@ -1713,11 +1713,15 @@ namespace MediaBrowser.Server.Implementations.Library
isNew = true; isNew = true;
} }
var refresh = isNew || (DateTime.UtcNow - item.DateLastSaved).TotalHours >= 6; var refresh = isNew || (DateTime.UtcNow - item.DateLastSaved).TotalHours >= 12;
if (refresh) if (refresh)
{ {
_providerManagerFactory().QueueRefresh(item.Id, new MetadataRefreshOptions()); _providerManagerFactory().QueueRefresh(item.Id, new MetadataRefreshOptions
{
// Need to force save to increment DateLastSaved
ForceSave = true
});
} }
return item; return item;

View File

@ -1,5 +1,4 @@
using System.IO; using MediaBrowser.Common.Extensions;
using MediaBrowser.Controller.Channels;
using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.MediaEncoding; using MediaBrowser.Controller.MediaEncoding;
@ -7,32 +6,35 @@ using MediaBrowser.Controller.Persistence;
using MediaBrowser.Model.Dto; 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.Serialization;
using System; using System;
using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO;
using System.Linq; using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using MediaBrowser.Model.MediaInfo;
namespace MediaBrowser.Server.Implementations.Library namespace MediaBrowser.Server.Implementations.Library
{ {
public class MediaSourceManager : IMediaSourceManager public class MediaSourceManager : IMediaSourceManager, IDisposable
{ {
private readonly IItemRepository _itemRepo; private readonly IItemRepository _itemRepo;
private readonly IUserManager _userManager; private readonly IUserManager _userManager;
private readonly ILibraryManager _libraryManager; private readonly ILibraryManager _libraryManager;
private readonly IChannelManager _channelManager; private readonly IJsonSerializer _jsonSerializer;
private IMediaSourceProvider[] _providers; private IMediaSourceProvider[] _providers;
private readonly ILogger _logger; private readonly ILogger _logger;
public MediaSourceManager(IItemRepository itemRepo, IUserManager userManager, ILibraryManager libraryManager, IChannelManager channelManager, ILogger logger) public MediaSourceManager(IItemRepository itemRepo, IUserManager userManager, ILibraryManager libraryManager, ILogger logger, IJsonSerializer jsonSerializer)
{ {
_itemRepo = itemRepo; _itemRepo = itemRepo;
_userManager = userManager; _userManager = userManager;
_libraryManager = libraryManager; _libraryManager = libraryManager;
_channelManager = channelManager;
_logger = logger; _logger = logger;
_jsonSerializer = jsonSerializer;
} }
public void AddParts(IEnumerable<IMediaSourceProvider> providers) public void AddParts(IEnumerable<IMediaSourceProvider> providers)
@ -127,31 +129,28 @@ namespace MediaBrowser.Server.Implementations.Library
return list; return list;
} }
public Task<IEnumerable<MediaSourceInfo>> GetPlayackMediaSources(string id, bool enablePathSubstitution, CancellationToken cancellationToken)
{
return GetPlayackMediaSources(id, null, enablePathSubstitution, cancellationToken);
}
public async Task<IEnumerable<MediaSourceInfo>> GetPlayackMediaSources(string id, string userId, bool enablePathSubstitution, CancellationToken cancellationToken) public async Task<IEnumerable<MediaSourceInfo>> GetPlayackMediaSources(string id, string userId, bool enablePathSubstitution, CancellationToken cancellationToken)
{ {
var item = _libraryManager.GetItemById(id); var item = _libraryManager.GetItemById(id);
IEnumerable<MediaSourceInfo> mediaSources; IEnumerable<MediaSourceInfo> mediaSources;
var hasMediaSources = (IHasMediaSources)item; var hasMediaSources = (IHasMediaSources)item;
var channelItem = item as IChannelMediaItem; User user = null;
if (channelItem != null)
{
mediaSources = await _channelManager.GetChannelItemMediaSources(id, true, cancellationToken)
.ConfigureAwait(false);
}
else
{
if (string.IsNullOrWhiteSpace(userId)) if (string.IsNullOrWhiteSpace(userId))
{ {
mediaSources = hasMediaSources.GetMediaSources(enablePathSubstitution); mediaSources = hasMediaSources.GetMediaSources(enablePathSubstitution);
} }
else else
{ {
var user = _userManager.GetUserById(userId); user = _userManager.GetUserById(userId);
mediaSources = GetStaticMediaSources(hasMediaSources, enablePathSubstitution, user); mediaSources = GetStaticMediaSources(hasMediaSources, enablePathSubstitution, user);
} }
}
var dynamicMediaSources = await GetDynamicMediaSources(hasMediaSources, cancellationToken).ConfigureAwait(false); var dynamicMediaSources = await GetDynamicMediaSources(hasMediaSources, cancellationToken).ConfigureAwait(false);
@ -161,17 +160,30 @@ namespace MediaBrowser.Server.Implementations.Library
foreach (var source in dynamicMediaSources) foreach (var source in dynamicMediaSources)
{ {
source.SupportsTranscoding = false; if (user != null)
{
SetUserProperties(source, user);
}
if (source.Protocol == MediaProtocol.File) if (source.Protocol == MediaProtocol.File)
{ {
source.SupportsDirectStream = File.Exists(source.Path); source.SupportsDirectStream = File.Exists(source.Path);
// TODO: Path substitution
}
else if (source.Protocol == MediaProtocol.Http)
{
// TODO: Allow this when the source is plain http, e.g. not HLS or Mpeg Dash
source.SupportsDirectStream = false;
}
else
{
source.SupportsDirectStream = false;
} }
list.Add(source); list.Add(source);
} }
return SortMediaSources(list); return SortMediaSources(list).Where(i => i.Type != MediaSourceType.Placeholder);
} }
private async Task<IEnumerable<MediaSourceInfo>> GetDynamicMediaSources(IHasMediaSources item, CancellationToken cancellationToken) private async Task<IEnumerable<MediaSourceInfo>> GetDynamicMediaSources(IHasMediaSources item, CancellationToken cancellationToken)
@ -186,7 +198,15 @@ namespace MediaBrowser.Server.Implementations.Library
{ {
try try
{ {
return await provider.GetMediaSources(item, cancellationToken).ConfigureAwait(false); var sources = await provider.GetMediaSources(item, cancellationToken).ConfigureAwait(false);
var list = sources.ToList();
foreach (var mediaSource in list)
{
SetKeyProperties(provider, mediaSource);
}
return list;
} }
catch (Exception ex) catch (Exception ex)
{ {
@ -195,9 +215,24 @@ namespace MediaBrowser.Server.Implementations.Library
} }
} }
public Task<IEnumerable<MediaSourceInfo>> GetPlayackMediaSources(string id, bool enablePathSubstitution, CancellationToken cancellationToken) private void SetKeyProperties(IMediaSourceProvider provider, MediaSourceInfo mediaSource)
{ {
return GetPlayackMediaSources(id, null, enablePathSubstitution, cancellationToken); var prefix = provider.GetType().FullName.GetMD5().ToString("N") + "|";
if (!string.IsNullOrWhiteSpace(mediaSource.OpenToken) && !mediaSource.OpenToken.StartsWith(prefix, StringComparison.OrdinalIgnoreCase))
{
mediaSource.OpenToken = prefix + mediaSource.OpenToken;
}
if (!string.IsNullOrWhiteSpace(mediaSource.LiveStreamId) && !mediaSource.LiveStreamId.StartsWith(prefix, StringComparison.OrdinalIgnoreCase))
{
mediaSource.LiveStreamId = prefix + mediaSource.LiveStreamId;
}
}
public MediaSourceInfo GetStaticMediaSource(IHasMediaSources item, string mediaSourceId, bool enablePathSubstitution)
{
return GetStaticMediaSources(item, enablePathSubstitution).FirstOrDefault(i => string.Equals(i.Id, mediaSourceId, StringComparison.OrdinalIgnoreCase));
} }
public IEnumerable<MediaSourceInfo> GetStaticMediaSources(IHasMediaSources item, bool enablePathSubstitution) public IEnumerable<MediaSourceInfo> GetStaticMediaSources(IHasMediaSources item, bool enablePathSubstitution)
@ -263,6 +298,9 @@ namespace MediaBrowser.Server.Implementations.Library
preferredSubs, preferredSubs,
user.Configuration.SubtitleMode, user.Configuration.SubtitleMode,
audioLangage); audioLangage);
MediaStreamSelector.SetSubtitleStreamScores(source.MediaStreams, preferredSubs,
user.Configuration.SubtitleMode, audioLangage);
} }
private IEnumerable<MediaSourceInfo> SortMediaSources(IEnumerable<MediaSourceInfo> sources) private IEnumerable<MediaSourceInfo> SortMediaSources(IEnumerable<MediaSourceInfo> sources)
@ -286,9 +324,238 @@ namespace MediaBrowser.Server.Implementations.Library
.ToList(); .ToList();
} }
public MediaSourceInfo GetStaticMediaSource(IHasMediaSources item, string mediaSourceId, bool enablePathSubstitution) private readonly ConcurrentDictionary<string, LiveStreamInfo> _openStreams = new ConcurrentDictionary<string, LiveStreamInfo>(StringComparer.OrdinalIgnoreCase);
private readonly SemaphoreSlim _liveStreamSemaphore = new SemaphoreSlim(1, 1);
public async Task<LiveStreamResponse> OpenLiveStream(LiveStreamRequest request, bool enableAutoClose, CancellationToken cancellationToken)
{ {
return GetStaticMediaSources(item, enablePathSubstitution).FirstOrDefault(i => string.Equals(i.Id, mediaSourceId, StringComparison.OrdinalIgnoreCase)); await _liveStreamSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
try
{
var tuple = GetProvider(request.OpenToken);
var provider = tuple.Item1;
var mediaSource = await provider.OpenMediaSource(tuple.Item2, cancellationToken).ConfigureAwait(false);
if (string.IsNullOrWhiteSpace(mediaSource.LiveStreamId))
{
throw new InvalidOperationException(string.Format("{0} returned null LiveStreamId", provider.GetType().Name));
}
SetKeyProperties(provider, mediaSource);
var info = new LiveStreamInfo
{
Date = DateTime.UtcNow,
EnableCloseTimer = enableAutoClose,
Id = mediaSource.LiveStreamId,
MediaSource = mediaSource
};
_openStreams.AddOrUpdate(mediaSource.LiveStreamId, info, (key, i) => info);
if (enableAutoClose)
{
StartCloseTimer();
}
var json = _jsonSerializer.SerializeToString(mediaSource);
var clone = _jsonSerializer.DeserializeFromString<MediaSourceInfo>(json);
if (!string.IsNullOrWhiteSpace(request.UserId))
{
var user = _userManager.GetUserById(request.UserId);
SetUserProperties(clone, user);
}
return new LiveStreamResponse
{
MediaSource = clone
};
}
finally
{
_liveStreamSemaphore.Release();
}
}
public async Task<MediaSourceInfo> GetLiveStream(string id, CancellationToken cancellationToken)
{
if (string.IsNullOrWhiteSpace(id))
{
throw new ArgumentNullException("id");
}
_logger.Debug("Getting live stream {0}", id);
await _liveStreamSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
try
{
LiveStreamInfo info;
if (_openStreams.TryGetValue(id, out info))
{
return info.MediaSource;
}
else
{
throw new ResourceNotFoundException();
}
}
finally
{
_liveStreamSemaphore.Release();
}
}
public async Task PingLiveStream(string id, CancellationToken cancellationToken)
{
await _liveStreamSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
try
{
LiveStreamInfo info;
if (_openStreams.TryGetValue(id, out info))
{
info.Date = DateTime.UtcNow;
}
else
{
_logger.Error("Failed to update MediaSource timestamp for {0}", id);
}
}
finally
{
_liveStreamSemaphore.Release();
}
}
public async Task CloseLiveStream(string id, CancellationToken cancellationToken)
{
await _liveStreamSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
try
{
LiveStreamInfo current;
if (_openStreams.TryGetValue(id, out current))
{
if (current.MediaSource.RequiresClosing)
{
var tuple = GetProvider(id);
await tuple.Item1.CloseMediaSource(tuple.Item2, cancellationToken).ConfigureAwait(false);
}
}
LiveStreamInfo removed;
if (_openStreams.TryRemove(id, out removed))
{
removed.Closed = true;
}
if (_openStreams.Count == 0)
{
StopCloseTimer();
}
}
finally
{
_liveStreamSemaphore.Release();
}
}
private Tuple<IMediaSourceProvider, string> GetProvider(string key)
{
var keys = key.Split(new[] { '|' }, 2);
var provider = _providers.FirstOrDefault(i => string.Equals(i.GetType().FullName.GetMD5().ToString("N"), keys[0], StringComparison.OrdinalIgnoreCase));
return new Tuple<IMediaSourceProvider, string>(provider, keys[1]);
}
private Timer _closeTimer;
private readonly TimeSpan _openStreamMaxAge = TimeSpan.FromSeconds(40);
private void StartCloseTimer()
{
StopCloseTimer();
_closeTimer = new Timer(CloseTimerCallback, null, _openStreamMaxAge, _openStreamMaxAge);
}
private void StopCloseTimer()
{
var timer = _closeTimer;
if (timer != null)
{
_closeTimer = null;
timer.Dispose();
}
}
private async void CloseTimerCallback(object state)
{
var infos = _openStreams
.Values
.Where(i => i.EnableCloseTimer && (DateTime.UtcNow - i.Date) > _openStreamMaxAge)
.ToList();
foreach (var info in infos)
{
if (!info.Closed)
{
try
{
await CloseLiveStream(info.Id, CancellationToken.None).ConfigureAwait(false);
}
catch (Exception ex)
{
_logger.ErrorException("Error closing media source", ex);
}
}
}
}
/// <summary>
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
/// </summary>
public void Dispose()
{
StopCloseTimer();
Dispose(true);
}
private readonly object _disposeLock = new object();
/// <summary>
/// Releases unmanaged and - optionally - managed resources.
/// </summary>
/// <param name="dispose"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
protected virtual void Dispose(bool dispose)
{
if (dispose)
{
lock (_disposeLock)
{
foreach (var key in _openStreams.Keys.ToList())
{
var task = CloseLiveStream(key, CancellationToken.None);
Task.WaitAll(task);
}
_openStreams.Clear();
}
}
}
private class LiveStreamInfo
{
public DateTime Date;
public bool EnableCloseTimer;
public string Id;
public bool Closed;
public MediaSourceInfo MediaSource;
} }
} }
} }

View File

@ -215,7 +215,7 @@ namespace MediaBrowser.Server.Implementations.Library
if (request.IsPlayed.HasValue) if (request.IsPlayed.HasValue)
{ {
var val = request.IsPlayed.Value; var val = request.IsPlayed.Value;
if (i.IsPlayed(currentUser) != val) if (i is Video && i.IsPlayed(currentUser) != val)
{ {
return false; return false;
} }

View File

@ -51,11 +51,13 @@ namespace MediaBrowser.Server.Implementations.LiveTv
var response = await _httpClient.GetResponse(options).ConfigureAwait(false); var response = await _httpClient.GetResponse(options).ConfigureAwait(false);
if (response.ContentType.StartsWith("image/", StringComparison.OrdinalIgnoreCase)) var contentType = response.ContentType;
if (contentType.StartsWith("image/", StringComparison.OrdinalIgnoreCase))
{ {
imageResponse.HasImage = true; imageResponse.HasImage = true;
imageResponse.Stream = response.Content; imageResponse.Stream = response.Content;
imageResponse.SetFormatFromMimeType(response.ContentType); imageResponse.SetFormatFromMimeType(contentType);
} }
else else
{ {

View File

@ -373,7 +373,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv
StartDate = item.StartDate, StartDate = item.StartDate,
OfficialRating = item.OfficialRating, OfficialRating = item.OfficialRating,
IsHD = item.IsHD, IsHD = item.IsHD,
OriginalAirDate = item.PremiereDate, OriginalAirDate = item.OriginalAirDate,
Audio = item.Audio, Audio = item.Audio,
CommunityRating = GetClientCommunityRating(item.CommunityRating), CommunityRating = GetClientCommunityRating(item.CommunityRating),
IsRepeat = item.IsRepeat, IsRepeat = item.IsRepeat,

View File

@ -1,10 +1,8 @@
using System.Globalization; using MediaBrowser.Common;
using MediaBrowser.Common;
using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Extensions;
using MediaBrowser.Common.Progress; using MediaBrowser.Common.Progress;
using MediaBrowser.Common.ScheduledTasks; using MediaBrowser.Common.ScheduledTasks;
using MediaBrowser.Controller.Channels;
using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Drawing; using MediaBrowser.Controller.Drawing;
using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Dto;
@ -15,7 +13,6 @@ using MediaBrowser.Controller.Localization;
using MediaBrowser.Controller.Persistence; using MediaBrowser.Controller.Persistence;
using MediaBrowser.Controller.Providers; using MediaBrowser.Controller.Providers;
using MediaBrowser.Controller.Sorting; using MediaBrowser.Controller.Sorting;
using MediaBrowser.Model.Dlna;
using MediaBrowser.Model.Dto; using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities; using MediaBrowser.Model.Entities;
using MediaBrowser.Model.LiveTv; using MediaBrowser.Model.LiveTv;
@ -88,8 +85,6 @@ namespace MediaBrowser.Server.Implementations.LiveTv
get { return _services; } get { return _services; }
} }
public ILiveTvService ActiveService { get; private set; }
private LiveTvOptions GetConfiguration() private LiveTvOptions GetConfiguration()
{ {
return _config.GetConfiguration<LiveTvOptions>("livetv"); return _config.GetConfiguration<LiveTvOptions>("livetv");
@ -103,8 +98,6 @@ namespace MediaBrowser.Server.Implementations.LiveTv
{ {
_services.AddRange(services); _services.AddRange(services);
ActiveService = _services.FirstOrDefault();
foreach (var service in _services) foreach (var service in _services)
{ {
service.DataSourceChanged += service_DataSourceChanged; service.DataSourceChanged += service_DataSourceChanged;
@ -316,6 +309,22 @@ namespace MediaBrowser.Server.Implementations.LiveTv
return await GetLiveStream(id, true, cancellationToken).ConfigureAwait(false); return await GetLiveStream(id, true, cancellationToken).ConfigureAwait(false);
} }
public async Task<IEnumerable<MediaSourceInfo>> GetRecordingMediaSources(string id, CancellationToken cancellationToken)
{
var item = await GetInternalRecording(id, cancellationToken).ConfigureAwait(false);
var service = GetService(item);
return await service.GetRecordingStreamMediaSources(id, cancellationToken).ConfigureAwait(false);
}
public async Task<IEnumerable<MediaSourceInfo>> GetChannelMediaSources(string id, CancellationToken cancellationToken)
{
var item = GetInternalChannel(id);
var service = GetService(item);
return await service.GetChannelStreamMediaSources(id, cancellationToken).ConfigureAwait(false);
}
private ILiveTvService GetService(ILiveTvItem item) private ILiveTvService GetService(ILiveTvItem item)
{ {
return GetService(item.ServiceName); return GetService(item.ServiceName);
@ -333,30 +342,48 @@ namespace MediaBrowser.Server.Implementations.LiveTv
try try
{ {
MediaSourceInfo info; MediaSourceInfo info;
bool isVideo;
if (isChannel) if (isChannel)
{ {
var channel = GetInternalChannel(id); var channel = GetInternalChannel(id);
isVideo = channel.ChannelType == ChannelType.TV;
var service = GetService(channel); var service = GetService(channel);
_logger.Info("Opening channel stream from {0}, external channel Id: {1}", service.Name, channel.ExternalId); _logger.Info("Opening channel stream from {0}, external channel Id: {1}", service.Name, channel.ExternalId);
info = await service.GetChannelStream(channel.ExternalId, null, cancellationToken).ConfigureAwait(false); info = await service.GetChannelStream(channel.ExternalId, null, cancellationToken).ConfigureAwait(false);
info.RequiresClosing = true;
if (info.RequiresClosing)
{
var idPrefix = service.GetType().FullName.GetMD5().ToString("N") + "_";
info.LiveStreamId = idPrefix + info.Id;
}
} }
else else
{ {
var recording = await GetInternalRecording(id, cancellationToken).ConfigureAwait(false); var recording = await GetInternalRecording(id, cancellationToken).ConfigureAwait(false);
isVideo = !string.Equals(recording.MediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase);
var service = GetService(recording); var service = GetService(recording);
_logger.Info("Opening recording stream from {0}, external recording Id: {1}", service.Name, recording.RecordingInfo.Id); _logger.Info("Opening recording stream from {0}, external recording Id: {1}", service.Name, recording.RecordingInfo.Id);
info = await service.GetRecordingStream(recording.RecordingInfo.Id, null, cancellationToken).ConfigureAwait(false); info = await service.GetRecordingStream(recording.RecordingInfo.Id, null, cancellationToken).ConfigureAwait(false);
info.RequiresClosing = true;
if (info.RequiresClosing)
{
var idPrefix = service.GetType().FullName.GetMD5().ToString("N") + "_";
info.LiveStreamId = idPrefix + info.Id;
}
} }
_logger.Info("Live stream info: {0}", _jsonSerializer.SerializeToString(info)); _logger.Info("Live stream info: {0}", _jsonSerializer.SerializeToString(info));
Sanitize(info); Normalize(info, isVideo);
var data = new LiveStreamData var data = new LiveStreamData
{ {
Info = info, Info = info,
ConsumerCount = 1,
IsChannel = isChannel, IsChannel = isChannel,
ItemId = id ItemId = id
}; };
@ -377,9 +404,11 @@ namespace MediaBrowser.Server.Implementations.LiveTv
} }
} }
private void Sanitize(MediaSourceInfo mediaSource) private void Normalize(MediaSourceInfo mediaSource, bool isVideo)
{ {
if (mediaSource.MediaStreams.Count == 0) if (mediaSource.MediaStreams.Count == 0)
{
if (isVideo)
{ {
mediaSource.MediaStreams.AddRange(new List<MediaStream> mediaSource.MediaStreams.AddRange(new List<MediaStream>
{ {
@ -387,7 +416,10 @@ namespace MediaBrowser.Server.Implementations.LiveTv
{ {
Type = MediaStreamType.Video, Type = MediaStreamType.Video,
// Set the index to -1 because we don't know the exact index of the video stream within the container // Set the index to -1 because we don't know the exact index of the video stream within the container
Index = -1 Index = -1,
// Set to true if unknown to enable deinterlacing
IsInterlaced = true
}, },
new MediaStream new MediaStream
{ {
@ -397,6 +429,19 @@ namespace MediaBrowser.Server.Implementations.LiveTv
} }
}); });
} }
else
{
mediaSource.MediaStreams.AddRange(new List<MediaStream>
{
new MediaStream
{
Type = MediaStreamType.Audio,
// Set the index to -1 because we don't know the exact index of the audio stream within the container
Index = -1
}
});
}
}
// Clean some bad data coming from providers // Clean some bad data coming from providers
foreach (var stream in mediaSource.MediaStreams) foreach (var stream in mediaSource.MediaStreams)
@ -544,12 +589,14 @@ namespace MediaBrowser.Server.Implementations.LiveTv
item.Name = info.Name; item.Name = info.Name;
item.OfficialRating = info.OfficialRating; item.OfficialRating = info.OfficialRating;
item.Overview = info.Overview; item.Overview = info.Overview;
item.PremiereDate = info.OriginalAirDate; item.OriginalAirDate = info.OriginalAirDate;
item.ProviderImagePath = info.ImagePath; item.ProviderImagePath = info.ImagePath;
item.ProviderImageUrl = info.ImageUrl; item.ProviderImageUrl = info.ImageUrl;
item.RunTimeTicks = (info.EndDate - info.StartDate).Ticks; item.RunTimeTicks = (info.EndDate - info.StartDate).Ticks;
item.StartDate = info.StartDate; item.StartDate = info.StartDate;
item.ProductionYear = info.ProductionYear; item.ProductionYear = info.ProductionYear;
item.PremiereDate = item.PremiereDate ?? info.OriginalAirDate;
await item.UpdateToRepository(ItemUpdateType.MetadataImport, cancellationToken).ConfigureAwait(false); await item.UpdateToRepository(ItemUpdateType.MetadataImport, cancellationToken).ConfigureAwait(false);
@ -1742,7 +1789,6 @@ namespace MediaBrowser.Server.Implementations.LiveTv
class LiveStreamData class LiveStreamData
{ {
internal MediaSourceInfo Info; internal MediaSourceInfo Info;
internal int ConsumerCount;
internal string ItemId; internal string ItemId;
internal bool IsChannel; internal bool IsChannel;
} }
@ -1753,19 +1799,18 @@ namespace MediaBrowser.Server.Implementations.LiveTv
try try
{ {
var service = ActiveService; var parts = id.Split(new[] { '_' }, 2);
var service = _services.FirstOrDefault(i => string.Equals(i.GetType().FullName.GetMD5().ToString("N"), parts[0], StringComparison.OrdinalIgnoreCase));
if (service == null)
{
throw new ArgumentException("Service not found.");
}
id = parts[1];
LiveStreamData data; LiveStreamData data;
if (_openStreams.TryGetValue(id, out data))
{
if (data.ConsumerCount > 1)
{
data.ConsumerCount--;
_logger.Info("Decrementing live stream client count.");
return;
}
}
_openStreams.TryRemove(id, out data); _openStreams.TryRemove(id, out data);
_logger.Info("Closing live stream from {0}, stream Id: {1}", service.Name, id); _logger.Info("Closing live stream from {0}, stream Id: {1}", service.Name, id);
@ -1846,6 +1891,8 @@ namespace MediaBrowser.Server.Implementations.LiveTv
Name = service.Name Name = service.Name
}; };
var tunerIdPrefix = service.GetType().FullName.GetMD5().ToString("N") + "_";
try try
{ {
var statusInfo = await service.GetStatusInfoAsync(cancellationToken).ConfigureAwait(false); var statusInfo = await service.GetStatusInfoAsync(cancellationToken).ConfigureAwait(false);
@ -1867,7 +1914,11 @@ namespace MediaBrowser.Server.Implementations.LiveTv
channelName = channel == null ? null : channel.Name; channelName = channel == null ? null : channel.Name;
} }
return _tvDtoService.GetTunerInfoDto(service.Name, i, channelName); var dto = _tvDtoService.GetTunerInfoDto(service.Name, i, channelName);
dto.Id = tunerIdPrefix + dto.Id;
return dto;
}).ToList(); }).ToList();
} }
@ -1920,7 +1971,16 @@ namespace MediaBrowser.Server.Implementations.LiveTv
/// <returns>Task.</returns> /// <returns>Task.</returns>
public Task ResetTuner(string id, CancellationToken cancellationToken) public Task ResetTuner(string id, CancellationToken cancellationToken)
{ {
return ActiveService.ResetTuner(id, cancellationToken); var parts = id.Split(new[] { '_' }, 2);
var service = _services.FirstOrDefault(i => string.Equals(i.GetType().FullName.GetMD5().ToString("N"), parts[0], StringComparison.OrdinalIgnoreCase));
if (service == null)
{
throw new ArgumentException("Service not found.");
}
return service.ResetTuner(parts[1], cancellationToken);
} }
public async Task<BaseItemDto> GetLiveTvFolder(string userId, CancellationToken cancellationToken) public async Task<BaseItemDto> GetLiveTvFolder(string userId, CancellationToken cancellationToken)

View File

@ -0,0 +1,108 @@
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.LiveTv;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Logging;
using MediaBrowser.Model.Serialization;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace MediaBrowser.Server.Implementations.LiveTv
{
public class LiveTvMediaSourceProvider : IMediaSourceProvider
{
private readonly ILiveTvManager _liveTvManager;
private readonly IJsonSerializer _jsonSerializer;
private readonly ILogger _logger;
private readonly IMediaSourceManager _mediaSourceManager;
public LiveTvMediaSourceProvider(ILiveTvManager liveTvManager, IJsonSerializer jsonSerializer, ILogManager logManager, IMediaSourceManager mediaSourceManager)
{
_liveTvManager = liveTvManager;
_jsonSerializer = jsonSerializer;
_mediaSourceManager = mediaSourceManager;
_logger = logManager.GetLogger(GetType().Name);
}
public Task<IEnumerable<MediaSourceInfo>> GetMediaSources(IHasMediaSources item, CancellationToken cancellationToken)
{
var channelItem = item as ILiveTvItem;
if (channelItem != null)
{
var hasMetadata = (IHasMetadata)channelItem;
if (string.IsNullOrWhiteSpace(hasMetadata.Path))
{
return GetMediaSourcesInternal(channelItem, cancellationToken);
}
}
return Task.FromResult<IEnumerable<MediaSourceInfo>>(new List<MediaSourceInfo>());
}
private async Task<IEnumerable<MediaSourceInfo>> GetMediaSourcesInternal(ILiveTvItem item, CancellationToken cancellationToken)
{
IEnumerable<MediaSourceInfo> sources;
try
{
if (item is ILiveTvRecording)
{
sources = await _liveTvManager.GetRecordingMediaSources(item.Id.ToString("N"), cancellationToken)
.ConfigureAwait(false);
}
else
{
sources = await _liveTvManager.GetChannelMediaSources(item.Id.ToString("N"), cancellationToken)
.ConfigureAwait(false);
}
}
catch (NotImplementedException)
{
var hasMediaSources = (IHasMediaSources)item;
sources = _mediaSourceManager.GetStaticMediaSources(hasMediaSources, false)
.ToList();
}
var list = sources.ToList();
foreach (var source in list)
{
source.Type = MediaSourceType.Default;
source.RequiresOpening = true;
source.BufferMs = source.BufferMs ?? 1500;
var openKeys = new List<string>();
openKeys.Add(item.GetType().Name);
openKeys.Add(item.Id.ToString("N"));
source.OpenToken = string.Join("|", openKeys.ToArray());
}
_logger.Debug("MediaSources: {0}", _jsonSerializer.SerializeToString(list));
return list;
}
public async Task<MediaSourceInfo> OpenMediaSource(string openToken, CancellationToken cancellationToken)
{
var keys = openToken.Split(new[] { '|' }, 2);
if (string.Equals(keys[0], typeof(LiveTvChannel).Name, StringComparison.OrdinalIgnoreCase))
{
return await _liveTvManager.GetChannelStream(keys[1], cancellationToken).ConfigureAwait(false);
}
return await _liveTvManager.GetRecordingStream(keys[1], cancellationToken).ConfigureAwait(false);
}
public Task CloseMediaSource(string liveStreamId, CancellationToken cancellationToken)
{
return _liveTvManager.CloseLiveStream(liveStreamId, cancellationToken);
}
}
}

Some files were not shown because too many files have changed in this diff Show More