aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLuke Pulverenti <luke.pulverenti@gmail.com>2016-10-05 03:15:29 -0400
committerLuke Pulverenti <luke.pulverenti@gmail.com>2016-10-05 03:15:29 -0400
commitb9cacd8076c00bfc371898c50315304927efaff8 (patch)
treee3fee51b628343baa2c4b8cc0e4b1d3b826c1726
parentc4e137e6cf7a52937e1169d1f170da4b8e72ba6a (diff)
update live streams
-rw-r--r--MediaBrowser.Api/Library/ChapterService.cs30
-rw-r--r--MediaBrowser.Api/LiveTv/LiveTvService.cs2
-rw-r--r--MediaBrowser.Api/MediaBrowser.Api.csproj1
-rw-r--r--MediaBrowser.Api/Playback/BaseStreamingService.cs4
-rw-r--r--MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs35
-rw-r--r--MediaBrowser.Api/Playback/Progressive/ProgressiveStreamWriter.cs29
-rw-r--r--MediaBrowser.Api/Playback/StreamState.cs1
-rw-r--r--MediaBrowser.Controller/Chapters/ChapterResponse.cs19
-rw-r--r--MediaBrowser.Controller/Chapters/ChapterSearchRequest.cs31
-rw-r--r--MediaBrowser.Controller/Chapters/IChapterManager.cs47
-rw-r--r--MediaBrowser.Controller/Chapters/IChapterProvider.cs39
-rw-r--r--MediaBrowser.Controller/Library/IMediaSourceManager.cs8
-rw-r--r--MediaBrowser.Controller/Library/IMediaSourceProvider.cs3
-rw-r--r--MediaBrowser.Controller/LiveTv/ILiveTvManager.cs3
-rw-r--r--MediaBrowser.Controller/LiveTv/ILiveTvService.cs6
-rw-r--r--MediaBrowser.Controller/MediaBrowser.Controller.csproj3
-rw-r--r--MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj9
-rw-r--r--MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj9
-rw-r--r--MediaBrowser.Model/Chapters/ChapterProviderInfo.cs8
-rw-r--r--MediaBrowser.Model/Chapters/RemoteChapterInfo.cs18
-rw-r--r--MediaBrowser.Model/Chapters/RemoteChapterResult.cs48
-rw-r--r--MediaBrowser.Model/Configuration/ChapterOptions.cs16
-rw-r--r--MediaBrowser.Model/MediaBrowser.Model.csproj3
-rw-r--r--MediaBrowser.Providers/Chapters/ChapterManager.cs220
-rw-r--r--MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs62
-rw-r--r--MediaBrowser.Providers/MediaInfo/SubtitleScheduledTask.cs90
-rw-r--r--MediaBrowser.Server.Implementations/Channels/ChannelDynamicMediaSourceProvider.cs2
-rw-r--r--MediaBrowser.Server.Implementations/Library/LibraryManager.cs19
-rw-r--r--MediaBrowser.Server.Implementations/Library/MediaSourceManager.cs19
-rw-r--r--MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs11
-rw-r--r--MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs28
-rw-r--r--MediaBrowser.Server.Implementations/LiveTv/LiveTvMediaSourceProvider.cs11
-rw-r--r--MediaBrowser.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunLiveStream.cs79
-rw-r--r--MediaBrowser.Server.Implementations/Sync/SyncedMediaSourceProvider.cs4
-rw-r--r--MediaBrowser.Server.Startup.Common/ApplicationHost.cs1
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&lt;MediaSourceInfo&gt;.</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&lt;MediaSourceInfo&gt;.</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>());