implement modular media sources
This commit is contained in:
parent
3add1872c8
commit
bd2ea703e3
|
@ -1,6 +1,5 @@
|
||||||
using MediaBrowser.Common.Extensions;
|
using MediaBrowser.Common.Extensions;
|
||||||
using MediaBrowser.Common.IO;
|
using MediaBrowser.Common.IO;
|
||||||
using MediaBrowser.Controller.Channels;
|
|
||||||
using MediaBrowser.Controller.Configuration;
|
using MediaBrowser.Controller.Configuration;
|
||||||
using MediaBrowser.Controller.Devices;
|
using MediaBrowser.Controller.Devices;
|
||||||
using MediaBrowser.Controller.Dlna;
|
using MediaBrowser.Controller.Dlna;
|
||||||
|
@ -65,7 +64,6 @@ namespace MediaBrowser.Api.Playback
|
||||||
|
|
||||||
protected IFileSystem FileSystem { get; private set; }
|
protected IFileSystem FileSystem { get; private set; }
|
||||||
|
|
||||||
protected ILiveTvManager LiveTvManager { get; private set; }
|
|
||||||
protected IDlnaManager DlnaManager { get; private set; }
|
protected IDlnaManager DlnaManager { get; private set; }
|
||||||
protected IDeviceManager DeviceManager { get; private set; }
|
protected IDeviceManager DeviceManager { get; private set; }
|
||||||
protected ISubtitleEncoder SubtitleEncoder { get; private set; }
|
protected ISubtitleEncoder SubtitleEncoder { get; private set; }
|
||||||
|
@ -75,14 +73,13 @@ namespace MediaBrowser.Api.Playback
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="BaseStreamingService" /> class.
|
/// Initializes a new instance of the <see cref="BaseStreamingService" /> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
protected BaseStreamingService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, ILiveTvManager liveTvManager, IDlnaManager dlnaManager, ISubtitleEncoder subtitleEncoder, IDeviceManager deviceManager, IMediaSourceManager mediaSourceManager, IZipClient zipClient)
|
protected BaseStreamingService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, IDlnaManager dlnaManager, ISubtitleEncoder subtitleEncoder, IDeviceManager deviceManager, IMediaSourceManager mediaSourceManager, IZipClient zipClient)
|
||||||
{
|
{
|
||||||
ZipClient = zipClient;
|
ZipClient = zipClient;
|
||||||
MediaSourceManager = mediaSourceManager;
|
MediaSourceManager = mediaSourceManager;
|
||||||
DeviceManager = deviceManager;
|
DeviceManager = deviceManager;
|
||||||
SubtitleEncoder = subtitleEncoder;
|
SubtitleEncoder = subtitleEncoder;
|
||||||
DlnaManager = dlnaManager;
|
DlnaManager = dlnaManager;
|
||||||
LiveTvManager = liveTvManager;
|
|
||||||
FileSystem = fileSystem;
|
FileSystem = fileSystem;
|
||||||
ServerConfigurationManager = serverConfig;
|
ServerConfigurationManager = serverConfig;
|
||||||
UserManager = userManager;
|
UserManager = userManager;
|
||||||
|
@ -95,11 +92,10 @@ namespace MediaBrowser.Api.Playback
|
||||||
/// Gets the command line arguments.
|
/// Gets the command line arguments.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="outputPath">The output path.</param>
|
/// <param name="outputPath">The output path.</param>
|
||||||
/// <param name="transcodingJobId">The transcoding job identifier.</param>
|
|
||||||
/// <param name="state">The state.</param>
|
/// <param name="state">The state.</param>
|
||||||
/// <param name="isEncoding">if set to <c>true</c> [is encoding].</param>
|
/// <param name="isEncoding">if set to <c>true</c> [is encoding].</param>
|
||||||
/// <returns>System.String.</returns>
|
/// <returns>System.String.</returns>
|
||||||
protected abstract string GetCommandLineArguments(string outputPath, string transcodingJobId, StreamState state, bool isEncoding);
|
protected abstract string GetCommandLineArguments(string outputPath, StreamState state, bool isEncoding);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the type of the transcoding job.
|
/// Gets the type of the transcoding job.
|
||||||
|
@ -128,7 +124,7 @@ namespace MediaBrowser.Api.Playback
|
||||||
|
|
||||||
var outputFileExtension = GetOutputFileExtension(state);
|
var outputFileExtension = GetOutputFileExtension(state);
|
||||||
|
|
||||||
var data = GetCommandLineArguments("dummy\\dummy", "dummyTranscodingId", state, false);
|
var data = GetCommandLineArguments("dummy\\dummy", state, false);
|
||||||
|
|
||||||
data += "-" + (state.Request.DeviceId ?? string.Empty);
|
data += "-" + (state.Request.DeviceId ?? string.Empty);
|
||||||
data += "-" + (state.Request.StreamId ?? string.Empty);
|
data += "-" + (state.Request.StreamId ?? string.Empty);
|
||||||
|
@ -719,8 +715,10 @@ namespace MediaBrowser.Api.Playback
|
||||||
seconds.ToString(UsCulture));
|
seconds.ToString(UsCulture));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var mediaPath = state.MediaPath ?? string.Empty;
|
||||||
|
|
||||||
return string.Format("subtitles='{0}:si={1}',setpts=PTS -{2}/TB",
|
return string.Format("subtitles='{0}:si={1}',setpts=PTS -{2}/TB",
|
||||||
state.MediaPath.Replace('\\', '/').Replace(":/", "\\:/"),
|
mediaPath.Replace('\\', '/').Replace(":/", "\\:/"),
|
||||||
state.InternalSubtitleStreamOffset.ToString(UsCulture),
|
state.InternalSubtitleStreamOffset.ToString(UsCulture),
|
||||||
seconds.ToString(UsCulture));
|
seconds.ToString(UsCulture));
|
||||||
}
|
}
|
||||||
|
@ -895,12 +893,11 @@ namespace MediaBrowser.Api.Playback
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the input argument.
|
/// Gets the input argument.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="transcodingJobId">The transcoding job identifier.</param>
|
|
||||||
/// <param name="state">The state.</param>
|
/// <param name="state">The state.</param>
|
||||||
/// <returns>System.String.</returns>
|
/// <returns>System.String.</returns>
|
||||||
protected string GetInputArgument(string transcodingJobId, StreamState state)
|
protected string GetInputArgument(StreamState state)
|
||||||
{
|
{
|
||||||
var arg = "-i " + GetInputPathArgument(transcodingJobId, state);
|
var arg = "-i " + GetInputPathArgument(state);
|
||||||
|
|
||||||
if (state.SubtitleStream != null)
|
if (state.SubtitleStream != null)
|
||||||
{
|
{
|
||||||
|
@ -913,27 +910,18 @@ namespace MediaBrowser.Api.Playback
|
||||||
return arg;
|
return arg;
|
||||||
}
|
}
|
||||||
|
|
||||||
private string GetInputPathArgument(string transcodingJobId, StreamState state)
|
private string GetInputPathArgument(StreamState state)
|
||||||
{
|
{
|
||||||
//if (state.InputProtocol == MediaProtocol.File &&
|
|
||||||
// state.RunTimeTicks.HasValue &&
|
|
||||||
// state.VideoType == VideoType.VideoFile &&
|
|
||||||
// !string.Equals(state.OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase))
|
|
||||||
//{
|
|
||||||
// if (state.RunTimeTicks.Value >= TimeSpan.FromMinutes(5).Ticks && state.IsInputVideo)
|
|
||||||
// {
|
|
||||||
// }
|
|
||||||
//}
|
|
||||||
|
|
||||||
var protocol = state.InputProtocol;
|
var protocol = state.InputProtocol;
|
||||||
|
var mediaPath = state.MediaPath ?? string.Empty;
|
||||||
|
|
||||||
var inputPath = new[] { state.MediaPath };
|
var inputPath = new[] { mediaPath };
|
||||||
|
|
||||||
if (state.IsInputVideo)
|
if (state.IsInputVideo)
|
||||||
{
|
{
|
||||||
if (!(state.VideoType == VideoType.Iso && state.IsoMount == null))
|
if (!(state.VideoType == VideoType.Iso && state.IsoMount == null))
|
||||||
{
|
{
|
||||||
inputPath = MediaEncoderHelpers.GetInputArgument(state.MediaPath, state.InputProtocol, state.IsoMount, state.PlayableStreamFileNames);
|
inputPath = MediaEncoderHelpers.GetInputArgument(mediaPath, state.InputProtocol, state.IsoMount, state.PlayableStreamFileNames);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -947,55 +935,20 @@ namespace MediaBrowser.Api.Playback
|
||||||
state.IsoMount = await IsoManager.Mount(state.MediaPath, cancellationTokenSource.Token).ConfigureAwait(false);
|
state.IsoMount = await IsoManager.Mount(state.MediaPath, cancellationTokenSource.Token).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(state.MediaPath))
|
if (state.MediaSource.RequiresOpening)
|
||||||
{
|
{
|
||||||
var checkCodecs = false;
|
var mediaSource = await MediaSourceManager.OpenMediaSource(state.MediaSource.OpenKey, cancellationTokenSource.Token)
|
||||||
|
.ConfigureAwait(false);
|
||||||
|
|
||||||
if (string.Equals(state.ItemType, typeof(LiveTvChannel).Name))
|
AttachMediaSourceInfo(state, mediaSource, state.VideoRequest, state.RequestedUrl);
|
||||||
|
|
||||||
|
if (state.VideoRequest != null)
|
||||||
{
|
{
|
||||||
var streamInfo = await LiveTvManager.GetChannelStream(state.Request.Id, cancellationTokenSource.Token).ConfigureAwait(false);
|
TryStreamCopy(state, state.VideoRequest);
|
||||||
|
}
|
||||||
state.LiveTvStreamId = streamInfo.Id;
|
|
||||||
|
|
||||||
state.MediaPath = streamInfo.Path;
|
|
||||||
state.InputProtocol = streamInfo.Protocol;
|
|
||||||
|
|
||||||
|
// TODO: This is only needed for live tv
|
||||||
await Task.Delay(1500, cancellationTokenSource.Token).ConfigureAwait(false);
|
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))
|
|
||||||
{
|
|
||||||
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";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1017,7 +970,7 @@ namespace MediaBrowser.Api.Playback
|
||||||
await AcquireResources(state, cancellationTokenSource).ConfigureAwait(false);
|
await AcquireResources(state, cancellationTokenSource).ConfigureAwait(false);
|
||||||
|
|
||||||
var transcodingId = Guid.NewGuid().ToString("N");
|
var transcodingId = Guid.NewGuid().ToString("N");
|
||||||
var commandLineArgs = GetCommandLineArguments(outputPath, transcodingId, state, true);
|
var commandLineArgs = GetCommandLineArguments(outputPath, state, true);
|
||||||
|
|
||||||
if (ApiEntryPoint.Instance.GetEncodingOptions().EnableDebugLogging)
|
if (ApiEntryPoint.Instance.GetEncodingOptions().EnableDebugLogging)
|
||||||
{
|
{
|
||||||
|
@ -1644,7 +1597,7 @@ namespace MediaBrowser.Api.Playback
|
||||||
request.AudioCodec = InferAudioCodec(url);
|
request.AudioCodec = InferAudioCodec(url);
|
||||||
}
|
}
|
||||||
|
|
||||||
var state = new StreamState(LiveTvManager, Logger)
|
var state = new StreamState(MediaSourceManager, Logger)
|
||||||
{
|
{
|
||||||
Request = request,
|
Request = request,
|
||||||
RequestedUrl = url
|
RequestedUrl = url
|
||||||
|
@ -1658,109 +1611,20 @@ namespace MediaBrowser.Api.Playback
|
||||||
|
|
||||||
var item = LibraryManager.GetItemById(request.Id);
|
var item = LibraryManager.GetItemById(request.Id);
|
||||||
|
|
||||||
List<MediaStream> mediaStreams = null;
|
state.IsInputVideo = string.Equals(item.MediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase);
|
||||||
|
|
||||||
state.ItemType = item.GetType().Name;
|
|
||||||
state.ItemId = item.Id.ToString("N");
|
|
||||||
var archivable = item as IArchivable;
|
var archivable = item as IArchivable;
|
||||||
state.IsInputArchive = archivable != null && archivable.IsArchive;
|
state.IsInputArchive = archivable != null && archivable.IsArchive;
|
||||||
|
|
||||||
if (item is ILiveTvRecording)
|
|
||||||
{
|
|
||||||
var recording = await LiveTvManager.GetInternalRecording(request.Id, cancellationToken).ConfigureAwait(false);
|
|
||||||
|
|
||||||
state.VideoType = VideoType.VideoFile;
|
|
||||||
state.IsInputVideo = string.Equals(recording.MediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase);
|
|
||||||
|
|
||||||
var path = recording.RecordingInfo.Path;
|
|
||||||
var mediaUrl = recording.RecordingInfo.Url;
|
|
||||||
|
|
||||||
var source = string.IsNullOrEmpty(request.MediaSourceId)
|
|
||||||
? recording.GetMediaSources(false).First()
|
|
||||||
: MediaSourceManager.GetStaticMediaSource(recording, request.MediaSourceId, false);
|
|
||||||
|
|
||||||
mediaStreams = source.MediaStreams;
|
|
||||||
|
|
||||||
// Just to prevent this from being null and causing other methods to fail
|
|
||||||
state.MediaPath = string.Empty;
|
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(path))
|
|
||||||
{
|
|
||||||
state.MediaPath = path;
|
|
||||||
state.InputProtocol = MediaProtocol.File;
|
|
||||||
}
|
|
||||||
else if (!string.IsNullOrEmpty(mediaUrl))
|
|
||||||
{
|
|
||||||
state.MediaPath = mediaUrl;
|
|
||||||
state.InputProtocol = MediaProtocol.Http;
|
|
||||||
}
|
|
||||||
|
|
||||||
state.RunTimeTicks = recording.RunTimeTicks;
|
|
||||||
state.DeInterlace = true;
|
|
||||||
state.OutputAudioSync = "1000";
|
|
||||||
state.InputVideoSync = "-1";
|
|
||||||
state.InputAudioSync = "1";
|
|
||||||
state.InputContainer = recording.Container;
|
|
||||||
state.ReadInputAtNativeFramerate = source.ReadAtNativeFramerate;
|
|
||||||
}
|
|
||||||
else if (item is LiveTvChannel)
|
|
||||||
{
|
|
||||||
var channel = LiveTvManager.GetInternalChannel(request.Id);
|
|
||||||
|
|
||||||
state.VideoType = VideoType.VideoFile;
|
|
||||||
state.IsInputVideo = string.Equals(channel.MediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase);
|
|
||||||
mediaStreams = new List<MediaStream>();
|
|
||||||
|
|
||||||
state.DeInterlace = true;
|
|
||||||
|
|
||||||
// Just to prevent this from being null and causing other methods to fail
|
|
||||||
state.MediaPath = string.Empty;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
var mediaSources = await MediaSourceManager.GetPlayackMediaSources(request.Id, false, cancellationToken).ConfigureAwait(false);
|
var mediaSources = await MediaSourceManager.GetPlayackMediaSources(request.Id, false, cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
var mediaSource = string.IsNullOrEmpty(request.MediaSourceId)
|
var mediaSource = string.IsNullOrEmpty(request.MediaSourceId)
|
||||||
? mediaSources.First()
|
? mediaSources.First()
|
||||||
: mediaSources.First(i => string.Equals(i.Id, request.MediaSourceId));
|
: mediaSources.First(i => string.Equals(i.Id, request.MediaSourceId));
|
||||||
|
|
||||||
mediaStreams = mediaSource.MediaStreams;
|
|
||||||
|
|
||||||
state.MediaPath = mediaSource.Path;
|
|
||||||
state.InputProtocol = mediaSource.Protocol;
|
|
||||||
state.InputContainer = mediaSource.Container;
|
|
||||||
state.InputFileSize = mediaSource.Size;
|
|
||||||
state.InputBitrate = mediaSource.Bitrate;
|
|
||||||
state.ReadInputAtNativeFramerate = mediaSource.ReadAtNativeFramerate;
|
|
||||||
state.RunTimeTicks = mediaSource.RunTimeTicks;
|
|
||||||
state.RemoteHttpHeaders = mediaSource.RequiredHttpHeaders;
|
|
||||||
|
|
||||||
var video = item as Video;
|
|
||||||
|
|
||||||
if (video != null)
|
|
||||||
{
|
|
||||||
state.IsInputVideo = true;
|
|
||||||
|
|
||||||
if (mediaSource.VideoType.HasValue)
|
|
||||||
{
|
|
||||||
state.VideoType = mediaSource.VideoType.Value;
|
|
||||||
}
|
|
||||||
|
|
||||||
state.IsoType = mediaSource.IsoType;
|
|
||||||
|
|
||||||
state.PlayableStreamFileNames = mediaSource.PlayableStreamFileNames.ToList();
|
|
||||||
|
|
||||||
if (mediaSource.Timestamp.HasValue)
|
|
||||||
{
|
|
||||||
state.InputTimestamp = mediaSource.Timestamp.Value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
var videoRequest = request as VideoStreamRequest;
|
var videoRequest = request as VideoStreamRequest;
|
||||||
|
|
||||||
AttachMediaStreamInfo(state, mediaStreams, videoRequest, url);
|
AttachMediaSourceInfo(state, mediaSource, videoRequest, url);
|
||||||
|
|
||||||
var container = Path.GetExtension(state.RequestedUrl);
|
var container = Path.GetExtension(state.RequestedUrl);
|
||||||
|
|
||||||
|
@ -1800,6 +1664,16 @@ namespace MediaBrowser.Api.Playback
|
||||||
ApplyDeviceProfileSettings(state);
|
ApplyDeviceProfileSettings(state);
|
||||||
|
|
||||||
if (videoRequest != null)
|
if (videoRequest != null)
|
||||||
|
{
|
||||||
|
TryStreamCopy(state, videoRequest);
|
||||||
|
}
|
||||||
|
|
||||||
|
state.OutputFilePath = GetOutputFilePath(state);
|
||||||
|
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void TryStreamCopy(StreamState state, VideoStreamRequest videoRequest)
|
||||||
{
|
{
|
||||||
if (state.VideoStream != null && CanStreamCopyVideo(videoRequest, state.VideoStream))
|
if (state.VideoStream != null && CanStreamCopyVideo(videoRequest, state.VideoStream))
|
||||||
{
|
{
|
||||||
|
@ -1812,16 +1686,34 @@ namespace MediaBrowser.Api.Playback
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
state.OutputFilePath = GetOutputFilePath(state);
|
private void AttachMediaSourceInfo(StreamState state,
|
||||||
|
|
||||||
return state;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void AttachMediaStreamInfo(StreamState state,
|
|
||||||
MediaSourceInfo mediaSource,
|
MediaSourceInfo mediaSource,
|
||||||
VideoStreamRequest videoRequest,
|
VideoStreamRequest videoRequest,
|
||||||
string requestedUrl)
|
string requestedUrl)
|
||||||
{
|
{
|
||||||
|
state.MediaPath = mediaSource.Path;
|
||||||
|
state.InputProtocol = mediaSource.Protocol;
|
||||||
|
state.InputContainer = mediaSource.Container;
|
||||||
|
state.InputFileSize = mediaSource.Size;
|
||||||
|
state.InputBitrate = mediaSource.Bitrate;
|
||||||
|
state.ReadInputAtNativeFramerate = mediaSource.ReadAtNativeFramerate;
|
||||||
|
state.RunTimeTicks = mediaSource.RunTimeTicks;
|
||||||
|
state.RemoteHttpHeaders = mediaSource.RequiredHttpHeaders;
|
||||||
|
|
||||||
|
if (mediaSource.VideoType.HasValue)
|
||||||
|
{
|
||||||
|
state.VideoType = mediaSource.VideoType.Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
state.IsoType = mediaSource.IsoType;
|
||||||
|
|
||||||
|
state.PlayableStreamFileNames = mediaSource.PlayableStreamFileNames.ToList();
|
||||||
|
|
||||||
|
if (mediaSource.Timestamp.HasValue)
|
||||||
|
{
|
||||||
|
state.InputTimestamp = mediaSource.Timestamp.Value;
|
||||||
|
}
|
||||||
|
|
||||||
state.InputProtocol = mediaSource.Protocol;
|
state.InputProtocol = mediaSource.Protocol;
|
||||||
state.MediaPath = mediaSource.Path;
|
state.MediaPath = mediaSource.Path;
|
||||||
state.RunTimeTicks = mediaSource.RunTimeTicks;
|
state.RunTimeTicks = mediaSource.RunTimeTicks;
|
||||||
|
@ -1830,21 +1722,16 @@ namespace MediaBrowser.Api.Playback
|
||||||
state.InputFileSize = mediaSource.Size;
|
state.InputFileSize = mediaSource.Size;
|
||||||
state.ReadInputAtNativeFramerate = mediaSource.ReadAtNativeFramerate;
|
state.ReadInputAtNativeFramerate = mediaSource.ReadAtNativeFramerate;
|
||||||
|
|
||||||
if (state.ReadInputAtNativeFramerate)
|
if (state.ReadInputAtNativeFramerate ||
|
||||||
|
mediaSource.Protocol == MediaProtocol.File && string.Equals(mediaSource.Container, "wtv", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
state.OutputAudioSync = "1000";
|
state.OutputAudioSync = "1000";
|
||||||
state.InputVideoSync = "-1";
|
state.InputVideoSync = "-1";
|
||||||
state.InputAudioSync = "1";
|
state.InputAudioSync = "1";
|
||||||
}
|
}
|
||||||
|
|
||||||
AttachMediaStreamInfo(state, mediaSource.MediaStreams, videoRequest, requestedUrl);
|
var mediaStreams = mediaSource.MediaStreams;
|
||||||
}
|
|
||||||
|
|
||||||
private void AttachMediaStreamInfo(StreamState state,
|
|
||||||
List<MediaStream> mediaStreams,
|
|
||||||
VideoStreamRequest videoRequest,
|
|
||||||
string requestedUrl)
|
|
||||||
{
|
|
||||||
if (videoRequest != null)
|
if (videoRequest != null)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(videoRequest.VideoCodec))
|
if (string.IsNullOrEmpty(videoRequest.VideoCodec))
|
||||||
|
@ -1873,7 +1760,7 @@ namespace MediaBrowser.Api.Playback
|
||||||
state.AudioStream = GetMediaStream(mediaStreams, null, MediaStreamType.Audio, true);
|
state.AudioStream = GetMediaStream(mediaStreams, null, MediaStreamType.Audio, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
state.AllMediaStreams = mediaStreams;
|
state.MediaSource = mediaSource;
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool CanStreamCopyVideo(VideoStreamRequest request, MediaStream videoStream)
|
private bool CanStreamCopyVideo(VideoStreamRequest request, MediaStream videoStream)
|
||||||
|
|
|
@ -54,7 +54,7 @@ namespace MediaBrowser.Api.Playback.Dash
|
||||||
|
|
||||||
public class MpegDashService : BaseHlsService
|
public class MpegDashService : BaseHlsService
|
||||||
{
|
{
|
||||||
public MpegDashService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, ILiveTvManager liveTvManager, IDlnaManager dlnaManager, ISubtitleEncoder subtitleEncoder, IDeviceManager deviceManager, IMediaSourceManager mediaSourceManager, IZipClient zipClient, INetworkManager networkManager) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, fileSystem, liveTvManager, dlnaManager, subtitleEncoder, deviceManager, mediaSourceManager, zipClient)
|
public MpegDashService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, IDlnaManager dlnaManager, ISubtitleEncoder subtitleEncoder, IDeviceManager deviceManager, IMediaSourceManager mediaSourceManager, IZipClient zipClient, INetworkManager networkManager) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, fileSystem, dlnaManager, subtitleEncoder, deviceManager, mediaSourceManager, zipClient)
|
||||||
{
|
{
|
||||||
NetworkManager = networkManager;
|
NetworkManager = networkManager;
|
||||||
}
|
}
|
||||||
|
@ -447,7 +447,7 @@ namespace MediaBrowser.Api.Playback.Dash
|
||||||
return args;
|
return args;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override string GetCommandLineArguments(string outputPath, string transcodingJobId, StreamState state, bool isEncoding)
|
protected override string GetCommandLineArguments(string outputPath, StreamState state, bool isEncoding)
|
||||||
{
|
{
|
||||||
// test url http://192.168.1.2:8096/videos/233e8905d559a8f230db9bffd2ac9d6d/master.mpd?mediasourceid=233e8905d559a8f230db9bffd2ac9d6d&videocodec=h264&audiocodec=aac&maxwidth=1280&videobitrate=500000&audiobitrate=128000&profile=baseline&level=3
|
// test url http://192.168.1.2:8096/videos/233e8905d559a8f230db9bffd2ac9d6d/master.mpd?mediasourceid=233e8905d559a8f230db9bffd2ac9d6d&videocodec=h264&audiocodec=aac&maxwidth=1280&videobitrate=500000&audiobitrate=128000&profile=baseline&level=3
|
||||||
// Good info on i-frames http://blog.streamroot.io/encode-multi-bitrate-videos-mpeg-dash-mse-based-media-players/
|
// Good info on i-frames http://blog.streamroot.io/encode-multi-bitrate-videos-mpeg-dash-mse-based-media-players/
|
||||||
|
@ -461,7 +461,7 @@ namespace MediaBrowser.Api.Playback.Dash
|
||||||
|
|
||||||
var args = string.Format("{0} {1} -map_metadata -1 -threads {2} {3} {4} -copyts {5} -f dash -init_seg_name \"{6}\" -media_seg_name \"{7}\" -use_template 0 -use_timeline 1 -min_seg_duration {8} -y \"{9}\"",
|
var args = string.Format("{0} {1} -map_metadata -1 -threads {2} {3} {4} -copyts {5} -f dash -init_seg_name \"{6}\" -media_seg_name \"{7}\" -use_template 0 -use_timeline 1 -min_seg_duration {8} -y \"{9}\"",
|
||||||
inputModifier,
|
inputModifier,
|
||||||
GetInputArgument(transcodingJobId, state),
|
GetInputArgument(state),
|
||||||
threads,
|
threads,
|
||||||
GetMapArgs(state),
|
GetMapArgs(state),
|
||||||
GetVideoArguments(state),
|
GetVideoArguments(state),
|
||||||
|
|
|
@ -22,7 +22,7 @@ namespace MediaBrowser.Api.Playback.Hls
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public abstract class BaseHlsService : BaseStreamingService
|
public abstract class BaseHlsService : BaseStreamingService
|
||||||
{
|
{
|
||||||
protected BaseHlsService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, ILiveTvManager liveTvManager, IDlnaManager dlnaManager, ISubtitleEncoder subtitleEncoder, IDeviceManager deviceManager, IMediaSourceManager mediaSourceManager, IZipClient zipClient) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, fileSystem, liveTvManager, dlnaManager, subtitleEncoder, deviceManager, mediaSourceManager, zipClient)
|
protected BaseHlsService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, IDlnaManager dlnaManager, ISubtitleEncoder subtitleEncoder, IDeviceManager deviceManager, IMediaSourceManager mediaSourceManager, IZipClient zipClient) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, fileSystem, dlnaManager, subtitleEncoder, deviceManager, mediaSourceManager, zipClient)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -212,7 +212,7 @@ namespace MediaBrowser.Api.Playback.Hls
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override string GetCommandLineArguments(string outputPath, string transcodingJobId, StreamState state, bool isEncoding)
|
protected override string GetCommandLineArguments(string outputPath, StreamState state, bool isEncoding)
|
||||||
{
|
{
|
||||||
var hlsVideoRequest = state.VideoRequest as GetHlsVideoStream;
|
var hlsVideoRequest = state.VideoRequest as GetHlsVideoStream;
|
||||||
|
|
||||||
|
@ -240,7 +240,7 @@ namespace MediaBrowser.Api.Playback.Hls
|
||||||
var args = string.Format("{0} {1} {2} -map_metadata -1 -threads {3} {4} {5} -sc_threshold 0 {6} -hls_time {7} -start_number {8} -hls_list_size {9}{10} -y \"{11}\"",
|
var args = string.Format("{0} {1} {2} -map_metadata -1 -threads {3} {4} {5} -sc_threshold 0 {6} -hls_time {7} -start_number {8} -hls_list_size {9}{10} -y \"{11}\"",
|
||||||
itsOffset,
|
itsOffset,
|
||||||
inputModifier,
|
inputModifier,
|
||||||
GetInputArgument(transcodingJobId, state),
|
GetInputArgument(state),
|
||||||
threads,
|
threads,
|
||||||
GetMapArgs(state),
|
GetMapArgs(state),
|
||||||
GetVideoArguments(state),
|
GetVideoArguments(state),
|
||||||
|
|
|
@ -62,7 +62,7 @@ namespace MediaBrowser.Api.Playback.Hls
|
||||||
|
|
||||||
public class DynamicHlsService : BaseHlsService
|
public class DynamicHlsService : BaseHlsService
|
||||||
{
|
{
|
||||||
public DynamicHlsService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, ILiveTvManager liveTvManager, IDlnaManager dlnaManager, ISubtitleEncoder subtitleEncoder, IDeviceManager deviceManager, IMediaSourceManager mediaSourceManager, IZipClient zipClient, INetworkManager networkManager) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, fileSystem, liveTvManager, dlnaManager, subtitleEncoder, deviceManager, mediaSourceManager, zipClient)
|
public DynamicHlsService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, IDlnaManager dlnaManager, ISubtitleEncoder subtitleEncoder, IDeviceManager deviceManager, IMediaSourceManager mediaSourceManager, IZipClient zipClient, INetworkManager networkManager) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, fileSystem, dlnaManager, subtitleEncoder, deviceManager, mediaSourceManager, zipClient)
|
||||||
{
|
{
|
||||||
NetworkManager = networkManager;
|
NetworkManager = networkManager;
|
||||||
}
|
}
|
||||||
|
@ -414,7 +414,8 @@ namespace MediaBrowser.Api.Playback.Hls
|
||||||
|
|
||||||
var request = (GetMasterHlsVideoStream)state.Request;
|
var request = (GetMasterHlsVideoStream)state.Request;
|
||||||
|
|
||||||
var subtitleStreams = state.AllMediaStreams
|
var subtitleStreams = state.MediaSource
|
||||||
|
.MediaStreams
|
||||||
.Where(i => i.IsTextSubtitleStream)
|
.Where(i => i.IsTextSubtitleStream)
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
|
@ -684,7 +685,7 @@ namespace MediaBrowser.Api.Playback.Hls
|
||||||
return args;
|
return args;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override string GetCommandLineArguments(string outputPath, string transcodingJobId, StreamState state, bool isEncoding)
|
protected override string GetCommandLineArguments(string outputPath, StreamState state, bool isEncoding)
|
||||||
{
|
{
|
||||||
var threads = GetNumberOfThreads(state, false);
|
var threads = GetNumberOfThreads(state, false);
|
||||||
|
|
||||||
|
@ -699,7 +700,7 @@ namespace MediaBrowser.Api.Playback.Hls
|
||||||
|
|
||||||
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 {5} -f segment -segment_time {6} -segment_start_number {7} -segment_list \"{8}\" -y \"{9}\"",
|
||||||
inputModifier,
|
inputModifier,
|
||||||
GetInputArgument(transcodingJobId, state),
|
GetInputArgument(state),
|
||||||
threads,
|
threads,
|
||||||
GetMapArgs(state),
|
GetMapArgs(state),
|
||||||
GetVideoArguments(state),
|
GetVideoArguments(state),
|
||||||
|
@ -713,7 +714,7 @@ namespace MediaBrowser.Api.Playback.Hls
|
||||||
|
|
||||||
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 {5} -hls_time {6} -start_number {7} -hls_list_size {8} -y \"{9}\"",
|
||||||
inputModifier,
|
inputModifier,
|
||||||
GetInputArgument(transcodingJobId, state),
|
GetInputArgument(state),
|
||||||
threads,
|
threads,
|
||||||
GetMapArgs(state),
|
GetMapArgs(state),
|
||||||
GetVideoArguments(state),
|
GetVideoArguments(state),
|
||||||
|
|
|
@ -42,7 +42,7 @@ namespace MediaBrowser.Api.Playback.Hls
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class VideoHlsService : BaseHlsService
|
public class VideoHlsService : BaseHlsService
|
||||||
{
|
{
|
||||||
public VideoHlsService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, ILiveTvManager liveTvManager, IDlnaManager dlnaManager, ISubtitleEncoder subtitleEncoder, IDeviceManager deviceManager, IMediaSourceManager mediaSourceManager, IZipClient zipClient) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, fileSystem, liveTvManager, dlnaManager, subtitleEncoder, deviceManager, mediaSourceManager, zipClient)
|
public VideoHlsService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, IDlnaManager dlnaManager, ISubtitleEncoder subtitleEncoder, IDeviceManager deviceManager, IMediaSourceManager mediaSourceManager, IZipClient zipClient) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, fileSystem, dlnaManager, subtitleEncoder, deviceManager, mediaSourceManager, zipClient)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,6 +4,7 @@ using MediaBrowser.Controller.Library;
|
||||||
using MediaBrowser.Controller.Net;
|
using MediaBrowser.Controller.Net;
|
||||||
using MediaBrowser.Model.Dlna;
|
using MediaBrowser.Model.Dlna;
|
||||||
using MediaBrowser.Model.Dto;
|
using MediaBrowser.Model.Dto;
|
||||||
|
using MediaBrowser.Model.Entities;
|
||||||
using MediaBrowser.Model.MediaInfo;
|
using MediaBrowser.Model.MediaInfo;
|
||||||
using MediaBrowser.Model.Session;
|
using MediaBrowser.Model.Session;
|
||||||
using ServiceStack;
|
using ServiceStack;
|
||||||
|
@ -38,20 +39,23 @@ namespace MediaBrowser.Api.Playback
|
||||||
[Route("/Items/{Id}/PlaybackInfo", "POST", Summary = "Gets live playback media info for an item")]
|
[Route("/Items/{Id}/PlaybackInfo", "POST", Summary = "Gets live playback media info for an item")]
|
||||||
public class GetPostedPlaybackInfo : PlaybackInfoRequest, IReturn<PlaybackInfoResponse>
|
public class GetPostedPlaybackInfo : PlaybackInfoRequest, IReturn<PlaybackInfoResponse>
|
||||||
{
|
{
|
||||||
[ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
|
[ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")]
|
||||||
public string Id { get; set; }
|
public string Id { get; set; }
|
||||||
|
|
||||||
[ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")]
|
[ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")]
|
||||||
public string UserId { get; set; }
|
public string UserId { get; set; }
|
||||||
|
|
||||||
[ApiMember(Name = "StartTimeTicks", Description = "Optional. Specify a starting offset, in ticks. 1 tick = 10000 ms", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
|
[ApiMember(Name = "StartTimeTicks", Description = "Optional. Specify a starting offset, in ticks. 1 tick = 10000 ms", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "POST")]
|
||||||
public long? StartTimeTicks { get; set; }
|
public long? StartTimeTicks { get; set; }
|
||||||
|
|
||||||
[ApiMember(Name = "AudioStreamIndex", Description = "Optional. The index of the audio stream to use. If omitted the first audio stream will be used.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
|
[ApiMember(Name = "AudioStreamIndex", Description = "Optional. The index of the audio stream to use. If omitted the first audio stream will be used.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "POST")]
|
||||||
public int? AudioStreamIndex { get; set; }
|
public int? AudioStreamIndex { get; set; }
|
||||||
|
|
||||||
[ApiMember(Name = "SubtitleStreamIndex", Description = "Optional. The index of the subtitle stream to use. If omitted no subtitles will be used.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
|
[ApiMember(Name = "SubtitleStreamIndex", Description = "Optional. The index of the subtitle stream to use. If omitted no subtitles will be used.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "POST")]
|
||||||
public int? SubtitleStreamIndex { get; set; }
|
public int? SubtitleStreamIndex { get; set; }
|
||||||
|
|
||||||
|
[ApiMember(Name = "MediaSourceId", Description = "The media version id, if playing an alternate version", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")]
|
||||||
|
public string MediaSourceId { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
[Authenticated]
|
[Authenticated]
|
||||||
|
@ -82,7 +86,7 @@ namespace MediaBrowser.Api.Playback
|
||||||
|
|
||||||
public async Task<object> Post(GetPostedPlaybackInfo request)
|
public async Task<object> Post(GetPostedPlaybackInfo request)
|
||||||
{
|
{
|
||||||
var info = await GetPlaybackInfo(request.Id, request.UserId, request.MediaSource).ConfigureAwait(false);
|
var info = await GetPlaybackInfo(request.Id, request.UserId, request.MediaSourceId).ConfigureAwait(false);
|
||||||
var authInfo = AuthorizationContext.GetAuthorizationInfo(Request);
|
var authInfo = AuthorizationContext.GetAuthorizationInfo(Request);
|
||||||
|
|
||||||
var profile = request.DeviceProfile;
|
var profile = request.DeviceProfile;
|
||||||
|
@ -97,19 +101,17 @@ namespace MediaBrowser.Api.Playback
|
||||||
|
|
||||||
if (profile != null)
|
if (profile != null)
|
||||||
{
|
{
|
||||||
var mediaSourceId = request.MediaSource == null ? null : request.MediaSource.Id;
|
var mediaSourceId = request.MediaSourceId;
|
||||||
SetDeviceSpecificData(request.Id, info, profile, authInfo, null, request.StartTimeTicks ?? 0, mediaSourceId, request.AudioStreamIndex, request.SubtitleStreamIndex);
|
SetDeviceSpecificData(request.Id, info, profile, authInfo, null, request.StartTimeTicks ?? 0, mediaSourceId, request.AudioStreamIndex, request.SubtitleStreamIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
return ToOptimizedResult(info);
|
return ToOptimizedResult(info);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<PlaybackInfoResponse> GetPlaybackInfo(string id, string userId, MediaSourceInfo mediaSource = null)
|
private async Task<PlaybackInfoResponse> GetPlaybackInfo(string id, string userId, string mediaSourceId = null)
|
||||||
{
|
{
|
||||||
var result = new PlaybackInfoResponse();
|
var result = new PlaybackInfoResponse();
|
||||||
|
|
||||||
if (mediaSource == null)
|
|
||||||
{
|
|
||||||
IEnumerable<MediaSourceInfo> mediaSources;
|
IEnumerable<MediaSourceInfo> mediaSources;
|
||||||
|
|
||||||
try
|
try
|
||||||
|
@ -123,10 +125,12 @@ namespace MediaBrowser.Api.Playback
|
||||||
}
|
}
|
||||||
|
|
||||||
result.MediaSources = mediaSources.ToList();
|
result.MediaSources = mediaSources.ToList();
|
||||||
}
|
|
||||||
else
|
if (!string.IsNullOrWhiteSpace(mediaSourceId))
|
||||||
{
|
{
|
||||||
result.MediaSources = new List<MediaSourceInfo> { mediaSource };
|
result.MediaSources = result.MediaSources
|
||||||
|
.Where(i => string.Equals(i.Id, mediaSourceId, StringComparison.OrdinalIgnoreCase))
|
||||||
|
.ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (result.MediaSources.Count == 0)
|
if (result.MediaSources.Count == 0)
|
||||||
|
@ -185,9 +189,9 @@ namespace MediaBrowser.Api.Playback
|
||||||
mediaSource.SupportsDirectStream = true;
|
mediaSource.SupportsDirectStream = true;
|
||||||
|
|
||||||
// The MediaSource supports direct stream, now test to see if the client supports it
|
// The MediaSource supports direct stream, now test to see if the client supports it
|
||||||
var streamInfo = item is Video ?
|
var streamInfo = string.Equals(item.MediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase) ?
|
||||||
streamBuilder.BuildVideoItem(options) :
|
streamBuilder.BuildAudioItem(options) :
|
||||||
streamBuilder.BuildAudioItem(options);
|
streamBuilder.BuildVideoItem(options);
|
||||||
|
|
||||||
if (streamInfo == null || !streamInfo.IsDirectStream)
|
if (streamInfo == null || !streamInfo.IsDirectStream)
|
||||||
{
|
{
|
||||||
|
@ -201,9 +205,9 @@ namespace MediaBrowser.Api.Playback
|
||||||
if (mediaSource.SupportsDirectStream)
|
if (mediaSource.SupportsDirectStream)
|
||||||
{
|
{
|
||||||
// The MediaSource supports direct stream, now test to see if the client supports it
|
// The MediaSource supports direct stream, now test to see if the client supports it
|
||||||
var streamInfo = item is Video ?
|
var streamInfo = string.Equals(item.MediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase) ?
|
||||||
streamBuilder.BuildVideoItem(options) :
|
streamBuilder.BuildAudioItem(options) :
|
||||||
streamBuilder.BuildAudioItem(options);
|
streamBuilder.BuildVideoItem(options);
|
||||||
|
|
||||||
if (streamInfo == null || !streamInfo.IsDirectStream)
|
if (streamInfo == null || !streamInfo.IsDirectStream)
|
||||||
{
|
{
|
||||||
|
@ -214,9 +218,9 @@ namespace MediaBrowser.Api.Playback
|
||||||
if (mediaSource.SupportsTranscoding)
|
if (mediaSource.SupportsTranscoding)
|
||||||
{
|
{
|
||||||
// The MediaSource supports direct stream, now test to see if the client supports it
|
// The MediaSource supports direct stream, now test to see if the client supports it
|
||||||
var streamInfo = item is Video ?
|
var streamInfo = string.Equals(item.MediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase) ?
|
||||||
streamBuilder.BuildVideoItem(options) :
|
streamBuilder.BuildAudioItem(options) :
|
||||||
streamBuilder.BuildAudioItem(options);
|
streamBuilder.BuildVideoItem(options);
|
||||||
|
|
||||||
if (streamInfo != null && streamInfo.PlayMethod == PlayMethod.Transcode)
|
if (streamInfo != null && streamInfo.PlayMethod == PlayMethod.Transcode)
|
||||||
{
|
{
|
||||||
|
@ -227,6 +231,46 @@ namespace MediaBrowser.Api.Playback
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SortMediaSources(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SortMediaSources(PlaybackInfoResponse result)
|
||||||
|
{
|
||||||
|
var originalList = result.MediaSources.ToList();
|
||||||
|
|
||||||
|
result.MediaSources = result.MediaSources.OrderBy(i =>
|
||||||
|
{
|
||||||
|
// Nothing beats direct playing a file
|
||||||
|
if (i.SupportsDirectPlay && i.Protocol == MediaProtocol.File)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
}).ThenBy(i =>
|
||||||
|
{
|
||||||
|
// Let's assume direct streaming a file is just as desirable as direct playing a remote url
|
||||||
|
if (i.SupportsDirectPlay || i.SupportsDirectStream)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
}).ThenBy(i =>
|
||||||
|
{
|
||||||
|
switch (i.Protocol)
|
||||||
|
{
|
||||||
|
case MediaProtocol.File:
|
||||||
|
return 0;
|
||||||
|
default:
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
}).ThenBy(originalList.IndexOf)
|
||||||
|
.ToList();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,7 +31,7 @@ namespace MediaBrowser.Api.Playback.Progressive
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class AudioService : BaseProgressiveStreamingService
|
public class AudioService : BaseProgressiveStreamingService
|
||||||
{
|
{
|
||||||
public AudioService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, ILiveTvManager liveTvManager, IDlnaManager dlnaManager, ISubtitleEncoder subtitleEncoder, IDeviceManager deviceManager, IMediaSourceManager mediaSourceManager, IZipClient zipClient, IImageProcessor imageProcessor, IHttpClient httpClient) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, fileSystem, liveTvManager, dlnaManager, subtitleEncoder, deviceManager, mediaSourceManager, zipClient, imageProcessor, httpClient)
|
public AudioService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, IDlnaManager dlnaManager, ISubtitleEncoder subtitleEncoder, IDeviceManager deviceManager, IMediaSourceManager mediaSourceManager, IZipClient zipClient, IImageProcessor imageProcessor, IHttpClient httpClient) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, fileSystem, dlnaManager, subtitleEncoder, deviceManager, mediaSourceManager, zipClient, imageProcessor, httpClient)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -55,7 +55,7 @@ namespace MediaBrowser.Api.Playback.Progressive
|
||||||
return ProcessRequest(request, true);
|
return ProcessRequest(request, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override string GetCommandLineArguments(string outputPath, string transcodingJobId, StreamState state, bool isEncoding)
|
protected override string GetCommandLineArguments(string outputPath, StreamState state, bool isEncoding)
|
||||||
{
|
{
|
||||||
var audioTranscodeParams = new List<string>();
|
var audioTranscodeParams = new List<string>();
|
||||||
|
|
||||||
|
@ -84,7 +84,7 @@ namespace MediaBrowser.Api.Playback.Progressive
|
||||||
|
|
||||||
return string.Format("{0} {1} -threads {2}{3} {4} -id3v2_version 3 -write_id3v1 1 -y \"{5}\"",
|
return string.Format("{0} {1} -threads {2}{3} {4} -id3v2_version 3 -write_id3v1 1 -y \"{5}\"",
|
||||||
inputModifier,
|
inputModifier,
|
||||||
GetInputArgument(transcodingJobId, state),
|
GetInputArgument(state),
|
||||||
threads,
|
threads,
|
||||||
vn,
|
vn,
|
||||||
string.Join(" ", audioTranscodeParams.ToArray()),
|
string.Join(" ", audioTranscodeParams.ToArray()),
|
||||||
|
|
|
@ -27,7 +27,7 @@ namespace MediaBrowser.Api.Playback.Progressive
|
||||||
protected readonly IImageProcessor ImageProcessor;
|
protected readonly IImageProcessor ImageProcessor;
|
||||||
protected readonly IHttpClient HttpClient;
|
protected readonly IHttpClient HttpClient;
|
||||||
|
|
||||||
protected BaseProgressiveStreamingService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, ILiveTvManager liveTvManager, IDlnaManager dlnaManager, ISubtitleEncoder subtitleEncoder, IDeviceManager deviceManager, IMediaSourceManager mediaSourceManager, IZipClient zipClient, IImageProcessor imageProcessor, IHttpClient httpClient) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, fileSystem, liveTvManager, dlnaManager, subtitleEncoder, deviceManager, mediaSourceManager, zipClient)
|
protected BaseProgressiveStreamingService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, IDlnaManager dlnaManager, ISubtitleEncoder subtitleEncoder, IDeviceManager deviceManager, IMediaSourceManager mediaSourceManager, IZipClient zipClient, IImageProcessor imageProcessor, IHttpClient httpClient) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, fileSystem, dlnaManager, subtitleEncoder, deviceManager, mediaSourceManager, zipClient)
|
||||||
{
|
{
|
||||||
ImageProcessor = imageProcessor;
|
ImageProcessor = imageProcessor;
|
||||||
HttpClient = httpClient;
|
HttpClient = httpClient;
|
||||||
|
|
|
@ -62,7 +62,7 @@ namespace MediaBrowser.Api.Playback.Progressive
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class VideoService : BaseProgressiveStreamingService
|
public class VideoService : BaseProgressiveStreamingService
|
||||||
{
|
{
|
||||||
public VideoService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, ILiveTvManager liveTvManager, IDlnaManager dlnaManager, ISubtitleEncoder subtitleEncoder, IDeviceManager deviceManager, IMediaSourceManager mediaSourceManager, IZipClient zipClient, IImageProcessor imageProcessor, IHttpClient httpClient) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, fileSystem, liveTvManager, dlnaManager, subtitleEncoder, deviceManager, mediaSourceManager, zipClient, imageProcessor, httpClient)
|
public VideoService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, IDlnaManager dlnaManager, ISubtitleEncoder subtitleEncoder, IDeviceManager deviceManager, IMediaSourceManager mediaSourceManager, IZipClient zipClient, IImageProcessor imageProcessor, IHttpClient httpClient) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, fileSystem, dlnaManager, subtitleEncoder, deviceManager, mediaSourceManager, zipClient, imageProcessor, httpClient)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -86,7 +86,7 @@ namespace MediaBrowser.Api.Playback.Progressive
|
||||||
return ProcessRequest(request, true);
|
return ProcessRequest(request, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override string GetCommandLineArguments(string outputPath, string transcodingJobId, StreamState state, bool isEncoding)
|
protected override string GetCommandLineArguments(string outputPath, StreamState state, bool isEncoding)
|
||||||
{
|
{
|
||||||
// Get the output codec name
|
// Get the output codec name
|
||||||
var videoCodec = state.OutputVideoCodec;
|
var videoCodec = state.OutputVideoCodec;
|
||||||
|
@ -106,7 +106,7 @@ namespace MediaBrowser.Api.Playback.Progressive
|
||||||
|
|
||||||
return string.Format("{0} {1}{2} {3} {4} -map_metadata -1 -threads {5} {6}{7} -y \"{8}\"",
|
return string.Format("{0} {1}{2} {3} {4} -map_metadata -1 -threads {5} {6}{7} -y \"{8}\"",
|
||||||
inputModifier,
|
inputModifier,
|
||||||
GetInputArgument(transcodingJobId, state),
|
GetInputArgument(state),
|
||||||
keyFrame,
|
keyFrame,
|
||||||
GetMapArgs(state),
|
GetMapArgs(state),
|
||||||
GetVideoArguments(state, videoCodec),
|
GetVideoArguments(state, videoCodec),
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
using MediaBrowser.Controller.LiveTv;
|
using MediaBrowser.Controller.Library;
|
||||||
using MediaBrowser.Model.Dlna;
|
using MediaBrowser.Model.Dlna;
|
||||||
using MediaBrowser.Model.Drawing;
|
using MediaBrowser.Model.Drawing;
|
||||||
|
using MediaBrowser.Model.Dto;
|
||||||
using MediaBrowser.Model.Entities;
|
using MediaBrowser.Model.Entities;
|
||||||
using MediaBrowser.Model.IO;
|
using MediaBrowser.Model.IO;
|
||||||
using MediaBrowser.Model.Logging;
|
using MediaBrowser.Model.Logging;
|
||||||
|
@ -17,7 +18,7 @@ namespace MediaBrowser.Api.Playback
|
||||||
public class StreamState : IDisposable
|
public class StreamState : IDisposable
|
||||||
{
|
{
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger _logger;
|
||||||
private readonly ILiveTvManager _liveTvManager;
|
private readonly IMediaSourceManager _mediaSourceManager;
|
||||||
|
|
||||||
public string RequestedUrl { get; set; }
|
public string RequestedUrl { get; set; }
|
||||||
|
|
||||||
|
@ -39,7 +40,7 @@ namespace MediaBrowser.Api.Playback
|
||||||
|
|
||||||
public string InputContainer { get; set; }
|
public string InputContainer { get; set; }
|
||||||
|
|
||||||
public List<MediaStream> AllMediaStreams { get; set; }
|
public MediaSourceInfo MediaSource { get; set; }
|
||||||
|
|
||||||
public MediaStream AudioStream { get; set; }
|
public MediaStream AudioStream { get; set; }
|
||||||
public MediaStream VideoStream { get; set; }
|
public MediaStream VideoStream { get; set; }
|
||||||
|
@ -64,8 +65,6 @@ namespace MediaBrowser.Api.Playback
|
||||||
|
|
||||||
public List<string> PlayableStreamFileNames { get; set; }
|
public List<string> PlayableStreamFileNames { get; set; }
|
||||||
|
|
||||||
public string LiveTvStreamId { get; set; }
|
|
||||||
|
|
||||||
public int SegmentLength = 3;
|
public int SegmentLength = 3;
|
||||||
public bool EnableGenericHlsSegmenter = false;
|
public bool EnableGenericHlsSegmenter = false;
|
||||||
public int HlsListSize
|
public int HlsListSize
|
||||||
|
@ -86,14 +85,13 @@ namespace MediaBrowser.Api.Playback
|
||||||
|
|
||||||
public List<string> SupportedAudioCodecs { get; set; }
|
public List<string> SupportedAudioCodecs { get; set; }
|
||||||
|
|
||||||
public StreamState(ILiveTvManager liveTvManager, ILogger logger)
|
public StreamState(IMediaSourceManager mediaSourceManager, ILogger logger)
|
||||||
{
|
{
|
||||||
_liveTvManager = liveTvManager;
|
_mediaSourceManager = mediaSourceManager;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
SupportedAudioCodecs = new List<string>();
|
SupportedAudioCodecs = new List<string>();
|
||||||
PlayableStreamFileNames = new List<string>();
|
PlayableStreamFileNames = new List<string>();
|
||||||
RemoteHttpHeaders = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
RemoteHttpHeaders = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||||
AllMediaStreams = new List<MediaStream>();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public string InputAudioSync { get; set; }
|
public string InputAudioSync { get; set; }
|
||||||
|
@ -113,9 +111,6 @@ namespace MediaBrowser.Api.Playback
|
||||||
|
|
||||||
public long? EncodingDurationTicks { get; set; }
|
public long? EncodingDurationTicks { get; set; }
|
||||||
|
|
||||||
public string ItemType { get; set; }
|
|
||||||
public string ItemId { get; set; }
|
|
||||||
|
|
||||||
public string GetMimeType(string outputPath)
|
public string GetMimeType(string outputPath)
|
||||||
{
|
{
|
||||||
if (!string.IsNullOrEmpty(MimeType))
|
if (!string.IsNullOrEmpty(MimeType))
|
||||||
|
@ -187,15 +182,15 @@ namespace MediaBrowser.Api.Playback
|
||||||
|
|
||||||
private async void DisposeLiveStream()
|
private async void DisposeLiveStream()
|
||||||
{
|
{
|
||||||
if (!string.IsNullOrEmpty(LiveTvStreamId))
|
if (MediaSource.RequiresClosing)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await _liveTvManager.CloseLiveStream(LiveTvStreamId, CancellationToken.None).ConfigureAwait(false);
|
await _mediaSourceManager.CloseMediaSource(MediaSource.CloseKey, CancellationToken.None).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
_logger.ErrorException("Error closing live tv stream", ex);
|
_logger.ErrorException("Error closing media source", ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -192,7 +192,7 @@ namespace MediaBrowser.Api.Subtitles
|
||||||
{
|
{
|
||||||
var item = (Video)_libraryManager.GetItemById(new Guid(request.Id));
|
var item = (Video)_libraryManager.GetItemById(new Guid(request.Id));
|
||||||
|
|
||||||
var mediaSource = item.GetMediaSources(false)
|
var mediaSource = _mediaSourceManager.GetStaticMediaSources(item, false, null)
|
||||||
.First(i => string.Equals(i.Id, request.MediaSourceId ?? request.Id));
|
.First(i => string.Equals(i.Id, request.MediaSourceId ?? request.Id));
|
||||||
|
|
||||||
var subtitleStream = mediaSource.MediaStreams
|
var subtitleStream = mediaSource.MediaStreams
|
||||||
|
|
|
@ -75,9 +75,7 @@ namespace MediaBrowser.Controller.Channels
|
||||||
|
|
||||||
public override IEnumerable<MediaSourceInfo> GetMediaSources(bool enablePathSubstitution)
|
public override IEnumerable<MediaSourceInfo> GetMediaSources(bool enablePathSubstitution)
|
||||||
{
|
{
|
||||||
var list = base.GetMediaSources(enablePathSubstitution).ToList();
|
var sources = ChannelManager.GetStaticMediaSources(this, false, CancellationToken.None)
|
||||||
|
|
||||||
var sources = ChannelManager.GetChannelItemMediaSources(Id.ToString("N"), false, CancellationToken.None)
|
|
||||||
.Result.ToList();
|
.Result.ToList();
|
||||||
|
|
||||||
if (sources.Count > 0)
|
if (sources.Count > 0)
|
||||||
|
@ -85,7 +83,15 @@ namespace MediaBrowser.Controller.Channels
|
||||||
return sources;
|
return sources;
|
||||||
}
|
}
|
||||||
|
|
||||||
list.InsertRange(0, sources);
|
var list = base.GetMediaSources(enablePathSubstitution).ToList();
|
||||||
|
|
||||||
|
foreach (var mediaSource in list)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(mediaSource.Path))
|
||||||
|
{
|
||||||
|
mediaSource.Type = MediaSourceType.Placeholder;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return list;
|
return list;
|
||||||
}
|
}
|
||||||
|
|
|
@ -90,9 +90,7 @@ namespace MediaBrowser.Controller.Channels
|
||||||
|
|
||||||
public override IEnumerable<MediaSourceInfo> GetMediaSources(bool enablePathSubstitution)
|
public override IEnumerable<MediaSourceInfo> GetMediaSources(bool enablePathSubstitution)
|
||||||
{
|
{
|
||||||
var list = base.GetMediaSources(enablePathSubstitution).ToList();
|
var sources = ChannelManager.GetStaticMediaSources(this, false, CancellationToken.None)
|
||||||
|
|
||||||
var sources = ChannelManager.GetChannelItemMediaSources(Id.ToString("N"), false, CancellationToken.None)
|
|
||||||
.Result.ToList();
|
.Result.ToList();
|
||||||
|
|
||||||
if (sources.Count > 0)
|
if (sources.Count > 0)
|
||||||
|
@ -100,7 +98,15 @@ namespace MediaBrowser.Controller.Channels
|
||||||
return sources;
|
return sources;
|
||||||
}
|
}
|
||||||
|
|
||||||
list.InsertRange(0, sources);
|
var list = base.GetMediaSources(enablePathSubstitution).ToList();
|
||||||
|
|
||||||
|
foreach (var mediaSource in list)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(mediaSource.Path))
|
||||||
|
{
|
||||||
|
mediaSource.Type = MediaSourceType.Placeholder;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return list;
|
return list;
|
||||||
}
|
}
|
||||||
|
|
|
@ -112,11 +112,11 @@ namespace MediaBrowser.Controller.Channels
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the channel item media sources.
|
/// Gets the channel item media sources.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="id">The identifier.</param>
|
/// <param name="item">The item.</param>
|
||||||
/// <param name="includeDynamicSources">if set to <c>true</c> [include dynamic sources].</param>
|
/// <param name="includeCachedVersions">if set to <c>true</c> [include cached versions].</param>
|
||||||
/// <param name="cancellationToken">The cancellation token.</param>
|
/// <param name="cancellationToken">The cancellation token.</param>
|
||||||
/// <returns>Task{IEnumerable{MediaSourceInfo}}.</returns>
|
/// <returns>Task{IEnumerable{MediaSourceInfo}}.</returns>
|
||||||
Task<IEnumerable<MediaSourceInfo>> GetChannelItemMediaSources(string id, bool includeDynamicSources, CancellationToken cancellationToken);
|
Task<IEnumerable<MediaSourceInfo>> GetStaticMediaSources(IChannelMediaItem item, bool includeCachedVersions, CancellationToken cancellationToken);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the channel folder.
|
/// Gets the channel folder.
|
||||||
|
|
|
@ -64,6 +64,14 @@ namespace MediaBrowser.Controller.Library
|
||||||
/// <returns>IEnumerable<MediaSourceInfo>.</returns>
|
/// <returns>IEnumerable<MediaSourceInfo>.</returns>
|
||||||
IEnumerable<MediaSourceInfo> GetStaticMediaSources(IHasMediaSources item, bool enablePathSubstitution, User user);
|
IEnumerable<MediaSourceInfo> GetStaticMediaSources(IHasMediaSources item, bool enablePathSubstitution, User user);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the static media sources.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="item">The item.</param>
|
||||||
|
/// <param name="enablePathSubstitution">if set to <c>true</c> [enable path substitution].</param>
|
||||||
|
/// <returns>IEnumerable<MediaSourceInfo>.</returns>
|
||||||
|
IEnumerable<MediaSourceInfo> GetStaticMediaSources(IHasMediaSources item, bool enablePathSubstitution);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the static media source.
|
/// Gets the static media source.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -72,5 +80,21 @@ namespace MediaBrowser.Controller.Library
|
||||||
/// <param name="enablePathSubstitution">if set to <c>true</c> [enable path substitution].</param>
|
/// <param name="enablePathSubstitution">if set to <c>true</c> [enable path substitution].</param>
|
||||||
/// <returns>MediaSourceInfo.</returns>
|
/// <returns>MediaSourceInfo.</returns>
|
||||||
MediaSourceInfo GetStaticMediaSource(IHasMediaSources item, string mediaSourceId, bool enablePathSubstitution);
|
MediaSourceInfo GetStaticMediaSource(IHasMediaSources item, string mediaSourceId, bool enablePathSubstitution);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Opens the media source.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="openKey">The open key.</param>
|
||||||
|
/// <param name="cancellationToken">The cancellation token.</param>
|
||||||
|
/// <returns>Task<MediaSourceInfo>.</returns>
|
||||||
|
Task<MediaSourceInfo> OpenMediaSource(string openKey, CancellationToken cancellationToken);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Closes the media source.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="closeKey">The close key.</param>
|
||||||
|
/// <param name="cancellationToken">The cancellation token.</param>
|
||||||
|
/// <returns>Task.</returns>
|
||||||
|
Task CloseMediaSource(string closeKey, CancellationToken cancellationToken);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,5 +15,21 @@ namespace MediaBrowser.Controller.Library
|
||||||
/// <param name="cancellationToken">The cancellation token.</param>
|
/// <param name="cancellationToken">The cancellation token.</param>
|
||||||
/// <returns>Task<IEnumerable<MediaSourceInfo>>.</returns>
|
/// <returns>Task<IEnumerable<MediaSourceInfo>>.</returns>
|
||||||
Task<IEnumerable<MediaSourceInfo>> GetMediaSources(IHasMediaSources item, CancellationToken cancellationToken);
|
Task<IEnumerable<MediaSourceInfo>> GetMediaSources(IHasMediaSources item, CancellationToken cancellationToken);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Opens the media source.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="openKey">The open key.</param>
|
||||||
|
/// <param name="cancellationToken">The cancellation token.</param>
|
||||||
|
/// <returns>Task<MediaSourceInfo>.</returns>
|
||||||
|
Task<MediaSourceInfo> OpenMediaSource(string openKey, CancellationToken cancellationToken);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Closes the media source.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="closeKey">The close key.</param>
|
||||||
|
/// <param name="cancellationToken">The cancellation token.</param>
|
||||||
|
/// <returns>Task.</returns>
|
||||||
|
Task CloseMediaSource(string closeKey, CancellationToken cancellationToken);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
|
using System;
|
||||||
|
|
||||||
namespace MediaBrowser.Controller.LiveTv
|
namespace MediaBrowser.Controller.LiveTv
|
||||||
{
|
{
|
||||||
public interface ILiveTvItem
|
public interface ILiveTvItem
|
||||||
{
|
{
|
||||||
|
Guid Id { get; }
|
||||||
string ServiceName { get; set; }
|
string ServiceName { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,12 @@
|
||||||
using System.Runtime.Serialization;
|
using MediaBrowser.Controller.Entities;
|
||||||
using MediaBrowser.Controller.Entities;
|
|
||||||
using MediaBrowser.Controller.Entities.Audio;
|
using MediaBrowser.Controller.Entities.Audio;
|
||||||
using MediaBrowser.Model.Configuration;
|
using MediaBrowser.Model.Configuration;
|
||||||
|
using MediaBrowser.Model.Dto;
|
||||||
using MediaBrowser.Model.Entities;
|
using MediaBrowser.Model.Entities;
|
||||||
using MediaBrowser.Model.Users;
|
using MediaBrowser.Model.Users;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Runtime.Serialization;
|
||||||
|
|
||||||
namespace MediaBrowser.Controller.LiveTv
|
namespace MediaBrowser.Controller.LiveTv
|
||||||
{
|
{
|
||||||
|
@ -99,5 +101,20 @@ namespace MediaBrowser.Controller.LiveTv
|
||||||
{
|
{
|
||||||
return user.Policy.EnableLiveTvManagement;
|
return user.Policy.EnableLiveTvManagement;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override IEnumerable<MediaSourceInfo> GetMediaSources(bool enablePathSubstitution)
|
||||||
|
{
|
||||||
|
var list = base.GetMediaSources(enablePathSubstitution).ToList();
|
||||||
|
|
||||||
|
foreach (var mediaSource in list)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(mediaSource.Path))
|
||||||
|
{
|
||||||
|
mediaSource.Type = MediaSourceType.Placeholder;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return list;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -127,7 +127,7 @@ namespace MediaBrowser.Controller.LiveTv
|
||||||
Name = Name,
|
Name = Name,
|
||||||
Path = Path,
|
Path = Path,
|
||||||
RunTimeTicks = RunTimeTicks,
|
RunTimeTicks = RunTimeTicks,
|
||||||
Type = MediaSourceType.Default
|
Type = MediaSourceType.Placeholder
|
||||||
};
|
};
|
||||||
|
|
||||||
list.Add(info);
|
list.Add(info);
|
||||||
|
|
|
@ -1,9 +1,11 @@
|
||||||
using System.Runtime.Serialization;
|
using MediaBrowser.Controller.Entities;
|
||||||
using MediaBrowser.Controller.Entities;
|
|
||||||
using MediaBrowser.Model.Configuration;
|
using MediaBrowser.Model.Configuration;
|
||||||
|
using MediaBrowser.Model.Dto;
|
||||||
using MediaBrowser.Model.Entities;
|
using MediaBrowser.Model.Entities;
|
||||||
using System.Linq;
|
|
||||||
using MediaBrowser.Model.Users;
|
using MediaBrowser.Model.Users;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Runtime.Serialization;
|
||||||
|
|
||||||
namespace MediaBrowser.Controller.LiveTv
|
namespace MediaBrowser.Controller.LiveTv
|
||||||
{
|
{
|
||||||
|
@ -97,5 +99,20 @@ namespace MediaBrowser.Controller.LiveTv
|
||||||
{
|
{
|
||||||
return user.Policy.EnableLiveTvManagement;
|
return user.Policy.EnableLiveTvManagement;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override IEnumerable<MediaSourceInfo> GetMediaSources(bool enablePathSubstitution)
|
||||||
|
{
|
||||||
|
var list = base.GetMediaSources(enablePathSubstitution).ToList();
|
||||||
|
|
||||||
|
foreach (var mediaSource in list)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(mediaSource.Path))
|
||||||
|
{
|
||||||
|
mediaSource.Type = MediaSourceType.Placeholder;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return list;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -476,7 +476,7 @@ namespace MediaBrowser.Dlna.PlayTo
|
||||||
var playlistItem = GetPlaylistItem(item, mediaSources, profile, _session.DeviceId, mediaSourceId, audioStreamIndex, subtitleStreamIndex);
|
var playlistItem = GetPlaylistItem(item, mediaSources, profile, _session.DeviceId, mediaSourceId, audioStreamIndex, subtitleStreamIndex);
|
||||||
playlistItem.StreamInfo.StartPositionTicks = startPostionTicks;
|
playlistItem.StreamInfo.StartPositionTicks = startPostionTicks;
|
||||||
|
|
||||||
playlistItem.StreamUrl = playlistItem.StreamInfo.ToUrl(_serverAddress, _accessToken);
|
playlistItem.StreamUrl = playlistItem.StreamInfo.ToDlnaUrl(_serverAddress, _accessToken);
|
||||||
|
|
||||||
var itemXml = new DidlBuilder(profile, user, _imageProcessor, _serverAddress, _accessToken, _userDataManager, _localization, _mediaSourceManager)
|
var itemXml = new DidlBuilder(profile, user, _imageProcessor, _serverAddress, _accessToken, _userDataManager, _localization, _mediaSourceManager)
|
||||||
.GetItemDidl(item, null, _session.DeviceId, new Filter(), playlistItem.StreamInfo);
|
.GetItemDidl(item, null, _session.DeviceId, new Filter(), playlistItem.StreamInfo);
|
||||||
|
|
|
@ -239,6 +239,16 @@ namespace MediaBrowser.Model.Dlna
|
||||||
return playlistItem;
|
return playlistItem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private int? GetBitrateForDirectPlayCheck(MediaSourceInfo item, AudioOptions options)
|
||||||
|
{
|
||||||
|
if (item.Protocol == MediaProtocol.File)
|
||||||
|
{
|
||||||
|
return options.Profile.MaxStaticBitrate;
|
||||||
|
}
|
||||||
|
|
||||||
|
return options.GetMaxBitrate();
|
||||||
|
}
|
||||||
|
|
||||||
private List<PlayMethod> GetAudioDirectPlayMethods(MediaSourceInfo item, MediaStream audioStream, AudioOptions options)
|
private List<PlayMethod> GetAudioDirectPlayMethods(MediaSourceInfo item, MediaStream audioStream, AudioOptions options)
|
||||||
{
|
{
|
||||||
DirectPlayProfile directPlayProfile = null;
|
DirectPlayProfile directPlayProfile = null;
|
||||||
|
@ -263,7 +273,7 @@ namespace MediaBrowser.Model.Dlna
|
||||||
|
|
||||||
// The profile describes what the device supports
|
// The profile describes what the device supports
|
||||||
// If device requirements are satisfied then allow both direct stream and direct play
|
// If device requirements are satisfied then allow both direct stream and direct play
|
||||||
if (IsAudioEligibleForDirectPlay(item, options.Profile.MaxStaticBitrate))
|
if (IsAudioEligibleForDirectPlay(item, GetBitrateForDirectPlayCheck(item, options)))
|
||||||
{
|
{
|
||||||
playMethods.Add(PlayMethod.DirectPlay);
|
playMethods.Add(PlayMethod.DirectPlay);
|
||||||
}
|
}
|
||||||
|
@ -293,7 +303,7 @@ namespace MediaBrowser.Model.Dlna
|
||||||
MediaStream videoStream = item.VideoStream;
|
MediaStream videoStream = item.VideoStream;
|
||||||
|
|
||||||
// TODO: This doesn't accout for situation of device being able to handle media bitrate, but wifi connection not fast enough
|
// TODO: This doesn't accout for situation of device being able to handle media bitrate, but wifi connection not fast enough
|
||||||
bool isEligibleForDirectPlay = IsEligibleForDirectPlay(item, options.Profile.MaxStaticBitrate, subtitleStream, options);
|
bool isEligibleForDirectPlay = IsEligibleForDirectPlay(item, GetBitrateForDirectPlayCheck(item, options), subtitleStream, options);
|
||||||
bool isEligibleForDirectStream = IsEligibleForDirectPlay(item, options.GetMaxBitrate(), subtitleStream, options);
|
bool isEligibleForDirectStream = IsEligibleForDirectPlay(item, options.GetMaxBitrate(), subtitleStream, options);
|
||||||
|
|
||||||
if (isEligibleForDirectPlay || isEligibleForDirectStream)
|
if (isEligibleForDirectPlay || isEligibleForDirectStream)
|
||||||
|
@ -604,6 +614,11 @@ namespace MediaBrowser.Model.Dlna
|
||||||
// Look for an external profile that matches the stream type (text/graphical)
|
// Look for an external profile that matches the stream type (text/graphical)
|
||||||
foreach (SubtitleProfile profile in subtitleProfiles)
|
foreach (SubtitleProfile profile in subtitleProfiles)
|
||||||
{
|
{
|
||||||
|
if (!profile.SupportsLanguage(subtitleStream.Language))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if (profile.Method == SubtitleDeliveryMethod.External && subtitleStream.IsTextSubtitleStream == MediaStream.IsTextFormat(profile.Format))
|
if (profile.Method == SubtitleDeliveryMethod.External && subtitleStream.IsTextSubtitleStream == MediaStream.IsTextFormat(profile.Format))
|
||||||
{
|
{
|
||||||
if (subtitleStream.SupportsExternalStream)
|
if (subtitleStream.SupportsExternalStream)
|
||||||
|
@ -621,6 +636,11 @@ namespace MediaBrowser.Model.Dlna
|
||||||
|
|
||||||
foreach (SubtitleProfile profile in subtitleProfiles)
|
foreach (SubtitleProfile profile in subtitleProfiles)
|
||||||
{
|
{
|
||||||
|
if (!profile.SupportsLanguage(subtitleStream.Language))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if (profile.Method == SubtitleDeliveryMethod.Embed && subtitleStream.IsTextSubtitleStream == MediaStream.IsTextFormat(profile.Format))
|
if (profile.Method == SubtitleDeliveryMethod.Embed && subtitleStream.IsTextSubtitleStream == MediaStream.IsTextFormat(profile.Format))
|
||||||
{
|
{
|
||||||
return profile;
|
return profile;
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
using System.Xml.Serialization;
|
using MediaBrowser.Model.Extensions;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Xml.Serialization;
|
||||||
|
|
||||||
namespace MediaBrowser.Model.Dlna
|
namespace MediaBrowser.Model.Dlna
|
||||||
{
|
{
|
||||||
|
@ -13,5 +15,28 @@ namespace MediaBrowser.Model.Dlna
|
||||||
[XmlAttribute("didlMode")]
|
[XmlAttribute("didlMode")]
|
||||||
public string DidlMode { get; set; }
|
public string DidlMode { get; set; }
|
||||||
|
|
||||||
|
[XmlAttribute("language")]
|
||||||
|
public string Language { get; set; }
|
||||||
|
|
||||||
|
public List<string> GetLanguages()
|
||||||
|
{
|
||||||
|
List<string> list = new List<string>();
|
||||||
|
foreach (string i in (Language ?? string.Empty).Split(','))
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrEmpty(i)) list.Add(i);
|
||||||
|
}
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool SupportsLanguage(string language)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(language))
|
||||||
|
{
|
||||||
|
language = "und";
|
||||||
|
}
|
||||||
|
|
||||||
|
List<string> languages = GetLanguages();
|
||||||
|
return languages.Count == 0 || ListHelper.ContainsIgnoreCase(languages, language);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -26,6 +26,11 @@ namespace MediaBrowser.Model.Dto
|
||||||
public bool SupportsDirectStream { get; set; }
|
public bool SupportsDirectStream { get; set; }
|
||||||
public bool SupportsDirectPlay { get; set; }
|
public bool SupportsDirectPlay { get; set; }
|
||||||
|
|
||||||
|
public bool RequiresOpening { get; set; }
|
||||||
|
public string OpenKey { get; set; }
|
||||||
|
public bool RequiresClosing { get; set; }
|
||||||
|
public string CloseKey { get; set; }
|
||||||
|
|
||||||
public VideoType? VideoType { get; set; }
|
public VideoType? VideoType { get; set; }
|
||||||
|
|
||||||
public IsoType? IsoType { get; set; }
|
public IsoType? IsoType { get; set; }
|
||||||
|
|
|
@ -4,6 +4,6 @@ namespace MediaBrowser.Model.Dto
|
||||||
{
|
{
|
||||||
Default = 0,
|
Default = 0,
|
||||||
Grouping = 1,
|
Grouping = 1,
|
||||||
Cache = 2
|
Placeholder = 2
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -141,6 +141,11 @@ namespace MediaBrowser.Model.Entities
|
||||||
{
|
{
|
||||||
if (Type != MediaStreamType.Subtitle) return false;
|
if (Type != MediaStreamType.Subtitle) return false;
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(Codec) && !IsExternal)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
return IsTextFormat(Codec);
|
return IsTextFormat(Codec);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,9 @@
|
||||||
using MediaBrowser.Model.Dlna;
|
using MediaBrowser.Model.Dlna;
|
||||||
using MediaBrowser.Model.Dto;
|
|
||||||
|
|
||||||
namespace MediaBrowser.Model.MediaInfo
|
namespace MediaBrowser.Model.MediaInfo
|
||||||
{
|
{
|
||||||
public class PlaybackInfoRequest
|
public class PlaybackInfoRequest
|
||||||
{
|
{
|
||||||
public DeviceProfile DeviceProfile { get; set; }
|
public DeviceProfile DeviceProfile { get; set; }
|
||||||
public MediaSourceInfo MediaSource { get; set; }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -96,7 +96,7 @@ namespace MediaBrowser.Providers.MediaInfo
|
||||||
var album = item.Parent as MusicAlbum;
|
var album = item.Parent as MusicAlbum;
|
||||||
|
|
||||||
var filename = item.Album ?? string.Empty;
|
var filename = item.Album ?? string.Empty;
|
||||||
filename += item.Artists.FirstOrDefault() ?? string.Empty;
|
filename += string.Join(",", item.Artists.ToArray());
|
||||||
filename += album == null ? item.Id.ToString("N") + "_primary" + item.DateModified.Ticks : album.Id.ToString("N") + album.DateModified.Ticks + "_primary";
|
filename += album == null ? item.Id.ToString("N") + "_primary" + item.DateModified.Ticks : album.Id.ToString("N") + album.DateModified.Ticks + "_primary";
|
||||||
|
|
||||||
filename = filename.GetMD5() + ".jpg";
|
filename = filename.GetMD5() + ".jpg";
|
||||||
|
|
|
@ -169,7 +169,7 @@ namespace MediaBrowser.Server.Implementations.Channels
|
||||||
|
|
||||||
foreach (var item in result.Items)
|
foreach (var item in result.Items)
|
||||||
{
|
{
|
||||||
var channelItem = (IChannelItem)item;
|
var channelItem = (IChannelMediaItem)item;
|
||||||
|
|
||||||
var channelFeatures = _manager.GetChannelFeatures(channelItem.ChannelId);
|
var channelFeatures = _manager.GetChannelFeatures(channelItem.ChannelId);
|
||||||
|
|
||||||
|
@ -179,7 +179,7 @@ namespace MediaBrowser.Server.Implementations.Channels
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await DownloadChannelItem(item, options, cancellationToken, path);
|
await DownloadChannelItem(channelItem, options, cancellationToken, path);
|
||||||
}
|
}
|
||||||
catch (OperationCanceledException)
|
catch (OperationCanceledException)
|
||||||
{
|
{
|
||||||
|
@ -210,13 +210,13 @@ namespace MediaBrowser.Server.Implementations.Channels
|
||||||
return channelOptions.DownloadSizeLimit;
|
return channelOptions.DownloadSizeLimit;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task DownloadChannelItem(BaseItem item,
|
private async Task DownloadChannelItem(IChannelMediaItem item,
|
||||||
ChannelOptions channelOptions,
|
ChannelOptions channelOptions,
|
||||||
CancellationToken cancellationToken,
|
CancellationToken cancellationToken,
|
||||||
string path)
|
string path)
|
||||||
{
|
{
|
||||||
var itemId = item.Id.ToString("N");
|
var itemId = item.Id.ToString("N");
|
||||||
var sources = await _manager.GetChannelItemMediaSources(itemId, false, cancellationToken)
|
var sources = await _manager.GetStaticMediaSources(item, true, cancellationToken)
|
||||||
.ConfigureAwait(false);
|
.ConfigureAwait(false);
|
||||||
|
|
||||||
var cachedVersions = sources.Where(i => i.Protocol == MediaProtocol.File).ToList();
|
var cachedVersions = sources.Where(i => i.Protocol == MediaProtocol.File).ToList();
|
||||||
|
@ -237,11 +237,9 @@ namespace MediaBrowser.Server.Implementations.Channels
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var channelItem = (IChannelMediaItem)item;
|
var destination = Path.Combine(path, item.ChannelId, itemId);
|
||||||
|
|
||||||
var destination = Path.Combine(path, channelItem.ChannelId, itemId);
|
await _manager.DownloadChannelItem(item, destination, new Progress<double>(), cancellationToken)
|
||||||
|
|
||||||
await _manager.DownloadChannelItem(channelItem, destination, new Progress<double>(), cancellationToken)
|
|
||||||
.ConfigureAwait(false);
|
.ConfigureAwait(false);
|
||||||
|
|
||||||
await RefreshMediaSourceItem(destination, cancellationToken).ConfigureAwait(false);
|
await RefreshMediaSourceItem(destination, cancellationToken).ConfigureAwait(false);
|
||||||
|
|
|
@ -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 openKey, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task CloseMediaSource(string closeKey, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -241,10 +241,25 @@ namespace MediaBrowser.Server.Implementations.Channels
|
||||||
return item;
|
return item;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<IEnumerable<MediaSourceInfo>> GetChannelItemMediaSources(string id, bool includeDynamicSources, CancellationToken cancellationToken)
|
public async Task<IEnumerable<MediaSourceInfo>> GetStaticMediaSources(IChannelMediaItem item, bool includeCachedVersions, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var item = (IChannelMediaItem)_libraryManager.GetItemById(id);
|
IEnumerable<ChannelMediaInfo> results = item.ChannelMediaSources;
|
||||||
|
|
||||||
|
var sources = SortMediaInfoResults(results)
|
||||||
|
.Select(i => GetMediaSource(item, i))
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
if (includeCachedVersions)
|
||||||
|
{
|
||||||
|
var cachedVersions = GetCachedChannelItemMediaSources(item);
|
||||||
|
sources.InsertRange(0, cachedVersions);
|
||||||
|
}
|
||||||
|
|
||||||
|
return sources.Where(IsValidMediaSource);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IEnumerable<MediaSourceInfo>> GetDynamicMediaSources(IChannelMediaItem item, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
var channel = GetChannel(item.ChannelId);
|
var channel = GetChannel(item.ChannelId);
|
||||||
var channelPlugin = GetChannelProvider(channel);
|
var channelPlugin = GetChannelProvider(channel);
|
||||||
|
|
||||||
|
@ -252,24 +267,25 @@ namespace MediaBrowser.Server.Implementations.Channels
|
||||||
|
|
||||||
IEnumerable<ChannelMediaInfo> results;
|
IEnumerable<ChannelMediaInfo> results;
|
||||||
|
|
||||||
if (requiresCallback != null && includeDynamicSources)
|
if (requiresCallback != null)
|
||||||
{
|
{
|
||||||
results = await GetChannelItemMediaSourcesInternal(requiresCallback, item.ExternalId, cancellationToken)
|
results = await GetChannelItemMediaSourcesInternal(requiresCallback, item.ExternalId, cancellationToken)
|
||||||
.ConfigureAwait(false);
|
.ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
results = item.ChannelMediaSources;
|
results = new List<ChannelMediaInfo>();
|
||||||
}
|
}
|
||||||
|
|
||||||
var sources = SortMediaInfoResults(results).Select(i => GetMediaSource(item, i))
|
var list = SortMediaInfoResults(results)
|
||||||
|
.Select(i => GetMediaSource(item, i))
|
||||||
|
.Where(IsValidMediaSource)
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
var cachedVersions = GetCachedChannelItemMediaSources(item);
|
var cachedVersions = GetCachedChannelItemMediaSources(item);
|
||||||
|
list.InsertRange(0, cachedVersions);
|
||||||
|
|
||||||
sources.InsertRange(0, cachedVersions);
|
return list;
|
||||||
|
|
||||||
return sources.Where(IsValidMediaSource);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly ConcurrentDictionary<string, Tuple<DateTime, List<ChannelMediaInfo>>> _channelItemMediaInfo =
|
private readonly ConcurrentDictionary<string, Tuple<DateTime, List<ChannelMediaInfo>>> _channelItemMediaInfo =
|
||||||
|
@ -297,14 +313,7 @@ namespace MediaBrowser.Server.Implementations.Channels
|
||||||
return list;
|
return list;
|
||||||
}
|
}
|
||||||
|
|
||||||
public IEnumerable<MediaSourceInfo> GetCachedChannelItemMediaSources(string id)
|
private IEnumerable<MediaSourceInfo> GetCachedChannelItemMediaSources(IChannelMediaItem item)
|
||||||
{
|
|
||||||
var item = (IChannelMediaItem)_libraryManager.GetItemById(id);
|
|
||||||
|
|
||||||
return GetCachedChannelItemMediaSources(item);
|
|
||||||
}
|
|
||||||
|
|
||||||
public IEnumerable<MediaSourceInfo> GetCachedChannelItemMediaSources(IChannelMediaItem item)
|
|
||||||
{
|
{
|
||||||
var filenamePrefix = item.Id.ToString("N");
|
var filenamePrefix = item.Id.ToString("N");
|
||||||
var parentPath = Path.Combine(ChannelDownloadPath, item.ChannelId);
|
var parentPath = Path.Combine(ChannelDownloadPath, item.ChannelId);
|
||||||
|
@ -339,7 +348,6 @@ namespace MediaBrowser.Server.Implementations.Channels
|
||||||
|
|
||||||
if (source != null)
|
if (source != null)
|
||||||
{
|
{
|
||||||
source.Type = MediaSourceType.Cache;
|
|
||||||
return new[] { source };
|
return new[] { source };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1408,8 +1416,7 @@ namespace MediaBrowser.Server.Implementations.Channels
|
||||||
public async Task DownloadChannelItem(IChannelMediaItem item, string destination,
|
public async Task DownloadChannelItem(IChannelMediaItem item, string destination,
|
||||||
IProgress<double> progress, CancellationToken cancellationToken)
|
IProgress<double> progress, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var itemId = item.Id.ToString("N");
|
var sources = await GetDynamicMediaSources(item, cancellationToken)
|
||||||
var sources = await GetChannelItemMediaSources(itemId, true, cancellationToken)
|
|
||||||
.ConfigureAwait(false);
|
.ConfigureAwait(false);
|
||||||
|
|
||||||
var list = sources.Where(i => i.Protocol == MediaProtocol.Http).ToList();
|
var list = sources.Where(i => i.Protocol == MediaProtocol.Http).ToList();
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
using MediaBrowser.Controller.Channels;
|
using System.Collections.Concurrent;
|
||||||
|
using MediaBrowser.Common.Extensions;
|
||||||
using MediaBrowser.Controller.Entities;
|
using MediaBrowser.Controller.Entities;
|
||||||
using MediaBrowser.Controller.Library;
|
using MediaBrowser.Controller.Library;
|
||||||
using MediaBrowser.Controller.MediaEncoding;
|
using MediaBrowser.Controller.MediaEncoding;
|
||||||
|
@ -13,25 +14,24 @@ using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using MediaBrowser.Server.Implementations.LiveTv;
|
||||||
|
|
||||||
namespace MediaBrowser.Server.Implementations.Library
|
namespace MediaBrowser.Server.Implementations.Library
|
||||||
{
|
{
|
||||||
public class MediaSourceManager : IMediaSourceManager
|
public class MediaSourceManager : IMediaSourceManager, IDisposable
|
||||||
{
|
{
|
||||||
private readonly IItemRepository _itemRepo;
|
private readonly IItemRepository _itemRepo;
|
||||||
private readonly IUserManager _userManager;
|
private readonly IUserManager _userManager;
|
||||||
private readonly ILibraryManager _libraryManager;
|
private readonly ILibraryManager _libraryManager;
|
||||||
private readonly IChannelManager _channelManager;
|
|
||||||
|
|
||||||
private IMediaSourceProvider[] _providers;
|
private IMediaSourceProvider[] _providers;
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger _logger;
|
||||||
|
|
||||||
public MediaSourceManager(IItemRepository itemRepo, IUserManager userManager, ILibraryManager libraryManager, IChannelManager channelManager, ILogger logger)
|
public MediaSourceManager(IItemRepository itemRepo, IUserManager userManager, ILibraryManager libraryManager, ILogger logger)
|
||||||
{
|
{
|
||||||
_itemRepo = itemRepo;
|
_itemRepo = itemRepo;
|
||||||
_userManager = userManager;
|
_userManager = userManager;
|
||||||
_libraryManager = libraryManager;
|
_libraryManager = libraryManager;
|
||||||
_channelManager = channelManager;
|
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -133,15 +133,7 @@ namespace MediaBrowser.Server.Implementations.Library
|
||||||
IEnumerable<MediaSourceInfo> mediaSources;
|
IEnumerable<MediaSourceInfo> mediaSources;
|
||||||
|
|
||||||
var hasMediaSources = (IHasMediaSources)item;
|
var hasMediaSources = (IHasMediaSources)item;
|
||||||
var channelItem = item as IChannelMediaItem;
|
|
||||||
|
|
||||||
if (channelItem != null)
|
|
||||||
{
|
|
||||||
mediaSources = await _channelManager.GetChannelItemMediaSources(id, true, cancellationToken)
|
|
||||||
.ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (string.IsNullOrWhiteSpace(userId))
|
if (string.IsNullOrWhiteSpace(userId))
|
||||||
{
|
{
|
||||||
mediaSources = hasMediaSources.GetMediaSources(enablePathSubstitution);
|
mediaSources = hasMediaSources.GetMediaSources(enablePathSubstitution);
|
||||||
|
@ -151,7 +143,6 @@ namespace MediaBrowser.Server.Implementations.Library
|
||||||
var user = _userManager.GetUserById(userId);
|
var user = _userManager.GetUserById(userId);
|
||||||
mediaSources = GetStaticMediaSources(hasMediaSources, enablePathSubstitution, user);
|
mediaSources = GetStaticMediaSources(hasMediaSources, enablePathSubstitution, user);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
var dynamicMediaSources = await GetDynamicMediaSources(hasMediaSources, cancellationToken).ConfigureAwait(false);
|
var dynamicMediaSources = await GetDynamicMediaSources(hasMediaSources, cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
|
@ -161,11 +152,16 @@ namespace MediaBrowser.Server.Implementations.Library
|
||||||
|
|
||||||
foreach (var source in dynamicMediaSources)
|
foreach (var source in dynamicMediaSources)
|
||||||
{
|
{
|
||||||
source.SupportsTranscoding = false;
|
|
||||||
|
|
||||||
if (source.Protocol == MediaProtocol.File)
|
if (source.Protocol == MediaProtocol.File)
|
||||||
{
|
{
|
||||||
source.SupportsDirectStream = File.Exists(source.Path);
|
source.SupportsDirectStream = File.Exists(source.Path);
|
||||||
|
|
||||||
|
// TODO: Path substitution
|
||||||
|
}
|
||||||
|
else if (source.Protocol == MediaProtocol.Http)
|
||||||
|
{
|
||||||
|
// TODO: Allow this when the source is plain http, e.g. not HLS or Mpeg Dash
|
||||||
|
source.SupportsDirectStream = false;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -175,7 +171,7 @@ namespace MediaBrowser.Server.Implementations.Library
|
||||||
list.Add(source);
|
list.Add(source);
|
||||||
}
|
}
|
||||||
|
|
||||||
return SortMediaSources(list);
|
return SortMediaSources(list).Where(i => i.Type != MediaSourceType.Placeholder);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<IEnumerable<MediaSourceInfo>> GetDynamicMediaSources(IHasMediaSources item, CancellationToken cancellationToken)
|
private async Task<IEnumerable<MediaSourceInfo>> GetDynamicMediaSources(IHasMediaSources item, CancellationToken cancellationToken)
|
||||||
|
@ -190,7 +186,15 @@ namespace MediaBrowser.Server.Implementations.Library
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
return await provider.GetMediaSources(item, cancellationToken).ConfigureAwait(false);
|
var sources = await provider.GetMediaSources(item, cancellationToken).ConfigureAwait(false);
|
||||||
|
var list = sources.ToList();
|
||||||
|
|
||||||
|
foreach (var mediaSource in list)
|
||||||
|
{
|
||||||
|
SetKeyProperties(provider, mediaSource);
|
||||||
|
}
|
||||||
|
|
||||||
|
return list;
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
@ -199,6 +203,21 @@ namespace MediaBrowser.Server.Implementations.Library
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void SetKeyProperties(IMediaSourceProvider provider, MediaSourceInfo mediaSource)
|
||||||
|
{
|
||||||
|
var prefix = provider.GetType().FullName.GetMD5().ToString("N") + "|";
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(mediaSource.OpenKey) && !mediaSource.OpenKey.StartsWith(prefix, StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
mediaSource.OpenKey = prefix + mediaSource.OpenKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(mediaSource.CloseKey) && !mediaSource.CloseKey.StartsWith(prefix, StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
mediaSource.CloseKey = prefix + mediaSource.CloseKey;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public Task<IEnumerable<MediaSourceInfo>> GetPlayackMediaSources(string id, bool enablePathSubstitution, CancellationToken cancellationToken)
|
public Task<IEnumerable<MediaSourceInfo>> GetPlayackMediaSources(string id, bool enablePathSubstitution, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
return GetPlayackMediaSources(id, null, enablePathSubstitution, cancellationToken);
|
return GetPlayackMediaSources(id, null, enablePathSubstitution, cancellationToken);
|
||||||
|
@ -294,5 +313,90 @@ namespace MediaBrowser.Server.Implementations.Library
|
||||||
{
|
{
|
||||||
return GetStaticMediaSources(item, enablePathSubstitution).FirstOrDefault(i => string.Equals(i.Id, mediaSourceId, StringComparison.OrdinalIgnoreCase));
|
return GetStaticMediaSources(item, enablePathSubstitution).FirstOrDefault(i => string.Equals(i.Id, mediaSourceId, StringComparison.OrdinalIgnoreCase));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private readonly ConcurrentDictionary<string, string> _openStreams =
|
||||||
|
new ConcurrentDictionary<string, string>();
|
||||||
|
private readonly SemaphoreSlim _liveStreamSemaphore = new SemaphoreSlim(1, 1);
|
||||||
|
public async Task<MediaSourceInfo> OpenMediaSource(string openKey, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
await _liveStreamSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var tuple = GetProvider(openKey);
|
||||||
|
var provider = tuple.Item1;
|
||||||
|
|
||||||
|
var mediaSource = await provider.OpenMediaSource(tuple.Item2, cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
|
SetKeyProperties(provider, mediaSource);
|
||||||
|
|
||||||
|
_openStreams.AddOrUpdate(mediaSource.CloseKey, mediaSource.CloseKey, (key, i) => mediaSource.CloseKey);
|
||||||
|
|
||||||
|
return mediaSource;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_liveStreamSemaphore.Release();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task CloseMediaSource(string closeKey, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
await _liveStreamSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var tuple = GetProvider(closeKey);
|
||||||
|
|
||||||
|
await tuple.Item1.OpenMediaSource(tuple.Item2, cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
|
string removedKey;
|
||||||
|
_openStreams.TryRemove(closeKey, out removedKey);
|
||||||
|
}
|
||||||
|
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]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
|
||||||
|
/// </summary>
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
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 = CloseMediaSource(key, CancellationToken.None);
|
||||||
|
|
||||||
|
Task.WaitAll(task);
|
||||||
|
}
|
||||||
|
|
||||||
|
_openStreams.Clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,8 @@
|
||||||
using System.Globalization;
|
using MediaBrowser.Common;
|
||||||
using MediaBrowser.Common;
|
|
||||||
using MediaBrowser.Common.Configuration;
|
using MediaBrowser.Common.Configuration;
|
||||||
using MediaBrowser.Common.Extensions;
|
using MediaBrowser.Common.Extensions;
|
||||||
using MediaBrowser.Common.Progress;
|
using MediaBrowser.Common.Progress;
|
||||||
using MediaBrowser.Common.ScheduledTasks;
|
using MediaBrowser.Common.ScheduledTasks;
|
||||||
using MediaBrowser.Controller.Channels;
|
|
||||||
using MediaBrowser.Controller.Configuration;
|
using MediaBrowser.Controller.Configuration;
|
||||||
using MediaBrowser.Controller.Drawing;
|
using MediaBrowser.Controller.Drawing;
|
||||||
using MediaBrowser.Controller.Dto;
|
using MediaBrowser.Controller.Dto;
|
||||||
|
@ -15,7 +13,6 @@ using MediaBrowser.Controller.Localization;
|
||||||
using MediaBrowser.Controller.Persistence;
|
using MediaBrowser.Controller.Persistence;
|
||||||
using MediaBrowser.Controller.Providers;
|
using MediaBrowser.Controller.Providers;
|
||||||
using MediaBrowser.Controller.Sorting;
|
using MediaBrowser.Controller.Sorting;
|
||||||
using MediaBrowser.Model.Dlna;
|
|
||||||
using MediaBrowser.Model.Dto;
|
using MediaBrowser.Model.Dto;
|
||||||
using MediaBrowser.Model.Entities;
|
using MediaBrowser.Model.Entities;
|
||||||
using MediaBrowser.Model.LiveTv;
|
using MediaBrowser.Model.LiveTv;
|
||||||
|
@ -342,6 +339,8 @@ namespace MediaBrowser.Server.Implementations.LiveTv
|
||||||
var service = GetService(channel);
|
var service = GetService(channel);
|
||||||
_logger.Info("Opening channel stream from {0}, external channel Id: {1}", service.Name, channel.ExternalId);
|
_logger.Info("Opening channel stream from {0}, external channel Id: {1}", service.Name, channel.ExternalId);
|
||||||
info = await service.GetChannelStream(channel.ExternalId, null, cancellationToken).ConfigureAwait(false);
|
info = await service.GetChannelStream(channel.ExternalId, null, cancellationToken).ConfigureAwait(false);
|
||||||
|
info.RequiresClosing = true;
|
||||||
|
info.CloseKey = info.Id;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -351,6 +350,8 @@ namespace MediaBrowser.Server.Implementations.LiveTv
|
||||||
|
|
||||||
_logger.Info("Opening recording stream from {0}, external recording Id: {1}", service.Name, recording.RecordingInfo.Id);
|
_logger.Info("Opening recording stream from {0}, external recording Id: {1}", service.Name, recording.RecordingInfo.Id);
|
||||||
info = await service.GetRecordingStream(recording.RecordingInfo.Id, null, cancellationToken).ConfigureAwait(false);
|
info = await service.GetRecordingStream(recording.RecordingInfo.Id, null, cancellationToken).ConfigureAwait(false);
|
||||||
|
info.RequiresClosing = true;
|
||||||
|
info.CloseKey = info.Id;
|
||||||
}
|
}
|
||||||
|
|
||||||
_logger.Info("Live stream info: {0}", _jsonSerializer.SerializeToString(info));
|
_logger.Info("Live stream info: {0}", _jsonSerializer.SerializeToString(info));
|
||||||
|
|
|
@ -0,0 +1,77 @@
|
||||||
|
using MediaBrowser.Controller.Entities;
|
||||||
|
using MediaBrowser.Controller.Library;
|
||||||
|
using MediaBrowser.Controller.LiveTv;
|
||||||
|
using MediaBrowser.Model.Dto;
|
||||||
|
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;
|
||||||
|
|
||||||
|
public LiveTvMediaSourceProvider(ILiveTvManager liveTvManager)
|
||||||
|
{
|
||||||
|
_liveTvManager = liveTvManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
var hasMediaSources = (IHasMediaSources)item;
|
||||||
|
|
||||||
|
var sources = hasMediaSources.GetMediaSources(false)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
foreach (var source in sources)
|
||||||
|
{
|
||||||
|
source.Type = MediaSourceType.Default;
|
||||||
|
source.RequiresOpening = true;
|
||||||
|
|
||||||
|
var openKeys = new List<string>();
|
||||||
|
openKeys.Add(item.GetType().Name);
|
||||||
|
openKeys.Add(item.Id.ToString("N"));
|
||||||
|
source.OpenKey = string.Join("|", openKeys.ToArray());
|
||||||
|
}
|
||||||
|
|
||||||
|
return sources;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<MediaSourceInfo> OpenMediaSource(string openKey, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var keys = openKey.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 closeKey, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
return _liveTvManager.CloseLiveStream(closeKey, cancellationToken);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -111,6 +111,7 @@
|
||||||
<Compile Include="Branding\BrandingConfigurationFactory.cs" />
|
<Compile Include="Branding\BrandingConfigurationFactory.cs" />
|
||||||
<Compile Include="Channels\ChannelConfigurations.cs" />
|
<Compile Include="Channels\ChannelConfigurations.cs" />
|
||||||
<Compile Include="Channels\ChannelDownloadScheduledTask.cs" />
|
<Compile Include="Channels\ChannelDownloadScheduledTask.cs" />
|
||||||
|
<Compile Include="Channels\ChannelDynamicMediaSourceProvider.cs" />
|
||||||
<Compile Include="Channels\ChannelImageProvider.cs" />
|
<Compile Include="Channels\ChannelImageProvider.cs" />
|
||||||
<Compile Include="Channels\ChannelItemImageProvider.cs" />
|
<Compile Include="Channels\ChannelItemImageProvider.cs" />
|
||||||
<Compile Include="Channels\ChannelManager.cs" />
|
<Compile Include="Channels\ChannelManager.cs" />
|
||||||
|
@ -225,6 +226,7 @@
|
||||||
<Compile Include="LiveTv\LiveTvConfigurationFactory.cs" />
|
<Compile Include="LiveTv\LiveTvConfigurationFactory.cs" />
|
||||||
<Compile Include="LiveTv\LiveTvDtoService.cs" />
|
<Compile Include="LiveTv\LiveTvDtoService.cs" />
|
||||||
<Compile Include="LiveTv\LiveTvManager.cs" />
|
<Compile Include="LiveTv\LiveTvManager.cs" />
|
||||||
|
<Compile Include="LiveTv\LiveTvMediaSourceProvider.cs" />
|
||||||
<Compile Include="LiveTv\ProgramImageProvider.cs" />
|
<Compile Include="LiveTv\ProgramImageProvider.cs" />
|
||||||
<Compile Include="LiveTv\RecordingImageProvider.cs" />
|
<Compile Include="LiveTv\RecordingImageProvider.cs" />
|
||||||
<Compile Include="LiveTv\RefreshChannelsScheduledTask.cs" />
|
<Compile Include="LiveTv\RefreshChannelsScheduledTask.cs" />
|
||||||
|
|
|
@ -161,6 +161,7 @@ namespace MediaBrowser.Server.Implementations.Sync
|
||||||
{
|
{
|
||||||
mediaSource.Path = sendFileResult.Path;
|
mediaSource.Path = sendFileResult.Path;
|
||||||
mediaSource.Protocol = sendFileResult.Protocol;
|
mediaSource.Protocol = sendFileResult.Protocol;
|
||||||
|
mediaSource.RequiredHttpHeaders = sendFileResult.RequiredHttpHeaders;
|
||||||
mediaSource.SupportsTranscoding = false;
|
mediaSource.SupportsTranscoding = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
using MediaBrowser.Controller;
|
using MediaBrowser.Common.Extensions;
|
||||||
|
using MediaBrowser.Controller;
|
||||||
using MediaBrowser.Controller.Entities;
|
using MediaBrowser.Controller.Entities;
|
||||||
using MediaBrowser.Controller.Library;
|
using MediaBrowser.Controller.Library;
|
||||||
using MediaBrowser.Controller.Sync;
|
using MediaBrowser.Controller.Sync;
|
||||||
|
@ -61,7 +62,7 @@ namespace MediaBrowser.Server.Implementations.Sync
|
||||||
{
|
{
|
||||||
foreach (var mediaSource in localItem.Item.MediaSources)
|
foreach (var mediaSource in localItem.Item.MediaSources)
|
||||||
{
|
{
|
||||||
await TryAddMediaSource(list, localItem, mediaSource, syncProvider, syncTarget, cancellationToken).ConfigureAwait(false);
|
AddMediaSource(list, localItem, mediaSource, syncProvider, syncTarget);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -71,24 +72,46 @@ namespace MediaBrowser.Server.Implementations.Sync
|
||||||
return list;
|
return list;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task TryAddMediaSource(List<MediaSourceInfo> list,
|
private void AddMediaSource(List<MediaSourceInfo> list,
|
||||||
LocalItem item,
|
LocalItem item,
|
||||||
MediaSourceInfo mediaSource,
|
MediaSourceInfo mediaSource,
|
||||||
IServerSyncProvider provider,
|
IServerSyncProvider provider,
|
||||||
SyncTarget target,
|
SyncTarget target)
|
||||||
CancellationToken cancellationToken)
|
|
||||||
{
|
{
|
||||||
|
SetStaticMediaSourceInfo(item, mediaSource);
|
||||||
|
|
||||||
var requiresDynamicAccess = provider as IHasDynamicAccess;
|
var requiresDynamicAccess = provider as IHasDynamicAccess;
|
||||||
|
|
||||||
if (requiresDynamicAccess == null)
|
if (requiresDynamicAccess != null)
|
||||||
{
|
{
|
||||||
list.Add(mediaSource);
|
mediaSource.RequiresOpening = true;
|
||||||
return;
|
|
||||||
|
var keyList = new List<string>();
|
||||||
|
keyList.Add(provider.GetType().FullName.GetMD5().ToString("N"));
|
||||||
|
keyList.Add(target.Id.GetMD5().ToString("N"));
|
||||||
|
keyList.Add(item.Id);
|
||||||
|
mediaSource.OpenKey = string.Join("|", keyList.ToArray());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
try
|
public async Task<MediaSourceInfo> OpenMediaSource(string openKey, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var dynamicInfo = await requiresDynamicAccess.GetSyncedFileInfo(item.LocalPath, target, cancellationToken).ConfigureAwait(false);
|
var openKeys = openKey.Split(new[] { '|' }, 3);
|
||||||
|
|
||||||
|
var provider = _syncManager.ServerSyncProviders
|
||||||
|
.FirstOrDefault(i => string.Equals(openKeys[0], i.GetType().FullName.GetMD5().ToString("N"), StringComparison.OrdinalIgnoreCase));
|
||||||
|
|
||||||
|
var target = provider.GetAllSyncTargets()
|
||||||
|
.FirstOrDefault(i => string.Equals(openKeys[1], i.Id.GetMD5().ToString("N"), StringComparison.OrdinalIgnoreCase));
|
||||||
|
|
||||||
|
var dataProvider = _syncManager.GetDataProvider(provider, target);
|
||||||
|
var localItem = await dataProvider.Get(target, openKeys[2]).ConfigureAwait(false);
|
||||||
|
|
||||||
|
var requiresDynamicAccess = (IHasDynamicAccess)provider;
|
||||||
|
var dynamicInfo = await requiresDynamicAccess.GetSyncedFileInfo(localItem.LocalPath, target, cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
|
var mediaSource = localItem.Item.MediaSources.First();
|
||||||
|
SetStaticMediaSourceInfo(localItem, mediaSource);
|
||||||
|
|
||||||
foreach (var stream in mediaSource.MediaStreams)
|
foreach (var stream in mediaSource.MediaStreams)
|
||||||
{
|
{
|
||||||
|
@ -99,13 +122,20 @@ namespace MediaBrowser.Server.Implementations.Sync
|
||||||
|
|
||||||
mediaSource.Path = dynamicInfo.Path;
|
mediaSource.Path = dynamicInfo.Path;
|
||||||
mediaSource.Protocol = dynamicInfo.Protocol;
|
mediaSource.Protocol = dynamicInfo.Protocol;
|
||||||
|
mediaSource.RequiredHttpHeaders = dynamicInfo.RequiredHttpHeaders;
|
||||||
|
|
||||||
list.Add(mediaSource);
|
return mediaSource;
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
|
||||||
|
private void SetStaticMediaSourceInfo(LocalItem item, MediaSourceInfo mediaSource)
|
||||||
{
|
{
|
||||||
_logger.ErrorException("Error getting dynamic media source info", ex);
|
mediaSource.Id = item.Id;
|
||||||
|
mediaSource.SupportsTranscoding = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Task CloseMediaSource(string closeKey, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -472,7 +472,7 @@ namespace MediaBrowser.Server.Startup.Common
|
||||||
ChannelManager = new ChannelManager(UserManager, DtoService, LibraryManager, LogManager.GetLogger("ChannelManager"), ServerConfigurationManager, FileSystemManager, UserDataManager, JsonSerializer, LocalizationManager, HttpClient);
|
ChannelManager = new ChannelManager(UserManager, DtoService, LibraryManager, LogManager.GetLogger("ChannelManager"), ServerConfigurationManager, FileSystemManager, UserDataManager, JsonSerializer, LocalizationManager, HttpClient);
|
||||||
RegisterSingleInstance(ChannelManager);
|
RegisterSingleInstance(ChannelManager);
|
||||||
|
|
||||||
MediaSourceManager = new MediaSourceManager(ItemRepository, UserManager, LibraryManager, ChannelManager, LogManager.GetLogger("MediaSourceManager"));
|
MediaSourceManager = new MediaSourceManager(ItemRepository, UserManager, LibraryManager, LogManager.GetLogger("MediaSourceManager"));
|
||||||
RegisterSingleInstance(MediaSourceManager);
|
RegisterSingleInstance(MediaSourceManager);
|
||||||
|
|
||||||
SessionManager = new SessionManager(UserDataManager, LogManager.GetLogger("SessionManager"), UserRepository, LibraryManager, UserManager, musicManager, DtoService, ImageProcessor, JsonSerializer, this, HttpClient, AuthenticationRepository, DeviceManager, MediaSourceManager);
|
SessionManager = new SessionManager(UserDataManager, LogManager.GetLogger("SessionManager"), UserRepository, LibraryManager, UserManager, musicManager, DtoService, ImageProcessor, JsonSerializer, this, HttpClient, AuthenticationRepository, DeviceManager, MediaSourceManager);
|
||||||
|
|
|
@ -9,8 +9,8 @@
|
||||||
<projectUrl>https://github.com/MediaBrowser/MediaBrowser</projectUrl>
|
<projectUrl>https://github.com/MediaBrowser/MediaBrowser</projectUrl>
|
||||||
<iconUrl>http://www.mb3admin.com/images/mb3icons1-1.png</iconUrl>
|
<iconUrl>http://www.mb3admin.com/images/mb3icons1-1.png</iconUrl>
|
||||||
<requireLicenseAcceptance>false</requireLicenseAcceptance>
|
<requireLicenseAcceptance>false</requireLicenseAcceptance>
|
||||||
<description>Contains common components shared by Media Browser Theater and Media Browser Server. Not intended for plugin developer consumption.</description>
|
<description>Contains common components shared by Emby Theater and Emby Server. Not intended for plugin developer consumption.</description>
|
||||||
<copyright>Copyright © Media Browser 2013</copyright>
|
<copyright>Copyright © Emby 2013</copyright>
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<dependency id="MediaBrowser.Common" version="3.0.603" />
|
<dependency id="MediaBrowser.Common" version="3.0.603" />
|
||||||
<dependency id="NLog" version="3.2.0.0" />
|
<dependency id="NLog" version="3.2.0.0" />
|
||||||
|
|
|
@ -4,13 +4,13 @@
|
||||||
<id>MediaBrowser.Common</id>
|
<id>MediaBrowser.Common</id>
|
||||||
<version>3.0.603</version>
|
<version>3.0.603</version>
|
||||||
<title>MediaBrowser.Common</title>
|
<title>MediaBrowser.Common</title>
|
||||||
<authors>Media Browser Team</authors>
|
<authors>Emby Team</authors>
|
||||||
<owners>ebr,Luke,scottisafool</owners>
|
<owners>ebr,Luke,scottisafool</owners>
|
||||||
<projectUrl>https://github.com/MediaBrowser/MediaBrowser</projectUrl>
|
<projectUrl>https://github.com/MediaBrowser/MediaBrowser</projectUrl>
|
||||||
<iconUrl>http://www.mb3admin.com/images/mb3icons1-1.png</iconUrl>
|
<iconUrl>http://www.mb3admin.com/images/mb3icons1-1.png</iconUrl>
|
||||||
<requireLicenseAcceptance>false</requireLicenseAcceptance>
|
<requireLicenseAcceptance>false</requireLicenseAcceptance>
|
||||||
<description>Contains common model objects and interfaces used by all Media Browser solutions.</description>
|
<description>Contains common model objects and interfaces used by all Emby solutions.</description>
|
||||||
<copyright>Copyright © Media Browser 2013</copyright>
|
<copyright>Copyright © Emby 2013</copyright>
|
||||||
</metadata>
|
</metadata>
|
||||||
<files>
|
<files>
|
||||||
<file src="dlls\net35\MediaBrowser.Model.dll" target="lib\net35\MediaBrowser.Model.dll" />
|
<file src="dlls\net35\MediaBrowser.Model.dll" target="lib\net35\MediaBrowser.Model.dll" />
|
||||||
|
|
|
@ -4,13 +4,13 @@
|
||||||
<id>MediaBrowser.Model.Signed</id>
|
<id>MediaBrowser.Model.Signed</id>
|
||||||
<version>3.0.603</version>
|
<version>3.0.603</version>
|
||||||
<title>MediaBrowser.Model - Signed Edition</title>
|
<title>MediaBrowser.Model - Signed Edition</title>
|
||||||
<authors>Media Browser Team</authors>
|
<authors>Emby Team</authors>
|
||||||
<owners>ebr,Luke,scottisafool</owners>
|
<owners>ebr,Luke,scottisafool</owners>
|
||||||
<projectUrl>https://github.com/MediaBrowser/MediaBrowser</projectUrl>
|
<projectUrl>https://github.com/MediaBrowser/MediaBrowser</projectUrl>
|
||||||
<iconUrl>http://www.mb3admin.com/images/mb3icons1-1.png</iconUrl>
|
<iconUrl>http://www.mb3admin.com/images/mb3icons1-1.png</iconUrl>
|
||||||
<requireLicenseAcceptance>false</requireLicenseAcceptance>
|
<requireLicenseAcceptance>false</requireLicenseAcceptance>
|
||||||
<description>Contains common model objects and interfaces used by all Media Browser solutions.</description>
|
<description>Contains common model objects and interfaces used by all Emby solutions.</description>
|
||||||
<copyright>Copyright © Media Browser 2013</copyright>
|
<copyright>Copyright © Emby 2013</copyright>
|
||||||
<dependencies>
|
<dependencies>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
</metadata>
|
</metadata>
|
||||||
|
|
|
@ -4,13 +4,13 @@
|
||||||
<id>MediaBrowser.Server.Core</id>
|
<id>MediaBrowser.Server.Core</id>
|
||||||
<version>3.0.603</version>
|
<version>3.0.603</version>
|
||||||
<title>Media Browser.Server.Core</title>
|
<title>Media Browser.Server.Core</title>
|
||||||
<authors>Media Browser Team</authors>
|
<authors>Emby Team</authors>
|
||||||
<owners>ebr,Luke,scottisafool</owners>
|
<owners>ebr,Luke,scottisafool</owners>
|
||||||
<projectUrl>https://github.com/MediaBrowser/MediaBrowser</projectUrl>
|
<projectUrl>https://github.com/MediaBrowser/MediaBrowser</projectUrl>
|
||||||
<iconUrl>http://www.mb3admin.com/images/mb3icons1-1.png</iconUrl>
|
<iconUrl>http://www.mb3admin.com/images/mb3icons1-1.png</iconUrl>
|
||||||
<requireLicenseAcceptance>false</requireLicenseAcceptance>
|
<requireLicenseAcceptance>false</requireLicenseAcceptance>
|
||||||
<description>Contains core components required to build plugins for Media Browser Server.</description>
|
<description>Contains core components required to build plugins for Emby Server.</description>
|
||||||
<copyright>Copyright © Media Browser 2013</copyright>
|
<copyright>Copyright © Emby 2013</copyright>
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<dependency id="MediaBrowser.Common" version="3.0.603" />
|
<dependency id="MediaBrowser.Common" version="3.0.603" />
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
Loading…
Reference in New Issue
Block a user