From 858c37b8607ff0698a94b9e7bfff6190d3bca56d Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Mon, 2 Jun 2014 15:32:41 -0400 Subject: [PATCH] add channel downloading settings --- MediaBrowser.Api/ApiEntryPoint.cs | 15 +- MediaBrowser.Api/ChannelService.cs | 12 + MediaBrowser.Api/Images/ImageByNameService.cs | 10 +- MediaBrowser.Api/MediaBrowser.Api.csproj | 1 - .../Playback/BaseStreamingService.cs | 179 ++++++----- .../Playback/Hls/BaseHlsService.cs | 24 +- .../Playback/Hls/DynamicHlsService.cs | 10 +- .../Playback/Hls/VideoHlsService.cs | 9 +- .../Playback/Progressive/AudioService.cs | 4 +- .../BaseProgressiveStreamingService.cs | 6 +- .../Progressive/ProgressiveStreamWriter.cs | 1 + .../Playback/Progressive/VideoService.cs | 7 +- .../Playback/ProgressiveStreamService.cs | 73 ----- MediaBrowser.Api/Playback/StreamState.cs | 9 +- .../HttpClientManager/HttpClientManager.cs | 4 +- MediaBrowser.Common/Net/HttpResponseInfo.cs | 6 + .../Channels/ChannelAudioItem.cs | 5 + .../Channels/ChannelFolderItem.cs | 5 + .../Channels/ChannelMediaInfo.cs | 5 +- .../Channels/ChannelVideoItem.cs | 2 +- MediaBrowser.Controller/Channels/IChannel.cs | 8 + .../Channels/IChannelManager.cs | 22 +- .../Channels/IChannelMediaItem.cs | 3 + .../Channels/InternalChannelFeatures.cs | 6 + .../Channels/InternalChannelItemQuery.cs | 5 + .../MediaBrowser.Model.Portable.csproj | 3 + .../MediaBrowser.Model.net35.csproj | 3 + .../Channels/ChannelFeatures.cs | 24 ++ MediaBrowser.Model/Channels/ChannelQuery.cs | 29 ++ .../Configuration/ServerConfiguration.cs | 11 + MediaBrowser.Model/Dto/ImageByNameInfo.cs | 32 ++ MediaBrowser.Model/Dto/MediaSourceInfo.cs | 3 +- MediaBrowser.Model/MediaBrowser.Model.csproj | 1 + .../Music/AlbumMetadataService.cs | 71 ++--- .../Channels/ChannelDownloadScheduledTask.cs | 277 ++++++++++++++++++ .../Channels/ChannelManager.cs | 264 ++++++++++++++++- .../Dto/DtoService.cs | 33 +-- .../LiveTv/LiveTvManager.cs | 53 ++++ .../Localization/JavaScript/javascript.json | 4 +- .../Localization/Server/server.json | 12 +- ...MediaBrowser.Server.Implementations.csproj | 1 + .../Session/HttpSessionController.cs | 21 +- Nuget/MediaBrowser.Common.Internal.nuspec | 4 +- Nuget/MediaBrowser.Common.nuspec | 2 +- Nuget/MediaBrowser.Server.Core.nuspec | 4 +- 45 files changed, 980 insertions(+), 303 deletions(-) delete mode 100644 MediaBrowser.Api/Playback/ProgressiveStreamService.cs create mode 100644 MediaBrowser.Model/Dto/ImageByNameInfo.cs create mode 100644 MediaBrowser.Server.Implementations/Channels/ChannelDownloadScheduledTask.cs diff --git a/MediaBrowser.Api/ApiEntryPoint.cs b/MediaBrowser.Api/ApiEntryPoint.cs index 9a7254c94a..75c2d406d3 100644 --- a/MediaBrowser.Api/ApiEntryPoint.cs +++ b/MediaBrowser.Api/ApiEntryPoint.cs @@ -117,7 +117,8 @@ namespace MediaBrowser.Api /// The start time ticks. /// The source path. /// The device id. - public void OnTranscodeBeginning(string path, TranscodingJobType type, Process process, long? startTimeTicks, string sourcePath, string deviceId) + /// The cancellation token source. + public void OnTranscodeBeginning(string path, TranscodingJobType type, Process process, long? startTimeTicks, string sourcePath, string deviceId, CancellationTokenSource cancellationTokenSource) { lock (_activeTranscodingJobs) { @@ -129,7 +130,8 @@ namespace MediaBrowser.Api ActiveRequestCount = 1, StartTimeTicks = startTimeTicks, SourcePath = sourcePath, - DeviceId = deviceId + DeviceId = deviceId, + CancellationTokenSource = cancellationTokenSource }); } } @@ -276,6 +278,11 @@ namespace MediaBrowser.Api { _activeTranscodingJobs.Remove(job); + if (!job.CancellationTokenSource.IsCancellationRequested) + { + job.CancellationTokenSource.Cancel(); + } + if (job.KillTimer != null) { job.KillTimer.Dispose(); @@ -329,7 +336,7 @@ namespace MediaBrowser.Api private async void DeletePartialStreamFiles(string path, TranscodingJobType jobType, int retryCount, int delayMs) { - if (retryCount >= 10) + if (retryCount >= 8) { return; } @@ -432,6 +439,8 @@ namespace MediaBrowser.Api public long? StartTimeTicks { get; set; } public string SourcePath { get; set; } public string DeviceId { get; set; } + + public CancellationTokenSource CancellationTokenSource { get; set; } } /// diff --git a/MediaBrowser.Api/ChannelService.cs b/MediaBrowser.Api/ChannelService.cs index 2a2d316d3c..a410a093e8 100644 --- a/MediaBrowser.Api/ChannelService.cs +++ b/MediaBrowser.Api/ChannelService.cs @@ -43,6 +43,11 @@ namespace MediaBrowser.Api public string Id { get; set; } } + [Route("/Channels/Features", "GET", Summary = "Gets features for a channel")] + public class GetAllChannelFeatures : IReturn> + { + } + [Route("/Channels/{Id}/Items", "GET", Summary = "Gets channel items")] public class GetChannelItems : IReturn> { @@ -108,6 +113,13 @@ namespace MediaBrowser.Api _channelManager = channelManager; } + public object Get(GetAllChannelFeatures request) + { + var result = _channelManager.GetAllChannelFeatures().ToList(); + + return ToOptimizedResult(result); + } + public object Get(GetChannelFeatures request) { var result = _channelManager.GetChannelFeatures(request.Id); diff --git a/MediaBrowser.Api/Images/ImageByNameService.cs b/MediaBrowser.Api/Images/ImageByNameService.cs index 16f3f0c1d8..d407629644 100644 --- a/MediaBrowser.Api/Images/ImageByNameService.cs +++ b/MediaBrowser.Api/Images/ImageByNameService.cs @@ -1,6 +1,7 @@ using MediaBrowser.Common.Extensions; using MediaBrowser.Controller; using MediaBrowser.Controller.Entities; +using MediaBrowser.Model.Dto; using ServiceStack; using System; using System.Collections.Generic; @@ -89,15 +90,6 @@ namespace MediaBrowser.Api.Images { } - public class ImageByNameInfo - { - public string Name { get; set; } - public string Theme { get; set; } - public string Context { get; set; } - public long FileLength { get; set; } - public string Format { get; set; } - } - /// /// Class ImageByNameService /// diff --git a/MediaBrowser.Api/MediaBrowser.Api.csproj b/MediaBrowser.Api/MediaBrowser.Api.csproj index 62d5a6ce22..92bbb61300 100644 --- a/MediaBrowser.Api/MediaBrowser.Api.csproj +++ b/MediaBrowser.Api/MediaBrowser.Api.csproj @@ -105,7 +105,6 @@ - diff --git a/MediaBrowser.Api/Playback/BaseStreamingService.cs b/MediaBrowser.Api/Playback/BaseStreamingService.cs index 5999a2b552..9f7c1a6c4f 100644 --- a/MediaBrowser.Api/Playback/BaseStreamingService.cs +++ b/MediaBrowser.Api/Playback/BaseStreamingService.cs @@ -1,5 +1,6 @@ using MediaBrowser.Common.Extensions; using MediaBrowser.Common.IO; +using MediaBrowser.Common.Net; using MediaBrowser.Controller.Channels; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Dlna; @@ -13,6 +14,7 @@ using MediaBrowser.Controller.Persistence; using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Drawing; +using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; using MediaBrowser.Model.IO; using MediaBrowser.Model.Library; @@ -72,20 +74,14 @@ namespace MediaBrowser.Api.Playback protected ILiveTvManager LiveTvManager { get; private set; } protected IDlnaManager DlnaManager { get; private set; } protected IChannelManager ChannelManager { get; private set; } + protected IHttpClient HttpClient { get; private set; } /// /// Initializes a new instance of the class. /// - /// The server configuration. - /// The user manager. - /// The library manager. - /// The iso manager. - /// The media encoder. - /// The dto service. - /// The file system. - /// The item repository. - protected BaseStreamingService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IDtoService dtoService, IFileSystem fileSystem, IItemRepository itemRepository, ILiveTvManager liveTvManager, IEncodingManager encodingManager, IDlnaManager dlnaManager, IChannelManager channelManager) + protected BaseStreamingService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IDtoService dtoService, IFileSystem fileSystem, IItemRepository itemRepository, ILiveTvManager liveTvManager, IEncodingManager encodingManager, IDlnaManager dlnaManager, IChannelManager channelManager, IHttpClient httpClient) { + HttpClient = httpClient; ChannelManager = channelManager; DlnaManager = dlnaManager; EncodingManager = encodingManager; @@ -483,8 +479,12 @@ namespace MediaBrowser.Api.Playback /// The state. /// The output video codec. /// if set to true [perform text subtitle conversion]. + /// The cancellation token. /// System.String. - protected string GetOutputSizeParam(StreamState state, string outputVideoCodec, bool performTextSubtitleConversion) + protected string GetOutputSizeParam(StreamState state, + string outputVideoCodec, + bool performTextSubtitleConversion, + CancellationToken cancellationToken) { // http://sonnati.wordpress.com/2012/10/19/ffmpeg-the-swiss-army-knife-of-internet-streaming-part-vi/ @@ -496,7 +496,7 @@ namespace MediaBrowser.Api.Playback if (state.SubtitleStream != null && !state.SubtitleStream.IsGraphicalSubtitleStream) { - assSubtitleParam = GetTextSubtitleParam(state, performTextSubtitleConversion); + assSubtitleParam = GetTextSubtitleParam(state, performTextSubtitleConversion, cancellationToken); copyTsParam = " -copyts"; } @@ -592,11 +592,15 @@ namespace MediaBrowser.Api.Playback /// /// The state. /// if set to true [perform conversion]. + /// The cancellation token. /// System.String. - protected string GetTextSubtitleParam(StreamState state, bool performConversion) + protected string GetTextSubtitleParam(StreamState state, + bool performConversion, + CancellationToken cancellationToken) { - var path = state.SubtitleStream.IsExternal ? GetConvertedAssPath(state.MediaPath, state.SubtitleStream, performConversion) : - GetExtractedAssPath(state, performConversion); + var path = state.SubtitleStream.IsExternal ? + GetConvertedAssPath(state.SubtitleStream, performConversion, cancellationToken) : + GetExtractedAssPath(state, performConversion, cancellationToken); if (string.IsNullOrEmpty(path)) { @@ -615,8 +619,11 @@ namespace MediaBrowser.Api.Playback /// /// The state. /// if set to true [perform conversion]. + /// The cancellation token. /// System.String. - private string GetExtractedAssPath(StreamState state, bool performConversion) + private string GetExtractedAssPath(StreamState state, + bool performConversion, + CancellationToken cancellationToken) { var path = EncodingManager.GetSubtitleCachePath(state.MediaPath, state.SubtitleStream.Index, ".ass"); @@ -636,7 +643,7 @@ namespace MediaBrowser.Api.Playback // See https://lists.ffmpeg.org/pipermail/ffmpeg-cvslog/2013-April/063616.html var isAssSubtitle = string.Equals(state.SubtitleStream.Codec, "ass", StringComparison.OrdinalIgnoreCase) || string.Equals(state.SubtitleStream.Codec, "ssa", StringComparison.OrdinalIgnoreCase); - var task = MediaEncoder.ExtractTextSubtitle(inputPath, type, state.SubtitleStream.Index, isAssSubtitle, path, CancellationToken.None); + var task = MediaEncoder.ExtractTextSubtitle(inputPath, type, state.SubtitleStream.Index, isAssSubtitle, path, cancellationToken); Task.WaitAll(task); } @@ -652,11 +659,13 @@ namespace MediaBrowser.Api.Playback /// /// Gets the converted ass path. /// - /// The media path. /// The subtitle stream. /// if set to true [perform conversion]. + /// The cancellation token. /// System.String. - private string GetConvertedAssPath(string mediaPath, MediaStream subtitleStream, bool performConversion) + private string GetConvertedAssPath(MediaStream subtitleStream, + bool performConversion, + CancellationToken cancellationToken) { var path = EncodingManager.GetSubtitleCachePath(subtitleStream.Path, ".ass"); @@ -668,7 +677,7 @@ namespace MediaBrowser.Api.Playback Directory.CreateDirectory(parentPath); - var task = MediaEncoder.ConvertTextSubtitleToAss(subtitleStream.Path, path, subtitleStream.Language, CancellationToken.None); + var task = MediaEncoder.ConvertTextSubtitleToAss(subtitleStream.Path, path, subtitleStream.Language, cancellationToken); Task.WaitAll(task); } @@ -696,7 +705,7 @@ namespace MediaBrowser.Api.Playback // Add resolution params, if specified if (request.Width.HasValue || request.Height.HasValue || request.MaxHeight.HasValue || request.MaxWidth.HasValue) { - outputSizeParam = GetOutputSizeParam(state, outputVideoCodec, false).TrimEnd('"'); + outputSizeParam = GetOutputSizeParam(state, outputVideoCodec, false, CancellationToken.None).TrimEnd('"'); outputSizeParam = "," + outputSizeParam.Substring(outputSizeParam.IndexOf("scale", StringComparison.OrdinalIgnoreCase)); } @@ -842,7 +851,7 @@ namespace MediaBrowser.Api.Playback /// System.String. protected string GetInputArgument(StreamState state) { - var type = InputType.File; + var type = state.IsRemote ? InputType.Url : InputType.File; var inputPath = new[] { state.MediaPath }; @@ -862,8 +871,10 @@ namespace MediaBrowser.Api.Playback /// /// The state. /// The output path. + /// The cancellation token source. /// Task. - protected async Task StartFfMpeg(StreamState state, string outputPath) + /// ffmpeg was not found at + MediaEncoder.EncoderPath + protected async Task StartFfMpeg(StreamState state, string outputPath, CancellationTokenSource cancellationTokenSource) { if (!File.Exists(MediaEncoder.EncoderPath)) { @@ -874,7 +885,7 @@ namespace MediaBrowser.Api.Playback if (state.IsInputVideo && state.VideoType == VideoType.Iso && state.IsoType.HasValue && IsoManager.CanMount(state.MediaPath)) { - state.IsoMount = await IsoManager.Mount(state.MediaPath, CancellationToken.None).ConfigureAwait(false); + state.IsoMount = await IsoManager.Mount(state.MediaPath, cancellationTokenSource.Token).ConfigureAwait(false); } var commandLineArgs = GetCommandLineArguments(outputPath, state, true); @@ -906,7 +917,7 @@ namespace MediaBrowser.Api.Playback EnableRaisingEvents = true }; - ApiEntryPoint.Instance.OnTranscodeBeginning(outputPath, TranscodingJobType, process, state.Request.StartTimeTicks, state.MediaPath, state.Request.DeviceId); + ApiEntryPoint.Instance.OnTranscodeBeginning(outputPath, TranscodingJobType, process, state.Request.StartTimeTicks, state.MediaPath, state.Request.DeviceId, cancellationTokenSource); var commandLineLogMessage = process.StartInfo.FileName + " " + process.StartInfo.Arguments; Logger.Info(commandLineLogMessage); @@ -918,7 +929,7 @@ namespace MediaBrowser.Api.Playback state.LogFileStream = FileSystem.GetFileStream(logFilePath, FileMode.Create, FileAccess.Write, FileShare.Read, true); var commandLineLogMessageBytes = Encoding.UTF8.GetBytes(commandLineLogMessage + Environment.NewLine + Environment.NewLine); - await state.LogFileStream.WriteAsync(commandLineLogMessageBytes, 0, commandLineLogMessageBytes.Length).ConfigureAwait(false); + await state.LogFileStream.WriteAsync(commandLineLogMessageBytes, 0, commandLineLogMessageBytes.Length, cancellationTokenSource.Token).ConfigureAwait(false); process.Exited += (sender, args) => OnFfMpegProcessExited(process, state); @@ -946,19 +957,19 @@ namespace MediaBrowser.Api.Playback // Wait for the file to exist before proceeeding while (!File.Exists(outputPath)) { - await Task.Delay(100).ConfigureAwait(false); + await Task.Delay(100, cancellationTokenSource.Token).ConfigureAwait(false); } // Allow a small amount of time to buffer a little if (state.IsInputVideo) { - await Task.Delay(500).ConfigureAwait(false); + await Task.Delay(500, cancellationTokenSource.Token).ConfigureAwait(false); } // This is arbitrary, but add a little buffer time when internet streaming if (state.IsRemote) { - await Task.Delay(3000).ConfigureAwait(false); + await Task.Delay(3000, cancellationTokenSource.Token).ConfigureAwait(false); } } @@ -1050,13 +1061,19 @@ namespace MediaBrowser.Api.Playback /// /// Gets the user agent param. /// - /// The path. + /// The state. /// System.String. - private string GetUserAgentParam(string path) + private string GetUserAgentParam(StreamState state) { - var useragent = GetUserAgent(path); + string useragent; + state.RemoteHttpHeaders.TryGetValue("User-Agent", out useragent); - if (!string.IsNullOrEmpty(useragent)) + if (string.IsNullOrWhiteSpace(useragent)) + { + useragent = GetUserAgent(state.MediaPath); + } + + if (!string.IsNullOrWhiteSpace(useragent)) { return "-user-agent \"" + useragent + "\""; } @@ -1337,9 +1354,7 @@ namespace MediaBrowser.Api.Playback state.Request.AudioCodec = state.SupportedAudioCodecs.FirstOrDefault(); } - var item = string.IsNullOrEmpty(request.MediaSourceId) ? - LibraryManager.GetItemById(request.Id) : - LibraryManager.GetItemById(request.MediaSourceId); + var item = LibraryManager.GetItemById(request.Id); if (user != null && item.GetPlayAccess(user) != PlayAccess.Full) { @@ -1427,19 +1442,24 @@ namespace MediaBrowser.Api.Playback } else if (item is IChannelMediaItem) { - var source = await GetChannelMediaInfo(request.Id, CancellationToken.None).ConfigureAwait(false); + var source = await GetChannelMediaInfo(request.Id, request.MediaSourceId, cancellationToken).ConfigureAwait(false); state.IsInputVideo = string.Equals(item.MediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase); - state.IsRemote = source.IsRemote; + state.IsRemote = source.LocationType == LocationType.Remote; state.MediaPath = source.Path; state.RunTimeTicks = item.RunTimeTicks; - mediaStreams = GetMediaStreams(source).ToList(); + state.RemoteHttpHeaders = source.RequiredHttpHeaders; + mediaStreams = source.MediaStreams; } else { - state.MediaPath = item.Path; - state.IsRemote = item.LocationType == LocationType.Remote; + var mediaSource = string.IsNullOrWhiteSpace(request.MediaSourceId) + ? item + : LibraryManager.GetItemById(request.MediaSourceId); - var video = item as Video; + state.MediaPath = mediaSource.Path; + state.IsRemote = mediaSource.LocationType == LocationType.Remote; + + var video = mediaSource as Video; if (video != null) { @@ -1461,20 +1481,20 @@ namespace MediaBrowser.Api.Playback state.InputContainer = video.Container; } - var audio = item as Audio; + var audio = mediaSource as Audio; if (audio != null) { state.InputContainer = audio.Container; } - state.RunTimeTicks = item.RunTimeTicks; + state.RunTimeTicks = mediaSource.RunTimeTicks; } var videoRequest = request as VideoStreamRequest; mediaStreams = mediaStreams ?? ItemRepository.GetMediaStreams(new MediaStreamQuery { - ItemId = item.Id + ItemId = new Guid(string.IsNullOrWhiteSpace(request.MediaSourceId) ? request.Id : request.MediaSourceId) }).ToList(); @@ -1545,65 +1565,32 @@ namespace MediaBrowser.Api.Playback } } - private IEnumerable GetMediaStreams(ChannelMediaInfo info) - { - var list = new List(); - - if (!string.IsNullOrWhiteSpace(info.VideoCodec) && - !string.IsNullOrWhiteSpace(info.AudioCodec)) - { - list.Add(new MediaStream - { - Type = MediaStreamType.Video, - Width = info.Width, - RealFrameRate = info.Framerate, - Profile = info.VideoProfile, - Level = info.VideoLevel, - Index = -1, - Height = info.Height, - Codec = info.VideoCodec, - BitRate = info.VideoBitrate, - AverageFrameRate = info.Framerate - }); - - list.Add(new MediaStream - { - Type = MediaStreamType.Audio, - Index = -1, - Codec = info.AudioCodec, - BitRate = info.AudioBitrate, - Channels = info.AudioChannels, - SampleRate = info.AudioSampleRate - }); - } - - return list; - } - - private async Task GetChannelMediaInfo(string id, CancellationToken cancellationToken) + private async Task GetChannelMediaInfo(string id, + string mediaSourceId, + CancellationToken cancellationToken) { var channelMediaSources = await ChannelManager.GetChannelItemMediaSources(id, cancellationToken) .ConfigureAwait(false); var list = channelMediaSources.ToList(); - var preferredWidth = ServerConfigurationManager.Configuration.ChannelOptions.PreferredStreamingWidth; - - if (preferredWidth.HasValue) + if (!string.IsNullOrWhiteSpace(mediaSourceId)) { - var val = preferredWidth.Value; + var source = list + .FirstOrDefault(i => string.Equals(mediaSourceId, i.Id)); - return list - .OrderBy(i => Math.Abs(i.Width ?? 0 - val)) - .ThenByDescending(i => i.Width ?? 0) - .ThenBy(list.IndexOf) - .First(); + if (source != null) + { + return source; + } + + Logger.Warn("Invalid channel MediaSourceId requested, defaulting to first. Item: {0}. Requested MediaSourceId: {1}.", + id, + mediaSourceId + ); } - return list - .OrderByDescending(i => i.Width ?? 0) - .ThenBy(list.IndexOf) - .First(); + return list.First(); } private bool CanStreamCopyVideo(VideoStreamRequest request, MediaStream videoStream) @@ -1932,7 +1919,11 @@ namespace MediaBrowser.Api.Playback inputModifier += " " + probeSize; inputModifier = inputModifier.Trim(); - inputModifier += " " + GetUserAgentParam(state.MediaPath); + if (state.IsRemote) + { + inputModifier += " " + GetUserAgentParam(state); + } + inputModifier = inputModifier.Trim(); inputModifier += " " + GetFastSeekCommandLineParameter(state.Request); diff --git a/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs b/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs index 553f023684..a7412e3d8c 100644 --- a/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs +++ b/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs @@ -25,7 +25,7 @@ namespace MediaBrowser.Api.Playback.Hls /// public abstract class BaseHlsService : BaseStreamingService { - protected BaseHlsService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IDtoService dtoService, IFileSystem fileSystem, IItemRepository itemRepository, ILiveTvManager liveTvManager, IEncodingManager encodingManager, IDlnaManager dlnaManager, IChannelManager channelManager) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, dtoService, fileSystem, itemRepository, liveTvManager, encodingManager, dlnaManager, channelManager) + protected BaseHlsService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IDtoService dtoService, IFileSystem fileSystem, IItemRepository itemRepository, ILiveTvManager liveTvManager, IEncodingManager encodingManager, IDlnaManager dlnaManager, IChannelManager channelManager, IHttpClient httpClient) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, dtoService, fileSystem, itemRepository, liveTvManager, encodingManager, dlnaManager, channelManager, httpClient) { } @@ -82,7 +82,9 @@ namespace MediaBrowser.Api.Playback.Hls /// private async Task ProcessRequestAsync(StreamRequest request) { - var state = GetState(request, CancellationToken.None).Result; + var cancellationTokenSource = new CancellationTokenSource(); + + var state = GetState(request, cancellationTokenSource.Token).Result; var playlist = GetOutputFilePath(state); @@ -92,7 +94,7 @@ namespace MediaBrowser.Api.Playback.Hls } else { - await FfmpegStartLock.WaitAsync().ConfigureAwait(false); + await FfmpegStartLock.WaitAsync(cancellationTokenSource.Token).ConfigureAwait(false); try { if (File.Exists(playlist)) @@ -104,16 +106,16 @@ namespace MediaBrowser.Api.Playback.Hls // If the playlist doesn't already exist, startup ffmpeg try { - await StartFfMpeg(state, playlist).ConfigureAwait(false); + await StartFfMpeg(state, playlist, cancellationTokenSource).ConfigureAwait(false); } catch { state.Dispose(); throw; } - } - await WaitForMinimumSegmentCount(playlist, GetSegmentWait()).ConfigureAwait(false); + await WaitForMinimumSegmentCount(playlist, GetSegmentWait(), cancellationTokenSource.Token).ConfigureAwait(false); + } } finally { @@ -220,10 +222,12 @@ namespace MediaBrowser.Api.Playback.Hls return builder.ToString(); } - protected async Task WaitForMinimumSegmentCount(string playlist, int segmentCount) + protected async Task WaitForMinimumSegmentCount(string playlist, int segmentCount, CancellationToken cancellationToken) { while (true) { + cancellationToken.ThrowIfCancellationRequested(); + string fileText; // Need to use FileShare.ReadWrite because we're reading the file at the same time it's being written @@ -240,7 +244,7 @@ namespace MediaBrowser.Api.Playback.Hls break; } - await Task.Delay(25).ConfigureAwait(false); + await Task.Delay(25, cancellationToken).ConfigureAwait(false); } } @@ -287,7 +291,7 @@ namespace MediaBrowser.Api.Playback.Hls // If performSubtitleConversions is true we're actually starting ffmpeg var startNumberParam = performSubtitleConversions ? GetStartNumber(state).ToString(UsCulture) : "0"; - var args = string.Format("{0} {1} -i {2} -map_metadata -1 -threads {3} {4} {5} -sc_threshold 0 {6} -hls_time {7} -start_number {8} -hls_list_size {9} \"{10}\"", + var args = string.Format("{0} {1} -i {2} -map_metadata -1 -threads {3} {4} {5} -sc_threshold 0 {6} -hls_time {7} -start_number {8} -hls_list_size {9} -y \"{10}\"", itsOffset, inputModifier, GetInputArgument(state), @@ -309,7 +313,7 @@ namespace MediaBrowser.Api.Playback.Hls var bitrate = hlsVideoRequest.BaselineStreamAudioBitRate ?? 64000; - var lowBitrateParams = string.Format(" -threads {0} -vn -codec:a:0 libmp3lame -ac 2 -ab {1} -hls_time {2} -start_number {3} -hls_list_size {4} \"{5}\"", + var lowBitrateParams = string.Format(" -threads {0} -vn -codec:a:0 libmp3lame -ac 2 -ab {1} -hls_time {2} -start_number {3} -hls_list_size {4} -y \"{5}\"", threads, bitrate / 2, state.SegmentLength.ToString(UsCulture), diff --git a/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs b/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs index c5405a3b5d..96ecfb055b 100644 --- a/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs +++ b/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs @@ -1,4 +1,5 @@ using MediaBrowser.Common.IO; +using MediaBrowser.Common.Net; using MediaBrowser.Controller.Channels; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Dlna; @@ -16,6 +17,7 @@ using System.IO; using System.Text; using System.Threading; using System.Threading.Tasks; +using MimeTypes = ServiceStack.MimeTypes; namespace MediaBrowser.Api.Playback.Hls { @@ -60,7 +62,7 @@ namespace MediaBrowser.Api.Playback.Hls public class DynamicHlsService : BaseHlsService { - public DynamicHlsService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IDtoService dtoService, IFileSystem fileSystem, IItemRepository itemRepository, ILiveTvManager liveTvManager, IEncodingManager encodingManager, IDlnaManager dlnaManager, IChannelManager channelManager) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, dtoService, fileSystem, itemRepository, liveTvManager, encodingManager, dlnaManager, channelManager) + public DynamicHlsService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IDtoService dtoService, IFileSystem fileSystem, IItemRepository itemRepository, ILiveTvManager liveTvManager, IEncodingManager encodingManager, IDlnaManager dlnaManager, IChannelManager channelManager, IHttpClient httpClient) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, dtoService, fileSystem, itemRepository, liveTvManager, encodingManager, dlnaManager, channelManager, httpClient) { } @@ -98,9 +100,9 @@ namespace MediaBrowser.Api.Playback.Hls if (!File.Exists(playlistPath)) { - await StartFfMpeg(state, playlistPath).ConfigureAwait(false); + await StartFfMpeg(state, playlistPath, new CancellationTokenSource()).ConfigureAwait(false); - await WaitForMinimumSegmentCount(playlistPath, GetSegmentWait()).ConfigureAwait(false); + await WaitForMinimumSegmentCount(playlistPath, GetSegmentWait(), CancellationToken.None).ConfigureAwait(false); } return GetSegementResult(path); @@ -283,7 +285,7 @@ namespace MediaBrowser.Api.Playback.Hls { if (state.VideoRequest.Width.HasValue || state.VideoRequest.Height.HasValue || state.VideoRequest.MaxHeight.HasValue || state.VideoRequest.MaxWidth.HasValue) { - args += GetOutputSizeParam(state, codec, performSubtitleConversion); + args += GetOutputSizeParam(state, codec, performSubtitleConversion, CancellationToken.None); } } diff --git a/MediaBrowser.Api/Playback/Hls/VideoHlsService.cs b/MediaBrowser.Api/Playback/Hls/VideoHlsService.cs index 162ab741aa..e4ee682411 100644 --- a/MediaBrowser.Api/Playback/Hls/VideoHlsService.cs +++ b/MediaBrowser.Api/Playback/Hls/VideoHlsService.cs @@ -1,4 +1,6 @@ +using System.Threading; using MediaBrowser.Common.IO; +using MediaBrowser.Common.Net; using MediaBrowser.Controller.Channels; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Dlna; @@ -54,7 +56,7 @@ namespace MediaBrowser.Api.Playback.Hls /// public class VideoHlsService : BaseHlsService { - public VideoHlsService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IDtoService dtoService, IFileSystem fileSystem, IItemRepository itemRepository, ILiveTvManager liveTvManager, IEncodingManager encodingManager, IDlnaManager dlnaManager, IChannelManager channelManager) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, dtoService, fileSystem, itemRepository, liveTvManager, encodingManager, dlnaManager, channelManager) + public VideoHlsService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IDtoService dtoService, IFileSystem fileSystem, IItemRepository itemRepository, ILiveTvManager liveTvManager, IEncodingManager encodingManager, IDlnaManager dlnaManager, IChannelManager channelManager, IHttpClient httpClient) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, dtoService, fileSystem, itemRepository, liveTvManager, encodingManager, dlnaManager, channelManager, httpClient) { } @@ -155,7 +157,8 @@ namespace MediaBrowser.Api.Playback.Hls /// The state. /// if set to true [perform subtitle conversion]. /// System.String. - protected override string GetVideoArguments(StreamState state, bool performSubtitleConversion) + protected override string GetVideoArguments(StreamState state, + bool performSubtitleConversion) { var codec = GetVideoCodec(state.VideoRequest); @@ -178,7 +181,7 @@ namespace MediaBrowser.Api.Playback.Hls { if (state.VideoRequest.Width.HasValue || state.VideoRequest.Height.HasValue || state.VideoRequest.MaxHeight.HasValue || state.VideoRequest.MaxWidth.HasValue) { - args += GetOutputSizeParam(state, codec, performSubtitleConversion); + args += GetOutputSizeParam(state, codec, performSubtitleConversion, CancellationToken.None); } } diff --git a/MediaBrowser.Api/Playback/Progressive/AudioService.cs b/MediaBrowser.Api/Playback/Progressive/AudioService.cs index 2e002cc9be..18cfbbdf2d 100644 --- a/MediaBrowser.Api/Playback/Progressive/AudioService.cs +++ b/MediaBrowser.Api/Playback/Progressive/AudioService.cs @@ -44,7 +44,7 @@ namespace MediaBrowser.Api.Playback.Progressive /// public class AudioService : BaseProgressiveStreamingService { - public AudioService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IDtoService dtoService, IFileSystem fileSystem, IItemRepository itemRepository, ILiveTvManager liveTvManager, IEncodingManager encodingManager, IDlnaManager dlnaManager, IChannelManager channelManager, IImageProcessor imageProcessor, IHttpClient httpClient) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, dtoService, fileSystem, itemRepository, liveTvManager, encodingManager, dlnaManager, channelManager, imageProcessor, httpClient) + public AudioService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IDtoService dtoService, IFileSystem fileSystem, IItemRepository itemRepository, ILiveTvManager liveTvManager, IEncodingManager encodingManager, IDlnaManager dlnaManager, IChannelManager channelManager, IHttpClient httpClient, IImageProcessor imageProcessor) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, dtoService, fileSystem, itemRepository, liveTvManager, encodingManager, dlnaManager, channelManager, httpClient, imageProcessor) { } @@ -105,7 +105,7 @@ namespace MediaBrowser.Api.Playback.Progressive var inputModifier = GetInputModifier(state); - return string.Format("{0} -i {1} -threads {2}{3} {4} -id3v2_version 3 -write_id3v1 1 \"{5}\"", + return string.Format("{0} -i {1} -threads {2}{3} {4} -id3v2_version 3 -write_id3v1 1 -y \"{5}\"", inputModifier, GetInputArgument(state), threads, diff --git a/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs b/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs index 06e8f7b2c9..5e3e85140e 100644 --- a/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs +++ b/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs @@ -25,12 +25,10 @@ namespace MediaBrowser.Api.Playback.Progressive public abstract class BaseProgressiveStreamingService : BaseStreamingService { protected readonly IImageProcessor ImageProcessor; - protected readonly IHttpClient HttpClient; - protected BaseProgressiveStreamingService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IDtoService dtoService, IFileSystem fileSystem, IItemRepository itemRepository, ILiveTvManager liveTvManager, IEncodingManager encodingManager, IDlnaManager dlnaManager, IChannelManager channelManager, IImageProcessor imageProcessor, IHttpClient httpClient) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, dtoService, fileSystem, itemRepository, liveTvManager, encodingManager, dlnaManager, channelManager) + protected BaseProgressiveStreamingService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IDtoService dtoService, IFileSystem fileSystem, IItemRepository itemRepository, ILiveTvManager liveTvManager, IEncodingManager encodingManager, IDlnaManager dlnaManager, IChannelManager channelManager, IHttpClient httpClient, IImageProcessor imageProcessor) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, dtoService, fileSystem, itemRepository, liveTvManager, encodingManager, dlnaManager, channelManager, httpClient) { ImageProcessor = imageProcessor; - HttpClient = httpClient; } /// @@ -280,7 +278,7 @@ namespace MediaBrowser.Api.Playback.Progressive if (!File.Exists(outputPath)) { - await StartFfMpeg(state, outputPath).ConfigureAwait(false); + await StartFfMpeg(state, outputPath, new CancellationTokenSource()).ConfigureAwait(false); } else { diff --git a/MediaBrowser.Api/Playback/Progressive/ProgressiveStreamWriter.cs b/MediaBrowser.Api/Playback/Progressive/ProgressiveStreamWriter.cs index 58f36a06c1..8ba504e14d 100644 --- a/MediaBrowser.Api/Playback/Progressive/ProgressiveStreamWriter.cs +++ b/MediaBrowser.Api/Playback/Progressive/ProgressiveStreamWriter.cs @@ -32,6 +32,7 @@ namespace MediaBrowser.Api.Playback.Progressive /// /// The path. /// The logger. + /// The file system. public ProgressiveStreamWriter(string path, ILogger logger, IFileSystem fileSystem) { Path = path; diff --git a/MediaBrowser.Api/Playback/Progressive/VideoService.cs b/MediaBrowser.Api/Playback/Progressive/VideoService.cs index 29bc3f8978..65a62aad3b 100644 --- a/MediaBrowser.Api/Playback/Progressive/VideoService.cs +++ b/MediaBrowser.Api/Playback/Progressive/VideoService.cs @@ -1,3 +1,4 @@ +using System.Threading; using MediaBrowser.Common.IO; using MediaBrowser.Common.Net; using MediaBrowser.Controller.Channels; @@ -60,7 +61,7 @@ namespace MediaBrowser.Api.Playback.Progressive /// public class VideoService : BaseProgressiveStreamingService { - public VideoService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IDtoService dtoService, IFileSystem fileSystem, IItemRepository itemRepository, ILiveTvManager liveTvManager, IEncodingManager encodingManager, IDlnaManager dlnaManager, IChannelManager channelManager, IImageProcessor imageProcessor, IHttpClient httpClient) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, dtoService, fileSystem, itemRepository, liveTvManager, encodingManager, dlnaManager, channelManager, imageProcessor, httpClient) + public VideoService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IDtoService dtoService, IFileSystem fileSystem, IItemRepository itemRepository, ILiveTvManager liveTvManager, IEncodingManager encodingManager, IDlnaManager dlnaManager, IChannelManager channelManager, IHttpClient httpClient, IImageProcessor imageProcessor) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, dtoService, fileSystem, itemRepository, liveTvManager, encodingManager, dlnaManager, channelManager, httpClient, imageProcessor) { } @@ -108,7 +109,7 @@ namespace MediaBrowser.Api.Playback.Progressive var inputModifier = GetInputModifier(state); - return string.Format("{0} -i {1}{2} {3} {4} -map_metadata -1 -threads {5} {6}{7} \"{8}\"", + return string.Format("{0} -i {1}{2} {3} {4} -map_metadata -1 -threads {5} {6}{7} -y \"{8}\"", inputModifier, GetInputArgument(state), keyFrame, @@ -156,7 +157,7 @@ namespace MediaBrowser.Api.Playback.Progressive { if (request.Width.HasValue || request.Height.HasValue || request.MaxHeight.HasValue || request.MaxWidth.HasValue) { - args += GetOutputSizeParam(state, codec, performSubtitleConversion); + args += GetOutputSizeParam(state, codec, performSubtitleConversion, CancellationToken.None); } } diff --git a/MediaBrowser.Api/Playback/ProgressiveStreamService.cs b/MediaBrowser.Api/Playback/ProgressiveStreamService.cs deleted file mode 100644 index 531f79a223..0000000000 --- a/MediaBrowser.Api/Playback/ProgressiveStreamService.cs +++ /dev/null @@ -1,73 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using MediaBrowser.Api.Playback.Progressive; - -namespace MediaBrowser.Api.Playback -{ - //public class GetProgressiveAudioStream : StreamRequest - //{ - - //} - - //public class ProgressiveStreamService : BaseApiService - //{ - // public object Get(GetProgressiveAudioStream request) - // { - // return ProcessRequest(request, false); - // } - - // /// - // /// Gets the specified request. - // /// - // /// The request. - // /// System.Object. - // public object Head(GetProgressiveAudioStream request) - // { - // return ProcessRequest(request, true); - // } - - // protected object ProcessRequest(StreamRequest request, bool isHeadRequest) - // { - // var state = GetState(request, CancellationToken.None).Result; - - // var responseHeaders = new Dictionary(); - - // if (request.Static && state.IsRemote) - // { - // AddDlnaHeaders(state, responseHeaders, true); - - // return GetStaticRemoteStreamResult(state.MediaPath, responseHeaders, isHeadRequest).Result; - // } - - // var outputPath = GetOutputFilePath(state); - // var outputPathExists = File.Exists(outputPath); - - // var isStatic = request.Static || - // (outputPathExists && !ApiEntryPoint.Instance.HasActiveTranscodingJob(outputPath, TranscodingJobType.Progressive)); - - // AddDlnaHeaders(state, responseHeaders, isStatic); - - // if (request.Static) - // { - // var contentType = state.GetMimeType(state.MediaPath); - - // return ResultFactory.GetStaticFileResult(Request, state.MediaPath, contentType, FileShare.Read, responseHeaders, isHeadRequest); - // } - - // if (outputPathExists && !ApiEntryPoint.Instance.HasActiveTranscodingJob(outputPath, TranscodingJobType.Progressive)) - // { - // var contentType = state.GetMimeType(outputPath); - - // return ResultFactory.GetStaticFileResult(Request, outputPath, contentType, FileShare.Read, responseHeaders, isHeadRequest); - // } - - // return GetStreamResult(state, responseHeaders, isHeadRequest).Result; - // } - - //} -} diff --git a/MediaBrowser.Api/Playback/StreamState.cs b/MediaBrowser.Api/Playback/StreamState.cs index f9e861e8d4..295ee44e75 100644 --- a/MediaBrowser.Api/Playback/StreamState.cs +++ b/MediaBrowser.Api/Playback/StreamState.cs @@ -31,8 +31,11 @@ namespace MediaBrowser.Api.Playback public StreamState() { PlayableStreamFileNames = new List(); + RemoteHttpHeaders = new Dictionary(StringComparer.OrdinalIgnoreCase); } + public Dictionary RemoteHttpHeaders { get; set; } + /// /// Gets or sets the log file stream. /// @@ -40,7 +43,7 @@ namespace MediaBrowser.Api.Playback public Stream LogFileStream { get; set; } public string InputContainer { get; set; } - + public MediaStream AudioStream { get; set; } public MediaStream VideoStream { get; set; } public MediaStream SubtitleStream { get; set; } @@ -277,8 +280,8 @@ namespace MediaBrowser.Api.Playback { get { - var defaultValue = string.Equals(OutputContainer, "m2ts", StringComparison.OrdinalIgnoreCase) ? - TransportStreamTimestamp.Valid : + var defaultValue = string.Equals(OutputContainer, "m2ts", StringComparison.OrdinalIgnoreCase) ? + TransportStreamTimestamp.Valid : TransportStreamTimestamp.None; return !Request.Static diff --git a/MediaBrowser.Common.Implementations/HttpClientManager/HttpClientManager.cs b/MediaBrowser.Common.Implementations/HttpClientManager/HttpClientManager.cs index 969916ef67..a4337bab46 100644 --- a/MediaBrowser.Common.Implementations/HttpClientManager/HttpClientManager.cs +++ b/MediaBrowser.Common.Implementations/HttpClientManager/HttpClientManager.cs @@ -371,7 +371,9 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager Headers = new NameValueCollection(httpResponse.Headers), - ContentLength = contentLength + ContentLength = contentLength, + + ResponseUrl = httpResponse.ResponseUri.ToString() }; } diff --git a/MediaBrowser.Common/Net/HttpResponseInfo.cs b/MediaBrowser.Common/Net/HttpResponseInfo.cs index bc03192092..10b5e1983c 100644 --- a/MediaBrowser.Common/Net/HttpResponseInfo.cs +++ b/MediaBrowser.Common/Net/HttpResponseInfo.cs @@ -15,6 +15,12 @@ namespace MediaBrowser.Common.Net /// The type of the content. public string ContentType { get; set; } + /// + /// Gets or sets the response URL. + /// + /// The response URL. + public string ResponseUrl { get; set; } + /// /// Gets or sets the content. /// diff --git a/MediaBrowser.Controller/Channels/ChannelAudioItem.cs b/MediaBrowser.Controller/Channels/ChannelAudioItem.cs index 7072d42848..8afaa73aca 100644 --- a/MediaBrowser.Controller/Channels/ChannelAudioItem.cs +++ b/MediaBrowser.Controller/Channels/ChannelAudioItem.cs @@ -28,6 +28,11 @@ namespace MediaBrowser.Controller.Channels return config.BlockUnratedItems.Contains(UnratedItem.ChannelContent); } + public override string GetUserDataKey() + { + return ExternalId; + } + public override bool SupportsLocalMetadata { get diff --git a/MediaBrowser.Controller/Channels/ChannelFolderItem.cs b/MediaBrowser.Controller/Channels/ChannelFolderItem.cs index 5be30d7c3b..afc6493e4a 100644 --- a/MediaBrowser.Controller/Channels/ChannelFolderItem.cs +++ b/MediaBrowser.Controller/Channels/ChannelFolderItem.cs @@ -35,5 +35,10 @@ namespace MediaBrowser.Controller.Channels { Tags = new List(); } + + public override string GetUserDataKey() + { + return ExternalId; + } } } diff --git a/MediaBrowser.Controller/Channels/ChannelMediaInfo.cs b/MediaBrowser.Controller/Channels/ChannelMediaInfo.cs index b38ef363a8..2e2f1912a1 100644 --- a/MediaBrowser.Controller/Channels/ChannelMediaInfo.cs +++ b/MediaBrowser.Controller/Channels/ChannelMediaInfo.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; namespace MediaBrowser.Controller.Channels { @@ -27,7 +28,7 @@ namespace MediaBrowser.Controller.Channels public ChannelMediaInfo() { - RequiredHttpHeaders = new Dictionary(); + RequiredHttpHeaders = new Dictionary(StringComparer.OrdinalIgnoreCase); IsRemote = true; } } diff --git a/MediaBrowser.Controller/Channels/ChannelVideoItem.cs b/MediaBrowser.Controller/Channels/ChannelVideoItem.cs index 572e316a0f..2f207c4201 100644 --- a/MediaBrowser.Controller/Channels/ChannelVideoItem.cs +++ b/MediaBrowser.Controller/Channels/ChannelVideoItem.cs @@ -44,7 +44,7 @@ namespace MediaBrowser.Controller.Channels } } - return base.GetUserDataKey(); + return ExternalId; } protected override bool GetBlockUnratedValue(UserConfiguration config) diff --git a/MediaBrowser.Controller/Channels/IChannel.cs b/MediaBrowser.Controller/Channels/IChannel.cs index 27334e4b94..1d7c298fab 100644 --- a/MediaBrowser.Controller/Channels/IChannel.cs +++ b/MediaBrowser.Controller/Channels/IChannel.cs @@ -49,6 +49,14 @@ namespace MediaBrowser.Controller.Channels /// Task{IEnumerable{ChannelItemInfo}}. Task> Search(ChannelSearchInfo searchInfo, User user, CancellationToken cancellationToken); + /// + /// Gets all media. + /// + /// The query. + /// The cancellation token. + /// Task{ChannelItemResult}. + Task GetAllMedia(InternalAllChannelMediaQuery query, CancellationToken cancellationToken); + /// /// Gets the channel items. /// diff --git a/MediaBrowser.Controller/Channels/IChannelManager.cs b/MediaBrowser.Controller/Channels/IChannelManager.cs index 4c2d665e54..4be38870b8 100644 --- a/MediaBrowser.Controller/Channels/IChannelManager.cs +++ b/MediaBrowser.Controller/Channels/IChannelManager.cs @@ -16,6 +16,12 @@ namespace MediaBrowser.Controller.Channels /// The factories. void AddParts(IEnumerable channels, IEnumerable factories); + /// + /// Gets the channel download path. + /// + /// The channel download path. + string ChannelDownloadPath { get; } + /// /// Gets the channel features. /// @@ -23,6 +29,12 @@ namespace MediaBrowser.Controller.Channels /// ChannelFeatures. ChannelFeatures GetChannelFeatures(string id); + /// + /// Gets all channel features. + /// + /// IEnumerable{ChannelFeatures}. + IEnumerable GetAllChannelFeatures(); + /// /// Gets the channel. /// @@ -38,6 +50,14 @@ namespace MediaBrowser.Controller.Channels /// Task{QueryResult{BaseItemDto}}. Task> GetChannels(ChannelQuery query, CancellationToken cancellationToken); + /// + /// Gets all media. + /// + /// The query. + /// The cancellation token. + /// Task{QueryResult{BaseItemDto}}. + Task> GetAllMedia(AllChannelMediaQuery query, CancellationToken cancellationToken); + /// /// Gets the channel items. /// @@ -52,6 +72,6 @@ namespace MediaBrowser.Controller.Channels /// The identifier. /// The cancellation token. /// Task{IEnumerable{ChannelMediaInfo}}. - Task> GetChannelItemMediaSources(string id, CancellationToken cancellationToken); + Task> GetChannelItemMediaSources(string id, CancellationToken cancellationToken); } } diff --git a/MediaBrowser.Controller/Channels/IChannelMediaItem.cs b/MediaBrowser.Controller/Channels/IChannelMediaItem.cs index 357856a254..db8e2b2927 100644 --- a/MediaBrowser.Controller/Channels/IChannelMediaItem.cs +++ b/MediaBrowser.Controller/Channels/IChannelMediaItem.cs @@ -7,6 +7,9 @@ namespace MediaBrowser.Controller.Channels { bool IsInfiniteStream { get; set; } + long? RunTimeTicks { get; set; } + string MediaType { get; } + ChannelMediaContentType ContentType { get; set; } List ChannelMediaSources { get; set; } diff --git a/MediaBrowser.Controller/Channels/InternalChannelFeatures.cs b/MediaBrowser.Controller/Channels/InternalChannelFeatures.cs index 57a1a5129a..44da646d04 100644 --- a/MediaBrowser.Controller/Channels/InternalChannelFeatures.cs +++ b/MediaBrowser.Controller/Channels/InternalChannelFeatures.cs @@ -11,6 +11,12 @@ namespace MediaBrowser.Controller.Channels /// true if this instance can search; otherwise, false. public bool CanSearch { get; set; } + /// + /// Gets or sets a value indicating whether this instance can get all media. + /// + /// true if this instance can get all media; otherwise, false. + public bool CanGetAllMedia { get; set; } + /// /// Gets or sets the media types. /// diff --git a/MediaBrowser.Controller/Channels/InternalChannelItemQuery.cs b/MediaBrowser.Controller/Channels/InternalChannelItemQuery.cs index 1b05e60b61..dceced14c4 100644 --- a/MediaBrowser.Controller/Channels/InternalChannelItemQuery.cs +++ b/MediaBrowser.Controller/Channels/InternalChannelItemQuery.cs @@ -17,4 +17,9 @@ namespace MediaBrowser.Controller.Channels public bool SortDescending { get; set; } } + + public class InternalAllChannelMediaQuery + { + public User User { get; set; } + } } \ No newline at end of file diff --git a/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj b/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj index 98f9417d00..63cb9210a1 100644 --- a/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj +++ b/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj @@ -284,6 +284,9 @@ Dto\IItemDto.cs + + Dto\ImageByNameInfo.cs + Dto\ImageInfo.cs diff --git a/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj b/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj index d0d93c26be..70124c3e1d 100644 --- a/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj +++ b/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj @@ -271,6 +271,9 @@ Dto\IItemDto.cs + + Dto\ImageByNameInfo.cs + Dto\ImageInfo.cs diff --git a/MediaBrowser.Model/Channels/ChannelFeatures.cs b/MediaBrowser.Model/Channels/ChannelFeatures.cs index 494ebe3a3c..2acbdf9c30 100644 --- a/MediaBrowser.Model/Channels/ChannelFeatures.cs +++ b/MediaBrowser.Model/Channels/ChannelFeatures.cs @@ -4,12 +4,30 @@ namespace MediaBrowser.Model.Channels { public class ChannelFeatures { + /// + /// Gets or sets the name. + /// + /// The name. + public string Name { get; set; } + + /// + /// Gets or sets the identifier. + /// + /// The identifier. + public string Id { get; set; } + /// /// Gets or sets a value indicating whether this instance can search. /// /// true if this instance can search; otherwise, false. public bool CanSearch { get; set; } + /// + /// Gets or sets a value indicating whether this instance can get all media. + /// + /// true if this instance can get all media; otherwise, false. + public bool CanGetAllMedia { get; set; } + /// /// Gets or sets the media types. /// @@ -44,6 +62,12 @@ namespace MediaBrowser.Model.Channels /// true if this instance can filter; otherwise, false. public bool CanFilter { get; set; } + /// + /// Gets or sets a value indicating whether this instance can download all media. + /// + /// true if this instance can download all media; otherwise, false. + public bool CanDownloadAllMedia { get; set; } + public ChannelFeatures() { MediaTypes = new List(); diff --git a/MediaBrowser.Model/Channels/ChannelQuery.cs b/MediaBrowser.Model/Channels/ChannelQuery.cs index 7c3f76fda9..bd2accf652 100644 --- a/MediaBrowser.Model/Channels/ChannelQuery.cs +++ b/MediaBrowser.Model/Channels/ChannelQuery.cs @@ -20,4 +20,33 @@ /// The limit. public int? Limit { get; set; } } + + public class AllChannelMediaQuery + { + public string[] ChannelIds { get; set; } + + /// + /// Gets or sets the user identifier. + /// + /// The user identifier. + public string UserId { get; set; } + + /// + /// Skips over a given number of items within the results. Use for paging. + /// + /// The start index. + public int? StartIndex { get; set; } + + /// + /// The maximum number of items to return + /// + /// The limit. + public int? Limit { get; set; } + + public AllChannelMediaQuery() + { + ChannelIds = new string[] { }; + } + } + } diff --git a/MediaBrowser.Model/Configuration/ServerConfiguration.cs b/MediaBrowser.Model/Configuration/ServerConfiguration.cs index 17c7e8b557..34707ecd7e 100644 --- a/MediaBrowser.Model/Configuration/ServerConfiguration.cs +++ b/MediaBrowser.Model/Configuration/ServerConfiguration.cs @@ -303,5 +303,16 @@ namespace MediaBrowser.Model.Configuration public class ChannelOptions { public int? PreferredStreamingWidth { get; set; } + + public string DownloadPath { get; set; } + public int? MaxDownloadAge { get; set; } + + public string[] DownloadingChannels { get; set; } + + public ChannelOptions() + { + DownloadingChannels = new string[] { }; + MaxDownloadAge = 30; + } } } diff --git a/MediaBrowser.Model/Dto/ImageByNameInfo.cs b/MediaBrowser.Model/Dto/ImageByNameInfo.cs new file mode 100644 index 0000000000..b7921d993d --- /dev/null +++ b/MediaBrowser.Model/Dto/ImageByNameInfo.cs @@ -0,0 +1,32 @@ + +namespace MediaBrowser.Model.Dto +{ + public class ImageByNameInfo + { + /// + /// Gets or sets the name. + /// + /// The name. + public string Name { get; set; } + /// + /// Gets or sets the theme. + /// + /// The theme. + public string Theme { get; set; } + /// + /// Gets or sets the context. + /// + /// The context. + public string Context { get; set; } + /// + /// Gets or sets the length of the file. + /// + /// The length of the file. + public long FileLength { get; set; } + /// + /// Gets or sets the format. + /// + /// The format. + public string Format { get; set; } + } +} diff --git a/MediaBrowser.Model/Dto/MediaSourceInfo.cs b/MediaBrowser.Model/Dto/MediaSourceInfo.cs index 660d4c09c8..598eacc21d 100644 --- a/MediaBrowser.Model/Dto/MediaSourceInfo.cs +++ b/MediaBrowser.Model/Dto/MediaSourceInfo.cs @@ -1,7 +1,6 @@ using MediaBrowser.Model.Entities; using MediaBrowser.Model.Extensions; using MediaBrowser.Model.MediaInfo; -using System; using System.Collections.Generic; using System.Runtime.Serialization; @@ -35,11 +34,13 @@ namespace MediaBrowser.Model.Dto public int? Bitrate { get; set; } public TransportStreamTimestamp? Timestamp { get; set; } + public Dictionary RequiredHttpHeaders { get; set; } public MediaSourceInfo() { Formats = new List(); MediaStreams = new List(); + RequiredHttpHeaders = new Dictionary(); } public int? DefaultAudioStreamIndex { get; set; } diff --git a/MediaBrowser.Model/MediaBrowser.Model.csproj b/MediaBrowser.Model/MediaBrowser.Model.csproj index cd489bb360..6b8605b7a1 100644 --- a/MediaBrowser.Model/MediaBrowser.Model.csproj +++ b/MediaBrowser.Model/MediaBrowser.Model.csproj @@ -125,6 +125,7 @@ + diff --git a/MediaBrowser.Providers/Music/AlbumMetadataService.cs b/MediaBrowser.Providers/Music/AlbumMetadataService.cs index 0a33591560..96020791e3 100644 --- a/MediaBrowser.Providers/Music/AlbumMetadataService.cs +++ b/MediaBrowser.Providers/Music/AlbumMetadataService.cs @@ -46,48 +46,51 @@ namespace MediaBrowser.Providers.Music { var updateType = base.BeforeSave(item); - var songs = item.RecursiveChildren.OfType