commit
be74de0236
|
@ -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,23 +274,29 @@ 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)
|
||||
{
|
||||
|
@ -302,7 +308,6 @@ namespace MediaBrowser.Api
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when [transcode kill timer stopped].
|
||||
|
@ -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;
|
||||
|
@ -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>
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
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";
|
||||
TryStreamCopy(state, state.VideoRequest);
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
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)
|
||||
else
|
||||
{
|
||||
state.InputTimestamp = mediaSource.Timestamp.Value;
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
|
@ -1800,6 +1680,16 @@ namespace MediaBrowser.Api.Playback
|
|||
ApplyDeviceProfileSettings(state);
|
||||
|
||||
if (videoRequest != null)
|
||||
{
|
||||
TryStreamCopy(state, videoRequest);
|
||||
}
|
||||
|
||||
state.OutputFilePath = GetOutputFilePath(state);
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
private void TryStreamCopy(StreamState state, VideoStreamRequest videoRequest)
|
||||
{
|
||||
if (state.VideoStream != null && CanStreamCopyVideo(videoRequest, state.VideoStream))
|
||||
{
|
||||
|
@ -1812,16 +1702,34 @@ namespace MediaBrowser.Api.Playback
|
|||
}
|
||||
}
|
||||
|
||||
state.OutputFilePath = GetOutputFilePath(state);
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
private void AttachMediaStreamInfo(StreamState state,
|
||||
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;
|
||||
}
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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,31 +36,121 @@ 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)
|
||||
{
|
||||
var authInfo = AuthorizationContext.GetAuthorizationInfo(Request);
|
||||
|
||||
var result = await _mediaSourceManager.OpenLiveStream(request, false, CancellationToken.None).ConfigureAwait(false);
|
||||
|
||||
var profile = request.DeviceProfile;
|
||||
if (profile == null)
|
||||
{
|
||||
var caps = _deviceManager.GetCapabilities(authInfo.DeviceId);
|
||||
if (caps != null)
|
||||
{
|
||||
profile = caps.DeviceProfile;
|
||||
}
|
||||
}
|
||||
|
||||
if (profile != null)
|
||||
{
|
||||
var item = _libraryManager.GetItemById(request.ItemId);
|
||||
|
||||
SetDeviceSpecificData(item, result.MediaSource, profile, authInfo, request.MaxStreamingBitrate,
|
||||
request.StartTimeTicks ?? 0, result.MediaSource.Id, request.AudioStreamIndex,
|
||||
request.SubtitleStreamIndex);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(result.MediaSource.TranscodingUrl))
|
||||
{
|
||||
result.MediaSource.TranscodingUrl += "&LiveStreamId=" + result.MediaSource.LiveStreamId;
|
||||
}
|
||||
}
|
||||
|
||||
return ToOptimizedResult(result);
|
||||
}
|
||||
|
||||
public void Post(CloseMediaSource request)
|
||||
{
|
||||
var task = _mediaSourceManager.CloseLiveStream(request.LiveStreamId, CancellationToken.None);
|
||||
Task.WaitAll(task);
|
||||
}
|
||||
|
||||
public async Task<object> Post(GetPostedPlaybackInfo request)
|
||||
{
|
||||
var info = await GetPlaybackInfo(request.Id, request.UserId, request.MediaSourceId, request.LiveStreamId).ConfigureAwait(false);
|
||||
var authInfo = AuthorizationContext.GetAuthorizationInfo(Request);
|
||||
|
||||
var profile = request.DeviceProfile;
|
||||
if (profile == null)
|
||||
{
|
||||
var caps = _deviceManager.GetCapabilities(authInfo.DeviceId);
|
||||
if (caps != null)
|
||||
{
|
||||
profile = caps.DeviceProfile;
|
||||
}
|
||||
}
|
||||
|
||||
if (profile != null)
|
||||
{
|
||||
var mediaSourceId = request.MediaSourceId;
|
||||
SetDeviceSpecificData(request.Id, info, profile, authInfo, request.MaxStreamingBitrate, request.StartTimeTicks ?? 0, mediaSourceId, request.AudioStreamIndex, request.SubtitleStreamIndex);
|
||||
}
|
||||
|
||||
return ToOptimizedResult(info);
|
||||
}
|
||||
|
||||
private async Task<PlaybackInfoResponse> GetPlaybackInfo(string id, string userId, string mediaSourceId = null, string liveStreamId = null)
|
||||
{
|
||||
var result = new PlaybackInfoResponse();
|
||||
|
||||
if (string.IsNullOrWhiteSpace(liveStreamId))
|
||||
{
|
||||
IEnumerable<MediaSourceInfo> mediaSources;
|
||||
var result = new LiveMediaInfoResult();
|
||||
|
||||
try
|
||||
{
|
||||
mediaSources = await _mediaSourceManager.GetPlayackMediaSources(id, userId, true, CancellationToken.None).ConfigureAwait(false);
|
||||
|
@ -68,9 +162,211 @@ namespace MediaBrowser.Api.Playback
|
|||
}
|
||||
|
||||
result.MediaSources = mediaSources.ToList();
|
||||
result.StreamId = Guid.NewGuid().ToString("N");
|
||||
|
||||
return ToOptimizedResult(result);
|
||||
if (!string.IsNullOrWhiteSpace(mediaSourceId))
|
||||
{
|
||||
result.MediaSources = result.MediaSources
|
||||
.Where(i => string.Equals(i.Id, mediaSourceId, StringComparison.OrdinalIgnoreCase))
|
||||
.ToList();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var mediaSource = await _mediaSourceManager.GetLiveStream(liveStreamId, CancellationToken.None).ConfigureAwait(false);
|
||||
|
||||
result.MediaSources = new List<MediaSourceInfo> { mediaSource };
|
||||
}
|
||||
|
||||
if (result.MediaSources.Count == 0)
|
||||
{
|
||||
if (!result.ErrorCode.HasValue)
|
||||
{
|
||||
result.ErrorCode = PlaybackErrorCode.NoCompatibleStream;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
result.PlaySessionId = Guid.NewGuid().ToString("N");
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private void SetDeviceSpecificData(string itemId,
|
||||
PlaybackInfoResponse result,
|
||||
DeviceProfile profile,
|
||||
AuthorizationInfo auth,
|
||||
int? maxBitrate,
|
||||
long startTimeTicks,
|
||||
string mediaSourceId,
|
||||
int? audioStreamIndex,
|
||||
int? subtitleStreamIndex)
|
||||
{
|
||||
var item = _libraryManager.GetItemById(itemId);
|
||||
|
||||
foreach (var mediaSource in result.MediaSources)
|
||||
{
|
||||
SetDeviceSpecificData(item, mediaSource, profile, auth, maxBitrate, startTimeTicks, mediaSourceId, audioStreamIndex, subtitleStreamIndex);
|
||||
}
|
||||
|
||||
SortMediaSources(result);
|
||||
}
|
||||
|
||||
private void SetDeviceSpecificData(BaseItem item,
|
||||
MediaSourceInfo mediaSource,
|
||||
DeviceProfile profile,
|
||||
AuthorizationInfo auth,
|
||||
int? maxBitrate,
|
||||
long startTimeTicks,
|
||||
string mediaSourceId,
|
||||
int? audioStreamIndex,
|
||||
int? subtitleStreamIndex)
|
||||
{
|
||||
var streamBuilder = new StreamBuilder();
|
||||
|
||||
var baseUrl = GetServerAddress();
|
||||
|
||||
var options = new VideoOptions
|
||||
{
|
||||
MediaSources = new List<MediaSourceInfo> { mediaSource },
|
||||
Context = EncodingContext.Streaming,
|
||||
DeviceId = auth.DeviceId,
|
||||
ItemId = item.Id.ToString("N"),
|
||||
Profile = profile,
|
||||
MaxBitrate = maxBitrate
|
||||
};
|
||||
|
||||
if (string.Equals(mediaSourceId, mediaSource.Id, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
options.MediaSourceId = mediaSourceId;
|
||||
options.AudioStreamIndex = audioStreamIndex;
|
||||
options.SubtitleStreamIndex = subtitleStreamIndex;
|
||||
}
|
||||
|
||||
if (mediaSource.SupportsDirectPlay)
|
||||
{
|
||||
var supportsDirectStream = mediaSource.SupportsDirectStream;
|
||||
|
||||
// Dummy this up to fool StreamBuilder
|
||||
mediaSource.SupportsDirectStream = true;
|
||||
|
||||
// The MediaSource supports direct stream, now test to see if the client supports it
|
||||
var streamInfo = string.Equals(item.MediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase) ?
|
||||
streamBuilder.BuildAudioItem(options) :
|
||||
streamBuilder.BuildVideoItem(options);
|
||||
|
||||
if (streamInfo == null || !streamInfo.IsDirectStream)
|
||||
{
|
||||
mediaSource.SupportsDirectPlay = false;
|
||||
}
|
||||
|
||||
// Set this back to what it was
|
||||
mediaSource.SupportsDirectStream = supportsDirectStream;
|
||||
|
||||
if (streamInfo != null)
|
||||
{
|
||||
SetDeviceSpecificSubtitleInfo(streamInfo, mediaSource, baseUrl, auth.Token);
|
||||
}
|
||||
}
|
||||
|
||||
if (mediaSource.SupportsDirectStream)
|
||||
{
|
||||
// The MediaSource supports direct stream, now test to see if the client supports it
|
||||
var streamInfo = string.Equals(item.MediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase) ?
|
||||
streamBuilder.BuildAudioItem(options) :
|
||||
streamBuilder.BuildVideoItem(options);
|
||||
|
||||
if (streamInfo == null || !streamInfo.IsDirectStream)
|
||||
{
|
||||
mediaSource.SupportsDirectStream = false;
|
||||
}
|
||||
|
||||
if (streamInfo != null)
|
||||
{
|
||||
SetDeviceSpecificSubtitleInfo(streamInfo, mediaSource, baseUrl, auth.Token);
|
||||
}
|
||||
}
|
||||
|
||||
if (mediaSource.SupportsTranscoding)
|
||||
{
|
||||
// The MediaSource supports direct stream, now test to see if the client supports it
|
||||
var streamInfo = string.Equals(item.MediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase) ?
|
||||
streamBuilder.BuildAudioItem(options) :
|
||||
streamBuilder.BuildVideoItem(options);
|
||||
|
||||
if (streamInfo != null && streamInfo.PlayMethod == PlayMethod.Transcode)
|
||||
{
|
||||
streamInfo.StartPositionTicks = startTimeTicks;
|
||||
mediaSource.TranscodingUrl = streamInfo.ToUrl(baseUrl, auth.Token);
|
||||
mediaSource.TranscodingContainer = streamInfo.Container;
|
||||
mediaSource.TranscodingSubProtocol = streamInfo.SubProtocol;
|
||||
}
|
||||
|
||||
if (streamInfo != null)
|
||||
{
|
||||
SetDeviceSpecificSubtitleInfo(streamInfo, mediaSource, baseUrl, auth.Token);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void SetDeviceSpecificSubtitleInfo(StreamInfo info, MediaSourceInfo mediaSource, string baseUrl, string accessToken)
|
||||
{
|
||||
var profiles = info.GetSubtitleProfiles(false, baseUrl, accessToken);
|
||||
mediaSource.DefaultSubtitleStreamIndex = info.SubtitleStreamIndex;
|
||||
|
||||
foreach (var profile in profiles)
|
||||
{
|
||||
foreach (var stream in mediaSource.MediaStreams)
|
||||
{
|
||||
if (stream.Type == MediaStreamType.Subtitle && stream.Index == profile.Index)
|
||||
{
|
||||
stream.DeliveryMethod = profile.DeliveryMethod;
|
||||
|
||||
if (profile.DeliveryMethod == SubtitleDeliveryMethod.External)
|
||||
{
|
||||
stream.DeliveryUrl = profile.Url;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void SortMediaSources(PlaybackInfoResponse result)
|
||||
{
|
||||
var originalList = result.MediaSources.ToList();
|
||||
|
||||
result.MediaSources = result.MediaSources.OrderBy(i =>
|
||||
{
|
||||
// Nothing beats direct playing a file
|
||||
if (i.SupportsDirectPlay && i.Protocol == MediaProtocol.File)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 1;
|
||||
|
||||
}).ThenBy(i =>
|
||||
{
|
||||
// Let's assume direct streaming a file is just as desirable as direct playing a remote url
|
||||
if (i.SupportsDirectPlay || i.SupportsDirectStream)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 1;
|
||||
|
||||
}).ThenBy(i =>
|
||||
{
|
||||
switch (i.Protocol)
|
||||
{
|
||||
case MediaProtocol.File:
|
||||
return 0;
|
||||
default:
|
||||
return 1;
|
||||
}
|
||||
|
||||
}).ThenBy(originalList.IndexOf)
|
||||
.ToList();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -31,7 +31,7 @@ namespace MediaBrowser.Api.Playback.Progressive
|
|||
/// </summary>
|
||||
public class AudioService : BaseProgressiveStreamingService
|
||||
{
|
||||
public AudioService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, ILiveTvManager liveTvManager, IDlnaManager dlnaManager, ISubtitleEncoder subtitleEncoder, IDeviceManager deviceManager, IMediaSourceManager mediaSourceManager, IZipClient zipClient, IImageProcessor imageProcessor, IHttpClient httpClient) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, fileSystem, liveTvManager, dlnaManager, subtitleEncoder, deviceManager, mediaSourceManager, zipClient, imageProcessor, httpClient)
|
||||
public AudioService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, IDlnaManager dlnaManager, ISubtitleEncoder subtitleEncoder, IDeviceManager deviceManager, IMediaSourceManager mediaSourceManager, IZipClient zipClient, IImageProcessor imageProcessor, IHttpClient httpClient) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, fileSystem, dlnaManager, subtitleEncoder, deviceManager, mediaSourceManager, zipClient, imageProcessor, httpClient)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -55,7 +55,7 @@ namespace MediaBrowser.Api.Playback.Progressive
|
|||
return ProcessRequest(request, true);
|
||||
}
|
||||
|
||||
protected override string GetCommandLineArguments(string outputPath, string transcodingJobId, StreamState state, bool isEncoding)
|
||||
protected override string GetCommandLineArguments(string outputPath, StreamState state, bool isEncoding)
|
||||
{
|
||||
var audioTranscodeParams = new List<string>();
|
||||
|
||||
|
@ -84,7 +84,7 @@ namespace MediaBrowser.Api.Playback.Progressive
|
|||
|
||||
return string.Format("{0} {1} -threads {2}{3} {4} -id3v2_version 3 -write_id3v1 1 -y \"{5}\"",
|
||||
inputModifier,
|
||||
GetInputArgument(transcodingJobId, state),
|
||||
GetInputArgument(state),
|
||||
threads,
|
||||
vn,
|
||||
string.Join(" ", audioTranscodeParams.ToArray()),
|
||||
|
|
|
@ -27,7 +27,7 @@ namespace MediaBrowser.Api.Playback.Progressive
|
|||
protected readonly IImageProcessor ImageProcessor;
|
||||
protected readonly IHttpClient HttpClient;
|
||||
|
||||
protected BaseProgressiveStreamingService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, ILiveTvManager liveTvManager, IDlnaManager dlnaManager, ISubtitleEncoder subtitleEncoder, IDeviceManager deviceManager, IMediaSourceManager mediaSourceManager, IZipClient zipClient, IImageProcessor imageProcessor, IHttpClient httpClient) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, fileSystem, liveTvManager, dlnaManager, subtitleEncoder, deviceManager, mediaSourceManager, zipClient)
|
||||
protected BaseProgressiveStreamingService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, IDlnaManager dlnaManager, ISubtitleEncoder subtitleEncoder, IDeviceManager deviceManager, IMediaSourceManager mediaSourceManager, IZipClient zipClient, IImageProcessor imageProcessor, IHttpClient httpClient) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, fileSystem, dlnaManager, subtitleEncoder, deviceManager, mediaSourceManager, zipClient)
|
||||
{
|
||||
ImageProcessor = imageProcessor;
|
||||
HttpClient = httpClient;
|
||||
|
|
|
@ -62,7 +62,7 @@ namespace MediaBrowser.Api.Playback.Progressive
|
|||
/// </summary>
|
||||
public class VideoService : BaseProgressiveStreamingService
|
||||
{
|
||||
public VideoService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, ILiveTvManager liveTvManager, IDlnaManager dlnaManager, ISubtitleEncoder subtitleEncoder, IDeviceManager deviceManager, IMediaSourceManager mediaSourceManager, IZipClient zipClient, IImageProcessor imageProcessor, IHttpClient httpClient) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, fileSystem, liveTvManager, dlnaManager, subtitleEncoder, deviceManager, mediaSourceManager, zipClient, imageProcessor, httpClient)
|
||||
public VideoService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, IDlnaManager dlnaManager, ISubtitleEncoder subtitleEncoder, IDeviceManager deviceManager, IMediaSourceManager mediaSourceManager, IZipClient zipClient, IImageProcessor imageProcessor, IHttpClient httpClient) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, fileSystem, dlnaManager, subtitleEncoder, deviceManager, mediaSourceManager, zipClient, imageProcessor, httpClient)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -86,7 +86,7 @@ namespace MediaBrowser.Api.Playback.Progressive
|
|||
return ProcessRequest(request, true);
|
||||
}
|
||||
|
||||
protected override string GetCommandLineArguments(string outputPath, string transcodingJobId, StreamState state, bool isEncoding)
|
||||
protected override string GetCommandLineArguments(string outputPath, StreamState state, bool isEncoding)
|
||||
{
|
||||
// Get the output codec name
|
||||
var videoCodec = state.OutputVideoCodec;
|
||||
|
@ -106,7 +106,7 @@ namespace MediaBrowser.Api.Playback.Progressive
|
|||
|
||||
return string.Format("{0} {1}{2} {3} {4} -map_metadata -1 -threads {5} {6}{7} -y \"{8}\"",
|
||||
inputModifier,
|
||||
GetInputArgument(transcodingJobId, state),
|
||||
GetInputArgument(state),
|
||||
keyFrame,
|
||||
GetMapArgs(state),
|
||||
GetVideoArguments(state, videoCodec),
|
||||
|
|
|
@ -71,9 +71,8 @@ namespace MediaBrowser.Api.Playback
|
|||
|
||||
public string Params { get; set; }
|
||||
public string ClientTime { get; set; }
|
||||
public string StreamId { get; set; }
|
||||
|
||||
public string TranscodingJobId { get; set; }
|
||||
public string PlaySessionId { get; set; }
|
||||
public string LiveStreamId { get; set; }
|
||||
}
|
||||
|
||||
public class VideoStreamRequest : StreamRequest
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
using MediaBrowser.Controller.LiveTv;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Model.Dlna;
|
||||
using MediaBrowser.Model.Drawing;
|
||||
using MediaBrowser.Model.Dto;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.IO;
|
||||
using MediaBrowser.Model.Logging;
|
||||
|
@ -17,7 +18,7 @@ namespace MediaBrowser.Api.Playback
|
|||
public class StreamState : IDisposable
|
||||
{
|
||||
private readonly ILogger _logger;
|
||||
private readonly ILiveTvManager _liveTvManager;
|
||||
private readonly IMediaSourceManager _mediaSourceManager;
|
||||
|
||||
public string RequestedUrl { get; set; }
|
||||
|
||||
|
@ -39,7 +40,7 @@ namespace MediaBrowser.Api.Playback
|
|||
|
||||
public string InputContainer { get; set; }
|
||||
|
||||
public List<MediaStream> AllMediaStreams { get; set; }
|
||||
public MediaSourceInfo MediaSource { get; set; }
|
||||
|
||||
public MediaStream AudioStream { get; set; }
|
||||
public MediaStream VideoStream { get; set; }
|
||||
|
@ -64,8 +65,6 @@ namespace MediaBrowser.Api.Playback
|
|||
|
||||
public List<string> PlayableStreamFileNames { get; set; }
|
||||
|
||||
public string LiveTvStreamId { get; set; }
|
||||
|
||||
public int SegmentLength = 3;
|
||||
public bool EnableGenericHlsSegmenter = false;
|
||||
public int HlsListSize
|
||||
|
@ -86,14 +85,13 @@ namespace MediaBrowser.Api.Playback
|
|||
|
||||
public List<string> SupportedAudioCodecs { get; set; }
|
||||
|
||||
public StreamState(ILiveTvManager liveTvManager, ILogger logger)
|
||||
public StreamState(IMediaSourceManager mediaSourceManager, ILogger logger)
|
||||
{
|
||||
_liveTvManager = liveTvManager;
|
||||
_mediaSourceManager = mediaSourceManager;
|
||||
_logger = logger;
|
||||
SupportedAudioCodecs = new List<string>();
|
||||
PlayableStreamFileNames = new List<string>();
|
||||
RemoteHttpHeaders = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||
AllMediaStreams = new List<MediaStream>();
|
||||
}
|
||||
|
||||
public string InputAudioSync { get; set; }
|
||||
|
@ -113,9 +111,6 @@ namespace MediaBrowser.Api.Playback
|
|||
|
||||
public long? EncodingDurationTicks { get; set; }
|
||||
|
||||
public string ItemType { get; set; }
|
||||
public string ItemId { get; set; }
|
||||
|
||||
public string GetMimeType(string outputPath)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(MimeType))
|
||||
|
@ -187,15 +182,15 @@ namespace MediaBrowser.Api.Playback
|
|||
|
||||
private async void DisposeLiveStream()
|
||||
{
|
||||
if (!string.IsNullOrEmpty(LiveTvStreamId))
|
||||
if (MediaSource.RequiresClosing && string.IsNullOrWhiteSpace(Request.LiveStreamId))
|
||||
{
|
||||
try
|
||||
{
|
||||
await _liveTvManager.CloseLiveStream(LiveTvStreamId, CancellationToken.None).ConfigureAwait(false);
|
||||
await _mediaSourceManager.CloseLiveStream(MediaSource.LiveStreamId, CancellationToken.None).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.ErrorException("Error closing live tv stream", ex);
|
||||
_logger.ErrorException("Error closing media source", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -351,6 +346,42 @@ namespace MediaBrowser.Api.Playback
|
|||
}
|
||||
}
|
||||
|
||||
public int? TargetVideoStreamCount
|
||||
{
|
||||
get
|
||||
{
|
||||
if (Request.Static)
|
||||
{
|
||||
return GetMediaStreamCount(MediaStreamType.Video, int.MaxValue);
|
||||
}
|
||||
return GetMediaStreamCount(MediaStreamType.Video, 1);
|
||||
}
|
||||
}
|
||||
|
||||
public int? TargetAudioStreamCount
|
||||
{
|
||||
get
|
||||
{
|
||||
if (Request.Static)
|
||||
{
|
||||
return GetMediaStreamCount(MediaStreamType.Audio, int.MaxValue);
|
||||
}
|
||||
return GetMediaStreamCount(MediaStreamType.Audio, 1);
|
||||
}
|
||||
}
|
||||
|
||||
private int? GetMediaStreamCount(MediaStreamType type, int limit)
|
||||
{
|
||||
var count = MediaSource.GetStreamCount(type);
|
||||
|
||||
if (count.HasValue)
|
||||
{
|
||||
count = Math.Min(count.Value, limit);
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Predicts the audio sample rate that will be in the output stream
|
||||
/// </summary>
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
using MediaBrowser.Model.Logging;
|
||||
using MediaBrowser.Common.Configuration;
|
||||
using MediaBrowser.Model.Configuration;
|
||||
using MediaBrowser.Model.Logging;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
|
@ -11,13 +13,18 @@ namespace MediaBrowser.Api.Playback
|
|||
private readonly ILogger _logger;
|
||||
private Timer _timer;
|
||||
private bool _isPaused;
|
||||
private readonly IConfigurationManager _config;
|
||||
|
||||
private readonly long _gapLengthInTicks = TimeSpan.FromMinutes(2).Ticks;
|
||||
|
||||
public TranscodingThrottler(TranscodingJob job, ILogger logger)
|
||||
public TranscodingThrottler(TranscodingJob job, ILogger logger, IConfigurationManager config)
|
||||
{
|
||||
_job = job;
|
||||
_logger = logger;
|
||||
_config = config;
|
||||
}
|
||||
|
||||
private EncodingOptions GetOptions()
|
||||
{
|
||||
return _config.GetConfiguration<EncodingOptions>("encoding");
|
||||
}
|
||||
|
||||
public void Start()
|
||||
|
@ -33,7 +40,9 @@ namespace MediaBrowser.Api.Playback
|
|||
return;
|
||||
}
|
||||
|
||||
if (IsThrottleAllowed(_job))
|
||||
var options = GetOptions();
|
||||
|
||||
if (options.EnableThrottling && IsThrottleAllowed(_job, options.ThrottleThresholdSeconds))
|
||||
{
|
||||
PauseTranscoding();
|
||||
}
|
||||
|
@ -79,19 +88,20 @@ namespace MediaBrowser.Api.Playback
|
|||
}
|
||||
}
|
||||
|
||||
private bool IsThrottleAllowed(TranscodingJob job)
|
||||
private bool IsThrottleAllowed(TranscodingJob job, int thresholdSeconds)
|
||||
{
|
||||
var bytesDownloaded = job.BytesDownloaded ?? 0;
|
||||
var transcodingPositionTicks = job.TranscodingPositionTicks ?? 0;
|
||||
var downloadPositionTicks = job.DownloadPositionTicks ?? 0;
|
||||
|
||||
var path = job.Path;
|
||||
var gapLengthInTicks = TimeSpan.FromSeconds(thresholdSeconds).Ticks;
|
||||
|
||||
if (downloadPositionTicks > 0 && transcodingPositionTicks > 0)
|
||||
{
|
||||
// HLS - time-based consideration
|
||||
|
||||
var targetGap = _gapLengthInTicks;
|
||||
var targetGap = gapLengthInTicks;
|
||||
var gap = transcodingPositionTicks - downloadPositionTicks;
|
||||
|
||||
if (gap < targetGap)
|
||||
|
@ -113,7 +123,7 @@ namespace MediaBrowser.Api.Playback
|
|||
var bytesTranscoded = job.BytesTranscoded ?? new FileInfo(path).Length;
|
||||
|
||||
// Estimate the bytes the transcoder should be ahead
|
||||
double gapFactor = _gapLengthInTicks;
|
||||
double gapFactor = gapLengthInTicks;
|
||||
gapFactor /= transcodingPositionTicks;
|
||||
var targetGap = bytesTranscoded * gapFactor;
|
||||
|
||||
|
|
|
@ -192,7 +192,7 @@ namespace MediaBrowser.Api.Subtitles
|
|||
{
|
||||
var item = (Video)_libraryManager.GetItemById(new Guid(request.Id));
|
||||
|
||||
var mediaSource = item.GetMediaSources(false)
|
||||
var mediaSource = _mediaSourceManager.GetStaticMediaSources(item, false, null)
|
||||
.First(i => string.Equals(i.Id, request.MediaSourceId ?? request.Id));
|
||||
|
||||
var subtitleStream = mediaSource.MediaStreams
|
||||
|
|
|
@ -55,22 +55,30 @@ 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);
|
||||
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>
|
||||
|
|
|
@ -304,6 +304,14 @@ namespace MediaBrowser.Api.UserLibrary
|
|||
{
|
||||
var user = _userManager.GetUserById(request.UserId);
|
||||
|
||||
if (!request.IsPlayed.HasValue)
|
||||
{
|
||||
if (user.Configuration.HidePlayedInLatest)
|
||||
{
|
||||
request.IsPlayed = false;
|
||||
}
|
||||
}
|
||||
|
||||
var list = _userViewManager.GetLatestItems(new LatestItemsQuery
|
||||
{
|
||||
GroupItems = request.GroupItems,
|
||||
|
|
|
@ -107,30 +107,40 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager
|
|||
|
||||
private WebRequest GetRequest(HttpRequestOptions options, string method, bool enableHttpCompression)
|
||||
{
|
||||
var request = (HttpWebRequest)WebRequest.Create(options.Url);
|
||||
var request = WebRequest.Create(options.Url);
|
||||
var httpWebRequest = request as HttpWebRequest;
|
||||
|
||||
AddRequestHeaders(request, options);
|
||||
if (httpWebRequest != null)
|
||||
{
|
||||
AddRequestHeaders(httpWebRequest, options);
|
||||
|
||||
request.AutomaticDecompression = enableHttpCompression ? DecompressionMethods.Deflate : DecompressionMethods.None;
|
||||
httpWebRequest.AutomaticDecompression = enableHttpCompression ? DecompressionMethods.Deflate : DecompressionMethods.None;
|
||||
}
|
||||
|
||||
request.CachePolicy = new RequestCachePolicy(RequestCacheLevel.BypassCache);
|
||||
|
||||
if (httpWebRequest != null)
|
||||
{
|
||||
if (options.EnableKeepAlive)
|
||||
{
|
||||
request.KeepAlive = true;
|
||||
httpWebRequest.KeepAlive = true;
|
||||
}
|
||||
}
|
||||
|
||||
request.Method = method;
|
||||
request.Timeout = options.TimeoutMs;
|
||||
|
||||
if (httpWebRequest != null)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(options.Host))
|
||||
{
|
||||
request.Host = options.Host;
|
||||
httpWebRequest.Host = options.Host;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(options.Referer))
|
||||
{
|
||||
request.Referer = options.Referer;
|
||||
httpWebRequest.Referer = options.Referer;
|
||||
}
|
||||
}
|
||||
|
||||
//request.ServicePoint.BindIPEndPointDelegate = BindIPEndPointCallback;
|
||||
|
|
|
@ -6,6 +6,7 @@ using MediaBrowser.Model.Logging;
|
|||
using MediaBrowser.Model.Serialization;
|
||||
using MediaBrowser.Model.Tasks;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
@ -29,7 +30,8 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks
|
|||
/// <summary>
|
||||
/// The _task queue
|
||||
/// </summary>
|
||||
private readonly SortedDictionary<Type, TaskExecutionOptions> _taskQueue = new SortedDictionary<Type, TaskExecutionOptions>();
|
||||
private readonly ConcurrentQueue<Tuple<Type, TaskExecutionOptions>> _taskQueue =
|
||||
new ConcurrentQueue<Tuple<Type, TaskExecutionOptions>>();
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the json serializer.
|
||||
|
@ -136,25 +138,17 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks
|
|||
{
|
||||
var type = task.ScheduledTask.GetType();
|
||||
|
||||
Logger.Info("Queueing task {0}", type.Name);
|
||||
|
||||
lock (_taskQueue)
|
||||
{
|
||||
// If it's idle just execute immediately
|
||||
if (task.State == TaskState.Idle)
|
||||
{
|
||||
Execute(task, options);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_taskQueue.ContainsKey(type))
|
||||
{
|
||||
Logger.Info("Queueing task {0}", type.Name);
|
||||
_taskQueue.Add(type, options);
|
||||
}
|
||||
else
|
||||
{
|
||||
_taskQueue[type] = options;
|
||||
Logger.Info("Task already queued: {0}", type.Name);
|
||||
}
|
||||
_taskQueue.Enqueue(new Tuple<Type, TaskExecutionOptions>(type, options));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -241,15 +235,24 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks
|
|||
// Execute queued tasks
|
||||
lock (_taskQueue)
|
||||
{
|
||||
foreach (var enqueuedType in _taskQueue.ToList())
|
||||
var list = new List<Tuple<Type, TaskExecutionOptions>>();
|
||||
|
||||
Tuple<Type, TaskExecutionOptions> item;
|
||||
while (_taskQueue.TryDequeue(out item))
|
||||
{
|
||||
var scheduledTask = ScheduledTasks.First(t => t.ScheduledTask.GetType() == enqueuedType.Key);
|
||||
if (list.All(i => i.Item1 != item.Item1))
|
||||
{
|
||||
list.Add(item);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var enqueuedType in list)
|
||||
{
|
||||
var scheduledTask = ScheduledTasks.First(t => t.ScheduledTask.GetType() == enqueuedType.Item1);
|
||||
|
||||
if (scheduledTask.State == TaskState.Idle)
|
||||
{
|
||||
Execute(scheduledTask, enqueuedType.Value);
|
||||
|
||||
_taskQueue.Remove(enqueuedType.Key);
|
||||
Execute(scheduledTask, enqueuedType.Item2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -75,9 +75,7 @@ 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)
|
||||
var sources = ChannelManager.GetStaticMediaSources(this, false, CancellationToken.None)
|
||||
.Result.ToList();
|
||||
|
||||
if (sources.Count > 0)
|
||||
|
@ -85,7 +83,15 @@ namespace MediaBrowser.Controller.Channels
|
|||
return sources;
|
||||
}
|
||||
|
||||
list.InsertRange(0, sources);
|
||||
var list = base.GetMediaSources(enablePathSubstitution).ToList();
|
||||
|
||||
foreach (var mediaSource in list)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(mediaSource.Path))
|
||||
{
|
||||
mediaSource.Type = MediaSourceType.Placeholder;
|
||||
}
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
|
|
@ -38,6 +38,7 @@ namespace MediaBrowser.Controller.Channels
|
|||
public string Id { get; set; }
|
||||
|
||||
public bool ReadAtNativeFramerate { get; set; }
|
||||
public bool SupportsDirectPlay { get; set; }
|
||||
|
||||
public ChannelMediaInfo()
|
||||
{
|
||||
|
@ -45,6 +46,7 @@ namespace MediaBrowser.Controller.Channels
|
|||
|
||||
// This is most common
|
||||
Protocol = MediaProtocol.Http;
|
||||
SupportsDirectPlay = true;
|
||||
}
|
||||
|
||||
public MediaSourceInfo ToMediaSource()
|
||||
|
@ -62,7 +64,9 @@ namespace MediaBrowser.Controller.Channels
|
|||
RunTimeTicks = RunTimeTicks,
|
||||
Name = id,
|
||||
Id = id,
|
||||
ReadAtNativeFramerate = ReadAtNativeFramerate
|
||||
ReadAtNativeFramerate = ReadAtNativeFramerate,
|
||||
SupportsDirectStream = Protocol == MediaProtocol.File || Protocol == MediaProtocol.Http,
|
||||
SupportsDirectPlay = SupportsDirectPlay
|
||||
};
|
||||
|
||||
var bitrate = (AudioBitrate ?? 0) + (VideoBitrate ?? 0);
|
||||
|
|
|
@ -90,9 +90,7 @@ 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)
|
||||
var sources = ChannelManager.GetStaticMediaSources(this, false, CancellationToken.None)
|
||||
.Result.ToList();
|
||||
|
||||
if (sources.Count > 0)
|
||||
|
@ -100,7 +98,15 @@ namespace MediaBrowser.Controller.Channels
|
|||
return sources;
|
||||
}
|
||||
|
||||
list.InsertRange(0, sources);
|
||||
var list = base.GetMediaSources(enablePathSubstitution).ToList();
|
||||
|
||||
foreach (var mediaSource in list)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(mediaSource.Path))
|
||||
{
|
||||
mediaSource.Type = MediaSourceType.Placeholder;
|
||||
}
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
|
|
@ -112,11 +112,11 @@ namespace MediaBrowser.Controller.Channels
|
|||
/// <summary>
|
||||
/// Gets the channel item media sources.
|
||||
/// </summary>
|
||||
/// <param name="id">The identifier.</param>
|
||||
/// <param name="includeDynamicSources">if set to <c>true</c> [include dynamic sources].</param>
|
||||
/// <param name="item">The item.</param>
|
||||
/// <param name="includeCachedVersions">if set to <c>true</c> [include cached versions].</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>Task{IEnumerable{MediaSourceInfo}}.</returns>
|
||||
Task<IEnumerable<MediaSourceInfo>> GetChannelItemMediaSources(string id, bool includeDynamicSources, CancellationToken cancellationToken);
|
||||
Task<IEnumerable<MediaSourceInfo>> GetStaticMediaSources(IChannelMediaItem item, bool includeCachedVersions, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the channel folder.
|
||||
|
|
|
@ -501,7 +501,8 @@ namespace MediaBrowser.Controller.Entities
|
|||
Formats = (i.FormatName ?? string.Empty).Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).ToList(),
|
||||
Timestamp = i.Timestamp,
|
||||
Type = type,
|
||||
PlayableStreamFileNames = i.PlayableStreamFileNames.ToList()
|
||||
PlayableStreamFileNames = i.PlayableStreamFileNames.ToList(),
|
||||
SupportsDirectStream = i.VideoType == VideoType.VideoFile
|
||||
};
|
||||
|
||||
if (i.IsShortcut)
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
using MediaBrowser.Controller.Persistence;
|
||||
using MediaBrowser.Model.Dto;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.MediaInfo;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
|
@ -64,6 +65,14 @@ namespace MediaBrowser.Controller.Library
|
|||
/// <returns>IEnumerable<MediaSourceInfo>.</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<MediaSourceInfo>.</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<MediaSourceInfo>.</returns>
|
||||
Task<LiveStreamResponse> OpenLiveStream(LiveStreamRequest request, bool enableAutoClose, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the live stream.
|
||||
/// </summary>
|
||||
/// <param name="id">The identifier.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>Task<MediaSourceInfo>.</returns>
|
||||
Task<MediaSourceInfo> GetLiveStream(string id, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Pings the media source.
|
||||
/// </summary>
|
||||
/// <param name="id">The live stream identifier.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>Task.</returns>
|
||||
Task PingLiveStream(string id, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Closes the media source.
|
||||
/// </summary>
|
||||
/// <param name="id">The live stream identifier.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>Task.</returns>
|
||||
Task CloseLiveStream(string id, CancellationToken cancellationToken);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,5 +15,21 @@ namespace MediaBrowser.Controller.Library
|
|||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>Task<IEnumerable<MediaSourceInfo>>.</returns>
|
||||
Task<IEnumerable<MediaSourceInfo>> GetMediaSources(IHasMediaSources item, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Opens the media source.
|
||||
/// </summary>
|
||||
/// <param name="openToken">The open token.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>Task<MediaSourceInfo>.</returns>
|
||||
Task<MediaSourceInfo> OpenMediaSource(string openToken, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Closes the media source.
|
||||
/// </summary>
|
||||
/// <param name="liveStreamId">The live stream identifier.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>Task.</returns>
|
||||
Task CloseMediaSource(string liveStreamId, CancellationToken cancellationToken);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
|
||||
using System;
|
||||
|
||||
namespace MediaBrowser.Controller.LiveTv
|
||||
{
|
||||
public interface ILiveTvItem
|
||||
{
|
||||
Guid Id { get; }
|
||||
string ServiceName { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -301,5 +301,21 @@ namespace MediaBrowser.Controller.LiveTv
|
|||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>Task<QueryResult<BaseItem>>.</returns>
|
||||
Task<QueryResult<BaseItem>> GetInternalRecordings(RecordingQuery query, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the recording media sources.
|
||||
/// </summary>
|
||||
/// <param name="id">The identifier.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>Task<IEnumerable<MediaSourceInfo>>.</returns>
|
||||
Task<IEnumerable<MediaSourceInfo>> GetRecordingMediaSources(string id, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the channel media sources.
|
||||
/// </summary>
|
||||
/// <param name="id">The identifier.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>Task<IEnumerable<MediaSourceInfo>>.</returns>
|
||||
Task<IEnumerable<MediaSourceInfo>> GetChannelMediaSources(string id, CancellationToken cancellationToken);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
using System.Runtime.Serialization;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Entities.Audio;
|
||||
using MediaBrowser.Model.Configuration;
|
||||
using MediaBrowser.Model.Dto;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.Users;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
namespace MediaBrowser.Controller.LiveTv
|
||||
{
|
||||
|
@ -99,5 +101,20 @@ namespace MediaBrowser.Controller.LiveTv
|
|||
{
|
||||
return user.Policy.EnableLiveTvManagement;
|
||||
}
|
||||
|
||||
public override IEnumerable<MediaSourceInfo> GetMediaSources(bool enablePathSubstitution)
|
||||
{
|
||||
var list = base.GetMediaSources(enablePathSubstitution).ToList();
|
||||
|
||||
foreach (var mediaSource in list)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(mediaSource.Path))
|
||||
{
|
||||
mediaSource.Type = MediaSourceType.Placeholder;
|
||||
}
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -82,6 +82,15 @@ namespace MediaBrowser.Controller.LiveTv
|
|||
/// <value><c>null</c> if [has image] contains no value, <c>true</c> if [has image]; otherwise, <c>false</c>.</value>
|
||||
public bool? HasProviderImage { get; set; }
|
||||
|
||||
public override LocationType LocationType
|
||||
{
|
||||
get
|
||||
{
|
||||
// TODO: This should be removed
|
||||
return LocationType.Remote;
|
||||
}
|
||||
}
|
||||
|
||||
protected override string CreateSortName()
|
||||
{
|
||||
double number = 0;
|
||||
|
@ -127,7 +136,7 @@ namespace MediaBrowser.Controller.LiveTv
|
|||
Name = Name,
|
||||
Path = Path,
|
||||
RunTimeTicks = RunTimeTicks,
|
||||
Type = MediaSourceType.Default
|
||||
Type = MediaSourceType.Placeholder
|
||||
};
|
||||
|
||||
list.Add(info);
|
||||
|
|
|
@ -34,6 +34,12 @@ namespace MediaBrowser.Controller.LiveTv
|
|||
/// <value>The channel identifier.</value>
|
||||
public string ExternalChannelId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the original air date.
|
||||
/// </summary>
|
||||
/// <value>The original air date.</value>
|
||||
public DateTime? OriginalAirDate { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the type of the channel.
|
||||
/// </summary>
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
using System.Runtime.Serialization;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Model.Configuration;
|
||||
using MediaBrowser.Model.Dto;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using System.Linq;
|
||||
using MediaBrowser.Model.Users;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
namespace MediaBrowser.Controller.LiveTv
|
||||
{
|
||||
|
@ -97,5 +99,20 @@ namespace MediaBrowser.Controller.LiveTv
|
|||
{
|
||||
return user.Policy.EnableLiveTvManagement;
|
||||
}
|
||||
|
||||
public override IEnumerable<MediaSourceInfo> GetMediaSources(bool enablePathSubstitution)
|
||||
{
|
||||
var list = base.GetMediaSources(enablePathSubstitution).ToList();
|
||||
|
||||
foreach (var mediaSource in list)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(mediaSource.Path))
|
||||
{
|
||||
mediaSource.Type = MediaSourceType.Placeholder;
|
||||
}
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -393,12 +393,13 @@
|
|||
<Compile Include="Subtitles\SubtitleDownloadEventArgs.cs" />
|
||||
<Compile Include="Subtitles\SubtitleResponse.cs" />
|
||||
<Compile Include="Subtitles\SubtitleSearchRequest.cs" />
|
||||
<Compile Include="Sync\IHasDynamicAccess.cs" />
|
||||
<Compile Include="Sync\IServerSyncProvider.cs" />
|
||||
<Compile Include="Sync\ISyncDataProvider.cs" />
|
||||
<Compile Include="Sync\ISyncManager.cs" />
|
||||
<Compile Include="Sync\ISyncProvider.cs" />
|
||||
<Compile Include="Sync\ISyncRepository.cs" />
|
||||
<Compile Include="Sync\SendFileResult.cs" />
|
||||
<Compile Include="Sync\SyncedFileInfo.cs" />
|
||||
<Compile Include="Themes\IAppThemeManager.cs" />
|
||||
<Compile Include="Themes\InternalThemeImage.cs" />
|
||||
<Compile Include="TV\ITVSeriesManager.cs" />
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
using System.IO;
|
||||
using MediaBrowser.Model.MediaInfo;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
|
@ -47,8 +48,9 @@ namespace MediaBrowser.Controller.MediaEncoding
|
|||
/// Gets the subtitle language encoding parameter.
|
||||
/// </summary>
|
||||
/// <param name="path">The path.</param>
|
||||
/// <param name="protocol">The protocol.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>System.String.</returns>
|
||||
string GetSubtitleFileCharacterSet(string path);
|
||||
|
||||
Task<string> GetSubtitleFileCharacterSet(string path, MediaProtocol protocol, CancellationToken cancellationToken);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -41,8 +41,6 @@ namespace MediaBrowser.Controller.MediaEncoding
|
|||
streams = GetSortedStreams(streams, MediaStreamType.Subtitle, preferredLanguages)
|
||||
.ToList();
|
||||
|
||||
var full = streams.Where(s => !s.IsForced);
|
||||
|
||||
MediaStream stream = null;
|
||||
|
||||
if (mode == SubtitlePlaybackMode.None)
|
||||
|
@ -55,13 +53,13 @@ namespace MediaBrowser.Controller.MediaEncoding
|
|||
// if the audio language is not understood by the user, load their preferred subs, if there are any
|
||||
if (!ContainsOrdinal(preferredLanguages, audioTrackLanguage))
|
||||
{
|
||||
stream = full.FirstOrDefault(s => ContainsOrdinal(preferredLanguages, s.Language));
|
||||
stream = streams.Where(s => !s.IsForced).FirstOrDefault(s => ContainsOrdinal(preferredLanguages, s.Language));
|
||||
}
|
||||
}
|
||||
else if (mode == SubtitlePlaybackMode.Always)
|
||||
{
|
||||
// always load the most suitable full subtitles
|
||||
stream = full.FirstOrDefault();
|
||||
stream = streams.FirstOrDefault(s => !s.IsForced);
|
||||
}
|
||||
|
||||
// load forced subs if we have found no suitable full subtitles
|
||||
|
@ -97,6 +95,77 @@ namespace MediaBrowser.Controller.MediaEncoding
|
|||
.ThenBy(i => i.Index);
|
||||
}
|
||||
|
||||
public static void SetSubtitleStreamScores(List<MediaStream> streams,
|
||||
List<string> preferredLanguages,
|
||||
SubtitlePlaybackMode mode,
|
||||
string audioTrackLanguage)
|
||||
{
|
||||
if (mode == SubtitlePlaybackMode.None)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
streams = GetSortedStreams(streams, MediaStreamType.Subtitle, preferredLanguages)
|
||||
.ToList();
|
||||
|
||||
var filteredStreams = new List<MediaStream>();
|
||||
|
||||
if (mode == SubtitlePlaybackMode.Default)
|
||||
{
|
||||
// if the audio language is not understood by the user, load their preferred subs, if there are any
|
||||
if (!ContainsOrdinal(preferredLanguages, audioTrackLanguage))
|
||||
{
|
||||
filteredStreams = streams.Where(s => !s.IsForced && ContainsOrdinal(preferredLanguages, s.Language))
|
||||
.ToList();
|
||||
}
|
||||
}
|
||||
else if (mode == SubtitlePlaybackMode.Always)
|
||||
{
|
||||
// always load the most suitable full subtitles
|
||||
filteredStreams = streams.Where(s => !s.IsForced)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
// load forced subs if we have found no suitable full subtitles
|
||||
if (filteredStreams.Count == 0)
|
||||
{
|
||||
filteredStreams = streams
|
||||
.Where(s => s.IsForced && string.Equals(s.Language, audioTrackLanguage, StringComparison.OrdinalIgnoreCase))
|
||||
.ToList();
|
||||
}
|
||||
|
||||
foreach (var stream in filteredStreams)
|
||||
{
|
||||
stream.Score = GetSubtitleScore(stream, preferredLanguages);
|
||||
}
|
||||
}
|
||||
|
||||
private static int GetSubtitleScore(MediaStream stream, List<string> languagePreferences)
|
||||
{
|
||||
var values = new List<int>();
|
||||
|
||||
var index = languagePreferences.FindIndex(l => string.Equals(stream.Language, l, StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
values.Add(index == -1 ? 0 : 100 - index);
|
||||
|
||||
values.Add(stream.IsDefault ? 1 : 0);
|
||||
values.Add(stream.SupportsExternalStream ? 1 : 0);
|
||||
values.Add(stream.IsTextSubtitleStream ? 1 : 0);
|
||||
values.Add(stream.IsExternal ? 1 : 0);
|
||||
|
||||
values.Reverse();
|
||||
var scale = 1;
|
||||
var score = 0;
|
||||
|
||||
foreach (var value in values)
|
||||
{
|
||||
score += scale * (value + 1);
|
||||
scale *= 10;
|
||||
}
|
||||
|
||||
return score;
|
||||
}
|
||||
|
||||
private static int GetBooleanOrderBy(bool value)
|
||||
{
|
||||
return value ? 0 : 1;
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
using MediaBrowser.Common.Net;
|
||||
using MediaBrowser.Model.Logging;
|
||||
using MediaBrowser.Model.Logging;
|
||||
using MediaBrowser.Model.Net;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
@ -75,12 +74,12 @@ namespace MediaBrowser.Controller.Net
|
|||
throw new ArgumentNullException("message");
|
||||
}
|
||||
|
||||
if (message.MessageType.Equals(Name + "Start", StringComparison.OrdinalIgnoreCase))
|
||||
if (string.Equals(message.MessageType, Name + "Start", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
Start(message);
|
||||
}
|
||||
|
||||
if (message.MessageType.Equals(Name + "Stop", StringComparison.OrdinalIgnoreCase))
|
||||
if (string.Equals(message.MessageType, Name + "Stop", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
Stop(message);
|
||||
}
|
||||
|
|
18
MediaBrowser.Controller/Sync/IHasDynamicAccess.cs
Normal file
18
MediaBrowser.Controller/Sync/IHasDynamicAccess.cs
Normal file
|
@ -0,0 +1,18 @@
|
|||
using MediaBrowser.Model.Sync;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MediaBrowser.Controller.Sync
|
||||
{
|
||||
public interface IHasDynamicAccess
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the synced file information.
|
||||
/// </summary>
|
||||
/// <param name="remotePath">The remote path.</param>
|
||||
/// <param name="target">The target.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>Task<SyncedFileInfo>.</returns>
|
||||
Task<SyncedFileInfo> GetSyncedFileInfo(string remotePath, SyncTarget target, CancellationToken cancellationToken);
|
||||
}
|
||||
}
|
|
@ -18,7 +18,7 @@ namespace MediaBrowser.Controller.Sync
|
|||
/// <param name="progress">The progress.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>Task.</returns>
|
||||
Task<SendFileResult> SendFile(Stream stream, string remotePath, SyncTarget target, IProgress<double> progress, CancellationToken cancellationToken);
|
||||
Task<SyncedFileInfo> SendFile(Stream stream, string remotePath, SyncTarget target, IProgress<double> progress, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Deletes the file.
|
||||
|
@ -54,14 +54,5 @@ namespace MediaBrowser.Controller.Sync
|
|||
/// <param name="target">The target.</param>
|
||||
/// <returns>System.String.</returns>
|
||||
string GetParentDirectoryPath(string path, SyncTarget target);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the file system entries.
|
||||
/// </summary>
|
||||
/// <param name="path">The path.</param>
|
||||
/// <param name="target">The target.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>Task<List<DeviceFileInfo>>.</returns>
|
||||
Task<List<DeviceFileInfo>> GetFileSystemEntries(string path, SyncTarget target, CancellationToken cancellationToken);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,6 +14,14 @@ namespace MediaBrowser.Controller.Sync
|
|||
/// <returns>Task<List<System.String>>.</returns>
|
||||
Task<List<string>> GetServerItemIds(SyncTarget target, string serverId);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the synchronize job item ids.
|
||||
/// </summary>
|
||||
/// <param name="target">The target.</param>
|
||||
/// <param name="serverId">The server identifier.</param>
|
||||
/// <returns>Task<List<System.String>>.</returns>
|
||||
Task<List<string>> GetSyncJobItemIds(SyncTarget target, string serverId);
|
||||
|
||||
/// <summary>
|
||||
/// Adds the or update.
|
||||
/// </summary>
|
||||
|
@ -46,5 +54,13 @@ namespace MediaBrowser.Controller.Sync
|
|||
/// <param name="itemId">The item identifier.</param>
|
||||
/// <returns>Task<LocalItem>.</returns>
|
||||
Task<List<LocalItem>> GetCachedItems(SyncTarget target, string serverId, string itemId);
|
||||
/// <summary>
|
||||
/// Gets the cached items by synchronize job item identifier.
|
||||
/// </summary>
|
||||
/// <param name="target">The target.</param>
|
||||
/// <param name="serverId">The server identifier.</param>
|
||||
/// <param name="syncJobItemId">The synchronize job item identifier.</param>
|
||||
/// <returns>Task<List<LocalItem>>.</returns>
|
||||
Task<List<LocalItem>> GetCachedItemsBySyncJobItemId(SyncTarget target, string serverId, string syncJobItemId);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
using MediaBrowser.Model.MediaInfo;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace MediaBrowser.Controller.Sync
|
||||
{
|
||||
public class SendFileResult
|
||||
public class SyncedFileInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the path.
|
||||
|
@ -14,5 +15,15 @@ namespace MediaBrowser.Controller.Sync
|
|||
/// </summary>
|
||||
/// <value>The protocol.</value>
|
||||
public MediaProtocol Protocol { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets the required HTTP headers.
|
||||
/// </summary>
|
||||
/// <value>The required HTTP headers.</value>
|
||||
public Dictionary<string, string> RequiredHttpHeaders { get; set; }
|
||||
|
||||
public SyncedFileInfo()
|
||||
{
|
||||
RequiredHttpHeaders = new Dictionary<string, string>();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -124,7 +124,7 @@ namespace MediaBrowser.Dlna.Didl
|
|||
{
|
||||
if (streamInfo == null)
|
||||
{
|
||||
var sources = _user == null ? video.GetMediaSources(true).ToList() : _mediaSourceManager.GetStaticMediaSources(video, true, _user).ToList();
|
||||
var sources = _user == null ? _mediaSourceManager.GetStaticMediaSources(video, true).ToList() : _mediaSourceManager.GetStaticMediaSources(video, true, _user).ToList();
|
||||
|
||||
streamInfo = new StreamBuilder().BuildVideoItem(new VideoOptions
|
||||
{
|
||||
|
@ -158,18 +158,23 @@ 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))
|
||||
{
|
||||
if (subtitle.DeliveryMethod == SubtitleDeliveryMethod.External)
|
||||
{
|
||||
AddSubtitleElement(container, subtitle);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void AddSubtitleElement(XmlElement container, SubtitleStreamInfo info)
|
||||
{
|
||||
|
@ -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
|
||||
{
|
||||
|
|
|
@ -470,13 +470,13 @@ namespace MediaBrowser.Dlna.PlayTo
|
|||
|
||||
var hasMediaSources = item as IHasMediaSources;
|
||||
var mediaSources = hasMediaSources != null
|
||||
? (user == null ? hasMediaSources.GetMediaSources(true) : _mediaSourceManager.GetStaticMediaSources(hasMediaSources, true, user)).ToList()
|
||||
? (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();
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -14,7 +14,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
|||
{
|
||||
public class AudioEncoder : BaseEncoder
|
||||
{
|
||||
public AudioEncoder(MediaEncoder mediaEncoder, ILogger logger, IServerConfigurationManager configurationManager, IFileSystem fileSystem, ILiveTvManager liveTvManager, IIsoManager isoManager, ILibraryManager libraryManager, IChannelManager channelManager, ISessionManager sessionManager, ISubtitleEncoder subtitleEncoder, IMediaSourceManager mediaSourceManager) : base(mediaEncoder, logger, configurationManager, fileSystem, liveTvManager, isoManager, libraryManager, channelManager, sessionManager, subtitleEncoder, mediaSourceManager)
|
||||
public AudioEncoder(MediaEncoder mediaEncoder, ILogger logger, IServerConfigurationManager configurationManager, IFileSystem fileSystem, IIsoManager isoManager, ILibraryManager libraryManager, ISessionManager sessionManager, ISubtitleEncoder subtitleEncoder, IMediaSourceManager mediaSourceManager) : base(mediaEncoder, logger, configurationManager, fileSystem, isoManager, libraryManager, sessionManager, subtitleEncoder, mediaSourceManager)
|
||||
{
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
using MediaBrowser.Common.Configuration;
|
||||
using MediaBrowser.Common.IO;
|
||||
using MediaBrowser.Controller.Channels;
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Controller.LiveTv;
|
||||
|
@ -14,6 +13,7 @@ using MediaBrowser.Model.Dto;
|
|||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.IO;
|
||||
using MediaBrowser.Model.Logging;
|
||||
using MediaBrowser.Model.MediaInfo;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
|
@ -31,10 +31,8 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
|||
protected readonly ILogger Logger;
|
||||
protected readonly IServerConfigurationManager ConfigurationManager;
|
||||
protected readonly IFileSystem FileSystem;
|
||||
protected readonly ILiveTvManager LiveTvManager;
|
||||
protected readonly IIsoManager IsoManager;
|
||||
protected readonly ILibraryManager LibraryManager;
|
||||
protected readonly IChannelManager ChannelManager;
|
||||
protected readonly ISessionManager SessionManager;
|
||||
protected readonly ISubtitleEncoder SubtitleEncoder;
|
||||
protected readonly IMediaSourceManager MediaSourceManager;
|
||||
|
@ -45,20 +43,18 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
|||
ILogger logger,
|
||||
IServerConfigurationManager configurationManager,
|
||||
IFileSystem fileSystem,
|
||||
ILiveTvManager liveTvManager,
|
||||
IIsoManager isoManager,
|
||||
ILibraryManager libraryManager,
|
||||
IChannelManager channelManager,
|
||||
ISessionManager sessionManager, ISubtitleEncoder subtitleEncoder, IMediaSourceManager mediaSourceManager)
|
||||
ISessionManager sessionManager,
|
||||
ISubtitleEncoder subtitleEncoder,
|
||||
IMediaSourceManager mediaSourceManager)
|
||||
{
|
||||
MediaEncoder = mediaEncoder;
|
||||
Logger = logger;
|
||||
ConfigurationManager = configurationManager;
|
||||
FileSystem = fileSystem;
|
||||
LiveTvManager = liveTvManager;
|
||||
IsoManager = isoManager;
|
||||
LibraryManager = libraryManager;
|
||||
ChannelManager = channelManager;
|
||||
SessionManager = sessionManager;
|
||||
SubtitleEncoder = subtitleEncoder;
|
||||
MediaSourceManager = mediaSourceManager;
|
||||
|
@ -68,7 +64,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
|||
IProgress<double> progress,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var encodingJob = await new EncodingJobFactory(Logger, LiveTvManager, LibraryManager, ChannelManager, MediaSourceManager)
|
||||
var encodingJob = await new EncodingJobFactory(Logger, LibraryManager, MediaSourceManager)
|
||||
.CreateJob(options, IsVideoEncoder, progress, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
encodingJob.OutputFilePath = GetOutputFilePath(encodingJob);
|
||||
|
@ -477,53 +473,25 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
|||
state.IsoMount = await IsoManager.Mount(state.MediaPath, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(state.MediaPath))
|
||||
if (state.MediaSource.RequiresOpening)
|
||||
{
|
||||
var checkCodecs = false;
|
||||
|
||||
if (string.Equals(state.ItemType, typeof(LiveTvChannel).Name))
|
||||
var liveStreamResponse = await MediaSourceManager.OpenLiveStream(new LiveStreamRequest
|
||||
{
|
||||
var streamInfo = await LiveTvManager.GetChannelStream(state.Options.ItemId, cancellationToken).ConfigureAwait(false);
|
||||
OpenToken = state.MediaSource.OpenToken
|
||||
|
||||
state.LiveTvStreamId = streamInfo.Id;
|
||||
}, false, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
state.MediaPath = streamInfo.Path;
|
||||
state.InputProtocol = streamInfo.Protocol;
|
||||
AttachMediaStreamInfo(state, liveStreamResponse.MediaSource, state.Options);
|
||||
|
||||
await Task.Delay(1500, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
AttachMediaStreamInfo(state, streamInfo, state.Options);
|
||||
checkCodecs = true;
|
||||
}
|
||||
|
||||
else if (string.Equals(state.ItemType, typeof(LiveTvVideoRecording).Name) ||
|
||||
string.Equals(state.ItemType, typeof(LiveTvAudioRecording).Name))
|
||||
if (state.IsVideoRequest)
|
||||
{
|
||||
var streamInfo = await LiveTvManager.GetRecordingStream(state.Options.ItemId, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
state.LiveTvStreamId = streamInfo.Id;
|
||||
|
||||
state.MediaPath = streamInfo.Path;
|
||||
state.InputProtocol = streamInfo.Protocol;
|
||||
|
||||
await Task.Delay(1500, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
AttachMediaStreamInfo(state, streamInfo, state.Options);
|
||||
checkCodecs = true;
|
||||
}
|
||||
|
||||
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";
|
||||
EncodingJobFactory.TryStreamCopy(state, state.Options);
|
||||
}
|
||||
}
|
||||
|
||||
if (state.MediaSource.BufferMs.HasValue)
|
||||
{
|
||||
await Task.Delay(state.MediaSource.BufferMs.Value, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -531,22 +499,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
|||
MediaSourceInfo mediaSource,
|
||||
EncodingJobOptions videoRequest)
|
||||
{
|
||||
state.InputProtocol = mediaSource.Protocol;
|
||||
state.MediaPath = mediaSource.Path;
|
||||
state.RunTimeTicks = mediaSource.RunTimeTicks;
|
||||
state.RemoteHttpHeaders = mediaSource.RequiredHttpHeaders;
|
||||
state.InputBitrate = mediaSource.Bitrate;
|
||||
state.InputFileSize = mediaSource.Size;
|
||||
state.ReadInputAtNativeFramerate = mediaSource.ReadAtNativeFramerate;
|
||||
|
||||
if (state.ReadInputAtNativeFramerate)
|
||||
{
|
||||
state.OutputAudioSync = "1000";
|
||||
state.InputVideoSync = "-1";
|
||||
state.InputAudioSync = "1";
|
||||
}
|
||||
|
||||
EncodingJobFactory.AttachMediaStreamInfo(state, mediaSource.MediaStreams, videoRequest);
|
||||
EncodingJobFactory.AttachMediaStreamInfo(state, mediaSource, videoRequest);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -998,7 +951,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
|||
|
||||
if (!string.IsNullOrEmpty(state.SubtitleStream.Language))
|
||||
{
|
||||
var charenc = SubtitleEncoder.GetSubtitleFileCharacterSet(subtitlePath);
|
||||
var charenc = SubtitleEncoder.GetSubtitleFileCharacterSet(subtitlePath, state.MediaSource.Protocol, CancellationToken.None).Result;
|
||||
|
||||
if (!string.IsNullOrEmpty(charenc))
|
||||
{
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
using MediaBrowser.Controller.LiveTv;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Controller.MediaEncoding;
|
||||
using MediaBrowser.Model.Dlna;
|
||||
using MediaBrowser.Model.Drawing;
|
||||
using MediaBrowser.Model.Dto;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.IO;
|
||||
using MediaBrowser.Model.Logging;
|
||||
|
@ -26,7 +27,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
|||
|
||||
public EncodingJobOptions Options { get; set; }
|
||||
public string InputContainer { get; set; }
|
||||
public List<MediaStream> AllMediaStreams { get; set; }
|
||||
public MediaSourceInfo MediaSource { get; set; }
|
||||
public MediaStream AudioStream { get; set; }
|
||||
public MediaStream VideoStream { get; set; }
|
||||
public MediaStream SubtitleStream { get; set; }
|
||||
|
@ -76,12 +77,12 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
|||
}
|
||||
|
||||
private readonly ILogger _logger;
|
||||
private readonly ILiveTvManager _liveTvManager;
|
||||
private readonly IMediaSourceManager _mediaSourceManager;
|
||||
|
||||
public EncodingJob(ILogger logger, ILiveTvManager liveTvManager)
|
||||
public EncodingJob(ILogger logger, IMediaSourceManager mediaSourceManager)
|
||||
{
|
||||
_logger = logger;
|
||||
_liveTvManager = liveTvManager;
|
||||
_mediaSourceManager = mediaSourceManager;
|
||||
Id = Guid.NewGuid().ToString("N");
|
||||
|
||||
RemoteHttpHeaders = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||
|
@ -89,7 +90,6 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
|||
SupportedAudioCodecs = new List<string>();
|
||||
PlayableStreamFileNames = new List<string>();
|
||||
RemoteHttpHeaders = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||
AllMediaStreams = new List<MediaStream>();
|
||||
TaskCompletionSource = new TaskCompletionSource<bool>();
|
||||
}
|
||||
|
||||
|
@ -136,15 +136,15 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
|||
|
||||
private async void DisposeLiveStream()
|
||||
{
|
||||
if (!string.IsNullOrEmpty(LiveTvStreamId))
|
||||
if (MediaSource.RequiresClosing)
|
||||
{
|
||||
try
|
||||
{
|
||||
await _liveTvManager.CloseLiveStream(LiveTvStreamId, CancellationToken.None).ConfigureAwait(false);
|
||||
await _mediaSourceManager.CloseLiveStream(MediaSource.LiveStreamId, CancellationToken.None).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.ErrorException("Error closing live tv stream", ex);
|
||||
_logger.ErrorException("Error closing media source", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -394,6 +394,42 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
|||
}
|
||||
}
|
||||
|
||||
public int? TargetVideoStreamCount
|
||||
{
|
||||
get
|
||||
{
|
||||
if (Options.Static)
|
||||
{
|
||||
return GetMediaStreamCount(MediaStreamType.Video, int.MaxValue);
|
||||
}
|
||||
return GetMediaStreamCount(MediaStreamType.Video, 1);
|
||||
}
|
||||
}
|
||||
|
||||
public int? TargetAudioStreamCount
|
||||
{
|
||||
get
|
||||
{
|
||||
if (Options.Static)
|
||||
{
|
||||
return GetMediaStreamCount(MediaStreamType.Audio, int.MaxValue);
|
||||
}
|
||||
return GetMediaStreamCount(MediaStreamType.Audio, 1);
|
||||
}
|
||||
}
|
||||
|
||||
private int? GetMediaStreamCount(MediaStreamType type, int limit)
|
||||
{
|
||||
var count = MediaSource.GetStreamCount(type);
|
||||
|
||||
if (count.HasValue)
|
||||
{
|
||||
count = Math.Min(count.Value, limit);
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
public void ReportTranscodingProgress(TimeSpan? transcodingPosition, float? framerate, double? percentComplete, long? bytesTranscoded)
|
||||
{
|
||||
var ticks = transcodingPosition.HasValue ? transcodingPosition.Value.Ticks : (long?)null;
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
using MediaBrowser.Controller.Channels;
|
||||
using System.IO;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Controller.LiveTv;
|
||||
using MediaBrowser.Controller.MediaEncoding;
|
||||
using MediaBrowser.Model.Dlna;
|
||||
using MediaBrowser.Model.Dto;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.Logging;
|
||||
using MediaBrowser.Model.MediaInfo;
|
||||
|
@ -19,19 +20,15 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
|||
public class EncodingJobFactory
|
||||
{
|
||||
private readonly ILogger _logger;
|
||||
private readonly ILiveTvManager _liveTvManager;
|
||||
private readonly ILibraryManager _libraryManager;
|
||||
private readonly IChannelManager _channelManager;
|
||||
private readonly IMediaSourceManager _mediaSourceManager;
|
||||
|
||||
protected static readonly CultureInfo UsCulture = new CultureInfo("en-US");
|
||||
|
||||
public EncodingJobFactory(ILogger logger, ILiveTvManager liveTvManager, ILibraryManager libraryManager, IChannelManager channelManager, IMediaSourceManager mediaSourceManager)
|
||||
public EncodingJobFactory(ILogger logger, ILibraryManager libraryManager, IMediaSourceManager mediaSourceManager)
|
||||
{
|
||||
_logger = logger;
|
||||
_liveTvManager = liveTvManager;
|
||||
_libraryManager = libraryManager;
|
||||
_channelManager = channelManager;
|
||||
_mediaSourceManager = mediaSourceManager;
|
||||
}
|
||||
|
||||
|
@ -44,7 +41,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
|||
request.AudioCodec = InferAudioCodec(request.OutputContainer);
|
||||
}
|
||||
|
||||
var state = new EncodingJob(_logger, _liveTvManager)
|
||||
var state = new EncodingJob(_logger, _mediaSourceManager)
|
||||
{
|
||||
Options = options,
|
||||
IsVideoRequest = isVideoRequest,
|
||||
|
@ -58,106 +55,17 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
|||
}
|
||||
|
||||
var item = _libraryManager.GetItemById(request.ItemId);
|
||||
|
||||
List<MediaStream> mediaStreams = null;
|
||||
|
||||
state.ItemType = item.GetType().Name;
|
||||
|
||||
if (item is ILiveTvRecording)
|
||||
{
|
||||
var recording = await _liveTvManager.GetInternalRecording(request.ItemId, cancellationToken).ConfigureAwait(false);
|
||||
state.IsInputVideo = string.Equals(item.MediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
state.VideoType = VideoType.VideoFile;
|
||||
state.IsInputVideo = string.Equals(recording.MediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
var path = recording.RecordingInfo.Path;
|
||||
var mediaUrl = recording.RecordingInfo.Url;
|
||||
|
||||
var source = string.IsNullOrEmpty(request.MediaSourceId)
|
||||
? recording.GetMediaSources(false).First()
|
||||
: _mediaSourceManager.GetStaticMediaSource(recording, request.MediaSourceId, false);
|
||||
|
||||
mediaStreams = source.MediaStreams;
|
||||
|
||||
// Just to prevent this from being null and causing other methods to fail
|
||||
state.MediaPath = string.Empty;
|
||||
|
||||
if (!string.IsNullOrEmpty(path))
|
||||
{
|
||||
state.MediaPath = path;
|
||||
state.InputProtocol = MediaProtocol.File;
|
||||
}
|
||||
else if (!string.IsNullOrEmpty(mediaUrl))
|
||||
{
|
||||
state.MediaPath = mediaUrl;
|
||||
state.InputProtocol = MediaProtocol.Http;
|
||||
}
|
||||
|
||||
state.RunTimeTicks = recording.RunTimeTicks;
|
||||
state.DeInterlace = true;
|
||||
state.OutputAudioSync = "1000";
|
||||
state.InputVideoSync = "-1";
|
||||
state.InputAudioSync = "1";
|
||||
state.InputContainer = recording.Container;
|
||||
state.ReadInputAtNativeFramerate = source.ReadAtNativeFramerate;
|
||||
}
|
||||
else if (item is LiveTvChannel)
|
||||
{
|
||||
var channel = _liveTvManager.GetInternalChannel(request.ItemId);
|
||||
|
||||
state.VideoType = VideoType.VideoFile;
|
||||
state.IsInputVideo = string.Equals(channel.MediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase);
|
||||
mediaStreams = new List<MediaStream>();
|
||||
|
||||
state.DeInterlace = true;
|
||||
|
||||
// Just to prevent this from being null and causing other methods to fail
|
||||
state.MediaPath = string.Empty;
|
||||
}
|
||||
else
|
||||
{
|
||||
var mediaSources = await _mediaSourceManager.GetPlayackMediaSources(request.ItemId, false, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
var mediaSource = string.IsNullOrEmpty(request.MediaSourceId)
|
||||
? mediaSources.First()
|
||||
: mediaSources.First(i => string.Equals(i.Id, request.MediaSourceId));
|
||||
|
||||
mediaStreams = mediaSource.MediaStreams;
|
||||
|
||||
state.MediaPath = mediaSource.Path;
|
||||
state.InputProtocol = mediaSource.Protocol;
|
||||
state.InputContainer = mediaSource.Container;
|
||||
state.InputFileSize = mediaSource.Size;
|
||||
state.InputBitrate = mediaSource.Bitrate;
|
||||
state.ReadInputAtNativeFramerate = mediaSource.ReadAtNativeFramerate;
|
||||
state.RunTimeTicks = mediaSource.RunTimeTicks;
|
||||
state.RemoteHttpHeaders = mediaSource.RequiredHttpHeaders;
|
||||
|
||||
var video = item as Video;
|
||||
|
||||
if (video != null)
|
||||
{
|
||||
state.IsInputVideo = true;
|
||||
|
||||
if (mediaSource.VideoType.HasValue)
|
||||
{
|
||||
state.VideoType = mediaSource.VideoType.Value;
|
||||
}
|
||||
|
||||
state.IsoType = mediaSource.IsoType;
|
||||
|
||||
state.PlayableStreamFileNames = mediaSource.PlayableStreamFileNames.ToList();
|
||||
|
||||
if (mediaSource.Timestamp.HasValue)
|
||||
{
|
||||
state.InputTimestamp = mediaSource.Timestamp.Value;
|
||||
}
|
||||
}
|
||||
|
||||
state.RunTimeTicks = mediaSource.RunTimeTicks;
|
||||
}
|
||||
|
||||
AttachMediaStreamInfo(state, mediaStreams, request);
|
||||
AttachMediaStreamInfo(state, mediaSource, options);
|
||||
|
||||
state.OutputAudioBitrate = GetAudioBitrateParam(request, state.AudioStream);
|
||||
state.OutputAudioSampleRate = request.AudioSampleRate;
|
||||
|
@ -185,26 +93,73 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
|||
|
||||
ApplyDeviceProfileSettings(state);
|
||||
|
||||
if (isVideoRequest)
|
||||
{
|
||||
if (state.VideoStream != null && CanStreamCopyVideo(request, state.VideoStream))
|
||||
{
|
||||
state.OutputVideoCodec = "copy";
|
||||
}
|
||||
|
||||
if (state.AudioStream != null && CanStreamCopyAudio(request, state.AudioStream, state.SupportedAudioCodecs))
|
||||
{
|
||||
state.OutputAudioCodec = "copy";
|
||||
}
|
||||
}
|
||||
TryStreamCopy(state, request);
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
internal static void AttachMediaStreamInfo(EncodingJob state,
|
||||
List<MediaStream> mediaStreams,
|
||||
internal static void TryStreamCopy(EncodingJob state,
|
||||
EncodingJobOptions videoRequest)
|
||||
{
|
||||
if (state.IsVideoRequest)
|
||||
{
|
||||
if (state.VideoStream != null && CanStreamCopyVideo(videoRequest, state.VideoStream))
|
||||
{
|
||||
state.OutputVideoCodec = "copy";
|
||||
}
|
||||
|
||||
if (state.AudioStream != null && CanStreamCopyAudio(videoRequest, state.AudioStream, state.SupportedAudioCodecs))
|
||||
{
|
||||
state.OutputAudioCodec = "copy";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal static void AttachMediaStreamInfo(EncodingJob state,
|
||||
MediaSourceInfo mediaSource,
|
||||
EncodingJobOptions videoRequest)
|
||||
{
|
||||
state.MediaPath = mediaSource.Path;
|
||||
state.InputProtocol = mediaSource.Protocol;
|
||||
state.InputContainer = mediaSource.Container;
|
||||
state.InputFileSize = mediaSource.Size;
|
||||
state.InputBitrate = mediaSource.Bitrate;
|
||||
state.ReadInputAtNativeFramerate = mediaSource.ReadAtNativeFramerate;
|
||||
state.RunTimeTicks = mediaSource.RunTimeTicks;
|
||||
state.RemoteHttpHeaders = mediaSource.RequiredHttpHeaders;
|
||||
|
||||
if (mediaSource.VideoType.HasValue)
|
||||
{
|
||||
state.VideoType = mediaSource.VideoType.Value;
|
||||
}
|
||||
|
||||
state.IsoType = mediaSource.IsoType;
|
||||
|
||||
state.PlayableStreamFileNames = mediaSource.PlayableStreamFileNames.ToList();
|
||||
|
||||
if (mediaSource.Timestamp.HasValue)
|
||||
{
|
||||
state.InputTimestamp = mediaSource.Timestamp.Value;
|
||||
}
|
||||
|
||||
state.InputProtocol = mediaSource.Protocol;
|
||||
state.MediaPath = mediaSource.Path;
|
||||
state.RunTimeTicks = mediaSource.RunTimeTicks;
|
||||
state.RemoteHttpHeaders = mediaSource.RequiredHttpHeaders;
|
||||
state.InputBitrate = mediaSource.Bitrate;
|
||||
state.InputFileSize = mediaSource.Size;
|
||||
state.ReadInputAtNativeFramerate = mediaSource.ReadAtNativeFramerate;
|
||||
|
||||
if (state.ReadInputAtNativeFramerate ||
|
||||
mediaSource.Protocol == MediaProtocol.File && string.Equals(mediaSource.Container, "wtv", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
state.OutputAudioSync = "1000";
|
||||
state.InputVideoSync = "-1";
|
||||
state.InputAudioSync = "1";
|
||||
}
|
||||
|
||||
var mediaStreams = mediaSource.MediaStreams;
|
||||
|
||||
if (videoRequest != null)
|
||||
{
|
||||
if (string.IsNullOrEmpty(videoRequest.VideoCodec))
|
||||
|
@ -233,7 +188,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
|||
state.AudioStream = GetMediaStream(mediaStreams, null, MediaStreamType.Audio, true);
|
||||
}
|
||||
|
||||
state.AllMediaStreams = mediaStreams;
|
||||
state.MediaSource = mediaSource;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -771,7 +726,9 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
|||
state.TargetTimestamp,
|
||||
state.IsTargetAnamorphic,
|
||||
state.IsTargetCabac,
|
||||
state.TargetRefFrames);
|
||||
state.TargetRefFrames,
|
||||
state.TargetVideoStreamCount,
|
||||
state.TargetAudioStreamCount);
|
||||
|
||||
if (mediaProfile != null)
|
||||
{
|
||||
|
|
|
@ -577,10 +577,8 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
|||
_logger,
|
||||
ConfigurationManager,
|
||||
FileSystem,
|
||||
LiveTvManager,
|
||||
IsoManager,
|
||||
LibraryManager,
|
||||
ChannelManager,
|
||||
SessionManager,
|
||||
SubtitleEncoder(),
|
||||
MediaSourceManager())
|
||||
|
@ -599,10 +597,8 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
|||
_logger,
|
||||
ConfigurationManager,
|
||||
FileSystem,
|
||||
LiveTvManager,
|
||||
IsoManager,
|
||||
LibraryManager,
|
||||
ChannelManager,
|
||||
SessionManager,
|
||||
SubtitleEncoder(),
|
||||
MediaSourceManager())
|
||||
|
|
|
@ -15,7 +15,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
|||
{
|
||||
public class VideoEncoder : BaseEncoder
|
||||
{
|
||||
public VideoEncoder(MediaEncoder mediaEncoder, ILogger logger, IServerConfigurationManager configurationManager, IFileSystem fileSystem, ILiveTvManager liveTvManager, IIsoManager isoManager, ILibraryManager libraryManager, IChannelManager channelManager, ISessionManager sessionManager, ISubtitleEncoder subtitleEncoder, IMediaSourceManager mediaSourceManager) : base(mediaEncoder, logger, configurationManager, fileSystem, liveTvManager, isoManager, libraryManager, channelManager, sessionManager, subtitleEncoder, mediaSourceManager)
|
||||
public VideoEncoder(MediaEncoder mediaEncoder, ILogger logger, IServerConfigurationManager configurationManager, IFileSystem fileSystem, IIsoManager isoManager, ILibraryManager libraryManager, ISessionManager sessionManager, ISubtitleEncoder subtitleEncoder, IMediaSourceManager mediaSourceManager) : base(mediaEncoder, logger, configurationManager, fileSystem, isoManager, libraryManager, sessionManager, subtitleEncoder, mediaSourceManager)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -73,10 +73,13 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
|||
args;
|
||||
}
|
||||
|
||||
if (state.Options.Context == EncodingContext.Streaming)
|
||||
{
|
||||
var keyFrameArg = string.Format(" -force_key_frames expr:gte(t,n_forced*{0})",
|
||||
5.ToString(UsCulture));
|
||||
|
||||
args += keyFrameArg;
|
||||
}
|
||||
|
||||
var hasGraphicalSubs = state.SubtitleStream != null && !state.SubtitleStream.IsTextSubtitleStream;
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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,7 +698,9 @@ namespace MediaBrowser.MediaEncoding.Subtitles
|
|||
}
|
||||
}
|
||||
|
||||
private string GetSubtitleCachePath(string mediaPath, int subtitleStreamIndex, string outputSubtitleExtension)
|
||||
private string GetSubtitleCachePath(string mediaPath, MediaProtocol protocol, int subtitleStreamIndex, string outputSubtitleExtension)
|
||||
{
|
||||
if (protocol == MediaProtocol.File)
|
||||
{
|
||||
var ticksParam = string.Empty;
|
||||
|
||||
|
@ -700,20 +712,27 @@ namespace MediaBrowser.MediaEncoding.Subtitles
|
|||
|
||||
return Path.Combine(SubtitleCachePath, prefix, filename);
|
||||
}
|
||||
else
|
||||
{
|
||||
var filename = (mediaPath + "_" + subtitleStreamIndex.ToString(CultureInfo.InvariantCulture)).GetMD5() + outputSubtitleExtension;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the subtitle language encoding param.
|
||||
/// </summary>
|
||||
/// <param name="path">The path.</param>
|
||||
/// <returns>System.String.</returns>
|
||||
public string GetSubtitleFileCharacterSet(string path)
|
||||
var prefix = filename.Substring(0, 1);
|
||||
|
||||
return Path.Combine(SubtitleCachePath, prefix, filename);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<string> GetSubtitleFileCharacterSet(string path, MediaProtocol protocol, CancellationToken cancellationToken)
|
||||
{
|
||||
if (protocol == MediaProtocol.File)
|
||||
{
|
||||
if (GetFileEncoding(path).Equals(Encoding.UTF8))
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
var charset = DetectCharset(path);
|
||||
var charset = await DetectCharset(path, protocol, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(charset))
|
||||
{
|
||||
|
@ -769,11 +788,11 @@ namespace MediaBrowser.MediaEncoding.Subtitles
|
|||
}
|
||||
}
|
||||
|
||||
private string DetectCharset(string path)
|
||||
private async Task<string> DetectCharset(string path, MediaProtocol protocol, CancellationToken cancellationToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
using (var file = _fileSystem.GetFileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
|
||||
using (var file = await GetStream(path, protocol, cancellationToken).ConfigureAwait(false))
|
||||
{
|
||||
var detector = new CharsetDetector();
|
||||
detector.Feed(file);
|
||||
|
@ -819,5 +838,19 @@ namespace MediaBrowser.MediaEncoding.Subtitles
|
|||
// It's ok - anything aside from utf is ok since that's what we're looking for
|
||||
return Encoding.Default;
|
||||
}
|
||||
|
||||
private async Task<Stream> GetStream(string path, MediaProtocol protocol, CancellationToken cancellationToken)
|
||||
{
|
||||
if (protocol == MediaProtocol.Http)
|
||||
{
|
||||
return await _httpClient.Get(path, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
if (protocol == MediaProtocol.File)
|
||||
{
|
||||
return _fileSystem.GetFileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
|
||||
}
|
||||
|
||||
throw new ArgumentOutOfRangeException("protocol");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -413,6 +413,9 @@
|
|||
<Compile Include="..\MediaBrowser.Model\Dlna\StreamInfo.cs">
|
||||
<Link>Dlna\StreamInfo.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\MediaBrowser.Model\Dlna\StreamInfoSorter.cs">
|
||||
<Link>Dlna\StreamInfoSorter.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\MediaBrowser.Model\Dlna\SubtitleDeliveryMethod.cs">
|
||||
<Link>Dlna\SubtitleDeliveryMethod.cs</Link>
|
||||
</Compile>
|
||||
|
@ -800,12 +803,21 @@
|
|||
<Compile Include="..\MediaBrowser.Model\MediaInfo\IBlurayExaminer.cs">
|
||||
<Link>MediaInfo\IBlurayExaminer.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\MediaBrowser.Model\MediaInfo\LiveMediaInfoResult.cs">
|
||||
<Link>MediaInfo\LiveMediaInfoResult.cs</Link>
|
||||
<Compile Include="..\MediaBrowser.Model\MediaInfo\LiveStreamRequest.cs">
|
||||
<Link>MediaInfo\LiveStreamRequest.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\MediaBrowser.Model\MediaInfo\LiveStreamResponse.cs">
|
||||
<Link>MediaInfo\LiveStreamResponse.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\MediaBrowser.Model\MediaInfo\MediaProtocol.cs">
|
||||
<Link>MediaInfo\MediaProtocol.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\MediaBrowser.Model\MediaInfo\PlaybackInfoRequest.cs">
|
||||
<Link>MediaInfo\PlaybackInfoRequest.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\MediaBrowser.Model\MediaInfo\PlaybackInfoResponse.cs">
|
||||
<Link>MediaInfo\PlaybackInfoResponse.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\MediaBrowser.Model\MediaInfo\SubtitleFormat.cs">
|
||||
<Link>MediaInfo\SubtitleFormat.cs</Link>
|
||||
</Compile>
|
||||
|
|
|
@ -378,6 +378,9 @@
|
|||
<Compile Include="..\MediaBrowser.Model\Dlna\StreamInfo.cs">
|
||||
<Link>Dlna\StreamInfo.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\MediaBrowser.Model\Dlna\StreamInfoSorter.cs">
|
||||
<Link>Dlna\StreamInfoSorter.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\MediaBrowser.Model\Dlna\SubtitleDeliveryMethod.cs">
|
||||
<Link>Dlna\SubtitleDeliveryMethod.cs</Link>
|
||||
</Compile>
|
||||
|
@ -756,12 +759,21 @@
|
|||
<Compile Include="..\MediaBrowser.Model\MediaInfo\IBlurayExaminer.cs">
|
||||
<Link>MediaInfo\IBlurayExaminer.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\MediaBrowser.Model\MediaInfo\LiveMediaInfoResult.cs">
|
||||
<Link>MediaInfo\LiveMediaInfoResult.cs</Link>
|
||||
<Compile Include="..\MediaBrowser.Model\MediaInfo\LiveStreamRequest.cs">
|
||||
<Link>MediaInfo\LiveStreamRequest.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\MediaBrowser.Model\MediaInfo\LiveStreamResponse.cs">
|
||||
<Link>MediaInfo\LiveStreamResponse.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\MediaBrowser.Model\MediaInfo\MediaProtocol.cs">
|
||||
<Link>MediaInfo\MediaProtocol.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\MediaBrowser.Model\MediaInfo\PlaybackInfoRequest.cs">
|
||||
<Link>MediaInfo\PlaybackInfoRequest.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\MediaBrowser.Model\MediaInfo\PlaybackInfoResponse.cs">
|
||||
<Link>MediaInfo\PlaybackInfoResponse.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\MediaBrowser.Model\MediaInfo\SubtitleFormat.cs">
|
||||
<Link>MediaInfo\SubtitleFormat.cs</Link>
|
||||
</Compile>
|
||||
|
|
|
@ -248,10 +248,9 @@ namespace MediaBrowser.Model.ApiClient
|
|||
/// <summary>
|
||||
/// Gets the playback information.
|
||||
/// </summary>
|
||||
/// <param name="itemId">The item identifier.</param>
|
||||
/// <param name="userId">The user identifier.</param>
|
||||
/// <param name="request">The request.</param>
|
||||
/// <returns>Task<LiveMediaInfoResult>.</returns>
|
||||
Task<LiveMediaInfoResult> GetPlaybackInfo(string itemId, string userId);
|
||||
Task<PlaybackInfoResponse> GetPlaybackInfo(PlaybackInfoRequest request);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the users async.
|
||||
|
@ -1486,5 +1485,12 @@ namespace MediaBrowser.Model.ApiClient
|
|||
/// <param name="query">The query.</param>
|
||||
/// <returns>Task<List<RecommendationDto>>.</returns>
|
||||
Task<List<RecommendationDto>> GetMovieRecommendations(MovieRecommendationQuery query);
|
||||
/// <summary>
|
||||
/// Opens the live stream.
|
||||
/// </summary>
|
||||
/// <param name="request">The request.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>Task<LiveStreamResponse>.</returns>
|
||||
Task<LiveStreamResponse> OpenLiveStream(LiveStreamRequest request, CancellationToken cancellationToken);
|
||||
}
|
||||
}
|
|
@ -172,5 +172,11 @@ namespace MediaBrowser.Model.ApiClient
|
|||
/// <param name="rememberCredentials">if set to <c>true</c> [remember credentials].</param>
|
||||
/// <returns>Task.</returns>
|
||||
Task AuthenticateOffline(UserDto user, string password, bool rememberCredentials);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the offline users.
|
||||
/// </summary>
|
||||
/// <returns>Task<List<UserDto>>.</returns>
|
||||
Task<List<UserDto>> GetOfflineUsers();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
using MediaBrowser.Model.Extensions;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace MediaBrowser.Model.ApiClient
|
||||
{
|
||||
|
@ -24,7 +23,12 @@ namespace MediaBrowser.Model.ApiClient
|
|||
throw new ArgumentNullException("server");
|
||||
}
|
||||
|
||||
var list = Servers.ToList();
|
||||
// Clone the existing list of servers
|
||||
var list = new List<ServerInfo>();
|
||||
foreach (ServerInfo serverInfo in Servers)
|
||||
{
|
||||
list.Add(serverInfo);
|
||||
}
|
||||
|
||||
var index = FindIndex(list, server.Id);
|
||||
|
||||
|
@ -32,8 +36,11 @@ namespace MediaBrowser.Model.ApiClient
|
|||
{
|
||||
var existing = list[index];
|
||||
|
||||
// Merge the data
|
||||
existing.DateLastAccessed = new[] { existing.DateLastAccessed, server.DateLastAccessed }.Max();
|
||||
// Take the most recent DateLastAccessed
|
||||
if (server.DateLastAccessed > existing.DateLastAccessed)
|
||||
{
|
||||
existing.DateLastAccessed = server.DateLastAccessed;
|
||||
}
|
||||
|
||||
existing.UserLinkType = server.UserLinkType;
|
||||
|
||||
|
@ -64,7 +71,11 @@ namespace MediaBrowser.Model.ApiClient
|
|||
}
|
||||
if (server.WakeOnLanInfos != null && server.WakeOnLanInfos.Count > 0)
|
||||
{
|
||||
existing.WakeOnLanInfos = server.WakeOnLanInfos.ToList();
|
||||
existing.WakeOnLanInfos = new List<WakeOnLanInfo>();
|
||||
foreach (WakeOnLanInfo info in server.WakeOnLanInfos)
|
||||
{
|
||||
existing.WakeOnLanInfos.Add(info);
|
||||
}
|
||||
}
|
||||
if (server.LastConnectionMode.HasValue)
|
||||
{
|
||||
|
|
|
@ -3,7 +3,6 @@ using MediaBrowser.Model.Extensions;
|
|||
using MediaBrowser.Model.System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace MediaBrowser.Model.ApiClient
|
||||
{
|
||||
|
@ -83,7 +82,12 @@ namespace MediaBrowser.Model.ApiClient
|
|||
throw new ArgumentNullException("user");
|
||||
}
|
||||
|
||||
var list = Users.ToList();
|
||||
// Clone the existing list of users
|
||||
var list = new List<ServerUserInfo>();
|
||||
foreach (ServerUserInfo serverUserInfo in Users)
|
||||
{
|
||||
list.Add(serverUserInfo);
|
||||
}
|
||||
|
||||
var index = FindIndex(list, user.Id);
|
||||
|
||||
|
|
|
@ -8,12 +8,16 @@ namespace MediaBrowser.Model.Configuration
|
|||
public double DownMixAudioBoost { get; set; }
|
||||
public string H264Encoder { get; set; }
|
||||
public bool EnableDebugLogging { get; set; }
|
||||
public bool EnableThrottling { get; set; }
|
||||
public int ThrottleThresholdSeconds { get; set; }
|
||||
|
||||
public EncodingOptions()
|
||||
{
|
||||
H264Encoder = "libx264";
|
||||
DownMixAudioBoost = 2;
|
||||
EncodingQuality = EncodingQuality.Auto;
|
||||
EnableThrottling = true;
|
||||
ThrottleThresholdSeconds = 120;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -20,7 +20,9 @@ namespace MediaBrowser.Model.Dlna
|
|||
TransportStreamTimestamp? timestamp,
|
||||
bool? isAnamorphic,
|
||||
bool? isCabac,
|
||||
int? refFrames)
|
||||
int? refFrames,
|
||||
int? numVideoStreams,
|
||||
int? numAudioStreams)
|
||||
{
|
||||
switch (condition.Property)
|
||||
{
|
||||
|
@ -56,6 +58,10 @@ namespace MediaBrowser.Model.Dlna
|
|||
return IsConditionSatisfied(condition, width);
|
||||
case ProfileConditionValue.RefFrames:
|
||||
return IsConditionSatisfied(condition, refFrames);
|
||||
case ProfileConditionValue.NumAudioStreams:
|
||||
return IsConditionSatisfied(condition, numAudioStreams);
|
||||
case ProfileConditionValue.NumVideoStreams:
|
||||
return IsConditionSatisfied(condition, numVideoStreams);
|
||||
case ProfileConditionValue.VideoTimestamp:
|
||||
return IsConditionSatisfied(condition, timestamp);
|
||||
default:
|
||||
|
@ -92,7 +98,8 @@ namespace MediaBrowser.Model.Dlna
|
|||
public bool IsVideoAudioConditionSatisfied(ProfileCondition condition,
|
||||
int? audioChannels,
|
||||
int? audioBitrate,
|
||||
string audioProfile)
|
||||
string audioProfile,
|
||||
bool? isSecondaryTrack)
|
||||
{
|
||||
switch (condition.Property)
|
||||
{
|
||||
|
@ -102,6 +109,8 @@ namespace MediaBrowser.Model.Dlna
|
|||
return IsConditionSatisfied(condition, audioBitrate);
|
||||
case ProfileConditionValue.AudioChannels:
|
||||
return IsConditionSatisfied(condition, audioChannels);
|
||||
case ProfileConditionValue.IsSecondaryAudio:
|
||||
return IsConditionSatisfied(condition, isSecondaryTrack);
|
||||
default:
|
||||
throw new ArgumentException("Unexpected condition on audio file: " + condition.Property);
|
||||
}
|
||||
|
|
|
@ -117,7 +117,9 @@ namespace MediaBrowser.Model.Dlna
|
|||
TranscodeSeekInfo transcodeSeekInfo,
|
||||
bool? isAnamorphic,
|
||||
bool? isCabac,
|
||||
int? refFrames)
|
||||
int? refFrames,
|
||||
int? numVideoStreams,
|
||||
int? numAudioStreams)
|
||||
{
|
||||
// first bit means Time based seek supported, second byte range seek supported (not sure about the order now), so 01 = only byte seek, 10 = time based, 11 = both, 00 = none
|
||||
string orgOp = ";DLNA.ORG_OP=" + DlnaMaps.GetOrgOpValue(runtimeTicks.HasValue, isDirectStream, transcodeSeekInfo);
|
||||
|
@ -158,7 +160,9 @@ namespace MediaBrowser.Model.Dlna
|
|||
timestamp,
|
||||
isAnamorphic,
|
||||
isCabac,
|
||||
refFrames);
|
||||
refFrames,
|
||||
numVideoStreams,
|
||||
numAudioStreams);
|
||||
|
||||
List<string> orgPnValues = new List<string>();
|
||||
|
||||
|
|
|
@ -281,7 +281,9 @@ namespace MediaBrowser.Model.Dlna
|
|||
TransportStreamTimestamp timestamp,
|
||||
bool? isAnamorphic,
|
||||
bool? isCabac,
|
||||
int? refFrames)
|
||||
int? refFrames,
|
||||
int? numVideoStreams,
|
||||
int? numAudioStreams)
|
||||
{
|
||||
container = StringHelper.TrimStart((container ?? string.Empty), '.');
|
||||
|
||||
|
@ -315,7 +317,7 @@ namespace MediaBrowser.Model.Dlna
|
|||
var anyOff = false;
|
||||
foreach (ProfileCondition c in i.Conditions)
|
||||
{
|
||||
if (!conditionProcessor.IsVideoConditionSatisfied(c, audioBitrate, audioChannels, width, height, bitDepth, videoBitrate, videoProfile, videoLevel, videoFramerate, packetLength, timestamp, isAnamorphic, isCabac, refFrames))
|
||||
if (!conditionProcessor.IsVideoConditionSatisfied(c, audioBitrate, audioChannels, width, height, bitDepth, videoBitrate, videoProfile, videoLevel, videoFramerate, packetLength, timestamp, isAnamorphic, isCabac, refFrames, numVideoStreams, numAudioStreams))
|
||||
{
|
||||
anyOff = true;
|
||||
break;
|
||||
|
|
|
@ -17,6 +17,9 @@
|
|||
VideoTimestamp = 12,
|
||||
IsAnamorphic = 13,
|
||||
RefFrames = 14,
|
||||
IsCabac = 15
|
||||
IsCabac = 15,
|
||||
NumAudioStreams = 16,
|
||||
NumVideoStreams = 17,
|
||||
IsSecondaryAudio
|
||||
}
|
||||
}
|
|
@ -89,38 +89,14 @@ namespace MediaBrowser.Model.Dlna
|
|||
|
||||
private StreamInfo GetOptimalStream(List<StreamInfo> streams)
|
||||
{
|
||||
// Grab the first one that can be direct streamed
|
||||
// If that doesn't produce anything, just take the first
|
||||
foreach (StreamInfo i in streams)
|
||||
{
|
||||
if (i.PlayMethod == PlayMethod.DirectPlay && i.MediaSource.Protocol == MediaProtocol.File)
|
||||
{
|
||||
return i;
|
||||
}
|
||||
}
|
||||
foreach (StreamInfo i in streams)
|
||||
{
|
||||
if (i.PlayMethod == PlayMethod.DirectPlay)
|
||||
{
|
||||
return i;
|
||||
}
|
||||
}
|
||||
foreach (StreamInfo i in streams)
|
||||
{
|
||||
if (i.PlayMethod == PlayMethod.DirectStream)
|
||||
{
|
||||
return i;
|
||||
}
|
||||
}
|
||||
streams = StreamInfoSorter.SortMediaSources(streams);
|
||||
|
||||
foreach (StreamInfo stream in streams)
|
||||
{
|
||||
return stream;
|
||||
}
|
||||
|
||||
PlaybackException error = new PlaybackException();
|
||||
error.ErrorCode = PlaybackErrorCode.NoCompatibleStream;
|
||||
throw error;
|
||||
return null;
|
||||
}
|
||||
|
||||
private StreamInfo BuildAudioItem(MediaSourceInfo item, AudioOptions options)
|
||||
|
@ -221,7 +197,7 @@ namespace MediaBrowser.Model.Dlna
|
|||
playlistItem.EstimateContentLength = transcodingProfile.EstimateContentLength;
|
||||
playlistItem.Container = transcodingProfile.Container;
|
||||
playlistItem.AudioCodec = transcodingProfile.AudioCodec;
|
||||
playlistItem.Protocol = transcodingProfile.Protocol;
|
||||
playlistItem.SubProtocol = transcodingProfile.Protocol;
|
||||
|
||||
List<CodecProfile> audioCodecProfiles = new List<CodecProfile>();
|
||||
foreach (CodecProfile i in options.Profile.CodecProfiles)
|
||||
|
@ -263,6 +239,16 @@ namespace MediaBrowser.Model.Dlna
|
|||
return playlistItem;
|
||||
}
|
||||
|
||||
private int? GetBitrateForDirectPlayCheck(MediaSourceInfo item, AudioOptions options)
|
||||
{
|
||||
if (item.Protocol == MediaProtocol.File)
|
||||
{
|
||||
return options.Profile.MaxStaticBitrate;
|
||||
}
|
||||
|
||||
return options.GetMaxBitrate();
|
||||
}
|
||||
|
||||
private List<PlayMethod> GetAudioDirectPlayMethods(MediaSourceInfo item, MediaStream audioStream, AudioOptions options)
|
||||
{
|
||||
DirectPlayProfile directPlayProfile = null;
|
||||
|
@ -287,7 +273,7 @@ namespace MediaBrowser.Model.Dlna
|
|||
|
||||
// The profile describes what the device supports
|
||||
// If device requirements are satisfied then allow both direct stream and direct play
|
||||
if (IsAudioEligibleForDirectPlay(item, options.Profile.MaxStaticBitrate))
|
||||
if (IsAudioEligibleForDirectPlay(item, GetBitrateForDirectPlayCheck(item, options)))
|
||||
{
|
||||
playMethods.Add(PlayMethod.DirectPlay);
|
||||
}
|
||||
|
@ -296,6 +282,49 @@ namespace MediaBrowser.Model.Dlna
|
|||
return playMethods;
|
||||
}
|
||||
|
||||
private int? GetDefaultSubtitleStreamIndex(MediaSourceInfo item, SubtitleProfile[] subtitleProfiles)
|
||||
{
|
||||
int highestScore = -1;
|
||||
|
||||
foreach (MediaStream stream in item.MediaStreams)
|
||||
{
|
||||
if (stream.Type == MediaStreamType.Subtitle && stream.Score.HasValue)
|
||||
{
|
||||
if (stream.Score.Value > highestScore)
|
||||
{
|
||||
highestScore = stream.Score.Value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
List<MediaStream> topStreams = new List<MediaStream>();
|
||||
foreach (MediaStream stream in item.MediaStreams)
|
||||
{
|
||||
if (stream.Type == MediaStreamType.Subtitle && stream.Score.HasValue && stream.Score.Value == highestScore)
|
||||
{
|
||||
topStreams.Add(stream);
|
||||
}
|
||||
}
|
||||
|
||||
// If multiple streams have an equal score, try to pick the most efficient one
|
||||
if (topStreams.Count > 1)
|
||||
{
|
||||
foreach (MediaStream stream in topStreams)
|
||||
{
|
||||
foreach (SubtitleProfile profile in subtitleProfiles)
|
||||
{
|
||||
if (profile.Method == SubtitleDeliveryMethod.External && StringHelper.EqualsIgnoreCase(profile.Format, stream.Codec))
|
||||
{
|
||||
return stream.Index;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If no optimization panned out, just use the original default
|
||||
return item.DefaultSubtitleStreamIndex;
|
||||
}
|
||||
|
||||
private StreamInfo BuildVideoItem(MediaSourceInfo item, VideoOptions options)
|
||||
{
|
||||
StreamInfo playlistItem = new StreamInfo
|
||||
|
@ -308,16 +337,20 @@ namespace MediaBrowser.Model.Dlna
|
|||
DeviceProfile = options.Profile
|
||||
};
|
||||
|
||||
playlistItem.SubtitleStreamIndex = options.SubtitleStreamIndex ?? item.DefaultSubtitleStreamIndex;
|
||||
playlistItem.SubtitleStreamIndex = options.SubtitleStreamIndex ?? GetDefaultSubtitleStreamIndex(item, options.Profile.SubtitleProfiles);
|
||||
MediaStream subtitleStream = playlistItem.SubtitleStreamIndex.HasValue ? item.GetMediaStream(MediaStreamType.Subtitle, playlistItem.SubtitleStreamIndex.Value) : null;
|
||||
|
||||
MediaStream audioStream = item.GetDefaultAudioStream(options.AudioStreamIndex ?? item.DefaultAudioStreamIndex);
|
||||
int? audioStreamIndex = audioStream == null ? (int?)null : audioStream.Index;
|
||||
int? audioStreamIndex = null;
|
||||
if (audioStream != null)
|
||||
{
|
||||
audioStreamIndex = audioStream.Index;
|
||||
}
|
||||
|
||||
MediaStream videoStream = item.VideoStream;
|
||||
|
||||
// TODO: This doesn't accout for situation of device being able to handle media bitrate, but wifi connection not fast enough
|
||||
bool isEligibleForDirectPlay = IsEligibleForDirectPlay(item, options.Profile.MaxStaticBitrate, subtitleStream, options);
|
||||
bool isEligibleForDirectPlay = IsEligibleForDirectPlay(item, GetBitrateForDirectPlayCheck(item, options), subtitleStream, options);
|
||||
bool isEligibleForDirectStream = IsEligibleForDirectPlay(item, options.GetMaxBitrate(), subtitleStream, options);
|
||||
|
||||
if (isEligibleForDirectPlay || isEligibleForDirectStream)
|
||||
|
@ -374,7 +407,7 @@ namespace MediaBrowser.Model.Dlna
|
|||
playlistItem.TranscodeSeekInfo = transcodingProfile.TranscodeSeekInfo;
|
||||
playlistItem.AudioCodec = transcodingProfile.AudioCodec.Split(',')[0];
|
||||
playlistItem.VideoCodec = transcodingProfile.VideoCodec;
|
||||
playlistItem.Protocol = transcodingProfile.Protocol;
|
||||
playlistItem.SubProtocol = transcodingProfile.Protocol;
|
||||
playlistItem.AudioStreamIndex = audioStreamIndex;
|
||||
|
||||
List<ProfileCondition> videoTranscodingConditions = new List<ProfileCondition>();
|
||||
|
@ -509,10 +542,13 @@ namespace MediaBrowser.Model.Dlna
|
|||
int? packetLength = videoStream == null ? null : videoStream.PacketLength;
|
||||
int? refFrames = videoStream == null ? null : videoStream.RefFrames;
|
||||
|
||||
int? numAudioStreams = mediaSource.GetStreamCount(MediaStreamType.Audio);
|
||||
int? numVideoStreams = mediaSource.GetStreamCount(MediaStreamType.Video);
|
||||
|
||||
// Check container conditions
|
||||
foreach (ProfileCondition i in conditions)
|
||||
{
|
||||
if (!conditionProcessor.IsVideoConditionSatisfied(i, audioBitrate, audioChannels, width, height, bitDepth, videoBitrate, videoProfile, videoLevel, videoFramerate, packetLength, timestamp, isAnamorphic, isCabac, refFrames))
|
||||
if (!conditionProcessor.IsVideoConditionSatisfied(i, audioBitrate, audioChannels, width, height, bitDepth, videoBitrate, videoProfile, videoLevel, videoFramerate, packetLength, timestamp, isAnamorphic, isCabac, refFrames, numVideoStreams, numAudioStreams))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
@ -539,7 +575,7 @@ namespace MediaBrowser.Model.Dlna
|
|||
|
||||
foreach (ProfileCondition i in conditions)
|
||||
{
|
||||
if (!conditionProcessor.IsVideoConditionSatisfied(i, audioBitrate, audioChannels, width, height, bitDepth, videoBitrate, videoProfile, videoLevel, videoFramerate, packetLength, timestamp, isAnamorphic, isCabac, refFrames))
|
||||
if (!conditionProcessor.IsVideoConditionSatisfied(i, audioBitrate, audioChannels, width, height, bitDepth, videoBitrate, videoProfile, videoLevel, videoFramerate, packetLength, timestamp, isAnamorphic, isCabac, refFrames, numVideoStreams, numAudioStreams))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
@ -568,7 +604,8 @@ namespace MediaBrowser.Model.Dlna
|
|||
|
||||
foreach (ProfileCondition i in conditions)
|
||||
{
|
||||
if (!conditionProcessor.IsVideoAudioConditionSatisfied(i, audioChannels, audioBitrate, audioProfile))
|
||||
bool? isSecondaryAudio = audioStream == null ? null : mediaSource.IsSecondaryAudio(audioStream);
|
||||
if (!conditionProcessor.IsVideoAudioConditionSatisfied(i, audioChannels, audioBitrate, audioProfile, isSecondaryAudio))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
@ -628,8 +665,20 @@ namespace MediaBrowser.Model.Dlna
|
|||
// Look for an external profile that matches the stream type (text/graphical)
|
||||
foreach (SubtitleProfile profile in subtitleProfiles)
|
||||
{
|
||||
bool requiresConversion = !StringHelper.EqualsIgnoreCase(subtitleStream.Codec, profile.Format);
|
||||
|
||||
if (!profile.SupportsLanguage(subtitleStream.Language))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (profile.Method == SubtitleDeliveryMethod.External && subtitleStream.IsTextSubtitleStream == MediaStream.IsTextFormat(profile.Format))
|
||||
{
|
||||
if (!requiresConversion)
|
||||
{
|
||||
return profile;
|
||||
}
|
||||
|
||||
if (subtitleStream.SupportsExternalStream)
|
||||
{
|
||||
return profile;
|
||||
|
@ -645,8 +694,20 @@ namespace MediaBrowser.Model.Dlna
|
|||
|
||||
foreach (SubtitleProfile profile in subtitleProfiles)
|
||||
{
|
||||
bool requiresConversion = !StringHelper.EqualsIgnoreCase(subtitleStream.Codec, profile.Format);
|
||||
|
||||
if (!profile.SupportsLanguage(subtitleStream.Language))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (profile.Method == SubtitleDeliveryMethod.Embed && subtitleStream.IsTextSubtitleStream == MediaStream.IsTextFormat(profile.Format))
|
||||
{
|
||||
if (!requiresConversion)
|
||||
{
|
||||
return profile;
|
||||
}
|
||||
|
||||
return profile;
|
||||
}
|
||||
}
|
||||
|
@ -756,6 +817,9 @@ namespace MediaBrowser.Model.Dlna
|
|||
case ProfileConditionValue.AudioProfile:
|
||||
case ProfileConditionValue.Has64BitOffsets:
|
||||
case ProfileConditionValue.PacketLength:
|
||||
case ProfileConditionValue.NumAudioStreams:
|
||||
case ProfileConditionValue.NumVideoStreams:
|
||||
case ProfileConditionValue.IsSecondaryAudio:
|
||||
case ProfileConditionValue.VideoTimestamp:
|
||||
{
|
||||
// Not supported yet
|
||||
|
|
|
@ -24,7 +24,7 @@ namespace MediaBrowser.Model.Dlna
|
|||
|
||||
public string Container { get; set; }
|
||||
|
||||
public string Protocol { get; set; }
|
||||
public string SubProtocol { get; set; }
|
||||
|
||||
public long StartPositionTicks { get; set; }
|
||||
|
||||
|
@ -69,7 +69,7 @@ namespace MediaBrowser.Model.Dlna
|
|||
public SubtitleDeliveryMethod SubtitleDeliveryMethod { get; set; }
|
||||
public string SubtitleFormat { get; set; }
|
||||
|
||||
public LiveMediaInfoResult PlaybackInfo { get; set; }
|
||||
public 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(item.IsDirectStream ? string.Empty : DateTime.UtcNow.Ticks.ToString(CultureInfo.InvariantCulture));
|
||||
list.Add(item.MaxRefFrames.HasValue ? StringHelper.ToStringCultureInvariant(item.MaxRefFrames.Value) : string.Empty);
|
||||
list.Add(item.MaxVideoBitDepth.HasValue ? StringHelper.ToStringCultureInvariant(item.MaxVideoBitDepth.Value) : string.Empty);
|
||||
list.Add(item.VideoProfile ?? string.Empty);
|
||||
list.Add(item.Cabac.HasValue ? item.Cabac.Value.ToString() : string.Empty);
|
||||
|
||||
string streamId = item.PlaybackInfo == null ? null : item.PlaybackInfo.StreamId;
|
||||
list.Add(streamId ?? string.Empty);
|
||||
list.Add(pair.Value);
|
||||
}
|
||||
|
||||
return string.Format("Params={0}", string.Join(";", list.ToArray()));
|
||||
}
|
||||
|
||||
public List<SubtitleStreamInfo> GetExternalSubtitles(bool includeSelectedTrackOnly)
|
||||
private static List<NameValuePair> BuildParams(StreamInfo item, string accessToken)
|
||||
{
|
||||
List<SubtitleStreamInfo> list = new List<SubtitleStreamInfo>();
|
||||
List<NameValuePair> list = new List<NameValuePair>();
|
||||
|
||||
// First add the selected track
|
||||
if (SubtitleStreamIndex.HasValue)
|
||||
{
|
||||
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,12 +266,7 @@ namespace MediaBrowser.Model.Dlna
|
|||
{
|
||||
if (stream.Type == MediaStreamType.Subtitle && (!SubtitleStreamIndex.HasValue || stream.Index != SubtitleStreamIndex.Value))
|
||||
{
|
||||
SubtitleStreamInfo info = GetSubtitleStreamInfo(stream, baseUrl, accessToken, startPositionTicks);
|
||||
|
||||
if (info != null)
|
||||
{
|
||||
list.Add(info);
|
||||
}
|
||||
AddSubtitleProfiles(list, stream, enableAllProfiles, baseUrl, accessToken, startPositionTicks);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -247,11 +274,41 @@ namespace MediaBrowser.Model.Dlna
|
|||
return list;
|
||||
}
|
||||
|
||||
private SubtitleStreamInfo GetSubtitleStreamInfo(MediaStream stream, string baseUrl, string accessToken, long startPositionTicks)
|
||||
private void AddSubtitleProfiles(List<SubtitleStreamInfo> list, MediaStream stream, bool enableAllProfiles, string baseUrl, string accessToken, long startPositionTicks)
|
||||
{
|
||||
SubtitleStreamInfo info = GetSubtitleStreamInfo(stream);
|
||||
if (enableAllProfiles)
|
||||
{
|
||||
foreach (SubtitleProfile profile in DeviceProfile.SubtitleProfiles)
|
||||
{
|
||||
SubtitleStreamInfo info = GetSubtitleStreamInfo(stream, baseUrl, accessToken, startPositionTicks, new[] { profile });
|
||||
|
||||
if (info != null)
|
||||
list.Add(info);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
SubtitleStreamInfo info = GetSubtitleStreamInfo(stream, baseUrl, accessToken, startPositionTicks, DeviceProfile.SubtitleProfiles);
|
||||
|
||||
list.Add(info);
|
||||
}
|
||||
}
|
||||
|
||||
private SubtitleStreamInfo GetSubtitleStreamInfo(MediaStream stream, string baseUrl, string accessToken, long startPositionTicks, SubtitleProfile[] subtitleProfiles)
|
||||
{
|
||||
SubtitleProfile subtitleProfile = StreamBuilder.GetSubtitleProfile(stream, subtitleProfiles, Context);
|
||||
SubtitleStreamInfo info = new SubtitleStreamInfo
|
||||
{
|
||||
IsForced = stream.IsForced,
|
||||
Language = stream.Language,
|
||||
Name = stream.Language ?? "Unknown",
|
||||
Format = subtitleProfile.Format,
|
||||
Index = stream.Index,
|
||||
DeliveryMethod = subtitleProfile.Method
|
||||
};
|
||||
|
||||
if (info.DeliveryMethod == SubtitleDeliveryMethod.External)
|
||||
{
|
||||
if (MediaSource.Protocol == MediaProtocol.File || !StringHelper.EqualsIgnoreCase(stream.Codec, subtitleProfile.Format))
|
||||
{
|
||||
info.Url = string.Format("{0}/Videos/{1}/{2}/Subtitles/{3}/{4}/Stream.{5}",
|
||||
baseUrl,
|
||||
|
@ -259,31 +316,17 @@ namespace MediaBrowser.Model.Dlna
|
|||
MediaSourceId,
|
||||
StringHelper.ToStringCultureInvariant(stream.Index),
|
||||
StringHelper.ToStringCultureInvariant(startPositionTicks),
|
||||
SubtitleFormat);
|
||||
subtitleProfile.Format);
|
||||
}
|
||||
else
|
||||
{
|
||||
info.Url = stream.Path;
|
||||
}
|
||||
}
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
private SubtitleStreamInfo GetSubtitleStreamInfo(MediaStream stream)
|
||||
{
|
||||
SubtitleProfile subtitleProfile = StreamBuilder.GetSubtitleProfile(stream, DeviceProfile.SubtitleProfiles, Context);
|
||||
|
||||
if (subtitleProfile.Method != SubtitleDeliveryMethod.External)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return new SubtitleStreamInfo
|
||||
{
|
||||
IsForced = stream.IsForced,
|
||||
Language = stream.Language,
|
||||
Name = stream.Language ?? "Unknown",
|
||||
Format = SubtitleFormat,
|
||||
Index = stream.Index
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the audio stream that will be used
|
||||
/// </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);
|
||||
|
|
47
MediaBrowser.Model/Dlna/StreamInfoSorter.cs
Normal file
47
MediaBrowser.Model/Dlna/StreamInfoSorter.cs
Normal file
|
@ -0,0 +1,47 @@
|
|||
using MediaBrowser.Model.MediaInfo;
|
||||
using MediaBrowser.Model.Session;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace MediaBrowser.Model.Dlna
|
||||
{
|
||||
public class StreamInfoSorter
|
||||
{
|
||||
public static List<StreamInfo> SortMediaSources(List<StreamInfo> streams)
|
||||
{
|
||||
return streams.OrderBy(i =>
|
||||
{
|
||||
// Nothing beats direct playing a file
|
||||
if (i.PlayMethod == PlayMethod.DirectPlay && i.MediaSource.Protocol == MediaProtocol.File)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 1;
|
||||
|
||||
}).ThenBy(i =>
|
||||
{
|
||||
switch (i.PlayMethod)
|
||||
{
|
||||
// Let's assume direct streaming a file is just as desirable as direct playing a remote url
|
||||
case PlayMethod.DirectStream:
|
||||
case PlayMethod.DirectPlay:
|
||||
return 0;
|
||||
default:
|
||||
return 1;
|
||||
}
|
||||
|
||||
}).ThenBy(i =>
|
||||
{
|
||||
switch (i.MediaSource.Protocol)
|
||||
{
|
||||
case MediaProtocol.File:
|
||||
return 0;
|
||||
default:
|
||||
return 1;
|
||||
}
|
||||
|
||||
}).ToList();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,4 +1,6 @@
|
|||
using System.Xml.Serialization;
|
||||
using MediaBrowser.Model.Extensions;
|
||||
using System.Collections.Generic;
|
||||
using System.Xml.Serialization;
|
||||
|
||||
namespace MediaBrowser.Model.Dlna
|
||||
{
|
||||
|
@ -13,5 +15,28 @@ namespace MediaBrowser.Model.Dlna
|
|||
[XmlAttribute("didlMode")]
|
||||
public string DidlMode { get; set; }
|
||||
|
||||
[XmlAttribute("language")]
|
||||
public string Language { get; set; }
|
||||
|
||||
public List<string> GetLanguages()
|
||||
{
|
||||
List<string> list = new List<string>();
|
||||
foreach (string i in (Language ?? string.Empty).Split(','))
|
||||
{
|
||||
if (!string.IsNullOrEmpty(i)) list.Add(i);
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
public bool SupportsLanguage(string language)
|
||||
{
|
||||
if (string.IsNullOrEmpty(language))
|
||||
{
|
||||
language = "und";
|
||||
}
|
||||
|
||||
List<string> languages = GetLanguages();
|
||||
return languages.Count == 0 || ListHelper.ContainsIgnoreCase(languages, language);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -8,5 +8,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; }
|
||||
}
|
||||
}
|
|
@ -24,6 +24,13 @@ namespace MediaBrowser.Model.Dto
|
|||
public bool ReadAtNativeFramerate { get; set; }
|
||||
public bool SupportsTranscoding { get; set; }
|
||||
public bool SupportsDirectStream { get; set; }
|
||||
public bool SupportsDirectPlay { get; set; }
|
||||
|
||||
public bool RequiresOpening { get; set; }
|
||||
public string OpenToken { get; set; }
|
||||
public bool RequiresClosing { get; set; }
|
||||
public string LiveStreamId { get; set; }
|
||||
public int? BufferMs { get; set; }
|
||||
|
||||
public VideoType? VideoType { get; set; }
|
||||
|
||||
|
@ -41,6 +48,10 @@ namespace MediaBrowser.Model.Dto
|
|||
public TransportStreamTimestamp? Timestamp { get; set; }
|
||||
public Dictionary<string, string> RequiredHttpHeaders { get; set; }
|
||||
|
||||
public string TranscodingUrl { get; set; }
|
||||
public string TranscodingSubProtocol { get; set; }
|
||||
public string TranscodingContainer { get; set; }
|
||||
|
||||
public MediaSourceInfo()
|
||||
{
|
||||
Formats = new List<string>();
|
||||
|
@ -49,6 +60,7 @@ namespace MediaBrowser.Model.Dto
|
|||
PlayableStreamFileNames = new List<string>();
|
||||
SupportsTranscoding = true;
|
||||
SupportsDirectStream = true;
|
||||
SupportsDirectPlay = true;
|
||||
}
|
||||
|
||||
public int? DefaultAudioStreamIndex { get; set; }
|
||||
|
@ -123,5 +135,40 @@ namespace MediaBrowser.Model.Dto
|
|||
|
||||
return null;
|
||||
}
|
||||
|
||||
public int? GetStreamCount(MediaStreamType type)
|
||||
{
|
||||
int numMatches = 0;
|
||||
int numStreams = 0;
|
||||
|
||||
foreach (MediaStream i in MediaStreams)
|
||||
{
|
||||
numStreams++;
|
||||
if (i.Type == type)
|
||||
{
|
||||
numMatches++;
|
||||
}
|
||||
}
|
||||
|
||||
if (numStreams == 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return numMatches;
|
||||
}
|
||||
|
||||
public bool? IsSecondaryAudio(MediaStream stream)
|
||||
{
|
||||
foreach (MediaStream currentStream in MediaStreams)
|
||||
{
|
||||
if (currentStream.Type == MediaStreamType.Audio)
|
||||
{
|
||||
return currentStream.Index != stream.Index;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,6 @@ namespace MediaBrowser.Model.Dto
|
|||
{
|
||||
Default = 0,
|
||||
Grouping = 1,
|
||||
Cache = 2
|
||||
Placeholder = 2
|
||||
}
|
||||
}
|
|
@ -3,6 +3,17 @@ namespace MediaBrowser.Model.Dto
|
|||
{
|
||||
public class NameValuePair
|
||||
{
|
||||
public NameValuePair()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public NameValuePair(string name, string value)
|
||||
{
|
||||
Name = name;
|
||||
Value = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the name.
|
||||
/// </summary>
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
using MediaBrowser.Model.Extensions;
|
||||
using MediaBrowser.Model.Dlna;
|
||||
using MediaBrowser.Model.Extensions;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace MediaBrowser.Model.Entities
|
||||
|
@ -129,18 +130,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>
|
||||
|
|
|
@ -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" />
|
||||
|
|
35
MediaBrowser.Model/MediaInfo/LiveStreamRequest.cs
Normal file
35
MediaBrowser.Model/MediaInfo/LiveStreamRequest.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
9
MediaBrowser.Model/MediaInfo/LiveStreamResponse.cs
Normal file
9
MediaBrowser.Model/MediaInfo/LiveStreamResponse.cs
Normal file
|
@ -0,0 +1,9 @@
|
|||
using MediaBrowser.Model.Dto;
|
||||
|
||||
namespace MediaBrowser.Model.MediaInfo
|
||||
{
|
||||
public class LiveStreamResponse
|
||||
{
|
||||
public MediaSourceInfo MediaSource { get; set; }
|
||||
}
|
||||
}
|
25
MediaBrowser.Model/MediaInfo/PlaybackInfoRequest.cs
Normal file
25
MediaBrowser.Model/MediaInfo/PlaybackInfoRequest.cs
Normal file
|
@ -0,0 +1,25 @@
|
|||
using MediaBrowser.Model.Dlna;
|
||||
|
||||
namespace MediaBrowser.Model.MediaInfo
|
||||
{
|
||||
public class PlaybackInfoRequest
|
||||
{
|
||||
public string Id { get; set; }
|
||||
|
||||
public string UserId { get; set; }
|
||||
|
||||
public int? MaxStreamingBitrate { get; set; }
|
||||
|
||||
public long? StartTimeTicks { get; set; }
|
||||
|
||||
public int? AudioStreamIndex { get; set; }
|
||||
|
||||
public int? SubtitleStreamIndex { get; set; }
|
||||
|
||||
public string MediaSourceId { get; set; }
|
||||
|
||||
public string LiveStreamId { get; set; }
|
||||
|
||||
public DeviceProfile DeviceProfile { get; set; }
|
||||
}
|
||||
}
|
|
@ -4,7 +4,7 @@ using System.Collections.Generic;
|
|||
|
||||
namespace MediaBrowser.Model.MediaInfo
|
||||
{
|
||||
public class LiveMediaInfoResult
|
||||
public class PlaybackInfoResponse
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the media sources.
|
||||
|
@ -13,10 +13,10 @@ namespace MediaBrowser.Model.MediaInfo
|
|||
public List<MediaSourceInfo> MediaSources { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the stream identifier.
|
||||
/// Gets or sets the play session identifier.
|
||||
/// </summary>
|
||||
/// <value>The stream identifier.</value>
|
||||
public string StreamId { get; set; }
|
||||
/// <value>The play session identifier.</value>
|
||||
public string PlaySessionId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the error code.
|
||||
|
@ -24,7 +24,7 @@ namespace MediaBrowser.Model.MediaInfo
|
|||
/// <value>The error code.</value>
|
||||
public PlaybackErrorCode? ErrorCode { get; set; }
|
||||
|
||||
public LiveMediaInfoResult()
|
||||
public PlaybackInfoResponse()
|
||||
{
|
||||
MediaSources = new List<MediaSourceInfo>();
|
||||
}
|
|
@ -95,6 +95,11 @@
|
|||
/// </summary>
|
||||
IndexOptions,
|
||||
|
||||
/// <summary>
|
||||
/// The item counts
|
||||
/// </summary>
|
||||
ItemCounts,
|
||||
|
||||
/// <summary>
|
||||
/// The keywords
|
||||
/// </summary>
|
||||
|
|
|
@ -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; }
|
||||
}
|
||||
}
|
|
@ -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; }
|
||||
}
|
||||
}
|
|
@ -31,13 +31,24 @@ namespace MediaBrowser.Model.Sync
|
|||
/// <value>The item identifier.</value>
|
||||
public string ItemId { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets the synchronize job item identifier.
|
||||
/// </summary>
|
||||
/// <value>The synchronize job item identifier.</value>
|
||||
public string SyncJobItemId { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets the user ids with access.
|
||||
/// </summary>
|
||||
/// <value>The user ids with access.</value>
|
||||
public List<string> UserIdsWithAccess { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets the additional files.
|
||||
/// </summary>
|
||||
/// <value>The additional files.</value>
|
||||
public List<string> AdditionalFiles { get; set; }
|
||||
|
||||
public LocalItem()
|
||||
{
|
||||
AdditionalFiles = new List<string>();
|
||||
UserIdsWithAccess = new List<string>();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@ namespace MediaBrowser.Model.Sync
|
|||
{
|
||||
public List<string> LocalItemIds { get; set; }
|
||||
public List<string> OfflineUserIds { get; set; }
|
||||
public List<string> SyncJobItemIds { get; set; }
|
||||
|
||||
public string TargetId { get; set; }
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -96,7 +96,7 @@ namespace MediaBrowser.Providers.MediaInfo
|
|||
var album = item.Parent as MusicAlbum;
|
||||
|
||||
var filename = item.Album ?? string.Empty;
|
||||
filename += item.Artists.FirstOrDefault() ?? string.Empty;
|
||||
filename += string.Join(",", item.Artists.ToArray());
|
||||
filename += album == null ? item.Id.ToString("N") + "_primary" + item.DateModified.Ticks : album.Id.ToString("N") + album.DateModified.Ticks + "_primary";
|
||||
|
||||
filename = filename.GetMD5() + ".jpg";
|
||||
|
|
|
@ -6,15 +6,14 @@ using MediaBrowser.Controller.Entities.Movies;
|
|||
using MediaBrowser.Controller.Entities.TV;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Controller.Subtitles;
|
||||
using MediaBrowser.Model.Configuration;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.Logging;
|
||||
using MediaBrowser.Model.Providers;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Model.Providers;
|
||||
|
||||
namespace MediaBrowser.Providers.MediaInfo
|
||||
{
|
||||
|
@ -23,14 +22,16 @@ namespace MediaBrowser.Providers.MediaInfo
|
|||
private readonly ILibraryManager _libraryManager;
|
||||
private readonly IServerConfigurationManager _config;
|
||||
private readonly ISubtitleManager _subtitleManager;
|
||||
private readonly IMediaSourceManager _mediaSourceManager;
|
||||
private readonly ILogger _logger;
|
||||
|
||||
public SubtitleScheduledTask(ILibraryManager libraryManager, IServerConfigurationManager config, ISubtitleManager subtitleManager, ILogger logger)
|
||||
public SubtitleScheduledTask(ILibraryManager libraryManager, IServerConfigurationManager config, ISubtitleManager subtitleManager, ILogger logger, IMediaSourceManager mediaSourceManager)
|
||||
{
|
||||
_libraryManager = libraryManager;
|
||||
_config = config;
|
||||
_subtitleManager = subtitleManager;
|
||||
_logger = logger;
|
||||
_mediaSourceManager = mediaSourceManager;
|
||||
}
|
||||
|
||||
public string Name
|
||||
|
@ -107,7 +108,7 @@ namespace MediaBrowser.Providers.MediaInfo
|
|||
(options.DownloadMovieSubtitles &&
|
||||
video is Movie))
|
||||
{
|
||||
var mediaStreams = video.GetMediaSources(false).First().MediaStreams;
|
||||
var mediaStreams = _mediaSourceManager.GetStaticMediaSources(video, false).First().MediaStreams;
|
||||
|
||||
var downloadedLanguages = await new SubtitleDownloader(_logger,
|
||||
_subtitleManager)
|
||||
|
|
|
@ -169,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);
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
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();
|
||||
|
|
|
@ -233,25 +233,12 @@ 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();
|
||||
|
||||
if (!File.Exists(cacheFilePath))
|
||||
{
|
||||
var newWidth = Convert.ToInt32(newSize.Width);
|
||||
var newHeight = Convert.ToInt32(newSize.Height);
|
||||
|
||||
|
@ -268,8 +255,6 @@ namespace MediaBrowser.Server.Implementations.Drawing
|
|||
originalImage.CurrentImage.CompressionQuality = quality;
|
||||
|
||||
originalImage.SaveImage(cacheFilePath);
|
||||
|
||||
return cacheFilePath;
|
||||
}
|
||||
}
|
||||
else
|
||||
|
@ -286,8 +271,7 @@ namespace MediaBrowser.Server.Implementations.Drawing
|
|||
wand.CurrentImage.CompressionQuality = quality;
|
||||
|
||||
wand.SaveImage(cacheFilePath);
|
||||
|
||||
return cacheFilePath;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -296,6 +280,8 @@ namespace MediaBrowser.Server.Implementations.Drawing
|
|||
{
|
||||
semaphore.Release();
|
||||
}
|
||||
|
||||
return cacheFilePath;
|
||||
}
|
||||
|
||||
private ImageFormat GetOutputFormat(ImageFormat requestedFormat)
|
||||
|
|
|
@ -96,6 +96,8 @@ namespace MediaBrowser.Server.Implementations.Dto
|
|||
var byName = item as IItemByName;
|
||||
|
||||
if (byName != null && !(item is LiveTvChannel))
|
||||
{
|
||||
//if (options.Fields.Contains(ItemFields.ItemCounts))
|
||||
{
|
||||
var itemFilter = byName.GetItemFilter();
|
||||
|
||||
|
@ -105,6 +107,7 @@ namespace MediaBrowser.Server.Implementations.Dto
|
|||
|
||||
SetItemByNameInfo(item, dto, libraryItems.ToList(), user);
|
||||
}
|
||||
}
|
||||
|
||||
FillSyncInfo(dto, item, itemIdsWithSyncJobs, options, user);
|
||||
|
||||
|
@ -121,6 +124,8 @@ namespace MediaBrowser.Server.Implementations.Dto
|
|||
var byName = item as IItemByName;
|
||||
|
||||
if (byName != null && !(item is LiveTvChannel))
|
||||
{
|
||||
//if (options.Fields.Contains(ItemFields.ItemCounts))
|
||||
{
|
||||
var itemFilter = byName.GetItemFilter();
|
||||
|
||||
|
@ -129,6 +134,7 @@ namespace MediaBrowser.Server.Implementations.Dto
|
|||
_libraryManager.RootFolder.GetRecursiveChildren(itemFilter);
|
||||
|
||||
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
|
||||
{
|
||||
|
@ -311,7 +317,11 @@ namespace MediaBrowser.Server.Implementations.Dto
|
|||
{
|
||||
var dto = GetBaseItemDtoInternal(item, options, 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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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,31 +129,28 @@ 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)
|
||||
{
|
||||
mediaSources = await _channelManager.GetChannelItemMediaSources(id, true, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(userId))
|
||||
{
|
||||
mediaSources = hasMediaSources.GetMediaSources(enablePathSubstitution);
|
||||
}
|
||||
else
|
||||
{
|
||||
var user = _userManager.GetUserById(userId);
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
{
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,9 +404,11 @@ namespace MediaBrowser.Server.Implementations.LiveTv
|
|||
}
|
||||
}
|
||||
|
||||
private void Sanitize(MediaSourceInfo mediaSource)
|
||||
private void Normalize(MediaSourceInfo mediaSource, bool isVideo)
|
||||
{
|
||||
if (mediaSource.MediaStreams.Count == 0)
|
||||
{
|
||||
if (isVideo)
|
||||
{
|
||||
mediaSource.MediaStreams.AddRange(new List<MediaStream>
|
||||
{
|
||||
|
@ -387,7 +416,10 @@ namespace MediaBrowser.Server.Implementations.LiveTv
|
|||
{
|
||||
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
|
||||
Index = -1,
|
||||
|
||||
// Set to true if unknown to enable deinterlacing
|
||||
IsInterlaced = true
|
||||
},
|
||||
new MediaStream
|
||||
{
|
||||
|
@ -397,6 +429,19 @@ namespace MediaBrowser.Server.Implementations.LiveTv
|
|||
}
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
mediaSource.MediaStreams.AddRange(new List<MediaStream>
|
||||
{
|
||||
new MediaStream
|
||||
{
|
||||
Type = MediaStreamType.Audio,
|
||||
// Set the index to -1 because we don't know the exact index of the audio stream within the container
|
||||
Index = -1
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Clean some bad data coming from providers
|
||||
foreach (var stream in mediaSource.MediaStreams)
|
||||
|
@ -544,12 +589,14 @@ 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.PremiereDate = item.PremiereDate ?? info.OriginalAirDate;
|
||||
|
||||
await item.UpdateToRepository(ItemUpdateType.MetadataImport, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
|
@ -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)
|
||||
|
|
|
@ -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
Loading…
Reference in New Issue
Block a user