From bd2ea703e31522d505407a33089b95f997f6b062 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Sat, 28 Mar 2015 16:22:27 -0400 Subject: implement modular media sources --- .../Channels/ChannelDownloadScheduledTask.cs | 14 +++---- .../Channels/ChannelDynamicMediaSourceProvider.cs | 43 ++++++++++++++++++++ .../Channels/ChannelManager.cs | 47 +++++++++++++--------- 3 files changed, 76 insertions(+), 28 deletions(-) create mode 100644 MediaBrowser.Server.Implementations/Channels/ChannelDynamicMediaSourceProvider.cs (limited to 'MediaBrowser.Server.Implementations/Channels') diff --git a/MediaBrowser.Server.Implementations/Channels/ChannelDownloadScheduledTask.cs b/MediaBrowser.Server.Implementations/Channels/ChannelDownloadScheduledTask.cs index e0b616605d..980c3f31b7 100644 --- a/MediaBrowser.Server.Implementations/Channels/ChannelDownloadScheduledTask.cs +++ b/MediaBrowser.Server.Implementations/Channels/ChannelDownloadScheduledTask.cs @@ -169,7 +169,7 @@ namespace MediaBrowser.Server.Implementations.Channels foreach (var item in result.Items) { - var channelItem = (IChannelItem)item; + var channelItem = (IChannelMediaItem)item; var channelFeatures = _manager.GetChannelFeatures(channelItem.ChannelId); @@ -179,7 +179,7 @@ namespace MediaBrowser.Server.Implementations.Channels { try { - await DownloadChannelItem(item, options, cancellationToken, path); + await DownloadChannelItem(channelItem, options, cancellationToken, path); } catch (OperationCanceledException) { @@ -210,13 +210,13 @@ namespace MediaBrowser.Server.Implementations.Channels return channelOptions.DownloadSizeLimit; } - private async Task DownloadChannelItem(BaseItem item, + private async Task DownloadChannelItem(IChannelMediaItem item, ChannelOptions channelOptions, CancellationToken cancellationToken, string path) { var itemId = item.Id.ToString("N"); - var sources = await _manager.GetChannelItemMediaSources(itemId, false, cancellationToken) + var sources = await _manager.GetStaticMediaSources(item, true, cancellationToken) .ConfigureAwait(false); var cachedVersions = sources.Where(i => i.Protocol == MediaProtocol.File).ToList(); @@ -237,11 +237,9 @@ namespace MediaBrowser.Server.Implementations.Channels } } - var channelItem = (IChannelMediaItem)item; + var destination = Path.Combine(path, item.ChannelId, itemId); - var destination = Path.Combine(path, channelItem.ChannelId, itemId); - - await _manager.DownloadChannelItem(channelItem, destination, new Progress(), cancellationToken) + await _manager.DownloadChannelItem(item, destination, new Progress(), cancellationToken) .ConfigureAwait(false); await RefreshMediaSourceItem(destination, cancellationToken).ConfigureAwait(false); diff --git a/MediaBrowser.Server.Implementations/Channels/ChannelDynamicMediaSourceProvider.cs b/MediaBrowser.Server.Implementations/Channels/ChannelDynamicMediaSourceProvider.cs new file mode 100644 index 0000000000..6a7163bb3b --- /dev/null +++ b/MediaBrowser.Server.Implementations/Channels/ChannelDynamicMediaSourceProvider.cs @@ -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> GetMediaSources(IHasMediaSources item, CancellationToken cancellationToken) + { + var channelItem = item as IChannelMediaItem; + + if (channelItem != null) + { + return _channelManager.GetDynamicMediaSources(channelItem, cancellationToken); + } + + return Task.FromResult>(new List()); + } + + public Task OpenMediaSource(string openKey, CancellationToken cancellationToken) + { + throw new NotImplementedException(); + } + + public Task CloseMediaSource(string closeKey, CancellationToken cancellationToken) + { + throw new NotImplementedException(); + } + } +} diff --git a/MediaBrowser.Server.Implementations/Channels/ChannelManager.cs b/MediaBrowser.Server.Implementations/Channels/ChannelManager.cs index f0f30229ec..e22bf2e7f7 100644 --- a/MediaBrowser.Server.Implementations/Channels/ChannelManager.cs +++ b/MediaBrowser.Server.Implementations/Channels/ChannelManager.cs @@ -241,10 +241,25 @@ namespace MediaBrowser.Server.Implementations.Channels return item; } - public async Task> GetChannelItemMediaSources(string id, bool includeDynamicSources, CancellationToken cancellationToken) + public async Task> GetStaticMediaSources(IChannelMediaItem item, bool includeCachedVersions, CancellationToken cancellationToken) { - var item = (IChannelMediaItem)_libraryManager.GetItemById(id); + IEnumerable 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> GetDynamicMediaSources(IChannelMediaItem item, CancellationToken cancellationToken) + { var channel = GetChannel(item.ChannelId); var channelPlugin = GetChannelProvider(channel); @@ -252,24 +267,25 @@ namespace MediaBrowser.Server.Implementations.Channels IEnumerable results; - if (requiresCallback != null && includeDynamicSources) + if (requiresCallback != null) { results = await GetChannelItemMediaSourcesInternal(requiresCallback, item.ExternalId, cancellationToken) - .ConfigureAwait(false); + .ConfigureAwait(false); } else { - results = item.ChannelMediaSources; + results = new List(); } - var sources = SortMediaInfoResults(results).Select(i => GetMediaSource(item, i)) + var list = SortMediaInfoResults(results) + .Select(i => GetMediaSource(item, i)) + .Where(IsValidMediaSource) .ToList(); var cachedVersions = GetCachedChannelItemMediaSources(item); + list.InsertRange(0, cachedVersions); - sources.InsertRange(0, cachedVersions); - - return sources.Where(IsValidMediaSource); + return list; } private readonly ConcurrentDictionary>> _channelItemMediaInfo = @@ -297,14 +313,7 @@ namespace MediaBrowser.Server.Implementations.Channels return list; } - public IEnumerable GetCachedChannelItemMediaSources(string id) - { - var item = (IChannelMediaItem)_libraryManager.GetItemById(id); - - return GetCachedChannelItemMediaSources(item); - } - - public IEnumerable GetCachedChannelItemMediaSources(IChannelMediaItem item) + private IEnumerable GetCachedChannelItemMediaSources(IChannelMediaItem item) { var filenamePrefix = item.Id.ToString("N"); var parentPath = Path.Combine(ChannelDownloadPath, item.ChannelId); @@ -339,7 +348,6 @@ namespace MediaBrowser.Server.Implementations.Channels if (source != null) { - source.Type = MediaSourceType.Cache; return new[] { source }; } } @@ -1408,8 +1416,7 @@ namespace MediaBrowser.Server.Implementations.Channels public async Task DownloadChannelItem(IChannelMediaItem item, string destination, IProgress progress, CancellationToken cancellationToken) { - var itemId = item.Id.ToString("N"); - var sources = await GetChannelItemMediaSources(itemId, true, cancellationToken) + var sources = await GetDynamicMediaSources(item, cancellationToken) .ConfigureAwait(false); var list = sources.Where(i => i.Protocol == MediaProtocol.Http).ToList(); -- cgit v1.2.3 From 578dec0c71361be17eed68f82f20840807a9c9f4 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Sun, 29 Mar 2015 00:56:39 -0400 Subject: update stream generation --- MediaBrowser.Api/Playback/BaseStreamingService.cs | 31 ++-- MediaBrowser.Api/Playback/MediaInfoService.cs | 160 +++++++++++++-------- MediaBrowser.Api/Playback/StreamRequest.cs | 3 +- MediaBrowser.Api/Playback/StreamState.cs | 4 +- .../Library/IMediaSourceManager.cs | 25 +++- .../Library/IMediaSourceProvider.cs | 8 +- MediaBrowser.Controller/LiveTv/ILiveTvManager.cs | 16 +++ MediaBrowser.Model/Dto/MediaSourceInfo.cs | 7 +- .../Channels/ChannelDynamicMediaSourceProvider.cs | 4 +- .../Library/MediaSourceManager.cs | 155 +++++++++++++++++--- .../LiveTv/LiveTvManager.cs | 27 +++- .../LiveTv/LiveTvMediaSourceProvider.cs | 51 +++++-- .../Sync/SyncedMediaSourceProvider.cs | 8 +- 13 files changed, 377 insertions(+), 122 deletions(-) (limited to 'MediaBrowser.Server.Implementations/Channels') diff --git a/MediaBrowser.Api/Playback/BaseStreamingService.cs b/MediaBrowser.Api/Playback/BaseStreamingService.cs index 435bda2c44..2e7c9a5a7b 100644 --- a/MediaBrowser.Api/Playback/BaseStreamingService.cs +++ b/MediaBrowser.Api/Playback/BaseStreamingService.cs @@ -5,7 +5,6 @@ using MediaBrowser.Controller.Devices; using MediaBrowser.Controller.Dlna; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.LiveTv; using MediaBrowser.Controller.MediaEncoding; using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Dlna; @@ -937,7 +936,7 @@ namespace MediaBrowser.Api.Playback if (state.MediaSource.RequiresOpening) { - var mediaSource = await MediaSourceManager.OpenMediaSource(state.MediaSource.OpenKey, cancellationTokenSource.Token) + var mediaSource = await MediaSourceManager.OpenLiveStream(state.MediaSource.OpenToken, false, cancellationTokenSource.Token) .ConfigureAwait(false); AttachMediaSourceInfo(state, mediaSource, state.VideoRequest, state.RequestedUrl); @@ -946,9 +945,11 @@ namespace MediaBrowser.Api.Playback { TryStreamCopy(state, state.VideoRequest); } + } - // TODO: This is only needed for live tv - await Task.Delay(1500, cancellationTokenSource.Token).ConfigureAwait(false); + if (state.MediaSource.BufferMs.HasValue) + { + await Task.Delay(state.MediaSource.BufferMs.Value, cancellationTokenSource.Token).ConfigureAwait(false); } } @@ -1616,12 +1617,20 @@ namespace MediaBrowser.Api.Playback var archivable = item as IArchivable; state.IsInputArchive = archivable != null && archivable.IsArchive; - var mediaSources = await MediaSourceManager.GetPlayackMediaSources(request.Id, false, cancellationToken).ConfigureAwait(false); + MediaSourceInfo mediaSource = null; + if (string.IsNullOrWhiteSpace(request.LiveStreamId)) + { + var mediaSources = await MediaSourceManager.GetPlayackMediaSources(request.Id, false, cancellationToken).ConfigureAwait(false); + + mediaSource = string.IsNullOrEmpty(request.MediaSourceId) + ? mediaSources.First() + : mediaSources.First(i => string.Equals(i.Id, request.MediaSourceId)); + } + else + { + mediaSource = await MediaSourceManager.GetLiveStream(request.LiveStreamId, cancellationToken).ConfigureAwait(false); + } - var mediaSource = string.IsNullOrEmpty(request.MediaSourceId) - ? mediaSources.First() - : mediaSources.First(i => string.Equals(i.Id, request.MediaSourceId)); - var videoRequest = request as VideoStreamRequest; AttachMediaSourceInfo(state, mediaSource, videoRequest, url); @@ -1699,7 +1708,7 @@ namespace MediaBrowser.Api.Playback state.ReadInputAtNativeFramerate = mediaSource.ReadAtNativeFramerate; state.RunTimeTicks = mediaSource.RunTimeTicks; state.RemoteHttpHeaders = mediaSource.RequiredHttpHeaders; - + if (mediaSource.VideoType.HasValue) { state.VideoType = mediaSource.VideoType.Value; @@ -1713,7 +1722,7 @@ namespace MediaBrowser.Api.Playback { state.InputTimestamp = mediaSource.Timestamp.Value; } - + state.InputProtocol = mediaSource.Protocol; state.MediaPath = mediaSource.Path; state.RunTimeTicks = mediaSource.RunTimeTicks; diff --git a/MediaBrowser.Api/Playback/MediaInfoService.cs b/MediaBrowser.Api/Playback/MediaInfoService.cs index cef8a34e57..d954c5b197 100644 --- a/MediaBrowser.Api/Playback/MediaInfoService.cs +++ b/MediaBrowser.Api/Playback/MediaInfoService.cs @@ -45,6 +45,9 @@ namespace MediaBrowser.Api.Playback [ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")] public string UserId { get; set; } + [ApiMember(Name = "MaxStreamingBitrate", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "POST")] + public int? MaxStreamingBitrate { get; set; } + [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; } @@ -58,6 +61,20 @@ namespace MediaBrowser.Api.Playback public string MediaSourceId { get; set; } } + [Route("/MediaSources/Open", "POST", Summary = "Opens a media source")] + public class OpenMediaSource : IReturn + { + [ApiMember(Name = "OpenToken", Description = "OpenToken", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] + public string OpenToken { get; set; } + } + + [Route("/MediaSources/Close", "POST", Summary = "Closes a media source")] + public class CloseMediaSource : IReturnVoid + { + [ApiMember(Name = "LiveStreamId", Description = "LiveStreamId", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] + public string LiveStreamId { get; set; } + } + [Authenticated] public class MediaInfoService : BaseApiService { @@ -84,6 +101,18 @@ namespace MediaBrowser.Api.Playback return ToOptimizedResult(result); } + public async Task Post(OpenMediaSource request) + { + var result = await _mediaSourceManager.OpenLiveStream(request.OpenToken, false, CancellationToken.None).ConfigureAwait(false); + return ToOptimizedResult(result); + } + + public void Post(CloseMediaSource request) + { + var task = _mediaSourceManager.CloseLiveStream(request.LiveStreamId, CancellationToken.None); + Task.WaitAll(task); + } + public async Task Post(GetPostedPlaybackInfo request) { var info = await GetPlaybackInfo(request.Id, request.UserId, request.MediaSourceId).ConfigureAwait(false); @@ -102,7 +131,7 @@ namespace MediaBrowser.Api.Playback if (profile != null) { 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, request.MaxStreamingBitrate, request.StartTimeTicks ?? 0, mediaSourceId, request.AudioStreamIndex, request.SubtitleStreamIndex); } return ToOptimizedResult(info); @@ -158,81 +187,94 @@ namespace MediaBrowser.Api.Playback int? audioStreamIndex, int? subtitleStreamIndex) { - var streamBuilder = new StreamBuilder(); - var item = _libraryManager.GetItemById(itemId); foreach (var mediaSource in result.MediaSources) { - var options = new VideoOptions - { - MediaSources = new List { mediaSource }, - Context = EncodingContext.Streaming, - DeviceId = auth.DeviceId, - ItemId = item.Id.ToString("N"), - Profile = profile, - MaxBitrate = maxBitrate - }; - - if (string.Equals(mediaSourceId, mediaSource.Id, StringComparison.OrdinalIgnoreCase)) - { - options.MediaSourceId = mediaSourceId; - options.AudioStreamIndex = audioStreamIndex; - options.SubtitleStreamIndex = subtitleStreamIndex; - } + SetDeviceSpecificData(item, mediaSource, profile, auth, maxBitrate, startTimeTicks, mediaSourceId, audioStreamIndex, subtitleStreamIndex); + } - if (mediaSource.SupportsDirectPlay) - { - var supportsDirectStream = mediaSource.SupportsDirectStream; + SortMediaSources(result); + } - // Dummy this up to fool StreamBuilder - mediaSource.SupportsDirectStream = true; + private void SetDeviceSpecificData(BaseItem item, + MediaSourceInfo mediaSource, + DeviceProfile profile, + AuthorizationInfo auth, + int? maxBitrate, + long startTimeTicks, + string mediaSourceId, + int? audioStreamIndex, + int? subtitleStreamIndex) + { + var streamBuilder = new StreamBuilder(); - // The MediaSource supports direct stream, now test to see if the client supports it - var streamInfo = string.Equals(item.MediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase) ? - streamBuilder.BuildAudioItem(options) : - streamBuilder.BuildVideoItem(options); + var options = new VideoOptions + { + MediaSources = new List { mediaSource }, + Context = EncodingContext.Streaming, + DeviceId = auth.DeviceId, + ItemId = item.Id.ToString("N"), + Profile = profile, + MaxBitrate = maxBitrate + }; + + if (string.Equals(mediaSourceId, mediaSource.Id, StringComparison.OrdinalIgnoreCase)) + { + options.MediaSourceId = mediaSourceId; + options.AudioStreamIndex = audioStreamIndex; + options.SubtitleStreamIndex = subtitleStreamIndex; + } - if (streamInfo == null || !streamInfo.IsDirectStream) - { - mediaSource.SupportsDirectPlay = false; - } + if (mediaSource.SupportsDirectPlay) + { + var supportsDirectStream = mediaSource.SupportsDirectStream; - // Set this back to what it was - mediaSource.SupportsDirectStream = supportsDirectStream; - } + // Dummy this up to fool StreamBuilder + mediaSource.SupportsDirectStream = true; - if (mediaSource.SupportsDirectStream) + // The MediaSource supports direct stream, now test to see if the client supports it + var streamInfo = string.Equals(item.MediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase) ? + streamBuilder.BuildAudioItem(options) : + streamBuilder.BuildVideoItem(options); + + if (streamInfo == null || !streamInfo.IsDirectStream) { - // The MediaSource supports direct stream, now test to see if the client supports it - var streamInfo = string.Equals(item.MediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase) ? - streamBuilder.BuildAudioItem(options) : - streamBuilder.BuildVideoItem(options); - - if (streamInfo == null || !streamInfo.IsDirectStream) - { - mediaSource.SupportsDirectStream = false; - } + mediaSource.SupportsDirectPlay = false; } - if (mediaSource.SupportsTranscoding) + // Set this back to what it was + mediaSource.SupportsDirectStream = supportsDirectStream; + } + + if (mediaSource.SupportsDirectStream) + { + // The MediaSource supports direct stream, now test to see if the client supports it + var streamInfo = string.Equals(item.MediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase) ? + streamBuilder.BuildAudioItem(options) : + streamBuilder.BuildVideoItem(options); + + if (streamInfo == null || !streamInfo.IsDirectStream) { - // The MediaSource supports direct stream, now test to see if the client supports it - var streamInfo = string.Equals(item.MediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase) ? - streamBuilder.BuildAudioItem(options) : - streamBuilder.BuildVideoItem(options); - - if (streamInfo != null && streamInfo.PlayMethod == PlayMethod.Transcode) - { - streamInfo.StartPositionTicks = startTimeTicks; - mediaSource.TranscodingUrl = streamInfo.ToUrl("-", auth.Token).Substring(1); - mediaSource.TranscodingContainer = streamInfo.Container; - mediaSource.TranscodingSubProtocol = streamInfo.SubProtocol; - } + mediaSource.SupportsDirectStream = false; } } - SortMediaSources(result); + if (mediaSource.SupportsTranscoding) + { + // The MediaSource supports direct stream, now test to see if the client supports it + var streamInfo = string.Equals(item.MediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase) ? + streamBuilder.BuildAudioItem(options) : + streamBuilder.BuildVideoItem(options); + + if (streamInfo != null && streamInfo.PlayMethod == PlayMethod.Transcode) + { + streamInfo.StartPositionTicks = startTimeTicks; + mediaSource.TranscodingUrl = streamInfo.ToUrl("-", auth.Token).Substring(1); + mediaSource.TranscodingContainer = streamInfo.Container; + mediaSource.TranscodingSubProtocol = streamInfo.SubProtocol; + } + } } private void SortMediaSources(PlaybackInfoResponse result) diff --git a/MediaBrowser.Api/Playback/StreamRequest.cs b/MediaBrowser.Api/Playback/StreamRequest.cs index b52260b506..7ed4fcd965 100644 --- a/MediaBrowser.Api/Playback/StreamRequest.cs +++ b/MediaBrowser.Api/Playback/StreamRequest.cs @@ -72,8 +72,7 @@ namespace MediaBrowser.Api.Playback public string Params { get; set; } public string ClientTime { get; set; } public string StreamId { get; set; } - - public string TranscodingJobId { get; set; } + public string LiveStreamId { get; set; } } public class VideoStreamRequest : StreamRequest diff --git a/MediaBrowser.Api/Playback/StreamState.cs b/MediaBrowser.Api/Playback/StreamState.cs index 37f2c7702c..b097f3b6af 100644 --- a/MediaBrowser.Api/Playback/StreamState.cs +++ b/MediaBrowser.Api/Playback/StreamState.cs @@ -182,11 +182,11 @@ namespace MediaBrowser.Api.Playback private async void DisposeLiveStream() { - if (MediaSource.RequiresClosing) + if (MediaSource.RequiresClosing && string.IsNullOrWhiteSpace(Request.LiveStreamId)) { try { - await _mediaSourceManager.CloseMediaSource(MediaSource.CloseKey, CancellationToken.None).ConfigureAwait(false); + await _mediaSourceManager.CloseLiveStream(MediaSource.LiveStreamId, CancellationToken.None).ConfigureAwait(false); } catch (Exception ex) { diff --git a/MediaBrowser.Controller/Library/IMediaSourceManager.cs b/MediaBrowser.Controller/Library/IMediaSourceManager.cs index fda17aa279..292205c033 100644 --- a/MediaBrowser.Controller/Library/IMediaSourceManager.cs +++ b/MediaBrowser.Controller/Library/IMediaSourceManager.cs @@ -84,17 +84,34 @@ namespace MediaBrowser.Controller.Library /// /// Opens the media source. /// - /// The open key. + /// The open token. + /// if set to true [enable automatic close]. /// The cancellation token. /// Task<MediaSourceInfo>. - Task OpenMediaSource(string openKey, CancellationToken cancellationToken); + Task OpenLiveStream(string openToken, bool enableAutoClose, CancellationToken cancellationToken); + + /// + /// Gets the live stream. + /// + /// The identifier. + /// The cancellation token. + /// Task<MediaSourceInfo>. + Task GetLiveStream(string id, CancellationToken cancellationToken); + + /// + /// Pings the media source. + /// + /// The live stream identifier. + /// The cancellation token. + /// Task. + Task PingLiveStream(string id, CancellationToken cancellationToken); /// /// Closes the media source. /// - /// The close key. + /// The live stream identifier. /// The cancellation token. /// Task. - Task CloseMediaSource(string closeKey, CancellationToken cancellationToken); + Task CloseLiveStream(string id, CancellationToken cancellationToken); } } diff --git a/MediaBrowser.Controller/Library/IMediaSourceProvider.cs b/MediaBrowser.Controller/Library/IMediaSourceProvider.cs index c5f5b54010..5b033af4af 100644 --- a/MediaBrowser.Controller/Library/IMediaSourceProvider.cs +++ b/MediaBrowser.Controller/Library/IMediaSourceProvider.cs @@ -19,17 +19,17 @@ namespace MediaBrowser.Controller.Library /// /// Opens the media source. /// - /// The open key. + /// The open token. /// The cancellation token. /// Task<MediaSourceInfo>. - Task OpenMediaSource(string openKey, CancellationToken cancellationToken); + Task OpenMediaSource(string openToken, CancellationToken cancellationToken); /// /// Closes the media source. /// - /// The close key. + /// The live stream identifier. /// The cancellation token. /// Task. - Task CloseMediaSource(string closeKey, CancellationToken cancellationToken); + Task CloseMediaSource(string liveStreamId, CancellationToken cancellationToken); } } diff --git a/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs b/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs index 0b58a92328..d5b5d92a6e 100644 --- a/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs +++ b/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs @@ -301,5 +301,21 @@ namespace MediaBrowser.Controller.LiveTv /// The cancellation token. /// Task<QueryResult<BaseItem>>. Task> GetInternalRecordings(RecordingQuery query, CancellationToken cancellationToken); + + /// + /// Gets the recording media sources. + /// + /// The identifier. + /// The cancellation token. + /// Task<IEnumerable<MediaSourceInfo>>. + Task> GetRecordingMediaSources(string id, CancellationToken cancellationToken); + + /// + /// Gets the channel media sources. + /// + /// The identifier. + /// The cancellation token. + /// Task<IEnumerable<MediaSourceInfo>>. + Task> GetChannelMediaSources(string id, CancellationToken cancellationToken); } } diff --git a/MediaBrowser.Model/Dto/MediaSourceInfo.cs b/MediaBrowser.Model/Dto/MediaSourceInfo.cs index 92af8d671c..3b45137241 100644 --- a/MediaBrowser.Model/Dto/MediaSourceInfo.cs +++ b/MediaBrowser.Model/Dto/MediaSourceInfo.cs @@ -27,10 +27,11 @@ namespace MediaBrowser.Model.Dto public bool SupportsDirectPlay { get; set; } public bool RequiresOpening { get; set; } - public string OpenKey { get; set; } + public string OpenToken { get; set; } public bool RequiresClosing { get; set; } - public string CloseKey { get; set; } - + public string LiveStreamId { get; set; } + public int? BufferMs { get; set; } + public VideoType? VideoType { get; set; } public IsoType? IsoType { get; set; } diff --git a/MediaBrowser.Server.Implementations/Channels/ChannelDynamicMediaSourceProvider.cs b/MediaBrowser.Server.Implementations/Channels/ChannelDynamicMediaSourceProvider.cs index 6a7163bb3b..dac3a80f2c 100644 --- a/MediaBrowser.Server.Implementations/Channels/ChannelDynamicMediaSourceProvider.cs +++ b/MediaBrowser.Server.Implementations/Channels/ChannelDynamicMediaSourceProvider.cs @@ -30,12 +30,12 @@ namespace MediaBrowser.Server.Implementations.Channels return Task.FromResult>(new List()); } - public Task OpenMediaSource(string openKey, CancellationToken cancellationToken) + public Task OpenMediaSource(string openToken, CancellationToken cancellationToken) { throw new NotImplementedException(); } - public Task CloseMediaSource(string closeKey, CancellationToken cancellationToken) + public Task CloseMediaSource(string liveStreamId, CancellationToken cancellationToken) { throw new NotImplementedException(); } diff --git a/MediaBrowser.Server.Implementations/Library/MediaSourceManager.cs b/MediaBrowser.Server.Implementations/Library/MediaSourceManager.cs index 40cf240d7e..3dbcf4aad4 100644 --- a/MediaBrowser.Server.Implementations/Library/MediaSourceManager.cs +++ b/MediaBrowser.Server.Implementations/Library/MediaSourceManager.cs @@ -207,14 +207,14 @@ namespace MediaBrowser.Server.Implementations.Library { var prefix = provider.GetType().FullName.GetMD5().ToString("N") + "|"; - if (!string.IsNullOrWhiteSpace(mediaSource.OpenKey) && !mediaSource.OpenKey.StartsWith(prefix, StringComparison.OrdinalIgnoreCase)) + if (!string.IsNullOrWhiteSpace(mediaSource.OpenToken) && !mediaSource.OpenToken.StartsWith(prefix, StringComparison.OrdinalIgnoreCase)) { - mediaSource.OpenKey = prefix + mediaSource.OpenKey; + mediaSource.OpenToken = prefix + mediaSource.OpenToken; } - if (!string.IsNullOrWhiteSpace(mediaSource.CloseKey) && !mediaSource.CloseKey.StartsWith(prefix, StringComparison.OrdinalIgnoreCase)) + if (!string.IsNullOrWhiteSpace(mediaSource.LiveStreamId) && !mediaSource.LiveStreamId.StartsWith(prefix, StringComparison.OrdinalIgnoreCase)) { - mediaSource.CloseKey = prefix + mediaSource.CloseKey; + mediaSource.LiveStreamId = prefix + mediaSource.LiveStreamId; } } @@ -314,24 +314,41 @@ namespace MediaBrowser.Server.Implementations.Library return GetStaticMediaSources(item, enablePathSubstitution).FirstOrDefault(i => string.Equals(i.Id, mediaSourceId, StringComparison.OrdinalIgnoreCase)); } - private readonly ConcurrentDictionary _openStreams = - new ConcurrentDictionary(); + private readonly ConcurrentDictionary _openStreams = new ConcurrentDictionary(); private readonly SemaphoreSlim _liveStreamSemaphore = new SemaphoreSlim(1, 1); - public async Task OpenMediaSource(string openKey, CancellationToken cancellationToken) + + public async Task OpenLiveStream(string openToken, bool enableAutoClose, CancellationToken cancellationToken) { await _liveStreamSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false); try { - var tuple = GetProvider(openKey); + var tuple = GetProvider(openToken); 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); - + var info = new LiveStreamInfo + { + Date = DateTime.UtcNow, + EnableCloseTimer = enableAutoClose, + Id = mediaSource.LiveStreamId, + MediaSource = mediaSource + }; + _openStreams.AddOrUpdate(mediaSource.LiveStreamId, info, (key, i) => info); + + if (enableAutoClose) + { + StartCloseTimer(); + } + + if (!string.IsNullOrWhiteSpace(mediaSource.TranscodingUrl)) + { + mediaSource.TranscodingUrl += "&LiveStreamId=" + mediaSource.LiveStreamId; + } + return mediaSource; } finally @@ -340,18 +357,70 @@ namespace MediaBrowser.Server.Implementations.Library } } - public async Task CloseMediaSource(string closeKey, CancellationToken cancellationToken) + public async Task GetLiveStream(string id, CancellationToken cancellationToken) + { + await _liveStreamSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false); + + try + { + LiveStreamInfo info; + if (_openStreams.TryGetValue(id, out info)) + { + return info.MediaSource; + } + else + { + throw new ResourceNotFoundException(); + } + } + finally + { + _liveStreamSemaphore.Release(); + } + } + + public async Task PingLiveStream(string id, CancellationToken cancellationToken) + { + await _liveStreamSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false); + + try + { + LiveStreamInfo info; + if (_openStreams.TryGetValue(id, out info)) + { + info.Date = DateTime.UtcNow; + } + else + { + _logger.Error("Failed to update MediaSource timestamp for {0}", id); + } + } + finally + { + _liveStreamSemaphore.Release(); + } + } + + public async Task CloseLiveStream(string id, CancellationToken cancellationToken) { await _liveStreamSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false); try { - var tuple = GetProvider(closeKey); + var tuple = GetProvider(id); - await tuple.Item1.OpenMediaSource(tuple.Item2, cancellationToken).ConfigureAwait(false); + await tuple.Item1.CloseMediaSource(tuple.Item2, cancellationToken).ConfigureAwait(false); - string removedKey; - _openStreams.TryRemove(closeKey, out removedKey); + LiveStreamInfo removed; + if (_openStreams.TryRemove(id, out removed)) + { + removed.Closed = true; + } + + if (_openStreams.Count == 0) + { + StopCloseTimer(); + } } finally { @@ -368,11 +437,56 @@ namespace MediaBrowser.Server.Implementations.Library return new Tuple(provider, keys[1]); } + private Timer _closeTimer; + private readonly TimeSpan _openStreamMaxAge = TimeSpan.FromSeconds(40); + + private void StartCloseTimer() + { + StopCloseTimer(); + + _closeTimer = new Timer(CloseTimerCallback, null, _openStreamMaxAge, _openStreamMaxAge); + } + + private void StopCloseTimer() + { + var timer = _closeTimer; + + if (timer != null) + { + _closeTimer = null; + timer.Dispose(); + } + } + + private async void CloseTimerCallback(object state) + { + var infos = _openStreams + .Values + .Where(i => i.EnableCloseTimer && (DateTime.UtcNow - i.Date) > _openStreamMaxAge) + .ToList(); + + foreach (var info in infos) + { + if (!info.Closed) + { + try + { + await CloseLiveStream(info.Id, CancellationToken.None).ConfigureAwait(false); + } + catch (Exception ex) + { + _logger.ErrorException("Error closing media source", ex); + } + } + } + } + /// /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. /// public void Dispose() { + StopCloseTimer(); Dispose(true); } @@ -389,7 +503,7 @@ namespace MediaBrowser.Server.Implementations.Library { foreach (var key in _openStreams.Keys.ToList()) { - var task = CloseMediaSource(key, CancellationToken.None); + var task = CloseLiveStream(key, CancellationToken.None); Task.WaitAll(task); } @@ -398,5 +512,14 @@ namespace MediaBrowser.Server.Implementations.Library } } } + + private class LiveStreamInfo + { + public DateTime Date; + public bool EnableCloseTimer; + public string Id; + public bool Closed; + public MediaSourceInfo MediaSource; + } } } diff --git a/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs b/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs index 202a051e3b..86b31e0e5d 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs @@ -313,6 +313,22 @@ namespace MediaBrowser.Server.Implementations.LiveTv return await GetLiveStream(id, true, cancellationToken).ConfigureAwait(false); } + public async Task> GetRecordingMediaSources(string id, CancellationToken cancellationToken) + { + var item = await GetInternalRecording(id, cancellationToken).ConfigureAwait(false); + var service = GetService(item); + + return await service.GetRecordingStreamMediaSources(id, cancellationToken).ConfigureAwait(false); + } + + public async Task> GetChannelMediaSources(string id, CancellationToken cancellationToken) + { + var item = GetInternalChannel(id); + var service = GetService(item); + + return await service.GetChannelStreamMediaSources(id, cancellationToken).ConfigureAwait(false); + } + private ILiveTvService GetService(ILiveTvItem item) { return GetService(item.ServiceName); @@ -330,7 +346,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv try { MediaSourceInfo info; - var isVideo = true; + bool isVideo; if (isChannel) { @@ -340,7 +356,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv _logger.Info("Opening channel stream from {0}, external channel Id: {1}", service.Name, channel.ExternalId); info = await service.GetChannelStream(channel.ExternalId, null, cancellationToken).ConfigureAwait(false); info.RequiresClosing = true; - info.CloseKey = info.Id; + info.LiveStreamId = info.Id; } else { @@ -351,7 +367,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv _logger.Info("Opening recording stream from {0}, external recording Id: {1}", service.Name, recording.RecordingInfo.Id); info = await service.GetRecordingStream(recording.RecordingInfo.Id, null, cancellationToken).ConfigureAwait(false); info.RequiresClosing = true; - info.CloseKey = info.Id; + info.LiveStreamId = info.Id; } _logger.Info("Live stream info: {0}", _jsonSerializer.SerializeToString(info)); @@ -393,7 +409,10 @@ namespace MediaBrowser.Server.Implementations.LiveTv { Type = MediaStreamType.Video, // Set the index to -1 because we don't know the exact index of the video stream within the container - Index = -1 + Index = -1, + + // Set to true if unknown to enable deinterlacing + IsInterlaced = true }, new MediaStream { diff --git a/MediaBrowser.Server.Implementations/LiveTv/LiveTvMediaSourceProvider.cs b/MediaBrowser.Server.Implementations/LiveTv/LiveTvMediaSourceProvider.cs index 186bc499db..5de4cf4998 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/LiveTvMediaSourceProvider.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/LiveTvMediaSourceProvider.cs @@ -2,6 +2,8 @@ using MediaBrowser.Controller.Library; using MediaBrowser.Controller.LiveTv; using MediaBrowser.Model.Dto; +using MediaBrowser.Model.Logging; +using MediaBrowser.Model.Serialization; using System; using System.Collections.Generic; using System.Linq; @@ -13,10 +15,14 @@ namespace MediaBrowser.Server.Implementations.LiveTv public class LiveTvMediaSourceProvider : IMediaSourceProvider { private readonly ILiveTvManager _liveTvManager; + private readonly IJsonSerializer _jsonSerializer; + private readonly ILogger _logger; - public LiveTvMediaSourceProvider(ILiveTvManager liveTvManager) + public LiveTvMediaSourceProvider(ILiveTvManager liveTvManager, IJsonSerializer jsonSerializer, ILogManager logManager) { _liveTvManager = liveTvManager; + _jsonSerializer = jsonSerializer; + _logger = logManager.GetLogger(GetType().Name); } public Task> GetMediaSources(IHasMediaSources item, CancellationToken cancellationToken) @@ -38,28 +44,51 @@ namespace MediaBrowser.Server.Implementations.LiveTv private async Task> GetMediaSourcesInternal(ILiveTvItem item, CancellationToken cancellationToken) { - var hasMediaSources = (IHasMediaSources)item; + IEnumerable sources; - var sources = hasMediaSources.GetMediaSources(false) - .ToList(); + try + { + if (item is ILiveTvRecording) + { + sources = await _liveTvManager.GetRecordingMediaSources(item.Id.ToString("N"), cancellationToken) + .ConfigureAwait(false); + } + else + { + sources = await _liveTvManager.GetChannelMediaSources(item.Id.ToString("N"), cancellationToken) + .ConfigureAwait(false); + } + } + catch (NotImplementedException) + { + var hasMediaSources = (IHasMediaSources)item; + + sources = hasMediaSources.GetMediaSources(false) + .ToList(); + } - foreach (var source in sources) + var list = sources.ToList(); + + foreach (var source in list) { source.Type = MediaSourceType.Default; source.RequiresOpening = true; + source.BufferMs = source.BufferMs ?? 1500; var openKeys = new List(); openKeys.Add(item.GetType().Name); openKeys.Add(item.Id.ToString("N")); - source.OpenKey = string.Join("|", openKeys.ToArray()); + source.OpenToken = string.Join("|", openKeys.ToArray()); } - return sources; + _logger.Debug("MediaSources: {0}", _jsonSerializer.SerializeToString(list)); + + return list; } - public async Task OpenMediaSource(string openKey, CancellationToken cancellationToken) + public async Task OpenMediaSource(string openToken, CancellationToken cancellationToken) { - var keys = openKey.Split(new[] { '|' }, 2); + var keys = openToken.Split(new[] { '|' }, 2); if (string.Equals(keys[0], typeof(LiveTvChannel).Name, StringComparison.OrdinalIgnoreCase)) { @@ -69,9 +98,9 @@ namespace MediaBrowser.Server.Implementations.LiveTv return await _liveTvManager.GetRecordingStream(keys[1], cancellationToken).ConfigureAwait(false); } - public Task CloseMediaSource(string closeKey, CancellationToken cancellationToken) + public Task CloseMediaSource(string liveStreamId, CancellationToken cancellationToken) { - return _liveTvManager.CloseLiveStream(closeKey, cancellationToken); + return _liveTvManager.CloseLiveStream(liveStreamId, cancellationToken); } } } diff --git a/MediaBrowser.Server.Implementations/Sync/SyncedMediaSourceProvider.cs b/MediaBrowser.Server.Implementations/Sync/SyncedMediaSourceProvider.cs index 25a52fb950..1c17b99936 100644 --- a/MediaBrowser.Server.Implementations/Sync/SyncedMediaSourceProvider.cs +++ b/MediaBrowser.Server.Implementations/Sync/SyncedMediaSourceProvider.cs @@ -90,13 +90,13 @@ namespace MediaBrowser.Server.Implementations.Sync 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()); + mediaSource.OpenToken = string.Join("|", keyList.ToArray()); } } - public async Task OpenMediaSource(string openKey, CancellationToken cancellationToken) + public async Task OpenMediaSource(string openToken, CancellationToken cancellationToken) { - var openKeys = openKey.Split(new[] { '|' }, 3); + var openKeys = openToken.Split(new[] { '|' }, 3); var provider = _syncManager.ServerSyncProviders .FirstOrDefault(i => string.Equals(openKeys[0], i.GetType().FullName.GetMD5().ToString("N"), StringComparison.OrdinalIgnoreCase)); @@ -133,7 +133,7 @@ namespace MediaBrowser.Server.Implementations.Sync mediaSource.SupportsTranscoding = false; } - public Task CloseMediaSource(string closeKey, CancellationToken cancellationToken) + public Task CloseMediaSource(string liveStreamId, CancellationToken cancellationToken) { throw new NotImplementedException(); } -- cgit v1.2.3 From 5965afecde2536c0a2ec566fd6afdb21add8aa50 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Tue, 31 Mar 2015 14:50:08 -0400 Subject: live tv fix --- MediaBrowser.Api/Playback/MediaInfoService.cs | 23 ------ MediaBrowser.Api/Playback/TranscodingThrottler.cs | 2 +- .../HttpClientManager/HttpClientManager.cs | 32 +++++--- MediaBrowser.Controller/LiveTv/LiveTvProgram.cs | 6 ++ MediaBrowser.Controller/Sync/ISyncDataProvider.cs | 16 ++++ MediaBrowser.Model/ApiClient/IApiClient.cs | 5 +- .../MediaInfo/PlaybackInfoRequest.cs | 16 ++++ MediaBrowser.Model/Sync/LocalItem.cs | 5 ++ MediaBrowser.Model/Sync/SyncDataRequest.cs | 1 + .../Channels/ChannelImageProvider.cs | 1 - .../Library/MediaSourceManager.cs | 10 +-- .../LiveTv/LiveTvDtoService.cs | 2 +- .../LiveTv/LiveTvManager.cs | 6 +- .../Sync/MediaSync.cs | 11 +-- .../Sync/SyncManager.cs | 95 +++++++++++++++++++++- .../Sync/TargetDataProvider.cs | 30 ++++++- Nuget/MediaBrowser.Common.Internal.nuspec | 4 +- Nuget/MediaBrowser.Common.nuspec | 2 +- Nuget/MediaBrowser.Model.Signed.nuspec | 2 +- Nuget/MediaBrowser.Server.Core.nuspec | 4 +- 20 files changed, 207 insertions(+), 66 deletions(-) (limited to 'MediaBrowser.Server.Implementations/Channels') diff --git a/MediaBrowser.Api/Playback/MediaInfoService.cs b/MediaBrowser.Api/Playback/MediaInfoService.cs index 0930c00024..ca735f0684 100644 --- a/MediaBrowser.Api/Playback/MediaInfoService.cs +++ b/MediaBrowser.Api/Playback/MediaInfoService.cs @@ -39,29 +39,6 @@ namespace MediaBrowser.Api.Playback [Route("/Items/{Id}/PlaybackInfo", "POST", Summary = "Gets live playback media info for an item")] public class GetPostedPlaybackInfo : PlaybackInfoRequest, IReturn { - [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] - public string Id { get; set; } - - [ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")] - public string UserId { get; set; } - - [ApiMember(Name = "MaxStreamingBitrate", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "POST")] - public int? MaxStreamingBitrate { get; set; } - - [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; } - - [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; } - - [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; } - - [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; } - - [ApiMember(Name = "LiveStreamId", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] - public string LiveStreamId { get; set; } } [Route("/LiveStreams/Open", "POST", Summary = "Opens a media source")] diff --git a/MediaBrowser.Api/Playback/TranscodingThrottler.cs b/MediaBrowser.Api/Playback/TranscodingThrottler.cs index ff79bb48f4..58cfa086e3 100644 --- a/MediaBrowser.Api/Playback/TranscodingThrottler.cs +++ b/MediaBrowser.Api/Playback/TranscodingThrottler.cs @@ -42,7 +42,7 @@ namespace MediaBrowser.Api.Playback var options = GetOptions(); - if (/*options.EnableThrottling &&*/ IsThrottleAllowed(_job, options.ThrottleThresholdSeconds)) + if (options.EnableThrottling && IsThrottleAllowed(_job, options.ThrottleThresholdSeconds)) { PauseTranscoding(); } diff --git a/MediaBrowser.Common.Implementations/HttpClientManager/HttpClientManager.cs b/MediaBrowser.Common.Implementations/HttpClientManager/HttpClientManager.cs index b925649fce..94c91c55aa 100644 --- a/MediaBrowser.Common.Implementations/HttpClientManager/HttpClientManager.cs +++ b/MediaBrowser.Common.Implementations/HttpClientManager/HttpClientManager.cs @@ -107,30 +107,40 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager private WebRequest GetRequest(HttpRequestOptions options, string method, bool enableHttpCompression) { - var request = (HttpWebRequest)WebRequest.Create(options.Url); + var request = WebRequest.Create(options.Url); + var httpWebRequest = request as HttpWebRequest; - AddRequestHeaders(request, options); + if (httpWebRequest != null) + { + AddRequestHeaders(httpWebRequest, options); - request.AutomaticDecompression = enableHttpCompression ? DecompressionMethods.Deflate : DecompressionMethods.None; + httpWebRequest.AutomaticDecompression = enableHttpCompression ? DecompressionMethods.Deflate : DecompressionMethods.None; + } request.CachePolicy = new RequestCachePolicy(RequestCacheLevel.BypassCache); - if (options.EnableKeepAlive) + if (httpWebRequest != null) { - request.KeepAlive = true; + if (options.EnableKeepAlive) + { + httpWebRequest.KeepAlive = true; + } } request.Method = method; request.Timeout = options.TimeoutMs; - if (!string.IsNullOrEmpty(options.Host)) + if (httpWebRequest != null) { - request.Host = options.Host; - } + if (!string.IsNullOrEmpty(options.Host)) + { + httpWebRequest.Host = options.Host; + } - if (!string.IsNullOrEmpty(options.Referer)) - { - request.Referer = options.Referer; + if (!string.IsNullOrEmpty(options.Referer)) + { + httpWebRequest.Referer = options.Referer; + } } //request.ServicePoint.BindIPEndPointDelegate = BindIPEndPointCallback; diff --git a/MediaBrowser.Controller/LiveTv/LiveTvProgram.cs b/MediaBrowser.Controller/LiveTv/LiveTvProgram.cs index 0b07d8b6d1..0609df4c6b 100644 --- a/MediaBrowser.Controller/LiveTv/LiveTvProgram.cs +++ b/MediaBrowser.Controller/LiveTv/LiveTvProgram.cs @@ -34,6 +34,12 @@ namespace MediaBrowser.Controller.LiveTv /// The channel identifier. public string ExternalChannelId { get; set; } + /// + /// Gets or sets the original air date. + /// + /// The original air date. + public DateTime? OriginalAirDate { get; set; } + /// /// Gets or sets the type of the channel. /// diff --git a/MediaBrowser.Controller/Sync/ISyncDataProvider.cs b/MediaBrowser.Controller/Sync/ISyncDataProvider.cs index f84748b971..04101fd465 100644 --- a/MediaBrowser.Controller/Sync/ISyncDataProvider.cs +++ b/MediaBrowser.Controller/Sync/ISyncDataProvider.cs @@ -14,6 +14,14 @@ namespace MediaBrowser.Controller.Sync /// Task<List<System.String>>. Task> GetServerItemIds(SyncTarget target, string serverId); + /// + /// Gets the synchronize job item ids. + /// + /// The target. + /// The server identifier. + /// Task<List<System.String>>. + Task> GetSyncJobItemIds(SyncTarget target, string serverId); + /// /// Adds the or update. /// @@ -46,5 +54,13 @@ namespace MediaBrowser.Controller.Sync /// The item identifier. /// Task<LocalItem>. Task> GetCachedItems(SyncTarget target, string serverId, string itemId); + /// + /// Gets the cached items by synchronize job item identifier. + /// + /// The target. + /// The server identifier. + /// The synchronize job item identifier. + /// Task<List<LocalItem>>. + Task> GetCachedItemsBySyncJobItemId(SyncTarget target, string serverId, string syncJobItemId); } } diff --git a/MediaBrowser.Model/ApiClient/IApiClient.cs b/MediaBrowser.Model/ApiClient/IApiClient.cs index 58af016150..9675de38b0 100644 --- a/MediaBrowser.Model/ApiClient/IApiClient.cs +++ b/MediaBrowser.Model/ApiClient/IApiClient.cs @@ -248,10 +248,9 @@ namespace MediaBrowser.Model.ApiClient /// /// Gets the playback information. /// - /// The item identifier. - /// The user identifier. + /// The request. /// Task<LiveMediaInfoResult>. - Task GetPlaybackInfo(string itemId, string userId); + Task GetPlaybackInfo(PlaybackInfoRequest request); /// /// Gets the users async. diff --git a/MediaBrowser.Model/MediaInfo/PlaybackInfoRequest.cs b/MediaBrowser.Model/MediaInfo/PlaybackInfoRequest.cs index ffd4995ad9..124739073a 100644 --- a/MediaBrowser.Model/MediaInfo/PlaybackInfoRequest.cs +++ b/MediaBrowser.Model/MediaInfo/PlaybackInfoRequest.cs @@ -4,6 +4,22 @@ namespace MediaBrowser.Model.MediaInfo { public class PlaybackInfoRequest { + public string Id { get; set; } + + public string UserId { get; set; } + + public int? MaxStreamingBitrate { get; set; } + + public long? StartTimeTicks { get; set; } + + public int? AudioStreamIndex { get; set; } + + public int? SubtitleStreamIndex { get; set; } + + public string MediaSourceId { get; set; } + + public string LiveStreamId { get; set; } + public DeviceProfile DeviceProfile { get; set; } } } diff --git a/MediaBrowser.Model/Sync/LocalItem.cs b/MediaBrowser.Model/Sync/LocalItem.cs index 37f605a590..dbbecaf057 100644 --- a/MediaBrowser.Model/Sync/LocalItem.cs +++ b/MediaBrowser.Model/Sync/LocalItem.cs @@ -31,6 +31,11 @@ namespace MediaBrowser.Model.Sync /// The item identifier. public string ItemId { get; set; } /// + /// Gets or sets the synchronize job item identifier. + /// + /// The synchronize job item identifier. + public string SyncJobItemId { get; set; } + /// /// Gets or sets the user ids with access. /// /// The user ids with access. diff --git a/MediaBrowser.Model/Sync/SyncDataRequest.cs b/MediaBrowser.Model/Sync/SyncDataRequest.cs index dc33239a04..0df4de86d1 100644 --- a/MediaBrowser.Model/Sync/SyncDataRequest.cs +++ b/MediaBrowser.Model/Sync/SyncDataRequest.cs @@ -6,6 +6,7 @@ namespace MediaBrowser.Model.Sync { public List LocalItemIds { get; set; } public List OfflineUserIds { get; set; } + public List SyncJobItemIds { get; set; } public string TargetId { get; set; } diff --git a/MediaBrowser.Server.Implementations/Channels/ChannelImageProvider.cs b/MediaBrowser.Server.Implementations/Channels/ChannelImageProvider.cs index 5c033e6bdc..f13c71c6df 100644 --- a/MediaBrowser.Server.Implementations/Channels/ChannelImageProvider.cs +++ b/MediaBrowser.Server.Implementations/Channels/ChannelImageProvider.cs @@ -2,7 +2,6 @@ using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Entities; -using System; using System.Collections.Generic; using System.Linq; using System.Threading; diff --git a/MediaBrowser.Server.Implementations/Library/MediaSourceManager.cs b/MediaBrowser.Server.Implementations/Library/MediaSourceManager.cs index 66eeb61f72..4fab95263f 100644 --- a/MediaBrowser.Server.Implementations/Library/MediaSourceManager.cs +++ b/MediaBrowser.Server.Implementations/Library/MediaSourceManager.cs @@ -129,6 +129,11 @@ namespace MediaBrowser.Server.Implementations.Library return list; } + public Task> GetPlayackMediaSources(string id, bool enablePathSubstitution, CancellationToken cancellationToken) + { + return GetPlayackMediaSources(id, null, enablePathSubstitution, cancellationToken); + } + public async Task> GetPlayackMediaSources(string id, string userId, bool enablePathSubstitution, CancellationToken cancellationToken) { var item = _libraryManager.GetItemById(id); @@ -225,11 +230,6 @@ namespace MediaBrowser.Server.Implementations.Library } } - public Task> GetPlayackMediaSources(string id, bool enablePathSubstitution, CancellationToken cancellationToken) - { - return GetPlayackMediaSources(id, null, enablePathSubstitution, cancellationToken); - } - public MediaSourceInfo GetStaticMediaSource(IHasMediaSources item, string mediaSourceId, bool enablePathSubstitution) { return GetStaticMediaSources(item, enablePathSubstitution).FirstOrDefault(i => string.Equals(i.Id, mediaSourceId, StringComparison.OrdinalIgnoreCase)); diff --git a/MediaBrowser.Server.Implementations/LiveTv/LiveTvDtoService.cs b/MediaBrowser.Server.Implementations/LiveTv/LiveTvDtoService.cs index 7f4440fbce..401cf87657 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/LiveTvDtoService.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/LiveTvDtoService.cs @@ -373,7 +373,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv StartDate = item.StartDate, OfficialRating = item.OfficialRating, IsHD = item.IsHD, - OriginalAirDate = item.PremiereDate, + OriginalAirDate = item.OriginalAirDate, Audio = item.Audio, CommunityRating = GetClientCommunityRating(item.CommunityRating), IsRepeat = item.IsRepeat, diff --git a/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs b/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs index f676918593..a39781d6a3 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs @@ -589,13 +589,15 @@ namespace MediaBrowser.Server.Implementations.LiveTv item.Name = info.Name; item.OfficialRating = info.OfficialRating; item.Overview = info.Overview; - item.PremiereDate = info.OriginalAirDate; + item.OriginalAirDate = info.OriginalAirDate; item.ProviderImagePath = info.ImagePath; item.ProviderImageUrl = info.ImageUrl; item.RunTimeTicks = (info.EndDate - info.StartDate).Ticks; item.StartDate = info.StartDate; - item.ProductionYear = info.ProductionYear; + item.ProductionYear = info.ProductionYear; + item.PremiereDate = item.PremiereDate ?? info.OriginalAirDate; + await item.UpdateToRepository(ItemUpdateType.MetadataImport, cancellationToken).ConfigureAwait(false); return item; diff --git a/MediaBrowser.Server.Implementations/Sync/MediaSync.cs b/MediaBrowser.Server.Implementations/Sync/MediaSync.cs index e7333939e3..5bc8b80887 100644 --- a/MediaBrowser.Server.Implementations/Sync/MediaSync.cs +++ b/MediaBrowser.Server.Implementations/Sync/MediaSync.cs @@ -67,12 +67,12 @@ namespace MediaBrowser.Server.Implementations.Sync SyncTarget target, CancellationToken cancellationToken) { - var localIds = await dataProvider.GetServerItemIds(target, serverId).ConfigureAwait(false); + var jobItemIds = await dataProvider.GetSyncJobItemIds(target, serverId).ConfigureAwait(false); var result = await _syncManager.SyncData(new SyncDataRequest { TargetId = target.Id, - LocalItemIds = localIds + SyncJobItemIds = jobItemIds }).ConfigureAwait(false); @@ -285,11 +285,11 @@ namespace MediaBrowser.Server.Implementations.Sync private async Task RemoveItem(IServerSyncProvider provider, ISyncDataProvider dataProvider, string serverId, - string itemId, + string syncJobItemId, SyncTarget target, CancellationToken cancellationToken) { - var localItems = await dataProvider.GetCachedItems(target, serverId, itemId); + var localItems = await dataProvider.GetCachedItemsBySyncJobItemId(target, serverId, syncJobItemId); foreach (var localItem in localItems) { @@ -350,7 +350,8 @@ namespace MediaBrowser.Server.Implementations.Sync ItemId = libraryItem.Id, ServerId = serverId, LocalPath = localPath, - Id = GetLocalId(syncedItem.SyncJobItemId, libraryItem.Id) + Id = GetLocalId(syncedItem.SyncJobItemId, libraryItem.Id), + SyncJobItemId = syncedItem.SyncJobItemId }; } diff --git a/MediaBrowser.Server.Implementations/Sync/SyncManager.cs b/MediaBrowser.Server.Implementations/Sync/SyncManager.cs index 3acc79088d..102218979c 100644 --- a/MediaBrowser.Server.Implementations/Sync/SyncManager.cs +++ b/MediaBrowser.Server.Implementations/Sync/SyncManager.cs @@ -3,6 +3,7 @@ using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Events; using MediaBrowser.Common.Extensions; using MediaBrowser.Common.IO; +using MediaBrowser.Controller; using MediaBrowser.Controller.Channels; using MediaBrowser.Controller.Drawing; using MediaBrowser.Controller.Dto; @@ -42,7 +43,7 @@ namespace MediaBrowser.Server.Implementations.Sync private readonly ILogger _logger; private readonly IUserManager _userManager; private readonly Func _dtoService; - private readonly IApplicationHost _appHost; + private readonly IServerApplicationHost _appHost; private readonly ITVSeriesManager _tvSeriesManager; private readonly Func _mediaEncoder; private readonly IFileSystem _fileSystem; @@ -60,7 +61,7 @@ namespace MediaBrowser.Server.Implementations.Sync public event EventHandler> SyncJobItemUpdated; public event EventHandler> SyncJobItemCreated; - public SyncManager(ILibraryManager libraryManager, ISyncRepository repo, IImageProcessor imageProcessor, ILogger logger, IUserManager userManager, Func dtoService, IApplicationHost appHost, ITVSeriesManager tvSeriesManager, Func mediaEncoder, IFileSystem fileSystem, Func subtitleEncoder, IConfigurationManager config, IUserDataManager userDataManager, Func mediaSourceManager, IJsonSerializer json) + public SyncManager(ILibraryManager libraryManager, ISyncRepository repo, IImageProcessor imageProcessor, ILogger logger, IUserManager userManager, Func dtoService, IServerApplicationHost appHost, ITVSeriesManager tvSeriesManager, Func mediaEncoder, IFileSystem fileSystem, Func subtitleEncoder, IConfigurationManager config, IUserDataManager userDataManager, Func mediaSourceManager, IJsonSerializer json) { _libraryManager = libraryManager; _repo = repo; @@ -94,7 +95,7 @@ namespace MediaBrowser.Server.Implementations.Sync public ISyncDataProvider GetDataProvider(IServerSyncProvider provider, SyncTarget target) { - return _dataProviders.GetOrAdd(target.Id, key => new TargetDataProvider(provider, target, _appHost.SystemId, _logger, _json, _fileSystem, _config.CommonApplicationPaths)); + return _dataProviders.GetOrAdd(target.Id, key => new TargetDataProvider(provider, target, _appHost, _logger, _json, _fileSystem, _config.CommonApplicationPaths)); } public async Task CreateJob(SyncJobRequest request) @@ -737,10 +738,15 @@ namespace MediaBrowser.Server.Implementations.Sync public async Task SyncData(SyncDataRequest request) { + if (request.SyncJobItemIds != null) + { + return await SyncDataUsingSyncJobItemIds(request).ConfigureAwait(false); + } + var jobItemResult = GetJobItems(new SyncJobItemQuery { TargetId = request.TargetId, - Statuses = new SyncJobItemStatus[] { SyncJobItemStatus.Synced } + Statuses = new[] { SyncJobItemStatus.Synced } }); var response = new SyncDataResponse(); @@ -816,6 +822,87 @@ namespace MediaBrowser.Server.Implementations.Sync return response; } + private async Task SyncDataUsingSyncJobItemIds(SyncDataRequest request) + { + var jobItemResult = GetJobItems(new SyncJobItemQuery + { + TargetId = request.TargetId, + Statuses = new[] { SyncJobItemStatus.Synced } + }); + + var response = new SyncDataResponse(); + + foreach (var jobItem in jobItemResult.Items) + { + if (request.SyncJobItemIds.Contains(jobItem.Id, StringComparer.OrdinalIgnoreCase)) + { + var job = _repo.GetJob(jobItem.JobId); + var user = _userManager.GetUserById(job.UserId); + + if (jobItem.IsMarkedForRemoval) + { + // Tell the device to remove it since it has been marked for removal + response.ItemIdsToRemove.Add(jobItem.Id); + } + else if (user == null) + { + // Tell the device to remove it since the user is gone now + response.ItemIdsToRemove.Add(jobItem.Id); + } + else if (job.UnwatchedOnly) + { + var libraryItem = _libraryManager.GetItemById(jobItem.ItemId); + + if (IsLibraryItemAvailable(libraryItem)) + { + if (libraryItem.IsPlayed(user) && libraryItem is Video) + { + // Tell the device to remove it since it has been played + response.ItemIdsToRemove.Add(jobItem.Id); + } + } + else + { + // Tell the device to remove it since it's no longer available + response.ItemIdsToRemove.Add(jobItem.Id); + } + } + } + else + { + // Content is no longer on the device + jobItem.Status = SyncJobItemStatus.RemovedFromDevice; + await UpdateSyncJobItemInternal(jobItem).ConfigureAwait(false); + } + } + + // Now check each item that's on the device + foreach (var syncJobItemId in request.SyncJobItemIds) + { + // See if it's already marked for removal + if (response.ItemIdsToRemove.Contains(syncJobItemId, StringComparer.OrdinalIgnoreCase)) + { + continue; + } + + // If there isn't a sync job for this item, mark it for removal + if (!jobItemResult.Items.Any(i => string.Equals(syncJobItemId, i.Id, StringComparison.OrdinalIgnoreCase))) + { + response.ItemIdsToRemove.Add(syncJobItemId); + } + } + + response.ItemIdsToRemove = response.ItemIdsToRemove.Distinct(StringComparer.OrdinalIgnoreCase).ToList(); + + var itemsOnDevice = request.LocalItemIds + .Except(response.ItemIdsToRemove) + .ToList(); + + SetUserAccess(request, response, itemsOnDevice); + + return response; + } + private void SetUserAccess(SyncDataRequest request, SyncDataResponse response, List itemIds) { var users = request.OfflineUserIds diff --git a/MediaBrowser.Server.Implementations/Sync/TargetDataProvider.cs b/MediaBrowser.Server.Implementations/Sync/TargetDataProvider.cs index ca9d96c12f..dea8688482 100644 --- a/MediaBrowser.Server.Implementations/Sync/TargetDataProvider.cs +++ b/MediaBrowser.Server.Implementations/Sync/TargetDataProvider.cs @@ -1,6 +1,7 @@ using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Extensions; using MediaBrowser.Common.IO; +using MediaBrowser.Controller; using MediaBrowser.Controller.Sync; using MediaBrowser.Model.Logging; using MediaBrowser.Model.Serialization; @@ -26,11 +27,11 @@ namespace MediaBrowser.Server.Implementations.Sync private readonly IJsonSerializer _json; private readonly IFileSystem _fileSystem; private readonly IApplicationPaths _appPaths; - private readonly string _serverId; + private readonly IServerApplicationHost _appHost; private readonly SemaphoreSlim _cacheFileLock = new SemaphoreSlim(1, 1); - public TargetDataProvider(IServerSyncProvider provider, SyncTarget target, string serverId, ILogger logger, IJsonSerializer json, IFileSystem fileSystem, IApplicationPaths appPaths) + public TargetDataProvider(IServerSyncProvider provider, SyncTarget target, IServerApplicationHost appHost, ILogger logger, IJsonSerializer json, IFileSystem fileSystem, IApplicationPaths appPaths) { _logger = logger; _json = json; @@ -38,7 +39,7 @@ namespace MediaBrowser.Server.Implementations.Sync _target = target; _fileSystem = fileSystem; _appPaths = appPaths; - _serverId = serverId; + _appHost = appHost; } private string GetCachePath() @@ -50,13 +51,21 @@ namespace MediaBrowser.Server.Implementations.Sync { var parts = new List { - _serverId, + _appHost.FriendlyName, "data.json" }; + parts = parts.Select(i => GetValidFilename(_provider, i)).ToList(); + return _provider.GetFullPath(parts, _target); } + private string GetValidFilename(IServerSyncProvider provider, string filename) + { + // We can always add this method to the sync provider if it's really needed + return _fileSystem.GetValidFilename(filename); + } + private async Task CacheData(Stream stream) { var cachePath = GetCachePath(); @@ -167,6 +176,11 @@ namespace MediaBrowser.Server.Implementations.Sync return GetData(items => items.Where(i => string.Equals(i.ServerId, serverId, StringComparison.OrdinalIgnoreCase)).Select(i => i.ItemId).ToList()); } + public Task> GetSyncJobItemIds(SyncTarget target, string serverId) + { + return GetData(items => items.Where(i => string.Equals(i.ServerId, serverId, StringComparison.OrdinalIgnoreCase)).Select(i => i.SyncJobItemId).Where(i => !string.IsNullOrWhiteSpace(i)).ToList()); + } + public Task AddOrUpdate(SyncTarget target, LocalItem item) { return UpdateData(items => @@ -239,5 +253,13 @@ namespace MediaBrowser.Server.Implementations.Sync return items.Where(i => string.Equals(i.ServerId, serverId, StringComparison.OrdinalIgnoreCase) && string.Equals(i.ItemId, itemId, StringComparison.OrdinalIgnoreCase)) .ToList(); } + + public async Task> GetCachedItemsBySyncJobItemId(SyncTarget target, string serverId, string syncJobItemId) + { + var items = await GetCachedData().ConfigureAwait(false); + + return items.Where(i => string.Equals(i.ServerId, serverId, StringComparison.OrdinalIgnoreCase) && string.Equals(i.SyncJobItemId, syncJobItemId, StringComparison.OrdinalIgnoreCase)) + .ToList(); + } } } diff --git a/Nuget/MediaBrowser.Common.Internal.nuspec b/Nuget/MediaBrowser.Common.Internal.nuspec index 40da3899c5..c5acc0a42d 100644 --- a/Nuget/MediaBrowser.Common.Internal.nuspec +++ b/Nuget/MediaBrowser.Common.Internal.nuspec @@ -2,7 +2,7 @@ MediaBrowser.Common.Internal - 3.0.609 + 3.0.611 MediaBrowser.Common.Internal Luke ebr,Luke,scottisafool @@ -12,7 +12,7 @@ Contains common components shared by Emby Theater and Emby Server. Not intended for plugin developer consumption. Copyright © Emby 2013 - + diff --git a/Nuget/MediaBrowser.Common.nuspec b/Nuget/MediaBrowser.Common.nuspec index 7aa9d30551..1b29b02729 100644 --- a/Nuget/MediaBrowser.Common.nuspec +++ b/Nuget/MediaBrowser.Common.nuspec @@ -2,7 +2,7 @@ MediaBrowser.Common - 3.0.609 + 3.0.611 MediaBrowser.Common Emby Team ebr,Luke,scottisafool diff --git a/Nuget/MediaBrowser.Model.Signed.nuspec b/Nuget/MediaBrowser.Model.Signed.nuspec index 8d65ec4315..6ee79dd8c9 100644 --- a/Nuget/MediaBrowser.Model.Signed.nuspec +++ b/Nuget/MediaBrowser.Model.Signed.nuspec @@ -2,7 +2,7 @@ MediaBrowser.Model.Signed - 3.0.609 + 3.0.611 MediaBrowser.Model - Signed Edition Emby Team ebr,Luke,scottisafool diff --git a/Nuget/MediaBrowser.Server.Core.nuspec b/Nuget/MediaBrowser.Server.Core.nuspec index 9a5264da95..c8b062f3f3 100644 --- a/Nuget/MediaBrowser.Server.Core.nuspec +++ b/Nuget/MediaBrowser.Server.Core.nuspec @@ -2,7 +2,7 @@ MediaBrowser.Server.Core - 3.0.609 + 3.0.611 Media Browser.Server.Core Emby Team ebr,Luke,scottisafool @@ -12,7 +12,7 @@ Contains core components required to build plugins for Emby Server. Copyright © Emby 2013 - + -- cgit v1.2.3 From 4655a60d2992b2bb674dda783fbaeb5fba48918f Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Fri, 3 Apr 2015 11:50:50 -0400 Subject: remove copyts from hls --- MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs | 4 +-- .../Configuration/UserConfiguration.cs | 1 + .../Channels/ChannelDownloadScheduledTask.cs | 41 ++++++++++++---------- 3 files changed, 25 insertions(+), 21 deletions(-) (limited to 'MediaBrowser.Server.Implementations/Channels') diff --git a/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs b/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs index 4f043d3217..cbfadb886d 100644 --- a/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs +++ b/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs @@ -698,7 +698,7 @@ namespace MediaBrowser.Api.Playback.Hls { var outputTsArg = Path.Combine(Path.GetDirectoryName(outputPath), Path.GetFileNameWithoutExtension(outputPath)) + "%d.ts"; - return string.Format("{0} {1} -map_metadata -1 -threads {2} {3} {4} -copyts -flags -global_header -sc_threshold 0 {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} -flags -global_header -sc_threshold 0 {5} -f segment -segment_time {6} -segment_start_number {7} -segment_list \"{8}\" -y \"{9}\"", inputModifier, GetInputArgument(state), threads, @@ -712,7 +712,7 @@ namespace MediaBrowser.Api.Playback.Hls ).Trim(); } - return string.Format("{0} {1} -map_metadata -1 -threads {2} {3} {4} -copyts -flags -global_header -sc_threshold 0 {5} -hls_time {6} -start_number {7} -hls_list_size {8} -y \"{9}\"", + return string.Format("{0} {1} -map_metadata -1 -threads {2} {3} {4} -flags -global_header -sc_threshold 0 {5} -hls_time {6} -start_number {7} -hls_list_size {8} -y \"{9}\"", inputModifier, GetInputArgument(state), threads, diff --git a/MediaBrowser.Model/Configuration/UserConfiguration.cs b/MediaBrowser.Model/Configuration/UserConfiguration.cs index 98641636ae..7390af0881 100644 --- a/MediaBrowser.Model/Configuration/UserConfiguration.cs +++ b/MediaBrowser.Model/Configuration/UserConfiguration.cs @@ -61,6 +61,7 @@ namespace MediaBrowser.Model.Configuration /// public UserConfiguration() { + HidePlayedInLatest = true; PlayDefaultAudioTrack = true; LatestItemsExcludes = new string[] { }; diff --git a/MediaBrowser.Server.Implementations/Channels/ChannelDownloadScheduledTask.cs b/MediaBrowser.Server.Implementations/Channels/ChannelDownloadScheduledTask.cs index 980c3f31b7..6514893477 100644 --- a/MediaBrowser.Server.Implementations/Channels/ChannelDownloadScheduledTask.cs +++ b/MediaBrowser.Server.Implementations/Channels/ChannelDownloadScheduledTask.cs @@ -169,29 +169,32 @@ namespace MediaBrowser.Server.Implementations.Channels foreach (var item in result.Items) { - var channelItem = (IChannelMediaItem)item; + var channelItem = item as IChannelMediaItem; - var channelFeatures = _manager.GetChannelFeatures(channelItem.ChannelId); - - if (channelFeatures.SupportsContentDownloading) + if (channelItem != null) { - if (options.DownloadingChannels.Contains(channelItem.ChannelId)) + var channelFeatures = _manager.GetChannelFeatures(channelItem.ChannelId); + + if (channelFeatures.SupportsContentDownloading) { - try - { - await DownloadChannelItem(channelItem, options, cancellationToken, path); - } - catch (OperationCanceledException) - { - break; - } - catch (ChannelDownloadException) - { - // Logged at lower levels - } - catch (Exception ex) + if (options.DownloadingChannels.Contains(channelItem.ChannelId)) { - _logger.ErrorException("Error downloading channel content for {0}", ex, item.Name); + try + { + await DownloadChannelItem(channelItem, options, cancellationToken, path); + } + catch (OperationCanceledException) + { + break; + } + catch (ChannelDownloadException) + { + // Logged at lower levels + } + catch (Exception ex) + { + _logger.ErrorException("Error downloading channel content for {0}", ex, item.Name); + } } } } -- cgit v1.2.3