diff options
| author | Luke Pulverenti <luke.pulverenti@gmail.com> | 2016-10-05 03:15:29 -0400 |
|---|---|---|
| committer | Luke Pulverenti <luke.pulverenti@gmail.com> | 2016-10-05 03:15:29 -0400 |
| commit | b9cacd8076c00bfc371898c50315304927efaff8 (patch) | |
| tree | e3fee51b628343baa2c4b8cc0e4b1d3b826c1726 | |
| parent | c4e137e6cf7a52937e1169d1f170da4b8e72ba6a (diff) | |
update live streams
35 files changed, 293 insertions, 625 deletions
diff --git a/MediaBrowser.Api/Library/ChapterService.cs b/MediaBrowser.Api/Library/ChapterService.cs deleted file mode 100644 index 6b8dd18f1..000000000 --- a/MediaBrowser.Api/Library/ChapterService.cs +++ /dev/null @@ -1,30 +0,0 @@ -using MediaBrowser.Controller.Chapters; -using MediaBrowser.Controller.Net; -using ServiceStack; -using System.Linq; - -namespace MediaBrowser.Api.Library -{ - [Route("/Providers/Chapters", "GET")] - public class GetChapterProviders : IReturnVoid - { - } - - [Authenticated] - public class ChapterService : BaseApiService - { - private readonly IChapterManager _chapterManager; - - public ChapterService(IChapterManager chapterManager) - { - _chapterManager = chapterManager; - } - - public object Get(GetChapterProviders request) - { - var result = _chapterManager.GetProviders().ToList(); - - return ToOptimizedResult(result); - } - } -} diff --git a/MediaBrowser.Api/LiveTv/LiveTvService.cs b/MediaBrowser.Api/LiveTv/LiveTvService.cs index 4baae031f..ed7a0990f 100644 --- a/MediaBrowser.Api/LiveTv/LiveTvService.cs +++ b/MediaBrowser.Api/LiveTv/LiveTvService.cs @@ -705,7 +705,7 @@ namespace MediaBrowser.Api.LiveTv var outputHeaders = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); - outputHeaders["Content-Type"] = MimeTypes.GetMimeType(filePath); + outputHeaders["Content-Type"] = MediaBrowser.Model.Net.MimeTypes.GetMimeType(filePath); long startPosition = 0; diff --git a/MediaBrowser.Api/MediaBrowser.Api.csproj b/MediaBrowser.Api/MediaBrowser.Api.csproj index 77949d179..a98637650 100644 --- a/MediaBrowser.Api/MediaBrowser.Api.csproj +++ b/MediaBrowser.Api/MediaBrowser.Api.csproj @@ -79,7 +79,6 @@ <Compile Include="Dlna\DlnaService.cs" /> <Compile Include="FilterService.cs" /> <Compile Include="IHasDtoOptions.cs" /> - <Compile Include="Library\ChapterService.cs" /> <Compile Include="Playback\MediaInfoService.cs" /> <Compile Include="Playback\TranscodingThrottler.cs" /> <Compile Include="PlaylistService.cs" /> diff --git a/MediaBrowser.Api/Playback/BaseStreamingService.cs b/MediaBrowser.Api/Playback/BaseStreamingService.cs index bcf3bd28e..0b2fad580 100644 --- a/MediaBrowser.Api/Playback/BaseStreamingService.cs +++ b/MediaBrowser.Api/Playback/BaseStreamingService.cs @@ -1888,7 +1888,9 @@ namespace MediaBrowser.Api.Playback } else { - mediaSource = await MediaSourceManager.GetLiveStream(request.LiveStreamId, cancellationToken).ConfigureAwait(false); + var liveStreamInfo = await MediaSourceManager.GetLiveStreamWithDirectStreamProvider(request.LiveStreamId, cancellationToken).ConfigureAwait(false); + mediaSource = liveStreamInfo.Item1; + state.DirectStreamProvider = liveStreamInfo.Item2; } var videoRequest = request as VideoStreamRequest; diff --git a/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs b/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs index 4adf6fbca..809eabef8 100644 --- a/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs +++ b/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs @@ -121,32 +121,33 @@ namespace MediaBrowser.Api.Playback.Progressive var responseHeaders = new Dictionary<string, string>(); - // Static remote stream - if (request.Static && state.InputProtocol == MediaProtocol.Http) + if (request.Static && state.DirectStreamProvider != null) { AddDlnaHeaders(state, responseHeaders, true); using (state) { - if (state.MediaPath.IndexOf("/livestreamfiles/", StringComparison.OrdinalIgnoreCase) != -1) - { - var parts = state.MediaPath.Split('/'); - var filename = parts[parts.Length - 2] + Path.GetExtension(parts[parts.Length - 1]); - var filePath = Path.Combine(ServerConfigurationManager.ApplicationPaths.TranscodingTempPath, filename); + var outputHeaders = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); - var outputHeaders = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); + // TODO: Don't hardcode this + outputHeaders["Content-Type"] = MediaBrowser.Model.Net.MimeTypes.GetMimeType("file.ts"); - outputHeaders["Content-Type"] = MimeTypes.GetMimeType(filePath); + var streamSource = new ProgressiveFileCopier(state.DirectStreamProvider, outputHeaders, null, Logger, CancellationToken.None) + { + AllowEndOfFile = false + }; + return ResultFactory.GetAsyncStreamWriter(streamSource); + } + } - var streamSource = new ProgressiveFileCopier(FileSystem, filePath, outputHeaders, null, Logger, CancellationToken.None) - { - AllowEndOfFile = false - }; - return ResultFactory.GetAsyncStreamWriter(streamSource); - } + // Static remote stream + if (request.Static && state.InputProtocol == MediaProtocol.Http) + { + AddDlnaHeaders(state, responseHeaders, true); - return await GetStaticRemoteStreamResult(state, responseHeaders, isHeadRequest, cancellationTokenSource) - .ConfigureAwait(false); + using (state) + { + return await GetStaticRemoteStreamResult(state, responseHeaders, isHeadRequest, cancellationTokenSource).ConfigureAwait(false); } } diff --git a/MediaBrowser.Api/Playback/Progressive/ProgressiveStreamWriter.cs b/MediaBrowser.Api/Playback/Progressive/ProgressiveStreamWriter.cs index f601f4aa3..3477ad57b 100644 --- a/MediaBrowser.Api/Playback/Progressive/ProgressiveStreamWriter.cs +++ b/MediaBrowser.Api/Playback/Progressive/ProgressiveStreamWriter.cs @@ -7,6 +7,7 @@ using CommonIO; using MediaBrowser.Controller.Net; using System.Collections.Generic; using ServiceStack.Web; +using MediaBrowser.Controller.Library; namespace MediaBrowser.Api.Playback.Progressive { @@ -26,6 +27,8 @@ namespace MediaBrowser.Api.Playback.Progressive public long StartPosition { get; set; } public bool AllowEndOfFile = true; + private IDirectStreamProvider _directStreamProvider; + public ProgressiveFileCopier(IFileSystem fileSystem, string path, Dictionary<string, string> outputHeaders, TranscodingJob job, ILogger logger, CancellationToken cancellationToken) { _fileSystem = fileSystem; @@ -36,6 +39,15 @@ namespace MediaBrowser.Api.Playback.Progressive _cancellationToken = cancellationToken; } + public ProgressiveFileCopier(IDirectStreamProvider directStreamProvider, Dictionary<string, string> outputHeaders, TranscodingJob job, ILogger logger, CancellationToken cancellationToken) + { + _directStreamProvider = directStreamProvider; + _outputHeaders = outputHeaders; + _job = job; + _logger = logger; + _cancellationToken = cancellationToken; + } + public IDictionary<string, string> Options { get @@ -44,22 +56,33 @@ namespace MediaBrowser.Api.Playback.Progressive } } + private Stream GetInputStream() + { + return _fileSystem.GetFileStream(_path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, true); + } + public async Task WriteToAsync(Stream outputStream) { try { + if (_directStreamProvider != null) + { + await _directStreamProvider.CopyToAsync(outputStream, _cancellationToken).ConfigureAwait(false); + return; + } + var eofCount = 0; - using (var fs = _fileSystem.GetFileStream(_path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, true)) + using (var inputStream = GetInputStream()) { if (StartPosition > 0) { - fs.Position = StartPosition; + inputStream.Position = StartPosition; } while (eofCount < 15 || !AllowEndOfFile) { - var bytesRead = await CopyToAsyncInternal(fs, outputStream, BufferSize, _cancellationToken).ConfigureAwait(false); + var bytesRead = await CopyToAsyncInternal(inputStream, outputStream, BufferSize, _cancellationToken).ConfigureAwait(false); //var position = fs.Position; //_logger.Debug("Streamed {0} bytes to position {1} from file {2}", bytesRead, position, path); diff --git a/MediaBrowser.Api/Playback/StreamState.cs b/MediaBrowser.Api/Playback/StreamState.cs index 863bc0193..a59a7fe09 100644 --- a/MediaBrowser.Api/Playback/StreamState.cs +++ b/MediaBrowser.Api/Playback/StreamState.cs @@ -37,6 +37,7 @@ namespace MediaBrowser.Api.Playback /// </summary> /// <value>The log file stream.</value> public Stream LogFileStream { get; set; } + public IDirectStreamProvider DirectStreamProvider { get; set; } public string InputContainer { get; set; } diff --git a/MediaBrowser.Controller/Chapters/ChapterResponse.cs b/MediaBrowser.Controller/Chapters/ChapterResponse.cs deleted file mode 100644 index 3c1b8ed07..000000000 --- a/MediaBrowser.Controller/Chapters/ChapterResponse.cs +++ /dev/null @@ -1,19 +0,0 @@ -using MediaBrowser.Model.Chapters; -using System.Collections.Generic; - -namespace MediaBrowser.Controller.Chapters -{ - public class ChapterResponse - { - /// <summary> - /// Gets or sets the chapters. - /// </summary> - /// <value>The chapters.</value> - public List<RemoteChapterInfo> Chapters { get; set; } - - public ChapterResponse() - { - Chapters = new List<RemoteChapterInfo>(); - } - } -}
\ No newline at end of file diff --git a/MediaBrowser.Controller/Chapters/ChapterSearchRequest.cs b/MediaBrowser.Controller/Chapters/ChapterSearchRequest.cs deleted file mode 100644 index 982dc35bb..000000000 --- a/MediaBrowser.Controller/Chapters/ChapterSearchRequest.cs +++ /dev/null @@ -1,31 +0,0 @@ -using MediaBrowser.Controller.Providers; -using MediaBrowser.Model.Entities; -using System; -using System.Collections.Generic; - -namespace MediaBrowser.Controller.Chapters -{ - public class ChapterSearchRequest : IHasProviderIds - { - public string Language { get; set; } - - public VideoContentType ContentType { get; set; } - - public string MediaPath { get; set; } - public string SeriesName { get; set; } - public string Name { get; set; } - public int? IndexNumber { get; set; } - public int? IndexNumberEnd { get; set; } - public int? ParentIndexNumber { get; set; } - public int? ProductionYear { get; set; } - public long? RuntimeTicks { get; set; } - public Dictionary<string, string> ProviderIds { get; set; } - - public bool SearchAllProviders { get; set; } - - public ChapterSearchRequest() - { - ProviderIds = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); - } - } -}
\ No newline at end of file diff --git a/MediaBrowser.Controller/Chapters/IChapterManager.cs b/MediaBrowser.Controller/Chapters/IChapterManager.cs index 27e06fb8d..4b39e66cc 100644 --- a/MediaBrowser.Controller/Chapters/IChapterManager.cs +++ b/MediaBrowser.Controller/Chapters/IChapterManager.cs @@ -1,6 +1,4 @@ -using MediaBrowser.Controller.Entities; -using MediaBrowser.Model.Chapters; -using System.Collections.Generic; +using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using MediaBrowser.Model.Configuration; @@ -14,12 +12,6 @@ namespace MediaBrowser.Controller.Chapters public interface IChapterManager { /// <summary> - /// Adds the parts. - /// </summary> - /// <param name="chapterProviders">The chapter providers.</param> - void AddParts(IEnumerable<IChapterProvider> chapterProviders); - - /// <summary> /// Gets the chapters. /// </summary> /// <param name="itemId">The item identifier.</param> @@ -36,43 +28,6 @@ namespace MediaBrowser.Controller.Chapters Task SaveChapters(string itemId, List<ChapterInfo> chapters, CancellationToken cancellationToken); /// <summary> - /// Searches the specified video. - /// </summary> - /// <param name="video">The video.</param> - /// <param name="cancellationToken">The cancellation token.</param> - /// <returns>Task{IEnumerable{RemoteChapterResult}}.</returns> - Task<IEnumerable<RemoteChapterResult>> Search(Video video, CancellationToken cancellationToken); - - /// <summary> - /// Searches the specified request. - /// </summary> - /// <param name="request">The request.</param> - /// <param name="cancellationToken">The cancellation token.</param> - /// <returns>Task{IEnumerable{RemoteChapterResult}}.</returns> - Task<IEnumerable<RemoteChapterResult>> Search(ChapterSearchRequest request, CancellationToken cancellationToken); - - /// <summary> - /// Gets the chapters. - /// </summary> - /// <param name="id">The identifier.</param> - /// <param name="cancellationToken">The cancellation token.</param> - /// <returns>Task{ChapterResponse}.</returns> - Task<ChapterResponse> GetChapters(string id, CancellationToken cancellationToken); - - /// <summary> - /// Gets the providers. - /// </summary> - /// <param name="itemId">The item identifier.</param> - /// <returns>IEnumerable{ChapterProviderInfo}.</returns> - IEnumerable<ChapterProviderInfo> GetProviders(string itemId); - - /// <summary> - /// Gets the providers. - /// </summary> - /// <returns>IEnumerable{ChapterProviderInfo}.</returns> - IEnumerable<ChapterProviderInfo> GetProviders(); - - /// <summary> /// Gets the configuration. /// </summary> /// <returns>ChapterOptions.</returns> diff --git a/MediaBrowser.Controller/Chapters/IChapterProvider.cs b/MediaBrowser.Controller/Chapters/IChapterProvider.cs deleted file mode 100644 index a7505347b..000000000 --- a/MediaBrowser.Controller/Chapters/IChapterProvider.cs +++ /dev/null @@ -1,39 +0,0 @@ -using MediaBrowser.Controller.Providers; -using MediaBrowser.Model.Chapters; -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; - -namespace MediaBrowser.Controller.Chapters -{ - public interface IChapterProvider - { - /// <summary> - /// Gets the name. - /// </summary> - /// <value>The name.</value> - string Name { get; } - - /// <summary> - /// Gets the supported media types. - /// </summary> - /// <value>The supported media types.</value> - IEnumerable<VideoContentType> SupportedMediaTypes { get; } - - /// <summary> - /// Searches the specified request. - /// </summary> - /// <param name="request">The request.</param> - /// <param name="cancellationToken">The cancellation token.</param> - /// <returns>Task{IEnumerable{RemoteChapterResult}}.</returns> - Task<IEnumerable<RemoteChapterResult>> Search(ChapterSearchRequest request, CancellationToken cancellationToken); - - /// <summary> - /// Gets the chapters. - /// </summary> - /// <param name="id">The identifier.</param> - /// <param name="cancellationToken">The cancellation token.</param> - /// <returns>Task{ChapterResponse}.</returns> - Task<ChapterResponse> GetChapters(string id, CancellationToken cancellationToken); - } -} diff --git a/MediaBrowser.Controller/Library/IMediaSourceManager.cs b/MediaBrowser.Controller/Library/IMediaSourceManager.cs index c06470c5e..1ab0e4cb0 100644 --- a/MediaBrowser.Controller/Library/IMediaSourceManager.cs +++ b/MediaBrowser.Controller/Library/IMediaSourceManager.cs @@ -7,6 +7,7 @@ using System; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; +using System.IO; namespace MediaBrowser.Controller.Library { @@ -79,6 +80,8 @@ namespace MediaBrowser.Controller.Library /// <param name="cancellationToken">The cancellation token.</param> /// <returns>Task<MediaSourceInfo>.</returns> Task<MediaSourceInfo> GetLiveStream(string id, CancellationToken cancellationToken); + + Task<Tuple<MediaSourceInfo, IDirectStreamProvider>> GetLiveStreamWithDirectStreamProvider(string id, CancellationToken cancellationToken); /// <summary> /// Pings the media source. @@ -95,4 +98,9 @@ namespace MediaBrowser.Controller.Library /// <returns>Task.</returns> Task CloseLiveStream(string id); } + + public interface IDirectStreamProvider + { + Task CopyToAsync(Stream stream, CancellationToken cancellationToken); + } } diff --git a/MediaBrowser.Controller/Library/IMediaSourceProvider.cs b/MediaBrowser.Controller/Library/IMediaSourceProvider.cs index 56366e5a8..b0881ba7c 100644 --- a/MediaBrowser.Controller/Library/IMediaSourceProvider.cs +++ b/MediaBrowser.Controller/Library/IMediaSourceProvider.cs @@ -3,6 +3,7 @@ using MediaBrowser.Model.Dto; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; +using System; namespace MediaBrowser.Controller.Library { @@ -22,7 +23,7 @@ namespace MediaBrowser.Controller.Library /// <param name="openToken">The open token.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <returns>Task<MediaSourceInfo>.</returns> - Task<MediaSourceInfo> OpenMediaSource(string openToken, CancellationToken cancellationToken); + Task<Tuple<MediaSourceInfo,IDirectStreamProvider>> OpenMediaSource(string openToken, CancellationToken cancellationToken); /// <summary> /// Closes the media source. diff --git a/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs b/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs index d65d1ae30..a381c7980 100644 --- a/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs +++ b/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs @@ -9,6 +9,7 @@ using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using MediaBrowser.Model.Events; +using MediaBrowser.Controller.Library; namespace MediaBrowser.Controller.LiveTv { @@ -156,7 +157,7 @@ namespace MediaBrowser.Controller.LiveTv /// <param name="mediaSourceId">The media source identifier.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <returns>Task{StreamResponseInfo}.</returns> - Task<MediaSourceInfo> GetChannelStream(string id, string mediaSourceId, CancellationToken cancellationToken); + Task<Tuple<MediaSourceInfo, IDirectStreamProvider>> GetChannelStream(string id, string mediaSourceId, CancellationToken cancellationToken); /// <summary> /// Gets the program. diff --git a/MediaBrowser.Controller/LiveTv/ILiveTvService.cs b/MediaBrowser.Controller/LiveTv/ILiveTvService.cs index d7d8336d0..94082b42e 100644 --- a/MediaBrowser.Controller/LiveTv/ILiveTvService.cs +++ b/MediaBrowser.Controller/LiveTv/ILiveTvService.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; +using MediaBrowser.Controller.Library; namespace MediaBrowser.Controller.LiveTv { @@ -245,4 +246,9 @@ namespace MediaBrowser.Controller.LiveTv /// <returns>Task.</returns> Task<string> CreateSeriesTimer(SeriesTimerInfo info, CancellationToken cancellationToken); } + + public interface ISupportsDirectStreamProvider + { + Task<Tuple<MediaSourceInfo, IDirectStreamProvider>> GetChannelStreamWithDirectStreamProvider(string channelId, string streamId, CancellationToken cancellationToken); + } } diff --git a/MediaBrowser.Controller/MediaBrowser.Controller.csproj b/MediaBrowser.Controller/MediaBrowser.Controller.csproj index d70fba742..e9d2054da 100644 --- a/MediaBrowser.Controller/MediaBrowser.Controller.csproj +++ b/MediaBrowser.Controller/MediaBrowser.Controller.csproj @@ -91,10 +91,7 @@ <Compile Include="Channels\InternalChannelItemQuery.cs" /> <Compile Include="Channels\IRequiresMediaInfoCallback.cs" /> <Compile Include="Channels\ISearchableChannel.cs" /> - <Compile Include="Chapters\ChapterSearchRequest.cs" /> <Compile Include="Chapters\IChapterManager.cs" /> - <Compile Include="Chapters\IChapterProvider.cs" /> - <Compile Include="Chapters\ChapterResponse.cs" /> <Compile Include="Collections\CollectionCreationOptions.cs" /> <Compile Include="Collections\CollectionEvents.cs" /> <Compile Include="Collections\ICollectionManager.cs" /> diff --git a/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj b/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj index ad7dea0a5..59e29087c 100644 --- a/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj +++ b/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj @@ -160,15 +160,6 @@ <Compile Include="..\MediaBrowser.Model\Channels\ChannelQuery.cs"> <Link>Channels\ChannelQuery.cs</Link> </Compile> - <Compile Include="..\MediaBrowser.Model\Chapters\ChapterProviderInfo.cs"> - <Link>Chapters\ChapterProviderInfo.cs</Link> - </Compile> - <Compile Include="..\MediaBrowser.Model\Chapters\RemoteChapterInfo.cs"> - <Link>Chapters\RemoteChapterInfo.cs</Link> - </Compile> - <Compile Include="..\MediaBrowser.Model\Chapters\RemoteChapterResult.cs"> - <Link>Chapters\RemoteChapterResult.cs</Link> - </Compile> <Compile Include="..\MediaBrowser.Model\Collections\CollectionCreationResult.cs"> <Link>Collections\CollectionCreationResult.cs</Link> </Compile> diff --git a/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj b/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj index 61f2f3f13..47ebb3a92 100644 --- a/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj +++ b/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj @@ -132,15 +132,6 @@ <Compile Include="..\MediaBrowser.Model\Channels\ChannelQuery.cs"> <Link>Channels\ChannelQuery.cs</Link> </Compile> - <Compile Include="..\MediaBrowser.Model\Chapters\ChapterProviderInfo.cs"> - <Link>Chapters\ChapterProviderInfo.cs</Link> - </Compile> - <Compile Include="..\MediaBrowser.Model\Chapters\RemoteChapterInfo.cs"> - <Link>Chapters\RemoteChapterInfo.cs</Link> - </Compile> - <Compile Include="..\MediaBrowser.Model\Chapters\RemoteChapterResult.cs"> - <Link>Chapters\RemoteChapterResult.cs</Link> - </Compile> <Compile Include="..\MediaBrowser.Model\Collections\CollectionCreationResult.cs"> <Link>Collections\CollectionCreationResult.cs</Link> </Compile> diff --git a/MediaBrowser.Model/Chapters/ChapterProviderInfo.cs b/MediaBrowser.Model/Chapters/ChapterProviderInfo.cs deleted file mode 100644 index 570407c57..000000000 --- a/MediaBrowser.Model/Chapters/ChapterProviderInfo.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace MediaBrowser.Model.Chapters -{ - public class ChapterProviderInfo - { - public string Name { get; set; } - public string Id { get; set; } - } -}
\ No newline at end of file diff --git a/MediaBrowser.Model/Chapters/RemoteChapterInfo.cs b/MediaBrowser.Model/Chapters/RemoteChapterInfo.cs deleted file mode 100644 index f2674c842..000000000 --- a/MediaBrowser.Model/Chapters/RemoteChapterInfo.cs +++ /dev/null @@ -1,18 +0,0 @@ - -namespace MediaBrowser.Model.Chapters -{ - public class RemoteChapterInfo - { - /// <summary> - /// Gets or sets the start position ticks. - /// </summary> - /// <value>The start position ticks.</value> - public long StartPositionTicks { get; set; } - - /// <summary> - /// Gets or sets the name. - /// </summary> - /// <value>The name.</value> - public string Name { get; set; } - } -} diff --git a/MediaBrowser.Model/Chapters/RemoteChapterResult.cs b/MediaBrowser.Model/Chapters/RemoteChapterResult.cs deleted file mode 100644 index 425c3ded8..000000000 --- a/MediaBrowser.Model/Chapters/RemoteChapterResult.cs +++ /dev/null @@ -1,48 +0,0 @@ - -namespace MediaBrowser.Model.Chapters -{ - public class RemoteChapterResult - { - /// <summary> - /// Gets or sets the identifier. - /// </summary> - /// <value>The identifier.</value> - public string Id { get; set; } - - /// <summary> - /// Gets or sets the run time ticks. - /// </summary> - /// <value>The run time ticks.</value> - public long? RunTimeTicks { get; set; } - - /// <summary> - /// Gets or sets the name. - /// </summary> - /// <value>The name.</value> - public string Name { get; set; } - - /// <summary> - /// Gets or sets the name of the provider. - /// </summary> - /// <value>The name of the provider.</value> - public string ProviderName { get; set; } - - /// <summary> - /// Gets or sets the community rating. - /// </summary> - /// <value>The community rating.</value> - public float? CommunityRating { get; set; } - - /// <summary> - /// Gets or sets the chapter count. - /// </summary> - /// <value>The chapter count.</value> - public int? ChapterCount { get; set; } - - /// <summary> - /// Gets or sets the name of the three letter iso language. - /// </summary> - /// <value>The name of the three letter iso language.</value> - public string ThreeLetterISOLanguageName { get; set; } - } -} diff --git a/MediaBrowser.Model/Configuration/ChapterOptions.cs b/MediaBrowser.Model/Configuration/ChapterOptions.cs index c7bb6f861..1c2b0c527 100644 --- a/MediaBrowser.Model/Configuration/ChapterOptions.cs +++ b/MediaBrowser.Model/Configuration/ChapterOptions.cs @@ -2,18 +2,10 @@ { public class ChapterOptions { - public bool DownloadMovieChapters { get; set; } - public bool DownloadEpisodeChapters { get; set; } + public bool EnableMovieChapterImageExtraction { get; set; } + public bool EnableEpisodeChapterImageExtraction { get; set; } + public bool EnableOtherVideoChapterImageExtraction { get; set; } - public string[] FetcherOrder { get; set; } - public string[] DisabledFetchers { get; set; } - - public ChapterOptions() - { - DownloadMovieChapters = true; - - DisabledFetchers = new string[] { }; - FetcherOrder = new string[] { }; - } + public bool ExtractDuringLibraryScan { get; set; } } }
\ No newline at end of file diff --git a/MediaBrowser.Model/MediaBrowser.Model.csproj b/MediaBrowser.Model/MediaBrowser.Model.csproj index c1a01680d..69754204e 100644 --- a/MediaBrowser.Model/MediaBrowser.Model.csproj +++ b/MediaBrowser.Model/MediaBrowser.Model.csproj @@ -84,9 +84,6 @@ <Compile Include="Channels\ChannelMediaContentType.cs" /> <Compile Include="Channels\ChannelMediaType.cs" /> <Compile Include="Channels\ChannelQuery.cs" /> - <Compile Include="Chapters\ChapterProviderInfo.cs" /> - <Compile Include="Chapters\RemoteChapterInfo.cs" /> - <Compile Include="Chapters\RemoteChapterResult.cs" /> <Compile Include="Collections\CollectionCreationResult.cs" /> <Compile Include="Configuration\AccessSchedule.cs" /> <Compile Include="Configuration\ChannelOptions.cs" /> diff --git a/MediaBrowser.Providers/Chapters/ChapterManager.cs b/MediaBrowser.Providers/Chapters/ChapterManager.cs index 88811c850..87aaafb39 100644 --- a/MediaBrowser.Providers/Chapters/ChapterManager.cs +++ b/MediaBrowser.Providers/Chapters/ChapterManager.cs @@ -8,7 +8,6 @@ using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Persistence; using MediaBrowser.Controller.Providers; -using MediaBrowser.Model.Chapters; using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Logging; @@ -22,7 +21,6 @@ namespace MediaBrowser.Providers.Chapters { public class ChapterManager : IChapterManager { - private IChapterProvider[] _providers; private readonly ILibraryManager _libraryManager; private readonly ILogger _logger; private readonly IServerConfigurationManager _config; @@ -36,224 +34,6 @@ namespace MediaBrowser.Providers.Chapters _itemRepo = itemRepo; } - public void AddParts(IEnumerable<IChapterProvider> chapterProviders) - { - _providers = chapterProviders.ToArray(); - } - - public Task<IEnumerable<RemoteChapterResult>> Search(Video video, CancellationToken cancellationToken) - { - VideoContentType mediaType; - - if (video is Episode) - { - mediaType = VideoContentType.Episode; - } - else if (video is Movie) - { - mediaType = VideoContentType.Movie; - } - else - { - // These are the only supported types - return Task.FromResult<IEnumerable<RemoteChapterResult>>(new List<RemoteChapterResult>()); - } - - var request = new ChapterSearchRequest - { - ContentType = mediaType, - IndexNumber = video.IndexNumber, - Language = video.GetPreferredMetadataLanguage(), - MediaPath = video.Path, - Name = video.Name, - ParentIndexNumber = video.ParentIndexNumber, - ProductionYear = video.ProductionYear, - ProviderIds = video.ProviderIds, - RuntimeTicks = video.RunTimeTicks, - SearchAllProviders = false - }; - - var episode = video as Episode; - - if (episode != null) - { - request.IndexNumberEnd = episode.IndexNumberEnd; - request.SeriesName = episode.SeriesName; - } - - return Search(request, cancellationToken); - } - - public async Task<IEnumerable<RemoteChapterResult>> Search(ChapterSearchRequest request, CancellationToken cancellationToken) - { - var contentType = request.ContentType; - var providers = GetInternalProviders(false) - .Where(i => i.SupportedMediaTypes.Contains(contentType)) - .ToList(); - - // If not searching all, search one at a time until something is found - if (!request.SearchAllProviders) - { - foreach (var provider in providers) - { - try - { - var currentResults = await Search(request, provider, cancellationToken).ConfigureAwait(false); - - if (currentResults.Count > 0) - { - return currentResults; - } - } - catch (Exception ex) - { - _logger.ErrorException("Error downloading subtitles from {0}", ex, provider.Name); - } - } - return new List<RemoteChapterResult>(); - } - - var tasks = providers.Select(async i => - { - try - { - return await Search(request, i, cancellationToken).ConfigureAwait(false); - } - catch (Exception ex) - { - _logger.ErrorException("Error downloading subtitles from {0}", ex, i.Name); - return new List<RemoteChapterResult>(); - } - }); - - var results = await Task.WhenAll(tasks).ConfigureAwait(false); - - return results.SelectMany(i => i); - } - - private async Task<List<RemoteChapterResult>> Search(ChapterSearchRequest request, - IChapterProvider provider, - CancellationToken cancellationToken) - { - var searchResults = await provider.Search(request, cancellationToken).ConfigureAwait(false); - - var list = searchResults.ToList(); - - foreach (var result in list) - { - result.Id = GetProviderId(provider.Name) + "_" + result.Id; - result.ProviderName = provider.Name; - } - - return list; - } - - public Task<ChapterResponse> GetChapters(string id, CancellationToken cancellationToken) - { - var parts = id.Split(new[] { '_' }, 2); - - var provider = GetProvider(parts.First()); - id = parts.Last(); - - return provider.GetChapters(id, cancellationToken); - } - - public IEnumerable<ChapterProviderInfo> GetProviders(string itemId) - { - var video = _libraryManager.GetItemById(itemId) as Video; - VideoContentType mediaType; - - if (video is Episode) - { - mediaType = VideoContentType.Episode; - } - else if (video is Movie) - { - mediaType = VideoContentType.Movie; - } - else - { - // These are the only supported types - return new List<ChapterProviderInfo>(); - } - - var providers = GetInternalProviders(false) - .Where(i => i.SupportedMediaTypes.Contains(mediaType)); - - return GetInfos(providers); - } - - public IEnumerable<ChapterProviderInfo> GetProviders() - { - return GetInfos(GetInternalProviders(true)); - } - - private IEnumerable<IChapterProvider> GetInternalProviders(bool includeDisabledProviders) - { - var providers = _providers; - - if (!includeDisabledProviders) - { - var options = GetConfiguration(); - - providers = providers - .Where(i => !options.DisabledFetchers.Contains(i.Name)) - .ToArray(); - } - - return providers - .OrderBy(GetConfiguredOrder) - .ThenBy(GetDefaultOrder) - .ToArray(); - } - - private IEnumerable<ChapterProviderInfo> GetInfos(IEnumerable<IChapterProvider> providers) - { - return providers.Select(i => new ChapterProviderInfo - { - Name = i.Name, - Id = GetProviderId(i.Name) - }); - } - - private string GetProviderId(string name) - { - return name.ToLower().GetMD5().ToString("N"); - } - - private IChapterProvider GetProvider(string id) - { - return _providers.First(i => string.Equals(id, GetProviderId(i.Name))); - } - - private int GetConfiguredOrder(IChapterProvider provider) - { - var options = GetConfiguration(); - - // See if there's a user-defined order - var index = Array.IndexOf(options.FetcherOrder, provider.Name); - - if (index != -1) - { - return index; - } - - // Not configured. Just return some high number to put it at the end. - return 100; - } - - private int GetDefaultOrder(IChapterProvider provider) - { - var hasOrder = provider as IHasOrder; - - if (hasOrder != null) - { - return hasOrder.Order; - } - - return 0; - } - public IEnumerable<ChapterInfo> GetChapters(string itemId) { return _itemRepo.GetChapters(new Guid(itemId)); diff --git a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs index 66fe7ea81..8c87e991e 100644 --- a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs +++ b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs @@ -238,22 +238,6 @@ namespace MediaBrowser.Providers.MediaInfo if (options.MetadataRefreshMode == MetadataRefreshMode.FullRefresh || options.MetadataRefreshMode == MetadataRefreshMode.Default) { - var chapterOptions = _chapterManager.GetConfiguration(); - - try - { - var remoteChapters = await DownloadChapters(video, chapters, chapterOptions, cancellationToken).ConfigureAwait(false); - - if (remoteChapters.Count > 0) - { - chapters = remoteChapters; - } - } - catch (Exception ex) - { - _logger.ErrorException("Error downloading chapters", ex); - } - if (chapters.Count == 0 && mediaStreams.Any(i => i.Type == MediaStreamType.Video)) { AddDummyChapters(video, chapters); @@ -561,52 +545,6 @@ namespace MediaBrowser.Providers.MediaInfo currentStreams.AddRange(externalSubtitleStreams); } - private async Task<List<ChapterInfo>> DownloadChapters(Video video, List<ChapterInfo> currentChapters, ChapterOptions options, CancellationToken cancellationToken) - { - if ((options.DownloadEpisodeChapters && - video is Episode) || - (options.DownloadMovieChapters && - video is Movie)) - { - var results = await _chapterManager.Search(video, cancellationToken).ConfigureAwait(false); - - var result = results.FirstOrDefault(); - - if (result != null) - { - var chapters = await _chapterManager.GetChapters(result.Id, cancellationToken).ConfigureAwait(false); - - var chapterInfos = chapters.Chapters.Select(i => new ChapterInfo - { - Name = i.Name, - StartPositionTicks = i.StartPositionTicks - - }).ToList(); - - if (chapterInfos.All(i => i.StartPositionTicks == 0)) - { - if (currentChapters.Count >= chapterInfos.Count) - { - var index = 0; - foreach (var info in chapterInfos) - { - info.StartPositionTicks = currentChapters[index].StartPositionTicks; - index++; - } - } - else - { - chapterInfos.Clear(); - } - } - - return chapterInfos; - } - } - - return new List<ChapterInfo>(); - } - /// <summary> /// The dummy chapter duration /// </summary> diff --git a/MediaBrowser.Providers/MediaInfo/SubtitleScheduledTask.cs b/MediaBrowser.Providers/MediaInfo/SubtitleScheduledTask.cs index 79da291b7..2490f7145 100644 --- a/MediaBrowser.Providers/MediaInfo/SubtitleScheduledTask.cs +++ b/MediaBrowser.Providers/MediaInfo/SubtitleScheduledTask.cs @@ -14,6 +14,8 @@ using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; +using System.IO; +using MediaBrowser.Model.Serialization; namespace MediaBrowser.Providers.MediaInfo { @@ -24,14 +26,16 @@ namespace MediaBrowser.Providers.MediaInfo private readonly ISubtitleManager _subtitleManager; private readonly IMediaSourceManager _mediaSourceManager; private readonly ILogger _logger; + private IJsonSerializer _json; - public SubtitleScheduledTask(ILibraryManager libraryManager, IServerConfigurationManager config, ISubtitleManager subtitleManager, ILogger logger, IMediaSourceManager mediaSourceManager) + public SubtitleScheduledTask(ILibraryManager libraryManager, IJsonSerializer json, IServerConfigurationManager config, ISubtitleManager subtitleManager, ILogger logger, IMediaSourceManager mediaSourceManager) { _libraryManager = libraryManager; _config = config; _subtitleManager = subtitleManager; _logger = logger; _mediaSourceManager = mediaSourceManager; + _json = json; } public string Name @@ -58,38 +62,65 @@ namespace MediaBrowser.Providers.MediaInfo { var options = GetOptions(); - var videos = _libraryManager.RootFolder - .GetRecursiveChildren(i => - { - if (!(i is Video)) - { - return false; - } + var types = new List<string>(); - if (i.LocationType == LocationType.Remote || i.LocationType == LocationType.Virtual) - { - return false; - } + if (options.DownloadEpisodeSubtitles) + { + types.Add("Episode"); + } + if (options.DownloadMovieSubtitles) + { + types.Add("Movie"); + } - return (options.DownloadEpisodeSubtitles && - i is Episode) || - (options.DownloadMovieSubtitles && - i is Movie); - }) - .Cast<Video>() + if (types.Count == 0) + { + return; + } + + var videos = _libraryManager.GetItemList(new InternalItemsQuery + { + MediaTypes = new string[] { MediaType.Video }, + IsVirtualItem = false, + ExcludeLocationTypes = new LocationType[] { LocationType.Remote, LocationType.Virtual }, + IncludeItemTypes = types.ToArray() + + }).OfType<Video>() .ToList(); + var failHistoryPath = Path.Combine(_config.ApplicationPaths.CachePath, "subtitlehistory.json"); + var history = GetHistory(failHistoryPath); + var numComplete = 0; foreach (var video in videos) { + DateTime lastAttempt; + if (history.TryGetValue(video.Id.ToString("N"), out lastAttempt)) + { + if ((DateTime.UtcNow - lastAttempt).TotalDays <= 7) + { + continue; + } + } + try { - await DownloadSubtitles(video, options, cancellationToken).ConfigureAwait(false); + var shouldRetry = await DownloadSubtitles(video, options, cancellationToken).ConfigureAwait(false); + + if (shouldRetry) + { + history[video.Id.ToString("N")] = DateTime.UtcNow; + } + else + { + history.Remove(video.Id.ToString("N")); + } } catch (Exception ex) { _logger.ErrorException("Error downloading subtitles for {0}", ex, video.Path); + history[video.Id.ToString("N")] = DateTime.UtcNow; } // Update progress @@ -99,9 +130,23 @@ namespace MediaBrowser.Providers.MediaInfo progress.Report(100 * percent); } + + _json.SerializeToFile(history, failHistoryPath); } - private async Task DownloadSubtitles(Video video, SubtitleOptions options, CancellationToken cancellationToken) + private Dictionary<string,DateTime> GetHistory(string path) + { + try + { + return _json.DeserializeFromFile<Dictionary<string, DateTime>>(path); + } + catch + { + return new Dictionary<string, DateTime>(); + } + } + + private async Task<bool> DownloadSubtitles(Video video, SubtitleOptions options, CancellationToken cancellationToken) { if ((options.DownloadEpisodeSubtitles && video is Episode) || @@ -124,8 +169,13 @@ namespace MediaBrowser.Providers.MediaInfo if (downloadedLanguages.Count > 0) { await video.RefreshMetadata(cancellationToken).ConfigureAwait(false); + return false; } + + return true; } + + return false; } public IEnumerable<ITaskTrigger> GetDefaultTriggers() diff --git a/MediaBrowser.Server.Implementations/Channels/ChannelDynamicMediaSourceProvider.cs b/MediaBrowser.Server.Implementations/Channels/ChannelDynamicMediaSourceProvider.cs index 6cba1b441..fae78b9bc 100644 --- a/MediaBrowser.Server.Implementations/Channels/ChannelDynamicMediaSourceProvider.cs +++ b/MediaBrowser.Server.Implementations/Channels/ChannelDynamicMediaSourceProvider.cs @@ -30,7 +30,7 @@ namespace MediaBrowser.Server.Implementations.Channels return Task.FromResult<IEnumerable<MediaSourceInfo>>(new List<MediaSourceInfo>()); } - public Task<MediaSourceInfo> OpenMediaSource(string openToken, CancellationToken cancellationToken) + public Task<Tuple<MediaSourceInfo, IDirectStreamProvider>> OpenMediaSource(string openToken, CancellationToken cancellationToken) { throw new NotImplementedException(); } diff --git a/MediaBrowser.Server.Implementations/Library/LibraryManager.cs b/MediaBrowser.Server.Implementations/Library/LibraryManager.cs index 93ee91c21..b2bddc70d 100644 --- a/MediaBrowser.Server.Implementations/Library/LibraryManager.cs +++ b/MediaBrowser.Server.Implementations/Library/LibraryManager.cs @@ -43,6 +43,7 @@ using MediaBrowser.Server.Implementations.Library.Resolvers; using MoreLinq; using SortOrder = MediaBrowser.Model.Entities.SortOrder; using VideoResolver = MediaBrowser.Naming.Video.VideoResolver; +using MediaBrowser.Common.Configuration; namespace MediaBrowser.Server.Implementations.Library { @@ -1900,6 +1901,24 @@ namespace MediaBrowser.Server.Implementations.Library options.EnableInternetProviders = ConfigurationManager.Configuration.EnableInternetProviders; } + if (options.SchemaVersion < 2) + { + var chapterOptions = ConfigurationManager.GetConfiguration<ChapterOptions>("chapters"); + options.ExtractChapterImagesDuringLibraryScan = chapterOptions.ExtractDuringLibraryScan; + + if (collectionFolder != null) + { + if (string.Equals(collectionFolder.CollectionType, "movies", StringComparison.OrdinalIgnoreCase)) + { + options.EnableChapterImageExtraction = chapterOptions.EnableMovieChapterImageExtraction; + } + else if (string.Equals(collectionFolder.CollectionType, CollectionType.TvShows, StringComparison.OrdinalIgnoreCase)) + { + options.EnableChapterImageExtraction = chapterOptions.EnableEpisodeChapterImageExtraction; + } + } + } + return options; } diff --git a/MediaBrowser.Server.Implementations/Library/MediaSourceManager.cs b/MediaBrowser.Server.Implementations/Library/MediaSourceManager.cs index ae32bdaf7..e7bfe56f2 100644 --- a/MediaBrowser.Server.Implementations/Library/MediaSourceManager.cs +++ b/MediaBrowser.Server.Implementations/Library/MediaSourceManager.cs @@ -367,7 +367,9 @@ namespace MediaBrowser.Server.Implementations.Library var tuple = GetProvider(request.OpenToken); var provider = tuple.Item1; - var mediaSource = await provider.OpenMediaSource(tuple.Item2, cancellationToken).ConfigureAwait(false); + var mediaSourceTuple = await provider.OpenMediaSource(tuple.Item2, cancellationToken).ConfigureAwait(false); + + var mediaSource = mediaSourceTuple.Item1; if (string.IsNullOrWhiteSpace(mediaSource.LiveStreamId)) { @@ -381,8 +383,10 @@ namespace MediaBrowser.Server.Implementations.Library Date = DateTime.UtcNow, EnableCloseTimer = enableAutoClose, Id = mediaSource.LiveStreamId, - MediaSource = mediaSource + MediaSource = mediaSource, + DirectStreamProvider = mediaSourceTuple.Item2 }; + _openStreams[mediaSource.LiveStreamId] = info; if (enableAutoClose) @@ -414,7 +418,7 @@ namespace MediaBrowser.Server.Implementations.Library } } - public async Task<MediaSourceInfo> GetLiveStream(string id, CancellationToken cancellationToken) + public async Task<Tuple<MediaSourceInfo, IDirectStreamProvider>> GetLiveStreamWithDirectStreamProvider(string id, CancellationToken cancellationToken) { if (string.IsNullOrWhiteSpace(id)) { @@ -430,7 +434,7 @@ namespace MediaBrowser.Server.Implementations.Library LiveStreamInfo info; if (_openStreams.TryGetValue(id, out info)) { - return info.MediaSource; + return new Tuple<MediaSourceInfo, IDirectStreamProvider>(info.MediaSource, info.DirectStreamProvider); } else { @@ -443,6 +447,12 @@ namespace MediaBrowser.Server.Implementations.Library } } + public async Task<MediaSourceInfo> GetLiveStream(string id, CancellationToken cancellationToken) + { + var result = await GetLiveStreamWithDirectStreamProvider(id, cancellationToken).ConfigureAwait(false); + return result.Item1; + } + public async Task PingLiveStream(string id, CancellationToken cancellationToken) { await _liveStreamSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false); @@ -630,6 +640,7 @@ namespace MediaBrowser.Server.Implementations.Library public string Id; public bool Closed; public MediaSourceInfo MediaSource; + public IDirectStreamProvider DirectStreamProvider; } } }
\ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs index a3ba2c6f8..5b99849cd 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs @@ -36,7 +36,7 @@ using Microsoft.Win32; namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV { - public class EmbyTV : ILiveTvService, ISupportsNewTimerIds, IDisposable + public class EmbyTV : ILiveTvService, ISupportsDirectStreamProvider, ISupportsNewTimerIds, IDisposable { private readonly IApplicationHost _appHpst; private readonly ILogger _logger; @@ -829,9 +829,16 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV public async Task<MediaSourceInfo> GetChannelStream(string channelId, string streamId, CancellationToken cancellationToken) { + var result = await GetChannelStreamWithDirectStreamProvider(channelId, streamId, cancellationToken).ConfigureAwait(false); + + return result.Item1; + } + + public async Task<Tuple<MediaSourceInfo, IDirectStreamProvider>> GetChannelStreamWithDirectStreamProvider(string channelId, string streamId, CancellationToken cancellationToken) + { var result = await GetChannelStreamInternal(channelId, streamId, cancellationToken).ConfigureAwait(false); - return result.Item2; + return new Tuple<MediaSourceInfo, IDirectStreamProvider>(result.Item2, result.Item1 as IDirectStreamProvider); } private MediaSourceInfo CloneMediaSource(MediaSourceInfo mediaSource, int consumerId, bool enableStreamSharing) diff --git a/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs b/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs index 84b162286..9970be908 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs @@ -227,10 +227,12 @@ namespace MediaBrowser.Server.Implementations.LiveTv public async Task<MediaSourceInfo> GetRecordingStream(string id, CancellationToken cancellationToken) { - return await GetLiveStream(id, null, false, cancellationToken).ConfigureAwait(false); + var info = await GetLiveStream(id, null, false, cancellationToken).ConfigureAwait(false); + + return info.Item1; } - public async Task<MediaSourceInfo> GetChannelStream(string id, string mediaSourceId, CancellationToken cancellationToken) + public async Task<Tuple<MediaSourceInfo, IDirectStreamProvider>> GetChannelStream(string id, string mediaSourceId, CancellationToken cancellationToken) { return await GetLiveStream(id, mediaSourceId, true, cancellationToken).ConfigureAwait(false); } @@ -280,7 +282,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv return _services.FirstOrDefault(i => string.Equals(i.Name, name, StringComparison.OrdinalIgnoreCase)); } - private async Task<MediaSourceInfo> GetLiveStream(string id, string mediaSourceId, bool isChannel, CancellationToken cancellationToken) + private async Task<Tuple<MediaSourceInfo, IDirectStreamProvider>> GetLiveStream(string id, string mediaSourceId, bool isChannel, CancellationToken cancellationToken) { await _liveStreamSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false); @@ -294,6 +296,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv MediaSourceInfo info; bool isVideo; ILiveTvService service; + IDirectStreamProvider directStreamProvider = null; if (isChannel) { @@ -301,7 +304,18 @@ namespace MediaBrowser.Server.Implementations.LiveTv isVideo = channel.ChannelType == ChannelType.TV; service = GetService(channel); _logger.Info("Opening channel stream from {0}, external channel Id: {1}", service.Name, channel.ExternalId); - info = await service.GetChannelStream(channel.ExternalId, mediaSourceId, cancellationToken).ConfigureAwait(false); + + var supportsManagedStream = service as ISupportsDirectStreamProvider; + if (supportsManagedStream != null) + { + var streamInfo = await supportsManagedStream.GetChannelStreamWithDirectStreamProvider(channel.ExternalId, mediaSourceId, cancellationToken).ConfigureAwait(false); + info = streamInfo.Item1; + directStreamProvider = streamInfo.Item2; + } + else + { + info = await service.GetChannelStream(channel.ExternalId, mediaSourceId, cancellationToken).ConfigureAwait(false); + } info.RequiresClosing = true; if (info.RequiresClosing) @@ -332,7 +346,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv _logger.Info("Live stream info: {0}", _jsonSerializer.SerializeToString(info)); Normalize(info, service, isVideo); - return info; + return new Tuple<MediaSourceInfo, IDirectStreamProvider>(info, directStreamProvider); } catch (Exception ex) { @@ -1881,11 +1895,11 @@ namespace MediaBrowser.Server.Implementations.LiveTv { if (query.IsScheduled.Value) { - timers = timers.Where(i => i.Item1.Status == RecordingStatus.New || i.Item1.Status == RecordingStatus.Scheduled); + timers = timers.Where(i => i.Item1.Status == RecordingStatus.New); } else { - timers = timers.Where(i => !(i.Item1.Status == RecordingStatus.New || i.Item1.Status == RecordingStatus.Scheduled)); + timers = timers.Where(i => !(i.Item1.Status == RecordingStatus.New)); } } diff --git a/MediaBrowser.Server.Implementations/LiveTv/LiveTvMediaSourceProvider.cs b/MediaBrowser.Server.Implementations/LiveTv/LiveTvMediaSourceProvider.cs index aacc0c22b..1861b79eb 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/LiveTvMediaSourceProvider.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/LiveTvMediaSourceProvider.cs @@ -116,17 +116,20 @@ namespace MediaBrowser.Server.Implementations.LiveTv return list; } - public async Task<MediaSourceInfo> OpenMediaSource(string openToken, CancellationToken cancellationToken) + public async Task<Tuple<MediaSourceInfo, IDirectStreamProvider>> OpenMediaSource(string openToken, CancellationToken cancellationToken) { - MediaSourceInfo stream; + MediaSourceInfo stream = null; const bool isAudio = false; var keys = openToken.Split(new[] { StreamIdDelimeter }, 3); var mediaSourceId = keys.Length >= 3 ? keys[2] : null; + IDirectStreamProvider directStreamProvider = null; if (string.Equals(keys[0], typeof(LiveTvChannel).Name, StringComparison.OrdinalIgnoreCase)) { - stream = await _liveTvManager.GetChannelStream(keys[1], mediaSourceId, cancellationToken).ConfigureAwait(false); + var info = await _liveTvManager.GetChannelStream(keys[1], mediaSourceId, cancellationToken).ConfigureAwait(false); + stream = info.Item1; + directStreamProvider = info.Item2; } else { @@ -142,7 +145,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv _logger.ErrorException("Error probing live tv stream", ex); } - return stream; + return new Tuple<MediaSourceInfo, IDirectStreamProvider>(stream, directStreamProvider); } private async Task AddMediaInfo(MediaSourceInfo mediaSource, bool isAudio, CancellationToken cancellationToken) diff --git a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunLiveStream.cs b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunLiveStream.cs index d49e3f258..dd635bd55 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunLiveStream.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunLiveStream.cs @@ -6,14 +6,16 @@ using CommonIO; using MediaBrowser.Common.Net; using MediaBrowser.Controller; using MediaBrowser.Controller.LiveTv; +using MediaBrowser.Controller.Library; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Logging; using MediaBrowser.Model.MediaInfo; using MediaBrowser.Server.Implementations.LiveTv.EmbyTV; +using System.Collections.Generic; namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.HdHomerun { - public class HdHomerunLiveStream : LiveStream + public class HdHomerunLiveStream : LiveStream, IDirectStreamProvider { private readonly ILogger _logger; private readonly IHttpClient _httpClient; @@ -106,7 +108,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.HdHomerun { onStarted = () => ResolveWhenExists(openTaskCompletionSource, tempFilePath, cancellationToken); } - await DirectRecorder.CopyUntilCancelled(response.Content, outputStream, onStarted, cancellationToken).ConfigureAwait(false); + await CopyUntilCancelled(response.Content, outputStream, onStarted, cancellationToken).ConfigureAwait(false); } } } @@ -137,6 +139,79 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.HdHomerun }).ConfigureAwait(false); } + private List<Tuple<Stream, CancellationToken, TaskCompletionSource<bool>>> _additionalStreams = new List<Tuple<Stream, CancellationToken, TaskCompletionSource<bool>>>(); + + public Task CopyToAsync(Stream stream, CancellationToken cancellationToken) + { + var taskCompletionSource = new TaskCompletionSource<bool>(); + _additionalStreams.Add(new Tuple<Stream, CancellationToken, TaskCompletionSource<bool>>(stream, cancellationToken, taskCompletionSource)); + + return taskCompletionSource.Task; + } + + private void PopAdditionalStream(Tuple<Stream, CancellationToken, TaskCompletionSource<bool>> stream, Exception exception) + { + if (_additionalStreams.Remove(stream)) + { + stream.Item3.TrySetException(exception); + } + } + + private const int BufferSize = 81920; + private async Task CopyUntilCancelled(Stream source, Stream target, Action onStarted, CancellationToken cancellationToken) + { + while (!cancellationToken.IsCancellationRequested) + { + var bytesRead = await CopyToAsyncInternal(source, target, BufferSize, onStarted, cancellationToken).ConfigureAwait(false); + + onStarted = null; + + //var position = fs.Position; + //_logger.Debug("Streamed {0} bytes to position {1} from file {2}", bytesRead, position, path); + + if (bytesRead == 0) + { + await Task.Delay(100).ConfigureAwait(false); + } + } + } + + private async Task<int> CopyToAsyncInternal(Stream source, Stream destination, Int32 bufferSize, Action onStarted, CancellationToken cancellationToken) + { + byte[] buffer = new byte[bufferSize]; + int bytesRead; + int totalBytesRead = 0; + + while ((bytesRead = await source.ReadAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false)) != 0) + { + await destination.WriteAsync(buffer, 0, bytesRead, cancellationToken).ConfigureAwait(false); + + foreach (var additionalStream in _additionalStreams) + { + cancellationToken.ThrowIfCancellationRequested(); + + try + { + await additionalStream.Item1.WriteAsync(buffer, 0, bytesRead, additionalStream.Item2).ConfigureAwait(false); + } + catch (Exception ex) + { + PopAdditionalStream(additionalStream, ex); + } + } + + totalBytesRead += bytesRead; + + if (onStarted != null) + { + onStarted(); + } + onStarted = null; + } + + return totalBytesRead; + } + private async void ResolveWhenExists(TaskCompletionSource<bool> taskCompletionSource, string file, CancellationToken cancellationToken) { while (!File.Exists(file) && !cancellationToken.IsCancellationRequested) diff --git a/MediaBrowser.Server.Implementations/Sync/SyncedMediaSourceProvider.cs b/MediaBrowser.Server.Implementations/Sync/SyncedMediaSourceProvider.cs index cb666b6ac..e0553b1b1 100644 --- a/MediaBrowser.Server.Implementations/Sync/SyncedMediaSourceProvider.cs +++ b/MediaBrowser.Server.Implementations/Sync/SyncedMediaSourceProvider.cs @@ -99,7 +99,7 @@ namespace MediaBrowser.Server.Implementations.Sync // Do not use a pipe here because Roku http requests to the server will fail, without any explicit error message. private const string StreamIdDelimeterString = "_"; - public async Task<MediaSourceInfo> OpenMediaSource(string openToken, CancellationToken cancellationToken) + public async Task<Tuple<MediaSourceInfo, IDirectStreamProvider>> OpenMediaSource(string openToken, CancellationToken cancellationToken) { var openKeys = openToken.Split(new[] { StreamIdDelimeterString[0] }, 3); @@ -137,7 +137,7 @@ namespace MediaBrowser.Server.Implementations.Sync mediaSource.Protocol = dynamicInfo.Protocol; mediaSource.RequiredHttpHeaders = dynamicInfo.RequiredHttpHeaders; - return mediaSource; + return new Tuple<MediaSourceInfo, IDirectStreamProvider>(mediaSource, null); } private void SetStaticMediaSourceInfo(LocalItem item, MediaSourceInfo mediaSource) diff --git a/MediaBrowser.Server.Startup.Common/ApplicationHost.cs b/MediaBrowser.Server.Startup.Common/ApplicationHost.cs index 59b553ecf..a72231893 100644 --- a/MediaBrowser.Server.Startup.Common/ApplicationHost.cs +++ b/MediaBrowser.Server.Startup.Common/ApplicationHost.cs @@ -826,7 +826,6 @@ namespace MediaBrowser.Server.Startup.Common LiveTvManager.AddParts(GetExports<ILiveTvService>(), GetExports<ITunerHost>(), GetExports<IListingsProvider>()); SubtitleManager.AddParts(GetExports<ISubtitleProvider>()); - ChapterManager.AddParts(GetExports<IChapterProvider>()); SessionManager.AddParts(GetExports<ISessionControllerFactory>()); |
