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].
/// </summary>
/// <param name="path">The path.</param>
/// <param name="streamId">The stream identifier.</param>
/// <param name="playSessionId">The play session identifier.</param>
/// <param name="transcodingJobId">The transcoding job identifier.</param>
/// <param name="type">The type.</param>
/// <param name="process">The process.</param>
@ -141,7 +141,7 @@ namespace MediaBrowser.Api
/// <param name="cancellationTokenSource">The cancellation token source.</param>
/// <returns>TranscodingJob.</returns>
public TranscodingJob OnTranscodeBeginning(string path,
string streamId,
string playSessionId,
string transcodingJobId,
TranscodingJobType type,
Process process,
@ -160,7 +160,7 @@ namespace MediaBrowser.Api
DeviceId = deviceId,
CancellationTokenSource = cancellationTokenSource,
Id = transcodingJobId,
StreamId = streamId
PlaySessionId = playSessionId
};
_activeTranscodingJobs.Add(job);
@ -187,7 +187,7 @@ namespace MediaBrowser.Api
if (!string.IsNullOrWhiteSpace(deviceId))
{
var audioCodec = state.ActualOutputVideoCodec;
var audioCodec = state.ActualOutputAudioCodec;
var videoCodec = state.ActualOutputVideoCodec;
_sessionManager.ReportTranscodingInfo(deviceId, new TranscodingInfo
@ -274,32 +274,37 @@ namespace MediaBrowser.Api
return null;
}
job.ActiveRequestCount++;
job.DisposeKillTimer();
OnTranscodeBeginRequest(job);
return job;
}
}
public void OnTranscodeBeginRequest(TranscodingJob job)
{
job.ActiveRequestCount++;
job.DisposeKillTimer();
}
public void OnTranscodeEndRequest(TranscodingJob job)
{
job.ActiveRequestCount--;
if (job.ActiveRequestCount == 0)
{
if (job.Type == TranscodingJobType.Progressive)
{
const int timerDuration = 1000;
// TODO: Lower this hls timeout
var timerDuration = job.Type == TranscodingJobType.Progressive ?
1000 :
7200000;
if (job.KillTimer == null)
{
job.KillTimer = new Timer(OnTranscodeKillTimerStopped, job, timerDuration, Timeout.Infinite);
}
else
{
job.KillTimer.Change(timerDuration, Timeout.Infinite);
}
if (job.KillTimer == null)
{
job.KillTimer = new Timer(OnTranscodeKillTimerStopped, job, timerDuration, Timeout.Infinite);
}
else
{
job.KillTimer.Change(timerDuration, Timeout.Infinite);
}
}
}
@ -319,10 +324,10 @@ namespace MediaBrowser.Api
/// Kills the single transcoding job.
/// </summary>
/// <param name="deviceId">The device id.</param>
/// <param name="streamId">The stream identifier.</param>
/// <param name="playSessionId">The play session identifier.</param>
/// <param name="deleteFiles">The delete files.</param>
/// <returns>Task.</returns>
internal void KillTranscodingJobs(string deviceId, string streamId, Func<string, bool> deleteFiles)
internal void KillTranscodingJobs(string deviceId, string playSessionId, Func<string, bool> deleteFiles)
{
if (string.IsNullOrEmpty(deviceId))
{
@ -333,7 +338,7 @@ namespace MediaBrowser.Api
{
if (string.Equals(deviceId, j.DeviceId, StringComparison.OrdinalIgnoreCase))
{
return string.IsNullOrWhiteSpace(streamId) || string.Equals(streamId, j.StreamId, StringComparison.OrdinalIgnoreCase);
return string.IsNullOrWhiteSpace(playSessionId) || string.Equals(playSessionId, j.PlaySessionId, StringComparison.OrdinalIgnoreCase);
}
return false;
@ -498,7 +503,7 @@ namespace MediaBrowser.Api
.ToList();
Exception e = null;
foreach (var file in filesToDelete)
{
try
@ -534,10 +539,10 @@ namespace MediaBrowser.Api
public class TranscodingJob
{
/// <summary>
/// Gets or sets the stream identifier.
/// Gets or sets the play session identifier.
/// </summary>
/// <value>The stream identifier.</value>
public string StreamId { get; set; }
/// <value>The play session identifier.</value>
public string PlaySessionId { get; set; }
/// <summary>
/// Gets or sets the path.
/// </summary>

View File

@ -73,6 +73,17 @@ namespace MediaBrowser.Api
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)
{
var auth = AuthorizationContext.GetAuthorizationInfo(Request);

View File

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

View File

@ -54,7 +54,7 @@ namespace MediaBrowser.Api.Playback.Dash
public class MpegDashService : BaseHlsService
{
public MpegDashService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, ILiveTvManager liveTvManager, IDlnaManager dlnaManager, ISubtitleEncoder subtitleEncoder, IDeviceManager deviceManager, IMediaSourceManager mediaSourceManager, IZipClient zipClient, INetworkManager networkManager) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, fileSystem, liveTvManager, dlnaManager, subtitleEncoder, deviceManager, mediaSourceManager, zipClient)
public MpegDashService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, IDlnaManager dlnaManager, ISubtitleEncoder subtitleEncoder, IDeviceManager deviceManager, IMediaSourceManager mediaSourceManager, IZipClient zipClient, INetworkManager networkManager) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, fileSystem, dlnaManager, subtitleEncoder, deviceManager, mediaSourceManager, zipClient)
{
NetworkManager = networkManager;
}
@ -160,7 +160,7 @@ namespace MediaBrowser.Api.Playback.Dash
// If the playlist doesn't already exist, startup ffmpeg
try
{
ApiEntryPoint.Instance.KillTranscodingJobs(request.DeviceId, request.StreamId, p => false);
ApiEntryPoint.Instance.KillTranscodingJobs(request.DeviceId, request.PlaySessionId, p => false);
if (currentTranscodingIndex.HasValue)
{
@ -447,7 +447,7 @@ namespace MediaBrowser.Api.Playback.Dash
return args;
}
protected override string GetCommandLineArguments(string outputPath, string transcodingJobId, StreamState state, bool isEncoding)
protected override string GetCommandLineArguments(string outputPath, StreamState state, bool isEncoding)
{
// test url http://192.168.1.2:8096/videos/233e8905d559a8f230db9bffd2ac9d6d/master.mpd?mediasourceid=233e8905d559a8f230db9bffd2ac9d6d&videocodec=h264&audiocodec=aac&maxwidth=1280&videobitrate=500000&audiobitrate=128000&profile=baseline&level=3
// Good info on i-frames http://blog.streamroot.io/encode-multi-bitrate-videos-mpeg-dash-mse-based-media-players/
@ -461,7 +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}\"",
inputModifier,
GetInputArgument(transcodingJobId, state),
GetInputArgument(state),
threads,
GetMapArgs(state),
GetVideoArguments(state),

View File

@ -22,7 +22,7 @@ namespace MediaBrowser.Api.Playback.Hls
/// </summary>
public abstract class BaseHlsService : BaseStreamingService
{
protected BaseHlsService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, ILiveTvManager liveTvManager, IDlnaManager dlnaManager, ISubtitleEncoder subtitleEncoder, IDeviceManager deviceManager, IMediaSourceManager mediaSourceManager, IZipClient zipClient) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, fileSystem, liveTvManager, dlnaManager, subtitleEncoder, deviceManager, mediaSourceManager, zipClient)
protected BaseHlsService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, IDlnaManager dlnaManager, ISubtitleEncoder subtitleEncoder, IDeviceManager deviceManager, IMediaSourceManager mediaSourceManager, IZipClient zipClient) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, fileSystem, dlnaManager, subtitleEncoder, deviceManager, mediaSourceManager, zipClient)
{
}
@ -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;
@ -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}\"",
itsOffset,
inputModifier,
GetInputArgument(transcodingJobId, state),
GetInputArgument(state),
threads,
GetMapArgs(state),
GetVideoArguments(state),

View File

@ -62,7 +62,7 @@ namespace MediaBrowser.Api.Playback.Hls
public class DynamicHlsService : BaseHlsService
{
public DynamicHlsService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, ILiveTvManager liveTvManager, IDlnaManager dlnaManager, ISubtitleEncoder subtitleEncoder, IDeviceManager deviceManager, IMediaSourceManager mediaSourceManager, IZipClient zipClient, INetworkManager networkManager) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, fileSystem, liveTvManager, dlnaManager, subtitleEncoder, deviceManager, mediaSourceManager, zipClient)
public DynamicHlsService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, IDlnaManager dlnaManager, ISubtitleEncoder subtitleEncoder, IDeviceManager deviceManager, IMediaSourceManager mediaSourceManager, IZipClient zipClient, INetworkManager networkManager) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, fileSystem, dlnaManager, subtitleEncoder, deviceManager, mediaSourceManager, zipClient)
{
NetworkManager = networkManager;
}
@ -114,7 +114,7 @@ namespace MediaBrowser.Api.Playback.Hls
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);
}
@ -123,7 +123,7 @@ namespace MediaBrowser.Api.Playback.Hls
{
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);
}
else
@ -135,7 +135,7 @@ namespace MediaBrowser.Api.Playback.Hls
// If the playlist doesn't already exist, startup ffmpeg
try
{
ApiEntryPoint.Instance.KillTranscodingJobs(request.DeviceId, request.StreamId, p => false);
ApiEntryPoint.Instance.KillTranscodingJobs(request.DeviceId, request.PlaySessionId, p => false);
if (currentTranscodingIndex.HasValue)
{
@ -145,6 +145,7 @@ namespace MediaBrowser.Api.Playback.Hls
request.StartTimeTicks = GetSeekPositionTicks(state, requestedIndex);
job = await StartFfMpeg(state, playlistPath, cancellationTokenSource).ConfigureAwait(false);
ApiEntryPoint.Instance.OnTranscodeBeginRequest(job);
}
catch
{
@ -168,7 +169,7 @@ namespace MediaBrowser.Api.Playback.Hls
}
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);
}
@ -368,8 +369,8 @@ namespace MediaBrowser.Api.Playback.Hls
if (transcodingJob != null)
{
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 subtitleStreams = state.AllMediaStreams
var subtitleStreams = state.MediaSource
.MediaStreams
.Where(i => i.IsTextSubtitleStream)
.ToList();
@ -683,7 +685,7 @@ namespace MediaBrowser.Api.Playback.Hls
return args;
}
protected override string GetCommandLineArguments(string outputPath, string transcodingJobId, StreamState state, bool isEncoding)
protected override string GetCommandLineArguments(string outputPath, StreamState state, bool isEncoding)
{
var threads = GetNumberOfThreads(state, false);
@ -696,9 +698,9 @@ namespace MediaBrowser.Api.Playback.Hls
{
var outputTsArg = Path.Combine(Path.GetDirectoryName(outputPath), Path.GetFileNameWithoutExtension(outputPath)) + "%d.ts";
return string.Format("{0} {1} -map_metadata -1 -threads {2} {3} {4} -copyts -flags -global_header {5} -f segment -segment_time {6} -segment_start_number {7} -segment_list \"{8}\" -y \"{9}\"",
return string.Format("{0} {1} -map_metadata -1 -threads {2} {3} {4} -copyts -flags -global_header -sc_threshold 0 {5} -f segment -segment_time {6} -segment_start_number {7} -segment_list \"{8}\" -y \"{9}\"",
inputModifier,
GetInputArgument(transcodingJobId, state),
GetInputArgument(state),
threads,
GetMapArgs(state),
GetVideoArguments(state),
@ -710,9 +712,9 @@ namespace MediaBrowser.Api.Playback.Hls
).Trim();
}
return string.Format("{0} {1} -map_metadata -1 -threads {2} {3} {4} -copyts -flags -global_header {5} -hls_time {6} -start_number {7} -hls_list_size {8} -y \"{9}\"",
return string.Format("{0} {1} -map_metadata -1 -threads {2} {3} {4} -copyts -flags -global_header -sc_threshold 0 {5} -hls_time {6} -start_number {7} -hls_list_size {8} -y \"{9}\"",
inputModifier,
GetInputArgument(transcodingJobId, state),
GetInputArgument(state),
threads,
GetMapArgs(state),
GetVideoArguments(state),

View File

@ -1,6 +1,10 @@
using MediaBrowser.Controller;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Net;
using ServiceStack;
using System;
using System.IO;
using System.Linq;
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.")]
public class GetHlsPlaylist
{
// TODO: Deprecate with new iOS app
/// <summary>
/// Gets or sets the id.
/// </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")]
public string DeviceId { get; set; }
[ApiMember(Name = "StreamId", Description = "The stream id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "DELETE")]
public string StreamId { get; set; }
[ApiMember(Name = "PlaySessionId", Description = "The play session id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "DELETE")]
public string PlaySessionId { get; set; }
}
/// <summary>
/// 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
{
private readonly IServerApplicationPaths _appPaths;
private readonly IServerConfigurationManager _config;
public HlsSegmentService(IServerApplicationPaths appPaths)
public HlsSegmentService(IServerApplicationPaths appPaths, IServerConfigurationManager config)
{
_appPaths = appPaths;
_config = config;
}
public object Get(GetHlsPlaylist request)
{
var file = request.PlaylistId + Path.GetExtension(Request.PathInfo);
file = Path.Combine(_appPaths.TranscodingTempPath, file);
return ResultFactory.GetStaticFileResult(Request, file, FileShare.ReadWrite);
return GetFileResult(file, file);
}
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>
@ -82,11 +123,29 @@ namespace MediaBrowser.Api.Playback.Hls
/// <returns>System.Object.</returns>
public object Get(GetHlsAudioSegment request)
{
// TODO: Deprecate with new iOS app
var file = request.SegmentId + Path.GetExtension(Request.PathInfo);
file = Path.Combine(_appPaths.TranscodingTempPath, file);
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.")]
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")]
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>
/// Class VideoHlsService
/// </summary>
public class VideoHlsService : BaseHlsService
{
public VideoHlsService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, ILiveTvManager liveTvManager, IDlnaManager dlnaManager, ISubtitleEncoder subtitleEncoder, IDeviceManager deviceManager, IMediaSourceManager mediaSourceManager, IZipClient zipClient) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, fileSystem, liveTvManager, dlnaManager, subtitleEncoder, deviceManager, mediaSourceManager, zipClient)
public VideoHlsService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, IDlnaManager dlnaManager, ISubtitleEncoder subtitleEncoder, IDeviceManager deviceManager, IMediaSourceManager mediaSourceManager, IZipClient zipClient) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, fileSystem, dlnaManager, subtitleEncoder, deviceManager, mediaSourceManager, zipClient)
{
}
/// <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>
/// Gets the specified request.
/// </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.Model.Dlna;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.MediaInfo;
using MediaBrowser.Model.Session;
using ServiceStack;
using System;
using System.Collections.Generic;
@ -13,7 +17,7 @@ using System.Threading.Tasks;
namespace MediaBrowser.Api.Playback
{
[Route("/Items/{Id}/MediaInfo", "GET", Summary = "Gets live playback media info for an item")]
public class GetLiveMediaInfo : IReturn<LiveMediaInfoResult>
public class GetLiveMediaInfo : IReturn<PlaybackInfoResponse>
{
[ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
public string Id { get; set; }
@ -23,7 +27,7 @@ namespace MediaBrowser.Api.Playback
}
[Route("/Items/{Id}/PlaybackInfo", "GET", Summary = "Gets live playback media info for an item")]
public class GetPlaybackInfo : IReturn<LiveMediaInfoResult>
public class GetPlaybackInfo : IReturn<PlaybackInfoResponse>
{
[ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
public string Id { get; set; }
@ -32,45 +36,337 @@ namespace MediaBrowser.Api.Playback
public string UserId { get; set; }
}
[Route("/Items/{Id}/PlaybackInfo", "POST", Summary = "Gets live playback media info for an item")]
public class GetPostedPlaybackInfo : PlaybackInfoRequest, IReturn<PlaybackInfoResponse>
{
}
[Route("/LiveStreams/Open", "POST", Summary = "Opens a media source")]
public class OpenMediaSource : LiveStreamRequest, IReturn<LiveStreamResponse>
{
}
[Route("/LiveStreams/Close", "POST", Summary = "Closes a media source")]
public class CloseMediaSource : IReturnVoid
{
[ApiMember(Name = "LiveStreamId", Description = "LiveStreamId", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")]
public string LiveStreamId { get; set; }
}
[Authenticated]
public class MediaInfoService : BaseApiService
{
private readonly IMediaSourceManager _mediaSourceManager;
private readonly IDeviceManager _deviceManager;
private readonly ILibraryManager _libraryManager;
public MediaInfoService(IMediaSourceManager mediaSourceManager)
public MediaInfoService(IMediaSourceManager mediaSourceManager, IDeviceManager deviceManager, ILibraryManager libraryManager)
{
_mediaSourceManager = mediaSourceManager;
_deviceManager = deviceManager;
_libraryManager = libraryManager;
}
public Task<object> Get(GetPlaybackInfo request)
public async Task<object> Get(GetPlaybackInfo request)
{
return GetPlaybackInfo(request.Id, request.UserId);
var result = await GetPlaybackInfo(request.Id, request.UserId).ConfigureAwait(false);
return ToOptimizedResult(result);
}
public Task<object> Get(GetLiveMediaInfo request)
public async Task<object> Get(GetLiveMediaInfo request)
{
return GetPlaybackInfo(request.Id, request.UserId);
var result = await GetPlaybackInfo(request.Id, request.UserId).ConfigureAwait(false);
return ToOptimizedResult(result);
}
private async Task<object> GetPlaybackInfo(string id, string userId)
public async Task<object> Post(OpenMediaSource request)
{
IEnumerable<MediaSourceInfo> mediaSources;
var result = new LiveMediaInfoResult();
var authInfo = AuthorizationContext.GetAuthorizationInfo(Request);
try
var result = await _mediaSourceManager.OpenLiveStream(request, false, CancellationToken.None).ConfigureAwait(false);
var profile = request.DeviceProfile;
if (profile == null)
{
mediaSources = await _mediaSourceManager.GetPlayackMediaSources(id, userId, true, CancellationToken.None).ConfigureAwait(false);
}
catch (PlaybackException ex)
{
mediaSources = new List<MediaSourceInfo>();
result.ErrorCode = ex.ErrorCode;
var caps = _deviceManager.GetCapabilities(authInfo.DeviceId);
if (caps != null)
{
profile = caps.DeviceProfile;
}
}
result.MediaSources = mediaSources.ToList();
result.StreamId = Guid.NewGuid().ToString("N");
if (profile != null)
{
var item = _libraryManager.GetItemById(request.ItemId);
SetDeviceSpecificData(item, result.MediaSource, profile, authInfo, request.MaxStreamingBitrate,
request.StartTimeTicks ?? 0, result.MediaSource.Id, request.AudioStreamIndex,
request.SubtitleStreamIndex);
}
else
{
if (!string.IsNullOrWhiteSpace(result.MediaSource.TranscodingUrl))
{
result.MediaSource.TranscodingUrl += "&LiveStreamId=" + result.MediaSource.LiveStreamId;
}
}
return ToOptimizedResult(result);
}
public void Post(CloseMediaSource request)
{
var task = _mediaSourceManager.CloseLiveStream(request.LiveStreamId, CancellationToken.None);
Task.WaitAll(task);
}
public async Task<object> Post(GetPostedPlaybackInfo request)
{
var info = await GetPlaybackInfo(request.Id, request.UserId, request.MediaSourceId, request.LiveStreamId).ConfigureAwait(false);
var authInfo = AuthorizationContext.GetAuthorizationInfo(Request);
var profile = request.DeviceProfile;
if (profile == null)
{
var caps = _deviceManager.GetCapabilities(authInfo.DeviceId);
if (caps != null)
{
profile = caps.DeviceProfile;
}
}
if (profile != null)
{
var mediaSourceId = request.MediaSourceId;
SetDeviceSpecificData(request.Id, info, profile, authInfo, request.MaxStreamingBitrate, request.StartTimeTicks ?? 0, mediaSourceId, request.AudioStreamIndex, request.SubtitleStreamIndex);
}
return ToOptimizedResult(info);
}
private async Task<PlaybackInfoResponse> GetPlaybackInfo(string id, string userId, string mediaSourceId = null, string liveStreamId = null)
{
var result = new PlaybackInfoResponse();
if (string.IsNullOrWhiteSpace(liveStreamId))
{
IEnumerable<MediaSourceInfo> mediaSources;
try
{
mediaSources = await _mediaSourceManager.GetPlayackMediaSources(id, userId, true, CancellationToken.None).ConfigureAwait(false);
}
catch (PlaybackException ex)
{
mediaSources = new List<MediaSourceInfo>();
result.ErrorCode = ex.ErrorCode;
}
result.MediaSources = mediaSources.ToList();
if (!string.IsNullOrWhiteSpace(mediaSourceId))
{
result.MediaSources = result.MediaSources
.Where(i => string.Equals(i.Id, mediaSourceId, StringComparison.OrdinalIgnoreCase))
.ToList();
}
}
else
{
var mediaSource = await _mediaSourceManager.GetLiveStream(liveStreamId, CancellationToken.None).ConfigureAwait(false);
result.MediaSources = new List<MediaSourceInfo> { mediaSource };
}
if (result.MediaSources.Count == 0)
{
if (!result.ErrorCode.HasValue)
{
result.ErrorCode = PlaybackErrorCode.NoCompatibleStream;
}
}
else
{
result.PlaySessionId = Guid.NewGuid().ToString("N");
}
return result;
}
private void SetDeviceSpecificData(string itemId,
PlaybackInfoResponse result,
DeviceProfile profile,
AuthorizationInfo auth,
int? maxBitrate,
long startTimeTicks,
string mediaSourceId,
int? audioStreamIndex,
int? subtitleStreamIndex)
{
var item = _libraryManager.GetItemById(itemId);
foreach (var mediaSource in result.MediaSources)
{
SetDeviceSpecificData(item, mediaSource, profile, auth, maxBitrate, startTimeTicks, mediaSourceId, audioStreamIndex, subtitleStreamIndex);
}
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>
public class AudioService : BaseProgressiveStreamingService
{
public AudioService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, ILiveTvManager liveTvManager, IDlnaManager dlnaManager, ISubtitleEncoder subtitleEncoder, IDeviceManager deviceManager, IMediaSourceManager mediaSourceManager, IZipClient zipClient, IImageProcessor imageProcessor, IHttpClient httpClient) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, fileSystem, liveTvManager, dlnaManager, subtitleEncoder, deviceManager, mediaSourceManager, zipClient, imageProcessor, httpClient)
public AudioService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, IDlnaManager dlnaManager, ISubtitleEncoder subtitleEncoder, IDeviceManager deviceManager, IMediaSourceManager mediaSourceManager, IZipClient zipClient, IImageProcessor imageProcessor, IHttpClient httpClient) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, fileSystem, dlnaManager, subtitleEncoder, deviceManager, mediaSourceManager, zipClient, imageProcessor, httpClient)
{
}
@ -55,7 +55,7 @@ namespace MediaBrowser.Api.Playback.Progressive
return ProcessRequest(request, true);
}
protected override string GetCommandLineArguments(string outputPath, string transcodingJobId, StreamState state, bool isEncoding)
protected override string GetCommandLineArguments(string outputPath, StreamState state, bool isEncoding)
{
var audioTranscodeParams = new List<string>();
@ -84,7 +84,7 @@ namespace MediaBrowser.Api.Playback.Progressive
return string.Format("{0} {1} -threads {2}{3} {4} -id3v2_version 3 -write_id3v1 1 -y \"{5}\"",
inputModifier,
GetInputArgument(transcodingJobId, state),
GetInputArgument(state),
threads,
vn,
string.Join(" ", audioTranscodeParams.ToArray()),

View File

@ -27,7 +27,7 @@ namespace MediaBrowser.Api.Playback.Progressive
protected readonly IImageProcessor ImageProcessor;
protected readonly IHttpClient HttpClient;
protected BaseProgressiveStreamingService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, ILiveTvManager liveTvManager, IDlnaManager dlnaManager, ISubtitleEncoder subtitleEncoder, IDeviceManager deviceManager, IMediaSourceManager mediaSourceManager, IZipClient zipClient, IImageProcessor imageProcessor, IHttpClient httpClient) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, fileSystem, liveTvManager, dlnaManager, subtitleEncoder, deviceManager, mediaSourceManager, zipClient)
protected BaseProgressiveStreamingService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, IDlnaManager dlnaManager, ISubtitleEncoder subtitleEncoder, IDeviceManager deviceManager, IMediaSourceManager mediaSourceManager, IZipClient zipClient, IImageProcessor imageProcessor, IHttpClient httpClient) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, fileSystem, dlnaManager, subtitleEncoder, deviceManager, mediaSourceManager, zipClient)
{
ImageProcessor = imageProcessor;
HttpClient = httpClient;

View File

@ -62,7 +62,7 @@ namespace MediaBrowser.Api.Playback.Progressive
/// </summary>
public class VideoService : BaseProgressiveStreamingService
{
public VideoService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, ILiveTvManager liveTvManager, IDlnaManager dlnaManager, ISubtitleEncoder subtitleEncoder, IDeviceManager deviceManager, IMediaSourceManager mediaSourceManager, IZipClient zipClient, IImageProcessor imageProcessor, IHttpClient httpClient) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, fileSystem, liveTvManager, dlnaManager, subtitleEncoder, deviceManager, mediaSourceManager, zipClient, imageProcessor, httpClient)
public VideoService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, IDlnaManager dlnaManager, ISubtitleEncoder subtitleEncoder, IDeviceManager deviceManager, IMediaSourceManager mediaSourceManager, IZipClient zipClient, IImageProcessor imageProcessor, IHttpClient httpClient) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, fileSystem, dlnaManager, subtitleEncoder, deviceManager, mediaSourceManager, zipClient, imageProcessor, httpClient)
{
}
@ -86,7 +86,7 @@ namespace MediaBrowser.Api.Playback.Progressive
return ProcessRequest(request, true);
}
protected override string GetCommandLineArguments(string outputPath, string transcodingJobId, StreamState state, bool isEncoding)
protected override string GetCommandLineArguments(string outputPath, StreamState state, bool isEncoding)
{
// Get the output codec name
var videoCodec = state.OutputVideoCodec;
@ -106,7 +106,7 @@ namespace MediaBrowser.Api.Playback.Progressive
return string.Format("{0} {1}{2} {3} {4} -map_metadata -1 -threads {5} {6}{7} -y \"{8}\"",
inputModifier,
GetInputArgument(transcodingJobId, state),
GetInputArgument(state),
keyFrame,
GetMapArgs(state),
GetVideoArguments(state, videoCodec),

View File

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

View File

@ -1,6 +1,7 @@
using MediaBrowser.Controller.LiveTv;
using MediaBrowser.Controller.Library;
using MediaBrowser.Model.Dlna;
using MediaBrowser.Model.Drawing;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.Logging;
@ -17,7 +18,7 @@ namespace MediaBrowser.Api.Playback
public class StreamState : IDisposable
{
private readonly ILogger _logger;
private readonly ILiveTvManager _liveTvManager;
private readonly IMediaSourceManager _mediaSourceManager;
public string RequestedUrl { get; set; }
@ -39,7 +40,7 @@ namespace MediaBrowser.Api.Playback
public string InputContainer { get; set; }
public List<MediaStream> AllMediaStreams { get; set; }
public MediaSourceInfo MediaSource { get; set; }
public MediaStream AudioStream { get; set; }
public MediaStream VideoStream { get; set; }
@ -64,8 +65,6 @@ namespace MediaBrowser.Api.Playback
public List<string> PlayableStreamFileNames { get; set; }
public string LiveTvStreamId { get; set; }
public int SegmentLength = 3;
public bool EnableGenericHlsSegmenter = false;
public int HlsListSize
@ -86,14 +85,13 @@ namespace MediaBrowser.Api.Playback
public List<string> SupportedAudioCodecs { get; set; }
public StreamState(ILiveTvManager liveTvManager, ILogger logger)
public StreamState(IMediaSourceManager mediaSourceManager, ILogger logger)
{
_liveTvManager = liveTvManager;
_mediaSourceManager = mediaSourceManager;
_logger = logger;
SupportedAudioCodecs = new List<string>();
PlayableStreamFileNames = new List<string>();
RemoteHttpHeaders = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
AllMediaStreams = new List<MediaStream>();
}
public string InputAudioSync { get; set; }
@ -113,9 +111,6 @@ namespace MediaBrowser.Api.Playback
public long? EncodingDurationTicks { get; set; }
public string ItemType { get; set; }
public string ItemId { get; set; }
public string GetMimeType(string outputPath)
{
if (!string.IsNullOrEmpty(MimeType))
@ -187,15 +182,15 @@ namespace MediaBrowser.Api.Playback
private async void DisposeLiveStream()
{
if (!string.IsNullOrEmpty(LiveTvStreamId))
if (MediaSource.RequiresClosing && string.IsNullOrWhiteSpace(Request.LiveStreamId))
{
try
{
await _liveTvManager.CloseLiveStream(LiveTvStreamId, CancellationToken.None).ConfigureAwait(false);
await _mediaSourceManager.CloseLiveStream(MediaSource.LiveStreamId, CancellationToken.None).ConfigureAwait(false);
}
catch (Exception ex)
{
_logger.ErrorException("Error closing live tv stream", ex);
_logger.ErrorException("Error closing media source", ex);
}
}
}
@ -351,6 +346,42 @@ namespace MediaBrowser.Api.Playback
}
}
public int? TargetVideoStreamCount
{
get
{
if (Request.Static)
{
return GetMediaStreamCount(MediaStreamType.Video, int.MaxValue);
}
return GetMediaStreamCount(MediaStreamType.Video, 1);
}
}
public int? TargetAudioStreamCount
{
get
{
if (Request.Static)
{
return GetMediaStreamCount(MediaStreamType.Audio, int.MaxValue);
}
return GetMediaStreamCount(MediaStreamType.Audio, 1);
}
}
private int? GetMediaStreamCount(MediaStreamType type, int limit)
{
var count = MediaSource.GetStreamCount(type);
if (count.HasValue)
{
count = Math.Min(count.Value, limit);
}
return count;
}
/// <summary>
/// Predicts the audio sample rate that will be in the output stream
/// </summary>

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.IO;
using System.Threading;
@ -11,13 +13,18 @@ namespace MediaBrowser.Api.Playback
private readonly ILogger _logger;
private Timer _timer;
private bool _isPaused;
private readonly IConfigurationManager _config;
private readonly long _gapLengthInTicks = TimeSpan.FromMinutes(2).Ticks;
public TranscodingThrottler(TranscodingJob job, ILogger logger)
public TranscodingThrottler(TranscodingJob job, ILogger logger, IConfigurationManager config)
{
_job = job;
_logger = logger;
_config = config;
}
private EncodingOptions GetOptions()
{
return _config.GetConfiguration<EncodingOptions>("encoding");
}
public void Start()
@ -33,7 +40,9 @@ namespace MediaBrowser.Api.Playback
return;
}
if (IsThrottleAllowed(_job))
var options = GetOptions();
if (options.EnableThrottling && IsThrottleAllowed(_job, options.ThrottleThresholdSeconds))
{
PauseTranscoding();
}
@ -79,19 +88,20 @@ namespace MediaBrowser.Api.Playback
}
}
private bool IsThrottleAllowed(TranscodingJob job)
private bool IsThrottleAllowed(TranscodingJob job, int thresholdSeconds)
{
var bytesDownloaded = job.BytesDownloaded ?? 0;
var transcodingPositionTicks = job.TranscodingPositionTicks ?? 0;
var downloadPositionTicks = job.DownloadPositionTicks ?? 0;
var path = job.Path;
var gapLengthInTicks = TimeSpan.FromSeconds(thresholdSeconds).Ticks;
if (downloadPositionTicks > 0 && transcodingPositionTicks > 0)
{
// HLS - time-based consideration
var targetGap = _gapLengthInTicks;
var targetGap = gapLengthInTicks;
var gap = transcodingPositionTicks - downloadPositionTicks;
if (gap < targetGap)
@ -113,7 +123,7 @@ namespace MediaBrowser.Api.Playback
var bytesTranscoded = job.BytesTranscoded ?? new FileInfo(path).Length;
// Estimate the bytes the transcoder should be ahead
double gapFactor = _gapLengthInTicks;
double gapFactor = gapLengthInTicks;
gapFactor /= transcodingPositionTicks;
var targetGap = bytesTranscoded * gapFactor;

View File

@ -192,7 +192,7 @@ namespace MediaBrowser.Api.Subtitles
{
var item = (Video)_libraryManager.GetItemById(new Guid(request.Id));
var mediaSource = item.GetMediaSources(false)
var mediaSource = _mediaSourceManager.GetStaticMediaSources(item, false, null)
.First(i => string.Equals(i.Id, request.MediaSourceId ?? request.Id));
var subtitleStream = mediaSource.MediaStreams

View File

@ -55,21 +55,29 @@ namespace MediaBrowser.Api.UserLibrary
/// <returns>Task{ItemsResult}.</returns>
protected ItemsResult GetResult(GetItemsByName request)
{
var dtoOptions = GetDtoOptions(request);
User user = null;
BaseItem parentItem;
List<BaseItem> libraryItems;
List<BaseItem> libraryItems = null;
if (request.UserId.HasValue)
{
user = UserManager.GetUserById(request.UserId.Value);
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
{
parentItem = string.IsNullOrEmpty(request.ParentId) ? LibraryManager.RootFolder : LibraryManager.GetItemById(request.ParentId);
libraryItems = LibraryManager.RootFolder.GetRecursiveChildren().ToList();
if (RequiresLibraryItems(request, dtoOptions))
{
libraryItems = LibraryManager.RootFolder.GetRecursiveChildren().ToList();
}
}
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 => GetDto(i.Item1, user, dtoOptions, i.Item2));
var dtos = tuples.Select(i => DtoService.GetItemByNameDto(i.Item1, dtoOptions, i.Item2, user));
result.Items = dtos.Where(i => i != null).ToArray();
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)
{
var filters = request.GetFilters().ToList();
@ -340,21 +377,6 @@ namespace MediaBrowser.Api.UserLibrary
/// <param name="items">The items.</param>
/// <returns>IEnumerable{Task{`0}}.</returns>
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>

View File

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

View File

@ -107,30 +107,40 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager
private WebRequest GetRequest(HttpRequestOptions options, string method, bool enableHttpCompression)
{
var request = (HttpWebRequest)WebRequest.Create(options.Url);
var request = WebRequest.Create(options.Url);
var httpWebRequest = request as HttpWebRequest;
AddRequestHeaders(request, options);
if (httpWebRequest != null)
{
AddRequestHeaders(httpWebRequest, options);
request.AutomaticDecompression = enableHttpCompression ? DecompressionMethods.Deflate : DecompressionMethods.None;
httpWebRequest.AutomaticDecompression = enableHttpCompression ? DecompressionMethods.Deflate : DecompressionMethods.None;
}
request.CachePolicy = new RequestCachePolicy(RequestCacheLevel.BypassCache);
if (options.EnableKeepAlive)
if (httpWebRequest != null)
{
request.KeepAlive = true;
if (options.EnableKeepAlive)
{
httpWebRequest.KeepAlive = true;
}
}
request.Method = method;
request.Timeout = options.TimeoutMs;
if (!string.IsNullOrEmpty(options.Host))
if (httpWebRequest != null)
{
request.Host = options.Host;
}
if (!string.IsNullOrEmpty(options.Host))
{
httpWebRequest.Host = options.Host;
}
if (!string.IsNullOrEmpty(options.Referer))
{
request.Referer = options.Referer;
if (!string.IsNullOrEmpty(options.Referer))
{
httpWebRequest.Referer = options.Referer;
}
}
//request.ServicePoint.BindIPEndPointDelegate = BindIPEndPointCallback;

View File

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

View File

@ -75,17 +75,23 @@ namespace MediaBrowser.Controller.Channels
public override IEnumerable<MediaSourceInfo> GetMediaSources(bool enablePathSubstitution)
{
var list = base.GetMediaSources(enablePathSubstitution).ToList();
var sources = ChannelManager.GetChannelItemMediaSources(Id.ToString("N"), false, CancellationToken.None)
.Result.ToList();
var sources = ChannelManager.GetStaticMediaSources(this, false, CancellationToken.None)
.Result.ToList();
if (sources.Count > 0)
{
return sources;
}
list.InsertRange(0, sources);
var list = base.GetMediaSources(enablePathSubstitution).ToList();
foreach (var mediaSource in list)
{
if (string.IsNullOrWhiteSpace(mediaSource.Path))
{
mediaSource.Type = MediaSourceType.Placeholder;
}
}
return list;
}

View File

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

View File

@ -90,17 +90,23 @@ namespace MediaBrowser.Controller.Channels
public override IEnumerable<MediaSourceInfo> GetMediaSources(bool enablePathSubstitution)
{
var list = base.GetMediaSources(enablePathSubstitution).ToList();
var sources = ChannelManager.GetChannelItemMediaSources(Id.ToString("N"), false, CancellationToken.None)
.Result.ToList();
var sources = ChannelManager.GetStaticMediaSources(this, false, CancellationToken.None)
.Result.ToList();
if (sources.Count > 0)
{
return sources;
}
list.InsertRange(0, sources);
var list = base.GetMediaSources(enablePathSubstitution).ToList();
foreach (var mediaSource in list)
{
if (string.IsNullOrWhiteSpace(mediaSource.Path))
{
mediaSource.Type = MediaSourceType.Placeholder;
}
}
return list;
}

View File

@ -112,11 +112,11 @@ namespace MediaBrowser.Controller.Channels
/// <summary>
/// Gets the channel item media sources.
/// </summary>
/// <param name="id">The identifier.</param>
/// <param name="includeDynamicSources">if set to <c>true</c> [include dynamic sources].</param>
/// <param name="item">The item.</param>
/// <param name="includeCachedVersions">if set to <c>true</c> [include cached versions].</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task{IEnumerable{MediaSourceInfo}}.</returns>
Task<IEnumerable<MediaSourceInfo>> GetChannelItemMediaSources(string id, bool includeDynamicSources, CancellationToken cancellationToken);
Task<IEnumerable<MediaSourceInfo>> GetStaticMediaSources(IChannelMediaItem item, bool includeCachedVersions, CancellationToken cancellationToken);
/// <summary>
/// Gets the channel folder.

View File

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

View File

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

View File

@ -301,5 +301,21 @@ namespace MediaBrowser.Controller.LiveTv
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task&lt;QueryResult&lt;BaseItem&gt;&gt;.</returns>
Task<QueryResult<BaseItem>> GetInternalRecordings(RecordingQuery query, CancellationToken cancellationToken);
/// <summary>
/// Gets the recording media sources.
/// </summary>
/// <param name="id">The identifier.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task&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.Model.Configuration;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Users;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
namespace MediaBrowser.Controller.LiveTv
{
@ -99,5 +101,20 @@ namespace MediaBrowser.Controller.LiveTv
{
return user.Policy.EnableLiveTvManagement;
}
public override IEnumerable<MediaSourceInfo> GetMediaSources(bool enablePathSubstitution)
{
var list = base.GetMediaSources(enablePathSubstitution).ToList();
foreach (var mediaSource in list)
{
if (string.IsNullOrWhiteSpace(mediaSource.Path))
{
mediaSource.Type = MediaSourceType.Placeholder;
}
}
return list;
}
}
}

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>
public bool? HasProviderImage { get; set; }
public override LocationType LocationType
{
get
{
// TODO: This should be removed
return LocationType.Remote;
}
}
protected override string CreateSortName()
{
double number = 0;
@ -127,7 +136,7 @@ namespace MediaBrowser.Controller.LiveTv
Name = Name,
Path = Path,
RunTimeTicks = RunTimeTicks,
Type = MediaSourceType.Default
Type = MediaSourceType.Placeholder
};
list.Add(info);

View File

@ -34,6 +34,12 @@ namespace MediaBrowser.Controller.LiveTv
/// <value>The channel identifier.</value>
public string ExternalChannelId { get; set; }
/// <summary>
/// Gets or sets the original air date.
/// </summary>
/// <value>The original air date.</value>
public DateTime? OriginalAirDate { get; set; }
/// <summary>
/// Gets or sets the type of the channel.
/// </summary>

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.Dto;
using MediaBrowser.Model.Entities;
using System.Linq;
using MediaBrowser.Model.Users;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
namespace MediaBrowser.Controller.LiveTv
{
@ -97,5 +99,20 @@ namespace MediaBrowser.Controller.LiveTv
{
return user.Policy.EnableLiveTvManagement;
}
public override IEnumerable<MediaSourceInfo> GetMediaSources(bool enablePathSubstitution)
{
var list = base.GetMediaSources(enablePathSubstitution).ToList();
foreach (var mediaSource in list)
{
if (string.IsNullOrWhiteSpace(mediaSource.Path))
{
mediaSource.Type = MediaSourceType.Placeholder;
}
}
return list;
}
}
}

View File

@ -393,12 +393,13 @@
<Compile Include="Subtitles\SubtitleDownloadEventArgs.cs" />
<Compile Include="Subtitles\SubtitleResponse.cs" />
<Compile Include="Subtitles\SubtitleSearchRequest.cs" />
<Compile Include="Sync\IHasDynamicAccess.cs" />
<Compile Include="Sync\IServerSyncProvider.cs" />
<Compile Include="Sync\ISyncDataProvider.cs" />
<Compile Include="Sync\ISyncManager.cs" />
<Compile Include="Sync\ISyncProvider.cs" />
<Compile Include="Sync\ISyncRepository.cs" />
<Compile Include="Sync\SendFileResult.cs" />
<Compile Include="Sync\SyncedFileInfo.cs" />
<Compile Include="Themes\IAppThemeManager.cs" />
<Compile Include="Themes\InternalThemeImage.cs" />
<Compile Include="TV\ITVSeriesManager.cs" />

View File

@ -1,4 +1,5 @@
using System.IO;
using MediaBrowser.Model.MediaInfo;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
@ -47,8 +48,9 @@ namespace MediaBrowser.Controller.MediaEncoding
/// Gets the subtitle language encoding parameter.
/// </summary>
/// <param name="path">The path.</param>
/// <param name="protocol">The protocol.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>System.String.</returns>
string GetSubtitleFileCharacterSet(string path);
Task<string> GetSubtitleFileCharacterSet(string path, MediaProtocol protocol, CancellationToken cancellationToken);
}
}

View File

@ -41,8 +41,6 @@ namespace MediaBrowser.Controller.MediaEncoding
streams = GetSortedStreams(streams, MediaStreamType.Subtitle, preferredLanguages)
.ToList();
var full = streams.Where(s => !s.IsForced);
MediaStream stream = null;
if (mode == SubtitlePlaybackMode.None)
@ -55,13 +53,13 @@ namespace MediaBrowser.Controller.MediaEncoding
// if the audio language is not understood by the user, load their preferred subs, if there are any
if (!ContainsOrdinal(preferredLanguages, audioTrackLanguage))
{
stream = full.FirstOrDefault(s => ContainsOrdinal(preferredLanguages, s.Language));
stream = streams.Where(s => !s.IsForced).FirstOrDefault(s => ContainsOrdinal(preferredLanguages, s.Language));
}
}
else if (mode == SubtitlePlaybackMode.Always)
{
// always load the most suitable full subtitles
stream = full.FirstOrDefault();
stream = streams.FirstOrDefault(s => !s.IsForced);
}
// load forced subs if we have found no suitable full subtitles
@ -97,6 +95,77 @@ namespace MediaBrowser.Controller.MediaEncoding
.ThenBy(i => i.Index);
}
public static void SetSubtitleStreamScores(List<MediaStream> streams,
List<string> preferredLanguages,
SubtitlePlaybackMode mode,
string audioTrackLanguage)
{
if (mode == SubtitlePlaybackMode.None)
{
return;
}
streams = GetSortedStreams(streams, MediaStreamType.Subtitle, preferredLanguages)
.ToList();
var filteredStreams = new List<MediaStream>();
if (mode == SubtitlePlaybackMode.Default)
{
// if the audio language is not understood by the user, load their preferred subs, if there are any
if (!ContainsOrdinal(preferredLanguages, audioTrackLanguage))
{
filteredStreams = streams.Where(s => !s.IsForced && ContainsOrdinal(preferredLanguages, s.Language))
.ToList();
}
}
else if (mode == SubtitlePlaybackMode.Always)
{
// always load the most suitable full subtitles
filteredStreams = streams.Where(s => !s.IsForced)
.ToList();
}
// load forced subs if we have found no suitable full subtitles
if (filteredStreams.Count == 0)
{
filteredStreams = streams
.Where(s => s.IsForced && string.Equals(s.Language, audioTrackLanguage, StringComparison.OrdinalIgnoreCase))
.ToList();
}
foreach (var stream in filteredStreams)
{
stream.Score = GetSubtitleScore(stream, preferredLanguages);
}
}
private static int GetSubtitleScore(MediaStream stream, List<string> languagePreferences)
{
var values = new List<int>();
var index = languagePreferences.FindIndex(l => string.Equals(stream.Language, l, StringComparison.OrdinalIgnoreCase));
values.Add(index == -1 ? 0 : 100 - index);
values.Add(stream.IsDefault ? 1 : 0);
values.Add(stream.SupportsExternalStream ? 1 : 0);
values.Add(stream.IsTextSubtitleStream ? 1 : 0);
values.Add(stream.IsExternal ? 1 : 0);
values.Reverse();
var scale = 1;
var score = 0;
foreach (var value in values)
{
score += scale * (value + 1);
scale *= 10;
}
return score;
}
private static int GetBooleanOrderBy(bool value)
{
return value ? 0 : 1;

View File

@ -1,5 +1,4 @@
using MediaBrowser.Common.Net;
using MediaBrowser.Model.Logging;
using MediaBrowser.Model.Logging;
using MediaBrowser.Model.Net;
using System;
using System.Collections.Generic;
@ -75,12 +74,12 @@ namespace MediaBrowser.Controller.Net
throw new ArgumentNullException("message");
}
if (message.MessageType.Equals(Name + "Start", StringComparison.OrdinalIgnoreCase))
if (string.Equals(message.MessageType, Name + "Start", StringComparison.OrdinalIgnoreCase))
{
Start(message);
}
if (message.MessageType.Equals(Name + "Stop", StringComparison.OrdinalIgnoreCase))
if (string.Equals(message.MessageType, Name + "Stop", StringComparison.OrdinalIgnoreCase))
{
Stop(message);
}

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="cancellationToken">The cancellation token.</param>
/// <returns>Task.</returns>
Task<SendFileResult> SendFile(Stream stream, string remotePath, SyncTarget target, IProgress<double> progress, CancellationToken cancellationToken);
Task<SyncedFileInfo> SendFile(Stream stream, string remotePath, SyncTarget target, IProgress<double> progress, CancellationToken cancellationToken);
/// <summary>
/// Deletes the file.
@ -54,14 +54,5 @@ namespace MediaBrowser.Controller.Sync
/// <param name="target">The target.</param>
/// <returns>System.String.</returns>
string GetParentDirectoryPath(string path, SyncTarget target);
/// <summary>
/// Gets the file system entries.
/// </summary>
/// <param name="path">The path.</param>
/// <param name="target">The target.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task&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>
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>
/// Adds the or update.
/// </summary>
@ -46,5 +54,13 @@ namespace MediaBrowser.Controller.Sync
/// <param name="itemId">The item identifier.</param>
/// <returns>Task&lt;LocalItem&gt;.</returns>
Task<List<LocalItem>> GetCachedItems(SyncTarget target, string serverId, string itemId);
/// <summary>
/// Gets the cached items by synchronize job item identifier.
/// </summary>
/// <param name="target">The target.</param>
/// <param name="serverId">The server identifier.</param>
/// <param name="syncJobItemId">The synchronize job item identifier.</param>
/// <returns>Task&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 System.Collections.Generic;
namespace MediaBrowser.Controller.Sync
{
public class SendFileResult
public class SyncedFileInfo
{
/// <summary>
/// Gets or sets the path.
@ -14,5 +15,15 @@ namespace MediaBrowser.Controller.Sync
/// </summary>
/// <value>The protocol.</value>
public MediaProtocol Protocol { get; set; }
/// <summary>
/// Gets or sets the required HTTP headers.
/// </summary>
/// <value>The required HTTP headers.</value>
public Dictionary<string, string> RequiredHttpHeaders { get; set; }
public SyncedFileInfo()
{
RequiredHttpHeaders = new Dictionary<string, string>();
}
}
}

View File

@ -124,7 +124,7 @@ namespace MediaBrowser.Dlna.Didl
{
if (streamInfo == null)
{
var sources = _user == null ? video.GetMediaSources(true).ToList() : _mediaSourceManager.GetStaticMediaSources(video, true, _user).ToList();
var sources = _user == null ? _mediaSourceManager.GetStaticMediaSources(video, true).ToList() : _mediaSourceManager.GetStaticMediaSources(video, true, _user).ToList();
streamInfo = new StreamBuilder().BuildVideoItem(new VideoOptions
{
@ -158,16 +158,21 @@ namespace MediaBrowser.Dlna.Didl
streamInfo.TranscodeSeekInfo,
streamInfo.IsTargetAnamorphic,
streamInfo.IsTargetCabac,
streamInfo.TargetRefFrames);
streamInfo.TargetRefFrames,
streamInfo.TargetVideoStreamCount,
streamInfo.TargetAudioStreamCount);
foreach (var contentFeature in contentFeatureList)
{
AddVideoResource(container, video, deviceId, filter, contentFeature, streamInfo);
}
foreach (var subtitle in streamInfo.GetExternalSubtitles(_serverAddress, _accessToken, false))
foreach (var subtitle in streamInfo.GetSubtitleProfiles(false, _serverAddress, _accessToken))
{
AddSubtitleElement(container, subtitle);
if (subtitle.DeliveryMethod == SubtitleDeliveryMethod.External)
{
AddSubtitleElement(container, subtitle);
}
}
}
@ -280,7 +285,9 @@ namespace MediaBrowser.Dlna.Didl
streamInfo.TargetTimestamp,
streamInfo.IsTargetAnamorphic,
streamInfo.IsTargetCabac,
streamInfo.TargetRefFrames);
streamInfo.TargetRefFrames,
streamInfo.TargetVideoStreamCount,
streamInfo.TargetAudioStreamCount);
var filename = url.Substring(0, url.IndexOf('?'));
@ -344,7 +351,7 @@ namespace MediaBrowser.Dlna.Didl
if (streamInfo == null)
{
var sources = _user == null ? audio.GetMediaSources(true).ToList() : _mediaSourceManager.GetStaticMediaSources(audio, true, _user).ToList();
var sources = _user == null ? _mediaSourceManager.GetStaticMediaSources(audio, true).ToList() : _mediaSourceManager.GetStaticMediaSources(audio, true, _user).ToList();
streamInfo = new StreamBuilder().BuildAudioItem(new AudioOptions
{

View File

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

View File

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

View File

@ -14,7 +14,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
{
public class AudioEncoder : BaseEncoder
{
public AudioEncoder(MediaEncoder mediaEncoder, ILogger logger, IServerConfigurationManager configurationManager, IFileSystem fileSystem, ILiveTvManager liveTvManager, IIsoManager isoManager, ILibraryManager libraryManager, IChannelManager channelManager, ISessionManager sessionManager, ISubtitleEncoder subtitleEncoder, IMediaSourceManager mediaSourceManager) : base(mediaEncoder, logger, configurationManager, fileSystem, liveTvManager, isoManager, libraryManager, channelManager, sessionManager, subtitleEncoder, mediaSourceManager)
public AudioEncoder(MediaEncoder mediaEncoder, ILogger logger, IServerConfigurationManager configurationManager, IFileSystem fileSystem, IIsoManager isoManager, ILibraryManager libraryManager, ISessionManager sessionManager, ISubtitleEncoder subtitleEncoder, IMediaSourceManager mediaSourceManager) : base(mediaEncoder, logger, configurationManager, fileSystem, isoManager, libraryManager, sessionManager, subtitleEncoder, mediaSourceManager)
{
}

View File

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

View File

@ -1,7 +1,8 @@
using MediaBrowser.Controller.LiveTv;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.Model.Dlna;
using MediaBrowser.Model.Drawing;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.Logging;
@ -26,7 +27,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
public EncodingJobOptions Options { get; set; }
public string InputContainer { get; set; }
public List<MediaStream> AllMediaStreams { get; set; }
public MediaSourceInfo MediaSource { get; set; }
public MediaStream AudioStream { get; set; }
public MediaStream VideoStream { get; set; }
public MediaStream SubtitleStream { get; set; }
@ -76,12 +77,12 @@ namespace MediaBrowser.MediaEncoding.Encoder
}
private readonly ILogger _logger;
private readonly ILiveTvManager _liveTvManager;
private readonly IMediaSourceManager _mediaSourceManager;
public EncodingJob(ILogger logger, ILiveTvManager liveTvManager)
public EncodingJob(ILogger logger, IMediaSourceManager mediaSourceManager)
{
_logger = logger;
_liveTvManager = liveTvManager;
_mediaSourceManager = mediaSourceManager;
Id = Guid.NewGuid().ToString("N");
RemoteHttpHeaders = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
@ -89,7 +90,6 @@ namespace MediaBrowser.MediaEncoding.Encoder
SupportedAudioCodecs = new List<string>();
PlayableStreamFileNames = new List<string>();
RemoteHttpHeaders = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
AllMediaStreams = new List<MediaStream>();
TaskCompletionSource = new TaskCompletionSource<bool>();
}
@ -136,15 +136,15 @@ namespace MediaBrowser.MediaEncoding.Encoder
private async void DisposeLiveStream()
{
if (!string.IsNullOrEmpty(LiveTvStreamId))
if (MediaSource.RequiresClosing)
{
try
{
await _liveTvManager.CloseLiveStream(LiveTvStreamId, CancellationToken.None).ConfigureAwait(false);
await _mediaSourceManager.CloseLiveStream(MediaSource.LiveStreamId, CancellationToken.None).ConfigureAwait(false);
}
catch (Exception ex)
{
_logger.ErrorException("Error closing live tv stream", ex);
_logger.ErrorException("Error closing media source", ex);
}
}
}
@ -394,6 +394,42 @@ namespace MediaBrowser.MediaEncoding.Encoder
}
}
public int? TargetVideoStreamCount
{
get
{
if (Options.Static)
{
return GetMediaStreamCount(MediaStreamType.Video, int.MaxValue);
}
return GetMediaStreamCount(MediaStreamType.Video, 1);
}
}
public int? TargetAudioStreamCount
{
get
{
if (Options.Static)
{
return GetMediaStreamCount(MediaStreamType.Audio, int.MaxValue);
}
return GetMediaStreamCount(MediaStreamType.Audio, 1);
}
}
private int? GetMediaStreamCount(MediaStreamType type, int limit)
{
var count = MediaSource.GetStreamCount(type);
if (count.HasValue)
{
count = Math.Min(count.Value, limit);
}
return count;
}
public void ReportTranscodingProgress(TimeSpan? transcodingPosition, float? framerate, double? percentComplete, long? bytesTranscoded)
{
var ticks = transcodingPosition.HasValue ? transcodingPosition.Value.Ticks : (long?)null;

View File

@ -1,9 +1,10 @@
using MediaBrowser.Controller.Channels;
using System.IO;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.LiveTv;
using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.Model.Dlna;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Logging;
using MediaBrowser.Model.MediaInfo;
@ -19,19 +20,15 @@ namespace MediaBrowser.MediaEncoding.Encoder
public class EncodingJobFactory
{
private readonly ILogger _logger;
private readonly ILiveTvManager _liveTvManager;
private readonly ILibraryManager _libraryManager;
private readonly IChannelManager _channelManager;
private readonly IMediaSourceManager _mediaSourceManager;
protected static readonly CultureInfo UsCulture = new CultureInfo("en-US");
public EncodingJobFactory(ILogger logger, ILiveTvManager liveTvManager, ILibraryManager libraryManager, IChannelManager channelManager, IMediaSourceManager mediaSourceManager)
public EncodingJobFactory(ILogger logger, ILibraryManager libraryManager, IMediaSourceManager mediaSourceManager)
{
_logger = logger;
_liveTvManager = liveTvManager;
_libraryManager = libraryManager;
_channelManager = channelManager;
_mediaSourceManager = mediaSourceManager;
}
@ -42,9 +39,9 @@ namespace MediaBrowser.MediaEncoding.Encoder
if (string.IsNullOrEmpty(request.AudioCodec))
{
request.AudioCodec = InferAudioCodec(request.OutputContainer);
}
var state = new EncodingJob(_logger, _liveTvManager)
}
var state = new EncodingJob(_logger, _mediaSourceManager)
{
Options = options,
IsVideoRequest = isVideoRequest,
@ -58,106 +55,17 @@ namespace MediaBrowser.MediaEncoding.Encoder
}
var item = _libraryManager.GetItemById(request.ItemId);
List<MediaStream> mediaStreams = null;
state.ItemType = item.GetType().Name;
if (item is ILiveTvRecording)
{
var recording = await _liveTvManager.GetInternalRecording(request.ItemId, cancellationToken).ConfigureAwait(false);
state.IsInputVideo = string.Equals(item.MediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase);
state.VideoType = VideoType.VideoFile;
state.IsInputVideo = string.Equals(recording.MediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase);
var mediaSources = await _mediaSourceManager.GetPlayackMediaSources(request.ItemId, false, cancellationToken).ConfigureAwait(false);
var path = recording.RecordingInfo.Path;
var mediaUrl = recording.RecordingInfo.Url;
var source = string.IsNullOrEmpty(request.MediaSourceId)
? recording.GetMediaSources(false).First()
: _mediaSourceManager.GetStaticMediaSource(recording, request.MediaSourceId, false);
var mediaSource = string.IsNullOrEmpty(request.MediaSourceId)
? mediaSources.First()
: mediaSources.First(i => string.Equals(i.Id, request.MediaSourceId));
mediaStreams = source.MediaStreams;
// Just to prevent this from being null and causing other methods to fail
state.MediaPath = string.Empty;
if (!string.IsNullOrEmpty(path))
{
state.MediaPath = path;
state.InputProtocol = MediaProtocol.File;
}
else if (!string.IsNullOrEmpty(mediaUrl))
{
state.MediaPath = mediaUrl;
state.InputProtocol = MediaProtocol.Http;
}
state.RunTimeTicks = recording.RunTimeTicks;
state.DeInterlace = true;
state.OutputAudioSync = "1000";
state.InputVideoSync = "-1";
state.InputAudioSync = "1";
state.InputContainer = recording.Container;
state.ReadInputAtNativeFramerate = source.ReadAtNativeFramerate;
}
else if (item is LiveTvChannel)
{
var channel = _liveTvManager.GetInternalChannel(request.ItemId);
state.VideoType = VideoType.VideoFile;
state.IsInputVideo = string.Equals(channel.MediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase);
mediaStreams = new List<MediaStream>();
state.DeInterlace = true;
// Just to prevent this from being null and causing other methods to fail
state.MediaPath = string.Empty;
}
else
{
var mediaSources = await _mediaSourceManager.GetPlayackMediaSources(request.ItemId, false, cancellationToken).ConfigureAwait(false);
var mediaSource = string.IsNullOrEmpty(request.MediaSourceId)
? mediaSources.First()
: mediaSources.First(i => string.Equals(i.Id, request.MediaSourceId));
mediaStreams = mediaSource.MediaStreams;
state.MediaPath = mediaSource.Path;
state.InputProtocol = mediaSource.Protocol;
state.InputContainer = mediaSource.Container;
state.InputFileSize = mediaSource.Size;
state.InputBitrate = mediaSource.Bitrate;
state.ReadInputAtNativeFramerate = mediaSource.ReadAtNativeFramerate;
state.RunTimeTicks = mediaSource.RunTimeTicks;
state.RemoteHttpHeaders = mediaSource.RequiredHttpHeaders;
var video = item as Video;
if (video != null)
{
state.IsInputVideo = true;
if (mediaSource.VideoType.HasValue)
{
state.VideoType = mediaSource.VideoType.Value;
}
state.IsoType = mediaSource.IsoType;
state.PlayableStreamFileNames = mediaSource.PlayableStreamFileNames.ToList();
if (mediaSource.Timestamp.HasValue)
{
state.InputTimestamp = mediaSource.Timestamp.Value;
}
}
state.RunTimeTicks = mediaSource.RunTimeTicks;
}
AttachMediaStreamInfo(state, mediaStreams, request);
AttachMediaStreamInfo(state, mediaSource, options);
state.OutputAudioBitrate = GetAudioBitrateParam(request, state.AudioStream);
state.OutputAudioSampleRate = request.AudioSampleRate;
@ -185,26 +93,73 @@ namespace MediaBrowser.MediaEncoding.Encoder
ApplyDeviceProfileSettings(state);
if (isVideoRequest)
{
if (state.VideoStream != null && CanStreamCopyVideo(request, state.VideoStream))
{
state.OutputVideoCodec = "copy";
}
if (state.AudioStream != null && CanStreamCopyAudio(request, state.AudioStream, state.SupportedAudioCodecs))
{
state.OutputAudioCodec = "copy";
}
}
TryStreamCopy(state, request);
return state;
}
internal static void AttachMediaStreamInfo(EncodingJob state,
List<MediaStream> mediaStreams,
internal static void TryStreamCopy(EncodingJob state,
EncodingJobOptions videoRequest)
{
if (state.IsVideoRequest)
{
if (state.VideoStream != null && CanStreamCopyVideo(videoRequest, state.VideoStream))
{
state.OutputVideoCodec = "copy";
}
if (state.AudioStream != null && CanStreamCopyAudio(videoRequest, state.AudioStream, state.SupportedAudioCodecs))
{
state.OutputAudioCodec = "copy";
}
}
}
internal static void AttachMediaStreamInfo(EncodingJob state,
MediaSourceInfo mediaSource,
EncodingJobOptions videoRequest)
{
state.MediaPath = mediaSource.Path;
state.InputProtocol = mediaSource.Protocol;
state.InputContainer = mediaSource.Container;
state.InputFileSize = mediaSource.Size;
state.InputBitrate = mediaSource.Bitrate;
state.ReadInputAtNativeFramerate = mediaSource.ReadAtNativeFramerate;
state.RunTimeTicks = mediaSource.RunTimeTicks;
state.RemoteHttpHeaders = mediaSource.RequiredHttpHeaders;
if (mediaSource.VideoType.HasValue)
{
state.VideoType = mediaSource.VideoType.Value;
}
state.IsoType = mediaSource.IsoType;
state.PlayableStreamFileNames = mediaSource.PlayableStreamFileNames.ToList();
if (mediaSource.Timestamp.HasValue)
{
state.InputTimestamp = mediaSource.Timestamp.Value;
}
state.InputProtocol = mediaSource.Protocol;
state.MediaPath = mediaSource.Path;
state.RunTimeTicks = mediaSource.RunTimeTicks;
state.RemoteHttpHeaders = mediaSource.RequiredHttpHeaders;
state.InputBitrate = mediaSource.Bitrate;
state.InputFileSize = mediaSource.Size;
state.ReadInputAtNativeFramerate = mediaSource.ReadAtNativeFramerate;
if (state.ReadInputAtNativeFramerate ||
mediaSource.Protocol == MediaProtocol.File && string.Equals(mediaSource.Container, "wtv", StringComparison.OrdinalIgnoreCase))
{
state.OutputAudioSync = "1000";
state.InputVideoSync = "-1";
state.InputAudioSync = "1";
}
var mediaStreams = mediaSource.MediaStreams;
if (videoRequest != null)
{
if (string.IsNullOrEmpty(videoRequest.VideoCodec))
@ -233,7 +188,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
state.AudioStream = GetMediaStream(mediaStreams, null, MediaStreamType.Audio, true);
}
state.AllMediaStreams = mediaStreams;
state.MediaSource = mediaSource;
}
/// <summary>
@ -771,7 +726,9 @@ namespace MediaBrowser.MediaEncoding.Encoder
state.TargetTimestamp,
state.IsTargetAnamorphic,
state.IsTargetCabac,
state.TargetRefFrames);
state.TargetRefFrames,
state.TargetVideoStreamCount,
state.TargetAudioStreamCount);
if (mediaProfile != null)
{

View File

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

View File

@ -15,7 +15,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
{
public class VideoEncoder : BaseEncoder
{
public VideoEncoder(MediaEncoder mediaEncoder, ILogger logger, IServerConfigurationManager configurationManager, IFileSystem fileSystem, ILiveTvManager liveTvManager, IIsoManager isoManager, ILibraryManager libraryManager, IChannelManager channelManager, ISessionManager sessionManager, ISubtitleEncoder subtitleEncoder, IMediaSourceManager mediaSourceManager) : base(mediaEncoder, logger, configurationManager, fileSystem, liveTvManager, isoManager, libraryManager, channelManager, sessionManager, subtitleEncoder, mediaSourceManager)
public VideoEncoder(MediaEncoder mediaEncoder, ILogger logger, IServerConfigurationManager configurationManager, IFileSystem fileSystem, IIsoManager isoManager, ILibraryManager libraryManager, ISessionManager sessionManager, ISubtitleEncoder subtitleEncoder, IMediaSourceManager mediaSourceManager) : base(mediaEncoder, logger, configurationManager, fileSystem, isoManager, libraryManager, sessionManager, subtitleEncoder, mediaSourceManager)
{
}
@ -73,10 +73,13 @@ namespace MediaBrowser.MediaEncoding.Encoder
args;
}
var keyFrameArg = string.Format(" -force_key_frames expr:gte(t,n_forced*{0})",
5.ToString(UsCulture));
if (state.Options.Context == EncodingContext.Streaming)
{
var keyFrameArg = string.Format(" -force_key_frames expr:gte(t,n_forced*{0})",
5.ToString(UsCulture));
args += keyFrameArg;
args += keyFrameArg;
}
var hasGraphicalSubs = state.SubtitleStream != null && !state.SubtitleStream.IsTextSubtitleStream;

View File

@ -43,10 +43,11 @@ namespace MediaBrowser.MediaEncoding.Subtitles
subEvent.StartPositionTicks = GetTicks(sections[headers["Start"]]);
subEvent.EndPositionTicks = GetTicks(sections[headers["End"]]);
//RemoteNativeFormatting(subEvent);
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);
trackInfo.TrackEvents.Add(subEvent);

View File

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

View File

@ -1,6 +1,7 @@
using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Common.IO;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.MediaEncoding;
@ -29,8 +30,10 @@ namespace MediaBrowser.MediaEncoding.Subtitles
private readonly IFileSystem _fileSystem;
private readonly IMediaEncoder _mediaEncoder;
private readonly IJsonSerializer _json;
private readonly IHttpClient _httpClient;
private readonly IMediaSourceManager _mediaSourceManager;
public SubtitleEncoder(ILibraryManager libraryManager, ILogger logger, IApplicationPaths appPaths, IFileSystem fileSystem, IMediaEncoder mediaEncoder, IJsonSerializer json)
public SubtitleEncoder(ILibraryManager libraryManager, ILogger logger, IApplicationPaths appPaths, IFileSystem fileSystem, IMediaEncoder mediaEncoder, IJsonSerializer json, IHttpClient httpClient, IMediaSourceManager mediaSourceManager)
{
_libraryManager = libraryManager;
_logger = logger;
@ -38,6 +41,8 @@ namespace MediaBrowser.MediaEncoding.Subtitles
_fileSystem = fileSystem;
_mediaEncoder = mediaEncoder;
_json = json;
_httpClient = httpClient;
_mediaSourceManager = mediaSourceManager;
}
private string SubtitleCachePath
@ -127,9 +132,9 @@ namespace MediaBrowser.MediaEncoding.Subtitles
int subtitleStreamIndex,
CancellationToken cancellationToken)
{
var item = (Video)_libraryManager.GetItemById(new Guid(itemId));
var mediaSources = await _mediaSourceManager.GetPlayackMediaSources(itemId, false, cancellationToken).ConfigureAwait(false);
var mediaSource = item.GetMediaSources(false)
var mediaSource = mediaSources
.First(i => string.Equals(i.Id, mediaSourceId));
var subtitleStream = mediaSource.MediaStreams
@ -149,20 +154,20 @@ namespace MediaBrowser.MediaEncoding.Subtitles
var fileInfo = await GetReadableFile(mediaSource.Path, inputFiles, mediaSource.Protocol, subtitleStream, cancellationToken).ConfigureAwait(false);
var stream = await GetSubtitleStream(fileInfo.Item1, fileInfo.Item3).ConfigureAwait(false);
var stream = await GetSubtitleStream(fileInfo.Item1, fileInfo.Item2, fileInfo.Item4, cancellationToken).ConfigureAwait(false);
return new Tuple<Stream, string>(stream, fileInfo.Item2);
return new Tuple<Stream, string>(stream, fileInfo.Item3);
}
private async Task<Stream> GetSubtitleStream(string path, bool requiresCharset)
private async Task<Stream> GetSubtitleStream(string path, MediaProtocol protocol, bool requiresCharset, CancellationToken cancellationToken)
{
if (requiresCharset)
{
var charset = GetSubtitleFileCharacterSet(path);
var charset = await GetSubtitleFileCharacterSet(path, protocol, cancellationToken).ConfigureAwait(false);
if (!string.IsNullOrEmpty(charset))
{
using (var fs = _fileSystem.GetFileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, true))
using (var fs = await GetStream(path, protocol, cancellationToken).ConfigureAwait(false))
{
using (var reader = new StreamReader(fs, GetEncoding(charset)))
{
@ -196,7 +201,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
}
}
private async Task<Tuple<string, string, bool>> GetReadableFile(string mediaPath,
private async Task<Tuple<string, MediaProtocol, string, bool>> GetReadableFile(string mediaPath,
string[] inputFiles,
MediaProtocol protocol,
MediaStream subtitleStream,
@ -228,12 +233,12 @@ namespace MediaBrowser.MediaEncoding.Subtitles
}
// Extract
var outputPath = GetSubtitleCachePath(mediaPath, subtitleStream.Index, "." + outputFormat);
var outputPath = GetSubtitleCachePath(mediaPath, protocol, subtitleStream.Index, "." + outputFormat);
await ExtractTextSubtitle(inputFiles, protocol, subtitleStream.Index, outputCodec, outputPath, cancellationToken)
.ConfigureAwait(false);
return new Tuple<string, string, bool>(outputPath, outputFormat, false);
return new Tuple<string, MediaProtocol, string, bool>(outputPath, MediaProtocol.File, outputFormat, false);
}
var currentFormat = (Path.GetExtension(subtitleStream.Path) ?? subtitleStream.Codec)
@ -242,14 +247,14 @@ namespace MediaBrowser.MediaEncoding.Subtitles
if (GetReader(currentFormat, false) == null)
{
// Convert
var outputPath = GetSubtitleCachePath(mediaPath, subtitleStream.Index, ".srt");
var outputPath = GetSubtitleCachePath(mediaPath, protocol, subtitleStream.Index, ".srt");
await ConvertTextSubtitleToSrt(subtitleStream.Path, outputPath, cancellationToken).ConfigureAwait(false);
await ConvertTextSubtitleToSrt(subtitleStream.Path, protocol, outputPath, cancellationToken).ConfigureAwait(false);
return new Tuple<string, string, bool>(outputPath, "srt", true);
return new Tuple<string, MediaProtocol, string, bool>(outputPath, MediaProtocol.File, "srt", true);
}
return new Tuple<string, string, bool>(subtitleStream.Path, currentFormat, true);
return new Tuple<string, MediaProtocol, string, bool>(subtitleStream.Path, protocol, currentFormat, true);
}
private async Task<SubtitleTrackInfo> GetTrackInfo(Stream stream,
@ -336,10 +341,11 @@ namespace MediaBrowser.MediaEncoding.Subtitles
/// Converts the text subtitle to SRT.
/// </summary>
/// <param name="inputPath">The input path.</param>
/// <param name="inputProtocol">The input protocol.</param>
/// <param name="outputPath">The output path.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task.</returns>
public async Task ConvertTextSubtitleToSrt(string inputPath, string outputPath, CancellationToken cancellationToken)
private async Task ConvertTextSubtitleToSrt(string inputPath, MediaProtocol inputProtocol, string outputPath, CancellationToken cancellationToken)
{
var semaphore = GetLock(outputPath);
@ -349,7 +355,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
{
if (!File.Exists(outputPath))
{
await ConvertTextSubtitleToSrtInternal(inputPath, outputPath).ConfigureAwait(false);
await ConvertTextSubtitleToSrtInternal(inputPath, inputProtocol, outputPath, cancellationToken).ConfigureAwait(false);
}
}
finally
@ -362,13 +368,17 @@ namespace MediaBrowser.MediaEncoding.Subtitles
/// Converts the text subtitle to SRT internal.
/// </summary>
/// <param name="inputPath">The input path.</param>
/// <param name="inputProtocol">The input protocol.</param>
/// <param name="outputPath">The output path.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task.</returns>
/// <exception cref="System.ArgumentNullException">inputPath
/// <exception cref="System.ArgumentNullException">
/// inputPath
/// or
/// outputPath</exception>
/// outputPath
/// </exception>
/// <exception cref="System.ApplicationException"></exception>
private async Task ConvertTextSubtitleToSrtInternal(string inputPath, string outputPath)
private async Task ConvertTextSubtitleToSrtInternal(string inputPath, MediaProtocol inputProtocol, string outputPath, CancellationToken cancellationToken)
{
if (string.IsNullOrEmpty(inputPath))
{
@ -382,7 +392,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
Directory.CreateDirectory(Path.GetDirectoryName(outputPath));
var encodingParam = GetSubtitleFileCharacterSet(inputPath);
var encodingParam = await GetSubtitleFileCharacterSet(inputPath, inputProtocol, cancellationToken).ConfigureAwait(false);
if (!string.IsNullOrEmpty(encodingParam))
{
@ -688,32 +698,41 @@ namespace MediaBrowser.MediaEncoding.Subtitles
}
}
private string GetSubtitleCachePath(string mediaPath, int subtitleStreamIndex, string outputSubtitleExtension)
private string GetSubtitleCachePath(string mediaPath, MediaProtocol protocol, int subtitleStreamIndex, string outputSubtitleExtension)
{
var ticksParam = string.Empty;
if (protocol == MediaProtocol.File)
{
var ticksParam = string.Empty;
var date = _fileSystem.GetLastWriteTimeUtc(mediaPath);
var date = _fileSystem.GetLastWriteTimeUtc(mediaPath);
var filename = (mediaPath + "_" + subtitleStreamIndex.ToString(CultureInfo.InvariantCulture) + "_" + date.Ticks.ToString(CultureInfo.InvariantCulture) + ticksParam).GetMD5() + outputSubtitleExtension;
var filename = (mediaPath + "_" + subtitleStreamIndex.ToString(CultureInfo.InvariantCulture) + "_" + date.Ticks.ToString(CultureInfo.InvariantCulture) + ticksParam).GetMD5() + outputSubtitleExtension;
var prefix = filename.Substring(0, 1);
var prefix = filename.Substring(0, 1);
return Path.Combine(SubtitleCachePath, prefix, filename);
return Path.Combine(SubtitleCachePath, prefix, filename);
}
else
{
var filename = (mediaPath + "_" + subtitleStreamIndex.ToString(CultureInfo.InvariantCulture)).GetMD5() + outputSubtitleExtension;
var prefix = filename.Substring(0, 1);
return Path.Combine(SubtitleCachePath, prefix, filename);
}
}
/// <summary>
/// Gets the subtitle language encoding param.
/// </summary>
/// <param name="path">The path.</param>
/// <returns>System.String.</returns>
public string GetSubtitleFileCharacterSet(string path)
public async Task<string> GetSubtitleFileCharacterSet(string path, MediaProtocol protocol, CancellationToken cancellationToken)
{
if (GetFileEncoding(path).Equals(Encoding.UTF8))
if (protocol == MediaProtocol.File)
{
return string.Empty;
if (GetFileEncoding(path).Equals(Encoding.UTF8))
{
return string.Empty;
}
}
var charset = DetectCharset(path);
var charset = await DetectCharset(path, protocol, cancellationToken).ConfigureAwait(false);
if (!string.IsNullOrWhiteSpace(charset))
{
@ -769,11 +788,11 @@ namespace MediaBrowser.MediaEncoding.Subtitles
}
}
private string DetectCharset(string path)
private async Task<string> DetectCharset(string path, MediaProtocol protocol, CancellationToken cancellationToken)
{
try
{
using (var file = _fileSystem.GetFileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
using (var file = await GetStream(path, protocol, cancellationToken).ConfigureAwait(false))
{
var detector = new CharsetDetector();
detector.Feed(file);
@ -819,5 +838,19 @@ namespace MediaBrowser.MediaEncoding.Subtitles
// It's ok - anything aside from utf is ok since that's what we're looking for
return Encoding.Default;
}
private async Task<Stream> GetStream(string path, MediaProtocol protocol, CancellationToken cancellationToken)
{
if (protocol == MediaProtocol.Http)
{
return await _httpClient.Get(path, cancellationToken).ConfigureAwait(false);
}
if (protocol == MediaProtocol.File)
{
return _fileSystem.GetFileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
}
throw new ArgumentOutOfRangeException("protocol");
}
}
}

View File

@ -413,6 +413,9 @@
<Compile Include="..\MediaBrowser.Model\Dlna\StreamInfo.cs">
<Link>Dlna\StreamInfo.cs</Link>
</Compile>
<Compile Include="..\MediaBrowser.Model\Dlna\StreamInfoSorter.cs">
<Link>Dlna\StreamInfoSorter.cs</Link>
</Compile>
<Compile Include="..\MediaBrowser.Model\Dlna\SubtitleDeliveryMethod.cs">
<Link>Dlna\SubtitleDeliveryMethod.cs</Link>
</Compile>
@ -800,12 +803,21 @@
<Compile Include="..\MediaBrowser.Model\MediaInfo\IBlurayExaminer.cs">
<Link>MediaInfo\IBlurayExaminer.cs</Link>
</Compile>
<Compile Include="..\MediaBrowser.Model\MediaInfo\LiveMediaInfoResult.cs">
<Link>MediaInfo\LiveMediaInfoResult.cs</Link>
<Compile Include="..\MediaBrowser.Model\MediaInfo\LiveStreamRequest.cs">
<Link>MediaInfo\LiveStreamRequest.cs</Link>
</Compile>
<Compile Include="..\MediaBrowser.Model\MediaInfo\LiveStreamResponse.cs">
<Link>MediaInfo\LiveStreamResponse.cs</Link>
</Compile>
<Compile Include="..\MediaBrowser.Model\MediaInfo\MediaProtocol.cs">
<Link>MediaInfo\MediaProtocol.cs</Link>
</Compile>
<Compile Include="..\MediaBrowser.Model\MediaInfo\PlaybackInfoRequest.cs">
<Link>MediaInfo\PlaybackInfoRequest.cs</Link>
</Compile>
<Compile Include="..\MediaBrowser.Model\MediaInfo\PlaybackInfoResponse.cs">
<Link>MediaInfo\PlaybackInfoResponse.cs</Link>
</Compile>
<Compile Include="..\MediaBrowser.Model\MediaInfo\SubtitleFormat.cs">
<Link>MediaInfo\SubtitleFormat.cs</Link>
</Compile>

View File

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

View File

@ -248,10 +248,9 @@ namespace MediaBrowser.Model.ApiClient
/// <summary>
/// Gets the playback information.
/// </summary>
/// <param name="itemId">The item identifier.</param>
/// <param name="userId">The user identifier.</param>
/// <param name="request">The request.</param>
/// <returns>Task&lt;LiveMediaInfoResult&gt;.</returns>
Task<LiveMediaInfoResult> GetPlaybackInfo(string itemId, string userId);
Task<PlaybackInfoResponse> GetPlaybackInfo(PlaybackInfoRequest request);
/// <summary>
/// Gets the users async.
@ -1486,5 +1485,12 @@ namespace MediaBrowser.Model.ApiClient
/// <param name="query">The query.</param>
/// <returns>Task&lt;List&lt;RecommendationDto&gt;&gt;.</returns>
Task<List<RecommendationDto>> GetMovieRecommendations(MovieRecommendationQuery query);
/// <summary>
/// Opens the live stream.
/// </summary>
/// <param name="request">The request.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task&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>
/// <returns>Task.</returns>
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 System;
using System.Collections.Generic;
using System.Linq;
namespace MediaBrowser.Model.ApiClient
{
@ -24,7 +23,12 @@ namespace MediaBrowser.Model.ApiClient
throw new ArgumentNullException("server");
}
var list = Servers.ToList();
// Clone the existing list of servers
var list = new List<ServerInfo>();
foreach (ServerInfo serverInfo in Servers)
{
list.Add(serverInfo);
}
var index = FindIndex(list, server.Id);
@ -32,8 +36,11 @@ namespace MediaBrowser.Model.ApiClient
{
var existing = list[index];
// Merge the data
existing.DateLastAccessed = new[] { existing.DateLastAccessed, server.DateLastAccessed }.Max();
// Take the most recent DateLastAccessed
if (server.DateLastAccessed > existing.DateLastAccessed)
{
existing.DateLastAccessed = server.DateLastAccessed;
}
existing.UserLinkType = server.UserLinkType;
@ -64,7 +71,11 @@ namespace MediaBrowser.Model.ApiClient
}
if (server.WakeOnLanInfos != null && server.WakeOnLanInfos.Count > 0)
{
existing.WakeOnLanInfos = server.WakeOnLanInfos.ToList();
existing.WakeOnLanInfos = new List<WakeOnLanInfo>();
foreach (WakeOnLanInfo info in server.WakeOnLanInfos)
{
existing.WakeOnLanInfos.Add(info);
}
}
if (server.LastConnectionMode.HasValue)
{

View File

@ -3,7 +3,6 @@ using MediaBrowser.Model.Extensions;
using MediaBrowser.Model.System;
using System;
using System.Collections.Generic;
using System.Linq;
namespace MediaBrowser.Model.ApiClient
{
@ -83,7 +82,12 @@ namespace MediaBrowser.Model.ApiClient
throw new ArgumentNullException("user");
}
var list = Users.ToList();
// Clone the existing list of users
var list = new List<ServerUserInfo>();
foreach (ServerUserInfo serverUserInfo in Users)
{
list.Add(serverUserInfo);
}
var index = FindIndex(list, user.Id);

View File

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

View File

@ -54,6 +54,7 @@ namespace MediaBrowser.Model.Configuration
public string[] LatestItemsExcludes { get; set; }
public bool HasMigratedToPolicy { get; set; }
public bool HidePlayedInLatest { get; set; }
/// <summary>
/// Initializes a new instance of the <see cref="UserConfiguration" /> class.

View File

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

View File

@ -117,7 +117,9 @@ namespace MediaBrowser.Model.Dlna
TranscodeSeekInfo transcodeSeekInfo,
bool? isAnamorphic,
bool? isCabac,
int? refFrames)
int? refFrames,
int? numVideoStreams,
int? numAudioStreams)
{
// first bit means Time based seek supported, second byte range seek supported (not sure about the order now), so 01 = only byte seek, 10 = time based, 11 = both, 00 = none
string orgOp = ";DLNA.ORG_OP=" + DlnaMaps.GetOrgOpValue(runtimeTicks.HasValue, isDirectStream, transcodeSeekInfo);
@ -158,7 +160,9 @@ namespace MediaBrowser.Model.Dlna
timestamp,
isAnamorphic,
isCabac,
refFrames);
refFrames,
numVideoStreams,
numAudioStreams);
List<string> orgPnValues = new List<string>();

View File

@ -281,7 +281,9 @@ namespace MediaBrowser.Model.Dlna
TransportStreamTimestamp timestamp,
bool? isAnamorphic,
bool? isCabac,
int? refFrames)
int? refFrames,
int? numVideoStreams,
int? numAudioStreams)
{
container = StringHelper.TrimStart((container ?? string.Empty), '.');
@ -315,7 +317,7 @@ namespace MediaBrowser.Model.Dlna
var anyOff = false;
foreach (ProfileCondition c in i.Conditions)
{
if (!conditionProcessor.IsVideoConditionSatisfied(c, audioBitrate, audioChannels, width, height, bitDepth, videoBitrate, videoProfile, videoLevel, videoFramerate, packetLength, timestamp, isAnamorphic, isCabac, refFrames))
if (!conditionProcessor.IsVideoConditionSatisfied(c, audioBitrate, audioChannels, width, height, bitDepth, videoBitrate, videoProfile, videoLevel, videoFramerate, packetLength, timestamp, isAnamorphic, isCabac, refFrames, numVideoStreams, numAudioStreams))
{
anyOff = true;
break;

View File

@ -17,6 +17,9 @@
VideoTimestamp = 12,
IsAnamorphic = 13,
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)
{
// Grab the first one that can be direct streamed
// If that doesn't produce anything, just take the first
foreach (StreamInfo i in streams)
{
if (i.PlayMethod == PlayMethod.DirectPlay && i.MediaSource.Protocol == MediaProtocol.File)
{
return i;
}
}
foreach (StreamInfo i in streams)
{
if (i.PlayMethod == PlayMethod.DirectPlay)
{
return i;
}
}
foreach (StreamInfo i in streams)
{
if (i.PlayMethod == PlayMethod.DirectStream)
{
return i;
}
}
streams = StreamInfoSorter.SortMediaSources(streams);
foreach (StreamInfo stream in streams)
{
return stream;
}
PlaybackException error = new PlaybackException();
error.ErrorCode = PlaybackErrorCode.NoCompatibleStream;
throw error;
return null;
}
private StreamInfo BuildAudioItem(MediaSourceInfo item, AudioOptions options)
@ -221,7 +197,7 @@ namespace MediaBrowser.Model.Dlna
playlistItem.EstimateContentLength = transcodingProfile.EstimateContentLength;
playlistItem.Container = transcodingProfile.Container;
playlistItem.AudioCodec = transcodingProfile.AudioCodec;
playlistItem.Protocol = transcodingProfile.Protocol;
playlistItem.SubProtocol = transcodingProfile.Protocol;
List<CodecProfile> audioCodecProfiles = new List<CodecProfile>();
foreach (CodecProfile i in options.Profile.CodecProfiles)
@ -263,6 +239,16 @@ namespace MediaBrowser.Model.Dlna
return playlistItem;
}
private int? GetBitrateForDirectPlayCheck(MediaSourceInfo item, AudioOptions options)
{
if (item.Protocol == MediaProtocol.File)
{
return options.Profile.MaxStaticBitrate;
}
return options.GetMaxBitrate();
}
private List<PlayMethod> GetAudioDirectPlayMethods(MediaSourceInfo item, MediaStream audioStream, AudioOptions options)
{
DirectPlayProfile directPlayProfile = null;
@ -287,7 +273,7 @@ namespace MediaBrowser.Model.Dlna
// The profile describes what the device supports
// If device requirements are satisfied then allow both direct stream and direct play
if (IsAudioEligibleForDirectPlay(item, options.Profile.MaxStaticBitrate))
if (IsAudioEligibleForDirectPlay(item, GetBitrateForDirectPlayCheck(item, options)))
{
playMethods.Add(PlayMethod.DirectPlay);
}
@ -296,6 +282,49 @@ namespace MediaBrowser.Model.Dlna
return playMethods;
}
private int? GetDefaultSubtitleStreamIndex(MediaSourceInfo item, SubtitleProfile[] subtitleProfiles)
{
int highestScore = -1;
foreach (MediaStream stream in item.MediaStreams)
{
if (stream.Type == MediaStreamType.Subtitle && stream.Score.HasValue)
{
if (stream.Score.Value > highestScore)
{
highestScore = stream.Score.Value;
}
}
}
List<MediaStream> topStreams = new List<MediaStream>();
foreach (MediaStream stream in item.MediaStreams)
{
if (stream.Type == MediaStreamType.Subtitle && stream.Score.HasValue && stream.Score.Value == highestScore)
{
topStreams.Add(stream);
}
}
// If multiple streams have an equal score, try to pick the most efficient one
if (topStreams.Count > 1)
{
foreach (MediaStream stream in topStreams)
{
foreach (SubtitleProfile profile in subtitleProfiles)
{
if (profile.Method == SubtitleDeliveryMethod.External && StringHelper.EqualsIgnoreCase(profile.Format, stream.Codec))
{
return stream.Index;
}
}
}
}
// If no optimization panned out, just use the original default
return item.DefaultSubtitleStreamIndex;
}
private StreamInfo BuildVideoItem(MediaSourceInfo item, VideoOptions options)
{
StreamInfo playlistItem = new StreamInfo
@ -308,16 +337,20 @@ namespace MediaBrowser.Model.Dlna
DeviceProfile = options.Profile
};
playlistItem.SubtitleStreamIndex = options.SubtitleStreamIndex ?? item.DefaultSubtitleStreamIndex;
playlistItem.SubtitleStreamIndex = options.SubtitleStreamIndex ?? GetDefaultSubtitleStreamIndex(item, options.Profile.SubtitleProfiles);
MediaStream subtitleStream = playlistItem.SubtitleStreamIndex.HasValue ? item.GetMediaStream(MediaStreamType.Subtitle, playlistItem.SubtitleStreamIndex.Value) : null;
MediaStream audioStream = item.GetDefaultAudioStream(options.AudioStreamIndex ?? item.DefaultAudioStreamIndex);
int? audioStreamIndex = audioStream == null ? (int?)null : audioStream.Index;
int? audioStreamIndex = null;
if (audioStream != null)
{
audioStreamIndex = audioStream.Index;
}
MediaStream videoStream = item.VideoStream;
// TODO: This doesn't accout for situation of device being able to handle media bitrate, but wifi connection not fast enough
bool isEligibleForDirectPlay = IsEligibleForDirectPlay(item, options.Profile.MaxStaticBitrate, subtitleStream, options);
bool isEligibleForDirectPlay = IsEligibleForDirectPlay(item, GetBitrateForDirectPlayCheck(item, options), subtitleStream, options);
bool isEligibleForDirectStream = IsEligibleForDirectPlay(item, options.GetMaxBitrate(), subtitleStream, options);
if (isEligibleForDirectPlay || isEligibleForDirectStream)
@ -374,7 +407,7 @@ namespace MediaBrowser.Model.Dlna
playlistItem.TranscodeSeekInfo = transcodingProfile.TranscodeSeekInfo;
playlistItem.AudioCodec = transcodingProfile.AudioCodec.Split(',')[0];
playlistItem.VideoCodec = transcodingProfile.VideoCodec;
playlistItem.Protocol = transcodingProfile.Protocol;
playlistItem.SubProtocol = transcodingProfile.Protocol;
playlistItem.AudioStreamIndex = audioStreamIndex;
List<ProfileCondition> videoTranscodingConditions = new List<ProfileCondition>();
@ -509,10 +542,13 @@ namespace MediaBrowser.Model.Dlna
int? packetLength = videoStream == null ? null : videoStream.PacketLength;
int? refFrames = videoStream == null ? null : videoStream.RefFrames;
int? numAudioStreams = mediaSource.GetStreamCount(MediaStreamType.Audio);
int? numVideoStreams = mediaSource.GetStreamCount(MediaStreamType.Video);
// Check container conditions
foreach (ProfileCondition i in conditions)
{
if (!conditionProcessor.IsVideoConditionSatisfied(i, audioBitrate, audioChannels, width, height, bitDepth, videoBitrate, videoProfile, videoLevel, videoFramerate, packetLength, timestamp, isAnamorphic, isCabac, refFrames))
if (!conditionProcessor.IsVideoConditionSatisfied(i, audioBitrate, audioChannels, width, height, bitDepth, videoBitrate, videoProfile, videoLevel, videoFramerate, packetLength, timestamp, isAnamorphic, isCabac, refFrames, numVideoStreams, numAudioStreams))
{
return null;
}
@ -539,7 +575,7 @@ namespace MediaBrowser.Model.Dlna
foreach (ProfileCondition i in conditions)
{
if (!conditionProcessor.IsVideoConditionSatisfied(i, audioBitrate, audioChannels, width, height, bitDepth, videoBitrate, videoProfile, videoLevel, videoFramerate, packetLength, timestamp, isAnamorphic, isCabac, refFrames))
if (!conditionProcessor.IsVideoConditionSatisfied(i, audioBitrate, audioChannels, width, height, bitDepth, videoBitrate, videoProfile, videoLevel, videoFramerate, packetLength, timestamp, isAnamorphic, isCabac, refFrames, numVideoStreams, numAudioStreams))
{
return null;
}
@ -568,7 +604,8 @@ namespace MediaBrowser.Model.Dlna
foreach (ProfileCondition i in conditions)
{
if (!conditionProcessor.IsVideoAudioConditionSatisfied(i, audioChannels, audioBitrate, audioProfile))
bool? isSecondaryAudio = audioStream == null ? null : mediaSource.IsSecondaryAudio(audioStream);
if (!conditionProcessor.IsVideoAudioConditionSatisfied(i, audioChannels, audioBitrate, audioProfile, isSecondaryAudio))
{
return null;
}
@ -628,8 +665,20 @@ namespace MediaBrowser.Model.Dlna
// Look for an external profile that matches the stream type (text/graphical)
foreach (SubtitleProfile profile in subtitleProfiles)
{
bool requiresConversion = !StringHelper.EqualsIgnoreCase(subtitleStream.Codec, profile.Format);
if (!profile.SupportsLanguage(subtitleStream.Language))
{
continue;
}
if (profile.Method == SubtitleDeliveryMethod.External && subtitleStream.IsTextSubtitleStream == MediaStream.IsTextFormat(profile.Format))
{
if (!requiresConversion)
{
return profile;
}
if (subtitleStream.SupportsExternalStream)
{
return profile;
@ -645,8 +694,20 @@ namespace MediaBrowser.Model.Dlna
foreach (SubtitleProfile profile in subtitleProfiles)
{
bool requiresConversion = !StringHelper.EqualsIgnoreCase(subtitleStream.Codec, profile.Format);
if (!profile.SupportsLanguage(subtitleStream.Language))
{
continue;
}
if (profile.Method == SubtitleDeliveryMethod.Embed && subtitleStream.IsTextSubtitleStream == MediaStream.IsTextFormat(profile.Format))
{
if (!requiresConversion)
{
return profile;
}
return profile;
}
}
@ -756,6 +817,9 @@ namespace MediaBrowser.Model.Dlna
case ProfileConditionValue.AudioProfile:
case ProfileConditionValue.Has64BitOffsets:
case ProfileConditionValue.PacketLength:
case ProfileConditionValue.NumAudioStreams:
case ProfileConditionValue.NumVideoStreams:
case ProfileConditionValue.IsSecondaryAudio:
case ProfileConditionValue.VideoTimestamp:
{
// Not supported yet

View File

@ -24,7 +24,7 @@ namespace MediaBrowser.Model.Dlna
public string Container { get; set; }
public string Protocol { get; set; }
public string SubProtocol { get; set; }
public long StartPositionTicks { get; set; }
@ -51,7 +51,7 @@ namespace MediaBrowser.Model.Dlna
public int? MaxVideoBitDepth { get; set; }
public int? MaxRefFrames { get; set; }
public float? MaxFramerate { get; set; }
public DeviceProfile DeviceProfile { get; set; }
@ -69,7 +69,7 @@ namespace MediaBrowser.Model.Dlna
public SubtitleDeliveryMethod SubtitleDeliveryMethod { get; set; }
public string SubtitleFormat { get; set; }
public LiveMediaInfoResult PlaybackInfo { get; set; }
public PlaybackInfoResponse PlaybackInfo { get; set; }
public string MediaSourceId
{
@ -81,18 +81,14 @@ namespace MediaBrowser.Model.Dlna
public bool IsDirectStream
{
get {
get
{
return PlayMethod == PlayMethod.DirectStream ||
PlayMethod == PlayMethod.DirectPlay;
}
}
public string ToUrl(string baseUrl, string accessToken)
{
return ToDlnaUrl(baseUrl, accessToken);
}
public string ToDlnaUrl(string baseUrl, string accessToken)
{
if (PlayMethod == PlayMethod.DirectPlay)
{
@ -104,7 +100,56 @@ namespace MediaBrowser.Model.Dlna
throw new ArgumentNullException(baseUrl);
}
string dlnaCommand = BuildDlnaParam(this);
List<string> list = new List<string>();
foreach (NameValuePair pair in BuildParams(this, accessToken))
{
if (string.IsNullOrEmpty(pair.Value))
{
continue;
}
// Try to keep the url clean by omitting defaults
if (StringHelper.EqualsIgnoreCase(pair.Name, "StartTimeTicks") &&
StringHelper.EqualsIgnoreCase(pair.Value, "0"))
{
continue;
}
if (StringHelper.EqualsIgnoreCase(pair.Name, "SubtitleStreamIndex") &&
StringHelper.EqualsIgnoreCase(pair.Value, "-1"))
{
continue;
}
if (StringHelper.EqualsIgnoreCase(pair.Name, "Static") &&
StringHelper.EqualsIgnoreCase(pair.Value, "false"))
{
continue;
}
list.Add(string.Format("{0}={1}", pair.Name, pair.Value));
}
string queryString = string.Join("&", list.ToArray());
return GetUrl(baseUrl, queryString);
}
public string ToDlnaUrl(string baseUrl, string accessToken)
{
if (PlayMethod == PlayMethod.DirectPlay)
{
return MediaSource.Path;
}
string dlnaCommand = BuildDlnaParam(this, accessToken);
return GetUrl(baseUrl, dlnaCommand);
}
private string GetUrl(string baseUrl, string queryString)
{
if (string.IsNullOrEmpty(baseUrl))
{
throw new ArgumentNullException(baseUrl);
}
string extension = string.IsNullOrEmpty(Container) ? string.Empty : "." + Container;
@ -112,102 +157,94 @@ namespace MediaBrowser.Model.Dlna
if (MediaType == DlnaProfileType.Audio)
{
return string.Format("{0}/audio/{1}/stream{2}?{3}", baseUrl, ItemId, extension, dlnaCommand);
return string.Format("{0}/audio/{1}/stream{2}?{3}", baseUrl, ItemId, extension, queryString);
}
if (StringHelper.EqualsIgnoreCase(Protocol, "hls"))
if (StringHelper.EqualsIgnoreCase(SubProtocol, "hls"))
{
return string.Format("{0}/videos/{1}/master.m3u8?{2}", baseUrl, ItemId, dlnaCommand);
return string.Format("{0}/videos/{1}/master.m3u8?{2}", baseUrl, ItemId, queryString);
}
return string.Format("{0}/videos/{1}/stream{2}?{3}", baseUrl, ItemId, extension, dlnaCommand);
return string.Format("{0}/videos/{1}/stream{2}?{3}", baseUrl, ItemId, extension, queryString);
}
private static string BuildDlnaParam(StreamInfo item)
private static string BuildDlnaParam(StreamInfo item, string accessToken)
{
List<string> list = new List<string>
List<string> list = new List<string>();
foreach (NameValuePair pair in BuildParams(item, accessToken))
{
item.DeviceProfileId ?? string.Empty,
item.DeviceId ?? string.Empty,
item.MediaSourceId ?? string.Empty,
(item.IsDirectStream).ToString().ToLower(),
item.VideoCodec ?? string.Empty,
item.AudioCodec ?? string.Empty,
item.AudioStreamIndex.HasValue ? StringHelper.ToStringCultureInvariant(item.AudioStreamIndex.Value) : string.Empty,
item.SubtitleStreamIndex.HasValue && item.SubtitleDeliveryMethod != SubtitleDeliveryMethod.External ? StringHelper.ToStringCultureInvariant(item.SubtitleStreamIndex.Value) : string.Empty,
item.VideoBitrate.HasValue ? StringHelper.ToStringCultureInvariant(item.VideoBitrate.Value) : string.Empty,
item.AudioBitrate.HasValue ? StringHelper.ToStringCultureInvariant(item.AudioBitrate.Value) : string.Empty,
item.MaxAudioChannels.HasValue ? StringHelper.ToStringCultureInvariant(item.MaxAudioChannels.Value) : string.Empty,
item.MaxFramerate.HasValue ? StringHelper.ToStringCultureInvariant(item.MaxFramerate.Value) : string.Empty,
item.MaxWidth.HasValue ? StringHelper.ToStringCultureInvariant(item.MaxWidth.Value) : string.Empty,
item.MaxHeight.HasValue ? StringHelper.ToStringCultureInvariant(item.MaxHeight.Value) : string.Empty,
StringHelper.ToStringCultureInvariant(item.StartPositionTicks),
item.VideoLevel.HasValue ? StringHelper.ToStringCultureInvariant(item.VideoLevel.Value) : string.Empty
};
list.Add(pair.Value);
}
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()));
}
public List<SubtitleStreamInfo> GetExternalSubtitles(bool includeSelectedTrackOnly)
private static List<NameValuePair> BuildParams(StreamInfo item, string accessToken)
{
List<SubtitleStreamInfo> list = new List<SubtitleStreamInfo>();
List<NameValuePair> list = new List<NameValuePair>();
// First add the selected track
if (SubtitleStreamIndex.HasValue)
{
foreach (MediaStream stream in MediaSource.MediaStreams)
{
if (stream.Type == MediaStreamType.Subtitle && stream.Index == SubtitleStreamIndex.Value)
{
SubtitleStreamInfo info = GetSubtitleStreamInfo(stream);
list.Add(new NameValuePair("DeviceProfileId", item.DeviceProfileId ?? string.Empty));
list.Add(new NameValuePair("DeviceId", item.DeviceId ?? string.Empty));
list.Add(new NameValuePair("MediaSourceId", item.MediaSourceId ?? string.Empty));
list.Add(new NameValuePair("Static", (item.IsDirectStream).ToString().ToLower()));
list.Add(new NameValuePair("VideoCodec", item.VideoCodec ?? string.Empty));
list.Add(new NameValuePair("AudioCodec", item.AudioCodec ?? string.Empty));
list.Add(new NameValuePair("AudioStreamIndex", item.AudioStreamIndex.HasValue ? StringHelper.ToStringCultureInvariant(item.AudioStreamIndex.Value) : string.Empty));
list.Add(new NameValuePair("SubtitleStreamIndex", item.SubtitleStreamIndex.HasValue && item.SubtitleDeliveryMethod != SubtitleDeliveryMethod.External ? StringHelper.ToStringCultureInvariant(item.SubtitleStreamIndex.Value) : string.Empty));
list.Add(new NameValuePair("VideoBitrate", item.VideoBitrate.HasValue ? StringHelper.ToStringCultureInvariant(item.VideoBitrate.Value) : string.Empty));
list.Add(new NameValuePair("AudioBitrate", item.AudioBitrate.HasValue ? StringHelper.ToStringCultureInvariant(item.AudioBitrate.Value) : string.Empty));
list.Add(new NameValuePair("MaxAudioChannels", item.MaxAudioChannels.HasValue ? StringHelper.ToStringCultureInvariant(item.MaxAudioChannels.Value) : string.Empty));
list.Add(new NameValuePair("MaxFramerate", item.MaxFramerate.HasValue ? StringHelper.ToStringCultureInvariant(item.MaxFramerate.Value) : string.Empty));
list.Add(new NameValuePair("MaxWidth", item.MaxWidth.HasValue ? StringHelper.ToStringCultureInvariant(item.MaxWidth.Value) : string.Empty));
list.Add(new NameValuePair("MaxHeight", item.MaxHeight.HasValue ? StringHelper.ToStringCultureInvariant(item.MaxHeight.Value) : string.Empty));
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(info);
}
}
}
}
list.Add(new NameValuePair("ClientTime", item.IsDirectStream ? string.Empty : DateTime.UtcNow.Ticks.ToString(CultureInfo.InvariantCulture)));
list.Add(new NameValuePair("MaxRefFrames", item.MaxRefFrames.HasValue ? StringHelper.ToStringCultureInvariant(item.MaxRefFrames.Value) : string.Empty));
list.Add(new NameValuePair("MaxVideoBitDepth", item.MaxVideoBitDepth.HasValue ? StringHelper.ToStringCultureInvariant(item.MaxVideoBitDepth.Value) : string.Empty));
list.Add(new NameValuePair("Profile", item.VideoProfile ?? string.Empty));
list.Add(new NameValuePair("Cabac", item.Cabac.HasValue ? item.Cabac.Value.ToString() : string.Empty));
if (!includeSelectedTrackOnly)
{
foreach (MediaStream stream in MediaSource.MediaStreams)
{
if (stream.Type == MediaStreamType.Subtitle && (!SubtitleStreamIndex.HasValue || stream.Index != SubtitleStreamIndex.Value))
{
SubtitleStreamInfo info = GetSubtitleStreamInfo(stream);
string playSessionId = item.PlaybackInfo == null ? null : item.PlaybackInfo.PlaySessionId;
list.Add(new NameValuePair("PlaySessionId", playSessionId ?? string.Empty));
list.Add(new NameValuePair("api_key", accessToken ?? string.Empty));
if (info != null)
{
list.Add(info);
}
}
}
}
string liveStreamId = item.MediaSource == null ? null : item.MediaSource.LiveStreamId;
list.Add(new NameValuePair("LiveStreamId", liveStreamId ?? string.Empty));
return list;
}
public List<SubtitleStreamInfo> GetExternalSubtitles(string baseUrl, string accessToken, bool includeSelectedTrackOnly)
public List<SubtitleStreamInfo> GetExternalSubtitles(bool includeSelectedTrackOnly, 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>();
// HLS will preserve timestamps so we can just grab the full subtitle stream
long startPositionTicks = StringHelper.EqualsIgnoreCase(Protocol, "hls")
long startPositionTicks = StringHelper.EqualsIgnoreCase(SubProtocol, "hls")
? 0
: StartPositionTicks;
@ -218,12 +255,7 @@ namespace MediaBrowser.Model.Dlna
{
if (stream.Type == MediaStreamType.Subtitle && stream.Index == SubtitleStreamIndex.Value)
{
SubtitleStreamInfo info = GetSubtitleStreamInfo(stream, baseUrl, accessToken, startPositionTicks);
if (info != null)
{
list.Add(info);
}
AddSubtitleProfiles(list, stream, enableAllProfiles, baseUrl, accessToken, startPositionTicks);
}
}
}
@ -234,54 +266,65 @@ namespace MediaBrowser.Model.Dlna
{
if (stream.Type == MediaStreamType.Subtitle && (!SubtitleStreamIndex.HasValue || stream.Index != SubtitleStreamIndex.Value))
{
SubtitleStreamInfo info = GetSubtitleStreamInfo(stream, baseUrl, accessToken, startPositionTicks);
if (info != null)
{
list.Add(info);
}
AddSubtitleProfiles(list, stream, enableAllProfiles, baseUrl, accessToken, startPositionTicks);
}
}
}
return list;
}
private SubtitleStreamInfo GetSubtitleStreamInfo(MediaStream stream, string baseUrl, string accessToken, long startPositionTicks)
private void AddSubtitleProfiles(List<SubtitleStreamInfo> list, MediaStream stream, bool enableAllProfiles, string baseUrl, string accessToken, long startPositionTicks)
{
SubtitleStreamInfo info = GetSubtitleStreamInfo(stream);
if (info != null)
if (enableAllProfiles)
{
info.Url = string.Format("{0}/Videos/{1}/{2}/Subtitles/{3}/{4}/Stream.{5}",
baseUrl,
ItemId,
MediaSourceId,
StringHelper.ToStringCultureInvariant(stream.Index),
StringHelper.ToStringCultureInvariant(startPositionTicks),
SubtitleFormat);
}
foreach (SubtitleProfile profile in DeviceProfile.SubtitleProfiles)
{
SubtitleStreamInfo info = GetSubtitleStreamInfo(stream, baseUrl, accessToken, startPositionTicks, new[] { profile });
return info;
list.Add(info);
}
}
else
{
SubtitleStreamInfo info = GetSubtitleStreamInfo(stream, baseUrl, accessToken, startPositionTicks, DeviceProfile.SubtitleProfiles);
list.Add(info);
}
}
private SubtitleStreamInfo GetSubtitleStreamInfo(MediaStream stream)
private SubtitleStreamInfo GetSubtitleStreamInfo(MediaStream stream, string baseUrl, string accessToken, long startPositionTicks, SubtitleProfile[] subtitleProfiles)
{
SubtitleProfile subtitleProfile = StreamBuilder.GetSubtitleProfile(stream, DeviceProfile.SubtitleProfiles, Context);
if (subtitleProfile.Method != SubtitleDeliveryMethod.External)
{
return null;
}
return new SubtitleStreamInfo
SubtitleProfile subtitleProfile = StreamBuilder.GetSubtitleProfile(stream, subtitleProfiles, Context);
SubtitleStreamInfo info = new SubtitleStreamInfo
{
IsForced = stream.IsForced,
Language = stream.Language,
Name = stream.Language ?? "Unknown",
Format = SubtitleFormat,
Index = stream.Index
Format = subtitleProfile.Format,
Index = stream.Index,
DeliveryMethod = subtitleProfile.Method
};
if (info.DeliveryMethod == SubtitleDeliveryMethod.External)
{
if (MediaSource.Protocol == MediaProtocol.File || !StringHelper.EqualsIgnoreCase(stream.Codec, subtitleProfile.Format))
{
info.Url = string.Format("{0}/Videos/{1}/{2}/Subtitles/{3}/{4}/Stream.{5}",
baseUrl,
ItemId,
MediaSourceId,
StringHelper.ToStringCultureInvariant(stream.Index),
StringHelper.ToStringCultureInvariant(startPositionTicks),
subtitleProfile.Format);
}
else
{
info.Url = stream.Path;
}
}
return info;
}
/// <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()
{
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
{
@ -13,5 +15,28 @@ namespace MediaBrowser.Model.Dlna
[XmlAttribute("didlMode")]
public string DidlMode { get; set; }
[XmlAttribute("language")]
public string Language { get; set; }
public List<string> GetLanguages()
{
List<string> list = new List<string>();
foreach (string i in (Language ?? string.Empty).Split(','))
{
if (!string.IsNullOrEmpty(i)) list.Add(i);
}
return list;
}
public bool SupportsLanguage(string language)
{
if (string.IsNullOrEmpty(language))
{
language = "und";
}
List<string> languages = GetLanguages();
return languages.Count == 0 || ListHelper.ContainsIgnoreCase(languages, language);
}
}
}

View File

@ -8,5 +8,6 @@ namespace MediaBrowser.Model.Dlna
public bool IsForced { get; set; }
public string Format { 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 SupportsTranscoding { get; set; }
public bool SupportsDirectStream { get; set; }
public bool SupportsDirectPlay { get; set; }
public bool RequiresOpening { get; set; }
public string OpenToken { get; set; }
public bool RequiresClosing { get; set; }
public string LiveStreamId { get; set; }
public int? BufferMs { get; set; }
public VideoType? VideoType { get; set; }
@ -41,6 +48,10 @@ namespace MediaBrowser.Model.Dto
public TransportStreamTimestamp? Timestamp { get; set; }
public Dictionary<string, string> RequiredHttpHeaders { get; set; }
public string TranscodingUrl { get; set; }
public string TranscodingSubProtocol { get; set; }
public string TranscodingContainer { get; set; }
public MediaSourceInfo()
{
Formats = new List<string>();
@ -49,6 +60,7 @@ namespace MediaBrowser.Model.Dto
PlayableStreamFileNames = new List<string>();
SupportsTranscoding = true;
SupportsDirectStream = true;
SupportsDirectPlay = true;
}
public int? DefaultAudioStreamIndex { get; set; }
@ -123,5 +135,40 @@ namespace MediaBrowser.Model.Dto
return null;
}
public int? GetStreamCount(MediaStreamType type)
{
int numMatches = 0;
int numStreams = 0;
foreach (MediaStream i in MediaStreams)
{
numStreams++;
if (i.Type == type)
{
numMatches++;
}
}
if (numStreams == 0)
{
return null;
}
return numMatches;
}
public bool? IsSecondaryAudio(MediaStream stream)
{
foreach (MediaStream currentStream in MediaStreams)
{
if (currentStream.Type == MediaStreamType.Audio)
{
return currentStream.Index != stream.Index;
}
}
return null;
}
}
}

View File

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

View File

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

View File

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

View File

@ -126,6 +126,7 @@
<Compile Include="Devices\DevicesOptions.cs" />
<Compile Include="Dlna\EncodingContext.cs" />
<Compile Include="Dlna\ILocalPlayer.cs" />
<Compile Include="Dlna\StreamInfoSorter.cs" />
<Compile Include="Dlna\NullLocalPlayer.cs" />
<Compile Include="Dlna\PlaybackErrorCode.cs" />
<Compile Include="Dlna\PlaybackException.cs" />
@ -140,7 +141,10 @@
<Compile Include="Dto\MetadataEditorInfo.cs" />
<Compile Include="Dto\NameIdPair.cs" />
<Compile Include="Dto\NameValuePair.cs" />
<Compile Include="MediaInfo\LiveMediaInfoResult.cs" />
<Compile Include="MediaInfo\LiveStreamRequest.cs" />
<Compile Include="MediaInfo\LiveStreamResponse.cs" />
<Compile Include="MediaInfo\PlaybackInfoRequest.cs" />
<Compile Include="MediaInfo\PlaybackInfoResponse.cs" />
<Compile Include="Dto\MediaSourceType.cs" />
<Compile Include="Configuration\DynamicDayOfWeek.cs" />
<Compile Include="Entities\ExtraType.cs" />

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

View File

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

View File

@ -78,5 +78,10 @@ namespace MediaBrowser.Model.Session
/// </summary>
/// <value>The play method.</value>
public PlayMethod PlayMethod { get; set; }
/// <summary>
/// Gets or sets the live stream identifier.
/// </summary>
/// <value>The live stream identifier.</value>
public string LiveStreamId { get; set; }
}
}

View File

@ -36,5 +36,10 @@ namespace MediaBrowser.Model.Session
/// </summary>
/// <value>The position ticks.</value>
public long? PositionTicks { get; set; }
/// <summary>
/// Gets or sets the live stream identifier.
/// </summary>
/// <value>The live stream identifier.</value>
public string LiveStreamId { get; set; }
}
}

View File

@ -31,13 +31,24 @@ namespace MediaBrowser.Model.Sync
/// <value>The item identifier.</value>
public string ItemId { get; set; }
/// <summary>
/// Gets or sets the synchronize job item identifier.
/// </summary>
/// <value>The synchronize job item identifier.</value>
public string SyncJobItemId { get; set; }
/// <summary>
/// Gets or sets the user ids with access.
/// </summary>
/// <value>The user ids with access.</value>
public List<string> UserIdsWithAccess { get; set; }
/// <summary>
/// Gets or sets the additional files.
/// </summary>
/// <value>The additional files.</value>
public List<string> AdditionalFiles { get; set; }
public LocalItem()
{
AdditionalFiles = new List<string>();
UserIdsWithAccess = new List<string>();
}
}

View File

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

View File

@ -1,4 +1,5 @@
using MediaBrowser.Model.Dto;
using System;
using System.Collections.Generic;
namespace MediaBrowser.Model.Sync
@ -16,6 +17,16 @@ namespace MediaBrowser.Model.Sync
/// <value>The synchronize job identifier.</value>
public string SyncJobId { get; set; }
/// <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.
/// </summary>
/// <value>The synchronize job item identifier.</value>

View File

@ -96,7 +96,7 @@ namespace MediaBrowser.Providers.MediaInfo
var album = item.Parent as MusicAlbum;
var filename = item.Album ?? string.Empty;
filename += item.Artists.FirstOrDefault() ?? string.Empty;
filename += string.Join(",", item.Artists.ToArray());
filename += album == null ? item.Id.ToString("N") + "_primary" + item.DateModified.Ticks : album.Id.ToString("N") + album.DateModified.Ticks + "_primary";
filename = filename.GetMD5() + ".jpg";

View File

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

View File

@ -169,7 +169,7 @@ namespace MediaBrowser.Server.Implementations.Channels
foreach (var item in result.Items)
{
var channelItem = (IChannelItem)item;
var channelItem = (IChannelMediaItem)item;
var channelFeatures = _manager.GetChannelFeatures(channelItem.ChannelId);
@ -179,7 +179,7 @@ namespace MediaBrowser.Server.Implementations.Channels
{
try
{
await DownloadChannelItem(item, options, cancellationToken, path);
await DownloadChannelItem(channelItem, options, cancellationToken, path);
}
catch (OperationCanceledException)
{
@ -210,13 +210,13 @@ namespace MediaBrowser.Server.Implementations.Channels
return channelOptions.DownloadSizeLimit;
}
private async Task DownloadChannelItem(BaseItem item,
private async Task DownloadChannelItem(IChannelMediaItem item,
ChannelOptions channelOptions,
CancellationToken cancellationToken,
string path)
{
var itemId = item.Id.ToString("N");
var sources = await _manager.GetChannelItemMediaSources(itemId, false, cancellationToken)
var sources = await _manager.GetStaticMediaSources(item, true, cancellationToken)
.ConfigureAwait(false);
var cachedVersions = sources.Where(i => i.Protocol == MediaProtocol.File).ToList();
@ -237,11 +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(channelItem, destination, new Progress<double>(), cancellationToken)
await _manager.DownloadChannelItem(item, destination, new Progress<double>(), 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.Providers;
using MediaBrowser.Model.Entities;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;

View File

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

View File

@ -233,61 +233,45 @@ namespace MediaBrowser.Server.Implementations.Drawing
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
{
CheckDisposed();
var newWidth = Convert.ToInt32(newSize.Width);
var newHeight = Convert.ToInt32(newSize.Height);
Directory.CreateDirectory(Path.GetDirectoryName(cacheFilePath));
if (string.IsNullOrWhiteSpace(options.BackgroundColor))
if (!File.Exists(cacheFilePath))
{
using (var originalImage = new MagickWand(originalImagePath))
{
originalImage.CurrentImage.ResizeImage(newWidth, newHeight);
var newWidth = Convert.ToInt32(newSize.Width);
var newHeight = Convert.ToInt32(newSize.Height);
DrawIndicator(originalImage, newWidth, newHeight, options);
Directory.CreateDirectory(Path.GetDirectoryName(cacheFilePath));
originalImage.CurrentImage.CompressionQuality = quality;
originalImage.SaveImage(cacheFilePath);
return cacheFilePath;
}
}
else
{
using (var wand = new MagickWand(newWidth, newHeight, options.BackgroundColor))
if (string.IsNullOrWhiteSpace(options.BackgroundColor))
{
using (var originalImage = new MagickWand(originalImagePath))
{
originalImage.CurrentImage.ResizeImage(newWidth, newHeight);
wand.CurrentImage.CompositeImage(originalImage, CompositeOperator.OverCompositeOp, 0, 0);
DrawIndicator(wand, newWidth, newHeight, options);
DrawIndicator(originalImage, newWidth, newHeight, options);
wand.CurrentImage.CompressionQuality = quality;
originalImage.CurrentImage.CompressionQuality = quality;
wand.SaveImage(cacheFilePath);
originalImage.SaveImage(cacheFilePath);
}
}
else
{
using (var wand = new MagickWand(newWidth, newHeight, options.BackgroundColor))
{
using (var originalImage = new MagickWand(originalImagePath))
{
originalImage.CurrentImage.ResizeImage(newWidth, newHeight);
return cacheFilePath;
wand.CurrentImage.CompositeImage(originalImage, CompositeOperator.OverCompositeOp, 0, 0);
DrawIndicator(wand, newWidth, newHeight, options);
wand.CurrentImage.CompressionQuality = quality;
wand.SaveImage(cacheFilePath);
}
}
}
}
@ -296,6 +280,8 @@ namespace MediaBrowser.Server.Implementations.Drawing
{
semaphore.Release();
}
return cacheFilePath;
}
private ImageFormat GetOutputFormat(ImageFormat requestedFormat)

View File

@ -97,13 +97,16 @@ namespace MediaBrowser.Server.Implementations.Dto
if (byName != null && !(item is LiveTvChannel))
{
var itemFilter = byName.GetItemFilter();
//if (options.Fields.Contains(ItemFields.ItemCounts))
{
var itemFilter = byName.GetItemFilter();
var libraryItems = user != null ?
user.RootFolder.GetRecursiveChildren(user, itemFilter) :
_libraryManager.RootFolder.GetRecursiveChildren(itemFilter);
var libraryItems = user != null ?
user.RootFolder.GetRecursiveChildren(user, itemFilter) :
_libraryManager.RootFolder.GetRecursiveChildren(itemFilter);
SetItemByNameInfo(item, dto, libraryItems.ToList(), user);
SetItemByNameInfo(item, dto, libraryItems.ToList(), user);
}
}
FillSyncInfo(dto, item, itemIdsWithSyncJobs, options, user);
@ -122,13 +125,16 @@ namespace MediaBrowser.Server.Implementations.Dto
if (byName != null && !(item is LiveTvChannel))
{
var itemFilter = byName.GetItemFilter();
//if (options.Fields.Contains(ItemFields.ItemCounts))
{
var itemFilter = byName.GetItemFilter();
var libraryItems = user != null ?
user.RootFolder.GetRecursiveChildren(user, itemFilter) :
_libraryManager.RootFolder.GetRecursiveChildren(itemFilter);
var libraryItems = user != null ?
user.RootFolder.GetRecursiveChildren(user, itemFilter) :
_libraryManager.RootFolder.GetRecursiveChildren(itemFilter);
SetItemByNameInfo(item, dto, libraryItems.ToList(), user);
SetItemByNameInfo(item, dto, libraryItems.ToList(), user);
}
FillSyncInfo(dto, item, options, user);
return dto;
@ -255,7 +261,7 @@ namespace MediaBrowser.Server.Implementations.Dto
{
if (user == null)
{
dto.MediaSources = hasMediaSources.GetMediaSources(true).ToList();
dto.MediaSources = _mediaSourceManager().GetStaticMediaSources(hasMediaSources, true).ToList();
}
else
{
@ -263,7 +269,7 @@ namespace MediaBrowser.Server.Implementations.Dto
}
}
}
if (fields.Contains(ItemFields.Studios))
{
AttachStudios(dto, item);
@ -311,7 +317,11 @@ namespace MediaBrowser.Server.Implementations.Dto
{
var dto = GetBaseItemDtoInternal(item, options, user);
SetItemByNameInfo(item, dto, taggedItems, user);
//if (options.Fields.Contains(ItemFields.ItemCounts))
{
SetItemByNameInfo(item, dto, taggedItems, user);
}
FillSyncInfo(dto, item, options, user);
return dto;
@ -1270,7 +1280,7 @@ namespace MediaBrowser.Server.Implementations.Dto
}
else
{
mediaStreams = iHasMediaSources.GetMediaSources(true).First().MediaStreams;
mediaStreams = _mediaSourceManager().GetStaticMediaSources(iHasMediaSources, true).First().MediaStreams;
}
dto.MediaStreams = mediaStreams;
@ -1443,7 +1453,7 @@ namespace MediaBrowser.Server.Implementations.Dto
var tvChannel = item as LiveTvChannel;
if (tvChannel != null)
{
dto.MediaSources = tvChannel.GetMediaSources(true).ToList();
dto.MediaSources = _mediaSourceManager().GetStaticMediaSources(tvChannel, true).ToList();
}
var channelItem = item as IChannelItem;

View File

@ -1713,11 +1713,15 @@ namespace MediaBrowser.Server.Implementations.Library
isNew = true;
}
var refresh = isNew || (DateTime.UtcNow - item.DateLastSaved).TotalHours >= 6;
var refresh = isNew || (DateTime.UtcNow - item.DateLastSaved).TotalHours >= 12;
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;

View File

@ -1,5 +1,4 @@
using System.IO;
using MediaBrowser.Controller.Channels;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.MediaEncoding;
@ -7,32 +6,35 @@ using MediaBrowser.Controller.Persistence;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Logging;
using MediaBrowser.Model.MediaInfo;
using MediaBrowser.Model.Serialization;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Model.MediaInfo;
namespace MediaBrowser.Server.Implementations.Library
{
public class MediaSourceManager : IMediaSourceManager
public class MediaSourceManager : IMediaSourceManager, IDisposable
{
private readonly IItemRepository _itemRepo;
private readonly IUserManager _userManager;
private readonly ILibraryManager _libraryManager;
private readonly IChannelManager _channelManager;
private readonly IJsonSerializer _jsonSerializer;
private IMediaSourceProvider[] _providers;
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;
_userManager = userManager;
_libraryManager = libraryManager;
_channelManager = channelManager;
_logger = logger;
_jsonSerializer = jsonSerializer;
}
public void AddParts(IEnumerable<IMediaSourceProvider> providers)
@ -127,30 +129,27 @@ namespace MediaBrowser.Server.Implementations.Library
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)
{
var item = _libraryManager.GetItemById(id);
IEnumerable<MediaSourceInfo> mediaSources;
var hasMediaSources = (IHasMediaSources)item;
var channelItem = item as IChannelMediaItem;
User user = null;
if (channelItem != null)
if (string.IsNullOrWhiteSpace(userId))
{
mediaSources = await _channelManager.GetChannelItemMediaSources(id, true, cancellationToken)
.ConfigureAwait(false);
mediaSources = hasMediaSources.GetMediaSources(enablePathSubstitution);
}
else
{
if (string.IsNullOrWhiteSpace(userId))
{
mediaSources = hasMediaSources.GetMediaSources(enablePathSubstitution);
}
else
{
var user = _userManager.GetUserById(userId);
mediaSources = GetStaticMediaSources(hasMediaSources, enablePathSubstitution, user);
}
user = _userManager.GetUserById(userId);
mediaSources = GetStaticMediaSources(hasMediaSources, enablePathSubstitution, user);
}
var dynamicMediaSources = await GetDynamicMediaSources(hasMediaSources, cancellationToken).ConfigureAwait(false);
@ -161,17 +160,30 @@ namespace MediaBrowser.Server.Implementations.Library
foreach (var source in dynamicMediaSources)
{
source.SupportsTranscoding = false;
if (user != null)
{
SetUserProperties(source, user);
}
if (source.Protocol == MediaProtocol.File)
{
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);
}
return SortMediaSources(list);
return SortMediaSources(list).Where(i => i.Type != MediaSourceType.Placeholder);
}
private async Task<IEnumerable<MediaSourceInfo>> GetDynamicMediaSources(IHasMediaSources item, CancellationToken cancellationToken)
@ -186,7 +198,15 @@ namespace MediaBrowser.Server.Implementations.Library
{
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)
{
@ -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)
@ -263,6 +298,9 @@ namespace MediaBrowser.Server.Implementations.Library
preferredSubs,
user.Configuration.SubtitleMode,
audioLangage);
MediaStreamSelector.SetSubtitleStreamScores(source.MediaStreams, preferredSubs,
user.Configuration.SubtitleMode, audioLangage);
}
private IEnumerable<MediaSourceInfo> SortMediaSources(IEnumerable<MediaSourceInfo> sources)
@ -286,9 +324,238 @@ namespace MediaBrowser.Server.Implementations.Library
.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)
{
var val = request.IsPlayed.Value;
if (i.IsPlayed(currentUser) != val)
if (i is Video && i.IsPlayed(currentUser) != val)
{
return false;
}

View File

@ -51,11 +51,13 @@ namespace MediaBrowser.Server.Implementations.LiveTv
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.Stream = response.Content;
imageResponse.SetFormatFromMimeType(response.ContentType);
imageResponse.SetFormatFromMimeType(contentType);
}
else
{

View File

@ -373,7 +373,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv
StartDate = item.StartDate,
OfficialRating = item.OfficialRating,
IsHD = item.IsHD,
OriginalAirDate = item.PremiereDate,
OriginalAirDate = item.OriginalAirDate,
Audio = item.Audio,
CommunityRating = GetClientCommunityRating(item.CommunityRating),
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.Extensions;
using MediaBrowser.Common.Progress;
using MediaBrowser.Common.ScheduledTasks;
using MediaBrowser.Controller.Channels;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Drawing;
using MediaBrowser.Controller.Dto;
@ -15,7 +13,6 @@ using MediaBrowser.Controller.Localization;
using MediaBrowser.Controller.Persistence;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Controller.Sorting;
using MediaBrowser.Model.Dlna;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.LiveTv;
@ -88,8 +85,6 @@ namespace MediaBrowser.Server.Implementations.LiveTv
get { return _services; }
}
public ILiveTvService ActiveService { get; private set; }
private LiveTvOptions GetConfiguration()
{
return _config.GetConfiguration<LiveTvOptions>("livetv");
@ -103,8 +98,6 @@ namespace MediaBrowser.Server.Implementations.LiveTv
{
_services.AddRange(services);
ActiveService = _services.FirstOrDefault();
foreach (var service in _services)
{
service.DataSourceChanged += service_DataSourceChanged;
@ -316,6 +309,22 @@ namespace MediaBrowser.Server.Implementations.LiveTv
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)
{
return GetService(item.ServiceName);
@ -333,30 +342,48 @@ namespace MediaBrowser.Server.Implementations.LiveTv
try
{
MediaSourceInfo info;
bool isVideo;
if (isChannel)
{
var channel = GetInternalChannel(id);
isVideo = channel.ChannelType == ChannelType.TV;
var service = GetService(channel);
_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.RequiresClosing = true;
if (info.RequiresClosing)
{
var idPrefix = service.GetType().FullName.GetMD5().ToString("N") + "_";
info.LiveStreamId = idPrefix + info.Id;
}
}
else
{
var recording = await GetInternalRecording(id, cancellationToken).ConfigureAwait(false);
isVideo = !string.Equals(recording.MediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase);
var service = GetService(recording);
_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.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));
Sanitize(info);
Normalize(info, isVideo);
var data = new LiveStreamData
{
Info = info,
ConsumerCount = 1,
IsChannel = isChannel,
ItemId = id
};
@ -377,25 +404,43 @@ namespace MediaBrowser.Server.Implementations.LiveTv
}
}
private void Sanitize(MediaSourceInfo mediaSource)
private void Normalize(MediaSourceInfo mediaSource, bool isVideo)
{
if (mediaSource.MediaStreams.Count == 0)
{
mediaSource.MediaStreams.AddRange(new List<MediaStream>
if (isVideo)
{
new MediaStream
mediaSource.MediaStreams.AddRange(new List<MediaStream>
{
Type = MediaStreamType.Video,
// Set the index to -1 because we don't know the exact index of the video stream within the container
Index = -1
},
new MediaStream
new MediaStream
{
Type = MediaStreamType.Video,
// Set the index to -1 because we don't know the exact index of the video stream within the container
Index = -1,
// Set to true if unknown to enable deinterlacing
IsInterlaced = true
},
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
}
});
}
else
{
mediaSource.MediaStreams.AddRange(new List<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
}
});
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
@ -544,13 +589,15 @@ namespace MediaBrowser.Server.Implementations.LiveTv
item.Name = info.Name;
item.OfficialRating = info.OfficialRating;
item.Overview = info.Overview;
item.PremiereDate = info.OriginalAirDate;
item.OriginalAirDate = info.OriginalAirDate;
item.ProviderImagePath = info.ImagePath;
item.ProviderImageUrl = info.ImageUrl;
item.RunTimeTicks = (info.EndDate - info.StartDate).Ticks;
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);
return item;
@ -1742,7 +1789,6 @@ namespace MediaBrowser.Server.Implementations.LiveTv
class LiveStreamData
{
internal MediaSourceInfo Info;
internal int ConsumerCount;
internal string ItemId;
internal bool IsChannel;
}
@ -1753,19 +1799,18 @@ namespace MediaBrowser.Server.Implementations.LiveTv
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;
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);
_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
};
var tunerIdPrefix = service.GetType().FullName.GetMD5().ToString("N") + "_";
try
{
var statusInfo = await service.GetStatusInfoAsync(cancellationToken).ConfigureAwait(false);
@ -1867,7 +1914,11 @@ namespace MediaBrowser.Server.Implementations.LiveTv
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();
}
@ -1920,7 +1971,16 @@ namespace MediaBrowser.Server.Implementations.LiveTv
/// <returns>Task.</returns>
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)

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