commit
a2fa7c4754
|
@ -56,7 +56,8 @@ namespace Emby.Drawing.ImageMagick
|
||||||
"bmp",
|
"bmp",
|
||||||
"erf",
|
"erf",
|
||||||
"raf",
|
"raf",
|
||||||
"rw2"
|
"rw2",
|
||||||
|
"nrw"
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -705,7 +705,7 @@ namespace MediaBrowser.Api.LiveTv
|
||||||
|
|
||||||
var outputHeaders = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
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;
|
long startPosition = 0;
|
||||||
|
|
||||||
|
|
|
@ -79,7 +79,6 @@
|
||||||
<Compile Include="Dlna\DlnaService.cs" />
|
<Compile Include="Dlna\DlnaService.cs" />
|
||||||
<Compile Include="FilterService.cs" />
|
<Compile Include="FilterService.cs" />
|
||||||
<Compile Include="IHasDtoOptions.cs" />
|
<Compile Include="IHasDtoOptions.cs" />
|
||||||
<Compile Include="Library\ChapterService.cs" />
|
|
||||||
<Compile Include="Playback\MediaInfoService.cs" />
|
<Compile Include="Playback\MediaInfoService.cs" />
|
||||||
<Compile Include="Playback\TranscodingThrottler.cs" />
|
<Compile Include="Playback\TranscodingThrottler.cs" />
|
||||||
<Compile Include="PlaylistService.cs" />
|
<Compile Include="PlaylistService.cs" />
|
||||||
|
|
|
@ -1888,7 +1888,9 @@ namespace MediaBrowser.Api.Playback
|
||||||
}
|
}
|
||||||
else
|
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;
|
var videoRequest = request as VideoStreamRequest;
|
||||||
|
|
|
@ -121,6 +121,25 @@ namespace MediaBrowser.Api.Playback.Progressive
|
||||||
|
|
||||||
var responseHeaders = new Dictionary<string, string>();
|
var responseHeaders = new Dictionary<string, string>();
|
||||||
|
|
||||||
|
if (request.Static && state.DirectStreamProvider != null)
|
||||||
|
{
|
||||||
|
AddDlnaHeaders(state, responseHeaders, true);
|
||||||
|
|
||||||
|
using (state)
|
||||||
|
{
|
||||||
|
var outputHeaders = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||||
|
|
||||||
|
// TODO: Don't hardcode this
|
||||||
|
outputHeaders["Content-Type"] = MediaBrowser.Model.Net.MimeTypes.GetMimeType("file.ts");
|
||||||
|
|
||||||
|
var streamSource = new ProgressiveFileCopier(state.DirectStreamProvider, outputHeaders, null, Logger, CancellationToken.None)
|
||||||
|
{
|
||||||
|
AllowEndOfFile = false
|
||||||
|
};
|
||||||
|
return ResultFactory.GetAsyncStreamWriter(streamSource);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Static remote stream
|
// Static remote stream
|
||||||
if (request.Static && state.InputProtocol == MediaProtocol.Http)
|
if (request.Static && state.InputProtocol == MediaProtocol.Http)
|
||||||
{
|
{
|
||||||
|
@ -128,25 +147,7 @@ namespace MediaBrowser.Api.Playback.Progressive
|
||||||
|
|
||||||
using (state)
|
using (state)
|
||||||
{
|
{
|
||||||
if (state.MediaPath.IndexOf("/livestreamfiles/", StringComparison.OrdinalIgnoreCase) != -1)
|
return await GetStaticRemoteStreamResult(state, responseHeaders, isHeadRequest, cancellationTokenSource).ConfigureAwait(false);
|
||||||
{
|
|
||||||
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);
|
|
||||||
|
|
||||||
outputHeaders["Content-Type"] = MimeTypes.GetMimeType(filePath);
|
|
||||||
|
|
||||||
var streamSource = new ProgressiveFileCopier(FileSystem, filePath, outputHeaders, null, Logger, CancellationToken.None)
|
|
||||||
{
|
|
||||||
AllowEndOfFile = false
|
|
||||||
};
|
|
||||||
return ResultFactory.GetAsyncStreamWriter(streamSource);
|
|
||||||
}
|
|
||||||
|
|
||||||
return await GetStaticRemoteStreamResult(state, responseHeaders, isHeadRequest, cancellationTokenSource)
|
|
||||||
.ConfigureAwait(false);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,6 +7,7 @@ using CommonIO;
|
||||||
using MediaBrowser.Controller.Net;
|
using MediaBrowser.Controller.Net;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using ServiceStack.Web;
|
using ServiceStack.Web;
|
||||||
|
using MediaBrowser.Controller.Library;
|
||||||
|
|
||||||
namespace MediaBrowser.Api.Playback.Progressive
|
namespace MediaBrowser.Api.Playback.Progressive
|
||||||
{
|
{
|
||||||
|
@ -26,6 +27,8 @@ namespace MediaBrowser.Api.Playback.Progressive
|
||||||
public long StartPosition { get; set; }
|
public long StartPosition { get; set; }
|
||||||
public bool AllowEndOfFile = true;
|
public bool AllowEndOfFile = true;
|
||||||
|
|
||||||
|
private IDirectStreamProvider _directStreamProvider;
|
||||||
|
|
||||||
public ProgressiveFileCopier(IFileSystem fileSystem, string path, Dictionary<string, string> outputHeaders, TranscodingJob job, ILogger logger, CancellationToken cancellationToken)
|
public ProgressiveFileCopier(IFileSystem fileSystem, string path, Dictionary<string, string> outputHeaders, TranscodingJob job, ILogger logger, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
_fileSystem = fileSystem;
|
_fileSystem = fileSystem;
|
||||||
|
@ -36,6 +39,15 @@ namespace MediaBrowser.Api.Playback.Progressive
|
||||||
_cancellationToken = cancellationToken;
|
_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
|
public IDictionary<string, string> Options
|
||||||
{
|
{
|
||||||
get
|
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)
|
public async Task WriteToAsync(Stream outputStream)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
if (_directStreamProvider != null)
|
||||||
|
{
|
||||||
|
await _directStreamProvider.CopyToAsync(outputStream, _cancellationToken).ConfigureAwait(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
var eofCount = 0;
|
var eofCount = 0;
|
||||||
|
|
||||||
using (var fs = _fileSystem.GetFileStream(_path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, true))
|
using (var inputStream = GetInputStream())
|
||||||
{
|
{
|
||||||
if (StartPosition > 0)
|
if (StartPosition > 0)
|
||||||
{
|
{
|
||||||
fs.Position = StartPosition;
|
inputStream.Position = StartPosition;
|
||||||
}
|
}
|
||||||
|
|
||||||
while (eofCount < 15 || !AllowEndOfFile)
|
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;
|
//var position = fs.Position;
|
||||||
//_logger.Debug("Streamed {0} bytes to position {1} from file {2}", bytesRead, position, path);
|
//_logger.Debug("Streamed {0} bytes to position {1} from file {2}", bytesRead, position, path);
|
||||||
|
|
|
@ -37,6 +37,7 @@ namespace MediaBrowser.Api.Playback
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <value>The log file stream.</value>
|
/// <value>The log file stream.</value>
|
||||||
public Stream LogFileStream { get; set; }
|
public Stream LogFileStream { get; set; }
|
||||||
|
public IDirectStreamProvider DirectStreamProvider { get; set; }
|
||||||
|
|
||||||
public string InputContainer { get; set; }
|
public string InputContainer { get; set; }
|
||||||
|
|
||||||
|
|
|
@ -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>();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,6 +1,4 @@
|
||||||
using MediaBrowser.Controller.Entities;
|
using System.Collections.Generic;
|
||||||
using MediaBrowser.Model.Chapters;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using MediaBrowser.Model.Configuration;
|
using MediaBrowser.Model.Configuration;
|
||||||
|
@ -13,12 +11,6 @@ namespace MediaBrowser.Controller.Chapters
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public interface IChapterManager
|
public interface IChapterManager
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// Adds the parts.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="chapterProviders">The chapter providers.</param>
|
|
||||||
void AddParts(IEnumerable<IChapterProvider> chapterProviders);
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the chapters.
|
/// Gets the chapters.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -35,43 +27,6 @@ namespace MediaBrowser.Controller.Chapters
|
||||||
/// <returns>Task.</returns>
|
/// <returns>Task.</returns>
|
||||||
Task SaveChapters(string itemId, List<ChapterInfo> chapters, CancellationToken cancellationToken);
|
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>
|
/// <summary>
|
||||||
/// Gets the configuration.
|
/// Gets the configuration.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -7,6 +7,7 @@ using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
namespace MediaBrowser.Controller.Library
|
namespace MediaBrowser.Controller.Library
|
||||||
{
|
{
|
||||||
|
@ -80,6 +81,8 @@ namespace MediaBrowser.Controller.Library
|
||||||
/// <returns>Task<MediaSourceInfo>.</returns>
|
/// <returns>Task<MediaSourceInfo>.</returns>
|
||||||
Task<MediaSourceInfo> GetLiveStream(string id, CancellationToken cancellationToken);
|
Task<MediaSourceInfo> GetLiveStream(string id, CancellationToken cancellationToken);
|
||||||
|
|
||||||
|
Task<Tuple<MediaSourceInfo, IDirectStreamProvider>> GetLiveStreamWithDirectStreamProvider(string id, CancellationToken cancellationToken);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Pings the media source.
|
/// Pings the media source.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -95,4 +98,9 @@ namespace MediaBrowser.Controller.Library
|
||||||
/// <returns>Task.</returns>
|
/// <returns>Task.</returns>
|
||||||
Task CloseLiveStream(string id);
|
Task CloseLiveStream(string id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public interface IDirectStreamProvider
|
||||||
|
{
|
||||||
|
Task CopyToAsync(Stream stream, CancellationToken cancellationToken);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@ using MediaBrowser.Model.Dto;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using System;
|
||||||
|
|
||||||
namespace MediaBrowser.Controller.Library
|
namespace MediaBrowser.Controller.Library
|
||||||
{
|
{
|
||||||
|
@ -22,7 +23,7 @@ namespace MediaBrowser.Controller.Library
|
||||||
/// <param name="openToken">The open token.</param>
|
/// <param name="openToken">The open token.</param>
|
||||||
/// <param name="cancellationToken">The cancellation token.</param>
|
/// <param name="cancellationToken">The cancellation token.</param>
|
||||||
/// <returns>Task<MediaSourceInfo>.</returns>
|
/// <returns>Task<MediaSourceInfo>.</returns>
|
||||||
Task<MediaSourceInfo> OpenMediaSource(string openToken, CancellationToken cancellationToken);
|
Task<Tuple<MediaSourceInfo,IDirectStreamProvider>> OpenMediaSource(string openToken, CancellationToken cancellationToken);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Closes the media source.
|
/// Closes the media source.
|
||||||
|
|
|
@ -9,6 +9,7 @@ using System.Collections.Generic;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using MediaBrowser.Model.Events;
|
using MediaBrowser.Model.Events;
|
||||||
|
using MediaBrowser.Controller.Library;
|
||||||
|
|
||||||
namespace MediaBrowser.Controller.LiveTv
|
namespace MediaBrowser.Controller.LiveTv
|
||||||
{
|
{
|
||||||
|
@ -156,7 +157,7 @@ namespace MediaBrowser.Controller.LiveTv
|
||||||
/// <param name="mediaSourceId">The media source identifier.</param>
|
/// <param name="mediaSourceId">The media source identifier.</param>
|
||||||
/// <param name="cancellationToken">The cancellation token.</param>
|
/// <param name="cancellationToken">The cancellation token.</param>
|
||||||
/// <returns>Task{StreamResponseInfo}.</returns>
|
/// <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>
|
/// <summary>
|
||||||
/// Gets the program.
|
/// Gets the program.
|
||||||
|
|
|
@ -4,6 +4,7 @@ using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using MediaBrowser.Controller.Library;
|
||||||
|
|
||||||
namespace MediaBrowser.Controller.LiveTv
|
namespace MediaBrowser.Controller.LiveTv
|
||||||
{
|
{
|
||||||
|
@ -245,4 +246,9 @@ namespace MediaBrowser.Controller.LiveTv
|
||||||
/// <returns>Task.</returns>
|
/// <returns>Task.</returns>
|
||||||
Task<string> CreateSeriesTimer(SeriesTimerInfo info, CancellationToken cancellationToken);
|
Task<string> CreateSeriesTimer(SeriesTimerInfo info, CancellationToken cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public interface ISupportsDirectStreamProvider
|
||||||
|
{
|
||||||
|
Task<Tuple<MediaSourceInfo, IDirectStreamProvider>> GetChannelStreamWithDirectStreamProvider(string channelId, string streamId, CancellationToken cancellationToken);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -91,10 +91,7 @@
|
||||||
<Compile Include="Channels\InternalChannelItemQuery.cs" />
|
<Compile Include="Channels\InternalChannelItemQuery.cs" />
|
||||||
<Compile Include="Channels\IRequiresMediaInfoCallback.cs" />
|
<Compile Include="Channels\IRequiresMediaInfoCallback.cs" />
|
||||||
<Compile Include="Channels\ISearchableChannel.cs" />
|
<Compile Include="Channels\ISearchableChannel.cs" />
|
||||||
<Compile Include="Chapters\ChapterSearchRequest.cs" />
|
|
||||||
<Compile Include="Chapters\IChapterManager.cs" />
|
<Compile Include="Chapters\IChapterManager.cs" />
|
||||||
<Compile Include="Chapters\IChapterProvider.cs" />
|
|
||||||
<Compile Include="Chapters\ChapterResponse.cs" />
|
|
||||||
<Compile Include="Collections\CollectionCreationOptions.cs" />
|
<Compile Include="Collections\CollectionCreationOptions.cs" />
|
||||||
<Compile Include="Collections\CollectionEvents.cs" />
|
<Compile Include="Collections\CollectionEvents.cs" />
|
||||||
<Compile Include="Collections\ICollectionManager.cs" />
|
<Compile Include="Collections\ICollectionManager.cs" />
|
||||||
|
|
|
@ -160,15 +160,6 @@
|
||||||
<Compile Include="..\MediaBrowser.Model\Channels\ChannelQuery.cs">
|
<Compile Include="..\MediaBrowser.Model\Channels\ChannelQuery.cs">
|
||||||
<Link>Channels\ChannelQuery.cs</Link>
|
<Link>Channels\ChannelQuery.cs</Link>
|
||||||
</Compile>
|
</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">
|
<Compile Include="..\MediaBrowser.Model\Collections\CollectionCreationResult.cs">
|
||||||
<Link>Collections\CollectionCreationResult.cs</Link>
|
<Link>Collections\CollectionCreationResult.cs</Link>
|
||||||
</Compile>
|
</Compile>
|
||||||
|
|
|
@ -132,15 +132,6 @@
|
||||||
<Compile Include="..\MediaBrowser.Model\Channels\ChannelQuery.cs">
|
<Compile Include="..\MediaBrowser.Model\Channels\ChannelQuery.cs">
|
||||||
<Link>Channels\ChannelQuery.cs</Link>
|
<Link>Channels\ChannelQuery.cs</Link>
|
||||||
</Compile>
|
</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">
|
<Compile Include="..\MediaBrowser.Model\Collections\CollectionCreationResult.cs">
|
||||||
<Link>Collections\CollectionCreationResult.cs</Link>
|
<Link>Collections\CollectionCreationResult.cs</Link>
|
||||||
</Compile>
|
</Compile>
|
||||||
|
|
|
@ -1,8 +0,0 @@
|
||||||
namespace MediaBrowser.Model.Chapters
|
|
||||||
{
|
|
||||||
public class ChapterProviderInfo
|
|
||||||
{
|
|
||||||
public string Name { get; set; }
|
|
||||||
public string Id { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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; }
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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; }
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -2,18 +2,10 @@
|
||||||
{
|
{
|
||||||
public class ChapterOptions
|
public class ChapterOptions
|
||||||
{
|
{
|
||||||
public bool DownloadMovieChapters { get; set; }
|
public bool EnableMovieChapterImageExtraction { get; set; }
|
||||||
public bool DownloadEpisodeChapters { get; set; }
|
public bool EnableEpisodeChapterImageExtraction { get; set; }
|
||||||
|
public bool EnableOtherVideoChapterImageExtraction { get; set; }
|
||||||
|
|
||||||
public string[] FetcherOrder { get; set; }
|
public bool ExtractDuringLibraryScan { get; set; }
|
||||||
public string[] DisabledFetchers { get; set; }
|
|
||||||
|
|
||||||
public ChapterOptions()
|
|
||||||
{
|
|
||||||
DownloadMovieChapters = true;
|
|
||||||
|
|
||||||
DisabledFetchers = new string[] { };
|
|
||||||
FetcherOrder = new string[] { };
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -84,9 +84,6 @@
|
||||||
<Compile Include="Channels\ChannelMediaContentType.cs" />
|
<Compile Include="Channels\ChannelMediaContentType.cs" />
|
||||||
<Compile Include="Channels\ChannelMediaType.cs" />
|
<Compile Include="Channels\ChannelMediaType.cs" />
|
||||||
<Compile Include="Channels\ChannelQuery.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="Collections\CollectionCreationResult.cs" />
|
||||||
<Compile Include="Configuration\AccessSchedule.cs" />
|
<Compile Include="Configuration\AccessSchedule.cs" />
|
||||||
<Compile Include="Configuration\ChannelOptions.cs" />
|
<Compile Include="Configuration\ChannelOptions.cs" />
|
||||||
|
|
|
@ -8,7 +8,6 @@ using MediaBrowser.Controller.Entities.TV;
|
||||||
using MediaBrowser.Controller.Library;
|
using MediaBrowser.Controller.Library;
|
||||||
using MediaBrowser.Controller.Persistence;
|
using MediaBrowser.Controller.Persistence;
|
||||||
using MediaBrowser.Controller.Providers;
|
using MediaBrowser.Controller.Providers;
|
||||||
using MediaBrowser.Model.Chapters;
|
|
||||||
using MediaBrowser.Model.Configuration;
|
using MediaBrowser.Model.Configuration;
|
||||||
using MediaBrowser.Model.Entities;
|
using MediaBrowser.Model.Entities;
|
||||||
using MediaBrowser.Model.Logging;
|
using MediaBrowser.Model.Logging;
|
||||||
|
@ -22,7 +21,6 @@ namespace MediaBrowser.Providers.Chapters
|
||||||
{
|
{
|
||||||
public class ChapterManager : IChapterManager
|
public class ChapterManager : IChapterManager
|
||||||
{
|
{
|
||||||
private IChapterProvider[] _providers;
|
|
||||||
private readonly ILibraryManager _libraryManager;
|
private readonly ILibraryManager _libraryManager;
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger _logger;
|
||||||
private readonly IServerConfigurationManager _config;
|
private readonly IServerConfigurationManager _config;
|
||||||
|
@ -36,224 +34,6 @@ namespace MediaBrowser.Providers.Chapters
|
||||||
_itemRepo = itemRepo;
|
_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)
|
public IEnumerable<ChapterInfo> GetChapters(string itemId)
|
||||||
{
|
{
|
||||||
return _itemRepo.GetChapters(new Guid(itemId));
|
return _itemRepo.GetChapters(new Guid(itemId));
|
||||||
|
|
|
@ -238,22 +238,6 @@ namespace MediaBrowser.Providers.MediaInfo
|
||||||
if (options.MetadataRefreshMode == MetadataRefreshMode.FullRefresh ||
|
if (options.MetadataRefreshMode == MetadataRefreshMode.FullRefresh ||
|
||||||
options.MetadataRefreshMode == MetadataRefreshMode.Default)
|
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))
|
if (chapters.Count == 0 && mediaStreams.Any(i => i.Type == MediaStreamType.Video))
|
||||||
{
|
{
|
||||||
AddDummyChapters(video, chapters);
|
AddDummyChapters(video, chapters);
|
||||||
|
@ -561,52 +545,6 @@ namespace MediaBrowser.Providers.MediaInfo
|
||||||
currentStreams.AddRange(externalSubtitleStreams);
|
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>
|
/// <summary>
|
||||||
/// The dummy chapter duration
|
/// The dummy chapter duration
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
@ -14,6 +14,8 @@ using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using System.IO;
|
||||||
|
using MediaBrowser.Model.Serialization;
|
||||||
|
|
||||||
namespace MediaBrowser.Providers.MediaInfo
|
namespace MediaBrowser.Providers.MediaInfo
|
||||||
{
|
{
|
||||||
|
@ -24,14 +26,16 @@ namespace MediaBrowser.Providers.MediaInfo
|
||||||
private readonly ISubtitleManager _subtitleManager;
|
private readonly ISubtitleManager _subtitleManager;
|
||||||
private readonly IMediaSourceManager _mediaSourceManager;
|
private readonly IMediaSourceManager _mediaSourceManager;
|
||||||
private readonly ILogger _logger;
|
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;
|
_libraryManager = libraryManager;
|
||||||
_config = config;
|
_config = config;
|
||||||
_subtitleManager = subtitleManager;
|
_subtitleManager = subtitleManager;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_mediaSourceManager = mediaSourceManager;
|
_mediaSourceManager = mediaSourceManager;
|
||||||
|
_json = json;
|
||||||
}
|
}
|
||||||
|
|
||||||
public string Name
|
public string Name
|
||||||
|
@ -58,38 +62,65 @@ namespace MediaBrowser.Providers.MediaInfo
|
||||||
{
|
{
|
||||||
var options = GetOptions();
|
var options = GetOptions();
|
||||||
|
|
||||||
var videos = _libraryManager.RootFolder
|
var types = new List<string>();
|
||||||
.GetRecursiveChildren(i =>
|
|
||||||
{
|
|
||||||
if (!(i is Video))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (i.LocationType == LocationType.Remote || i.LocationType == LocationType.Virtual)
|
if (options.DownloadEpisodeSubtitles)
|
||||||
{
|
{
|
||||||
return false;
|
types.Add("Episode");
|
||||||
}
|
}
|
||||||
|
if (options.DownloadMovieSubtitles)
|
||||||
|
{
|
||||||
|
types.Add("Movie");
|
||||||
|
}
|
||||||
|
|
||||||
return (options.DownloadEpisodeSubtitles &&
|
if (types.Count == 0)
|
||||||
i is Episode) ||
|
{
|
||||||
(options.DownloadMovieSubtitles &&
|
return;
|
||||||
i is Movie);
|
}
|
||||||
})
|
|
||||||
.Cast<Video>()
|
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();
|
.ToList();
|
||||||
|
|
||||||
|
var failHistoryPath = Path.Combine(_config.ApplicationPaths.CachePath, "subtitlehistory.json");
|
||||||
|
var history = GetHistory(failHistoryPath);
|
||||||
|
|
||||||
var numComplete = 0;
|
var numComplete = 0;
|
||||||
|
|
||||||
foreach (var video in videos)
|
foreach (var video in videos)
|
||||||
{
|
{
|
||||||
|
DateTime lastAttempt;
|
||||||
|
if (history.TryGetValue(video.Id.ToString("N"), out lastAttempt))
|
||||||
|
{
|
||||||
|
if ((DateTime.UtcNow - lastAttempt).TotalDays <= 7)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
try
|
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)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
_logger.ErrorException("Error downloading subtitles for {0}", ex, video.Path);
|
_logger.ErrorException("Error downloading subtitles for {0}", ex, video.Path);
|
||||||
|
history[video.Id.ToString("N")] = DateTime.UtcNow;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update progress
|
// Update progress
|
||||||
|
@ -99,9 +130,23 @@ namespace MediaBrowser.Providers.MediaInfo
|
||||||
|
|
||||||
progress.Report(100 * percent);
|
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 &&
|
if ((options.DownloadEpisodeSubtitles &&
|
||||||
video is Episode) ||
|
video is Episode) ||
|
||||||
|
@ -124,8 +169,13 @@ namespace MediaBrowser.Providers.MediaInfo
|
||||||
if (downloadedLanguages.Count > 0)
|
if (downloadedLanguages.Count > 0)
|
||||||
{
|
{
|
||||||
await video.RefreshMetadata(cancellationToken).ConfigureAwait(false);
|
await video.RefreshMetadata(cancellationToken).ConfigureAwait(false);
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public IEnumerable<ITaskTrigger> GetDefaultTriggers()
|
public IEnumerable<ITaskTrigger> GetDefaultTriggers()
|
||||||
|
|
|
@ -30,7 +30,7 @@ namespace MediaBrowser.Server.Implementations.Channels
|
||||||
return Task.FromResult<IEnumerable<MediaSourceInfo>>(new List<MediaSourceInfo>());
|
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();
|
throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
|
|
|
@ -43,6 +43,7 @@ using MediaBrowser.Server.Implementations.Library.Resolvers;
|
||||||
using MoreLinq;
|
using MoreLinq;
|
||||||
using SortOrder = MediaBrowser.Model.Entities.SortOrder;
|
using SortOrder = MediaBrowser.Model.Entities.SortOrder;
|
||||||
using VideoResolver = MediaBrowser.Naming.Video.VideoResolver;
|
using VideoResolver = MediaBrowser.Naming.Video.VideoResolver;
|
||||||
|
using MediaBrowser.Common.Configuration;
|
||||||
|
|
||||||
namespace MediaBrowser.Server.Implementations.Library
|
namespace MediaBrowser.Server.Implementations.Library
|
||||||
{
|
{
|
||||||
|
@ -1900,6 +1901,24 @@ namespace MediaBrowser.Server.Implementations.Library
|
||||||
options.EnableInternetProviders = ConfigurationManager.Configuration.EnableInternetProviders;
|
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;
|
return options;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -367,7 +367,9 @@ namespace MediaBrowser.Server.Implementations.Library
|
||||||
var tuple = GetProvider(request.OpenToken);
|
var tuple = GetProvider(request.OpenToken);
|
||||||
var provider = tuple.Item1;
|
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))
|
if (string.IsNullOrWhiteSpace(mediaSource.LiveStreamId))
|
||||||
{
|
{
|
||||||
|
@ -381,8 +383,10 @@ namespace MediaBrowser.Server.Implementations.Library
|
||||||
Date = DateTime.UtcNow,
|
Date = DateTime.UtcNow,
|
||||||
EnableCloseTimer = enableAutoClose,
|
EnableCloseTimer = enableAutoClose,
|
||||||
Id = mediaSource.LiveStreamId,
|
Id = mediaSource.LiveStreamId,
|
||||||
MediaSource = mediaSource
|
MediaSource = mediaSource,
|
||||||
|
DirectStreamProvider = mediaSourceTuple.Item2
|
||||||
};
|
};
|
||||||
|
|
||||||
_openStreams[mediaSource.LiveStreamId] = info;
|
_openStreams[mediaSource.LiveStreamId] = info;
|
||||||
|
|
||||||
if (enableAutoClose)
|
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))
|
if (string.IsNullOrWhiteSpace(id))
|
||||||
{
|
{
|
||||||
|
@ -430,7 +434,7 @@ namespace MediaBrowser.Server.Implementations.Library
|
||||||
LiveStreamInfo info;
|
LiveStreamInfo info;
|
||||||
if (_openStreams.TryGetValue(id, out info))
|
if (_openStreams.TryGetValue(id, out info))
|
||||||
{
|
{
|
||||||
return info.MediaSource;
|
return new Tuple<MediaSourceInfo, IDirectStreamProvider>(info.MediaSource, info.DirectStreamProvider);
|
||||||
}
|
}
|
||||||
else
|
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)
|
public async Task PingLiveStream(string id, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
await _liveStreamSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
|
await _liveStreamSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
|
||||||
|
@ -630,6 +640,7 @@ namespace MediaBrowser.Server.Implementations.Library
|
||||||
public string Id;
|
public string Id;
|
||||||
public bool Closed;
|
public bool Closed;
|
||||||
public MediaSourceInfo MediaSource;
|
public MediaSourceInfo MediaSource;
|
||||||
|
public IDirectStreamProvider DirectStreamProvider;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -36,7 +36,7 @@ using Microsoft.Win32;
|
||||||
|
|
||||||
namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
|
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 IApplicationHost _appHpst;
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger _logger;
|
||||||
|
@ -828,10 +828,17 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
|
||||||
private readonly Dictionary<string, LiveStream> _liveStreams = new Dictionary<string, LiveStream>();
|
private readonly Dictionary<string, LiveStream> _liveStreams = new Dictionary<string, LiveStream>();
|
||||||
|
|
||||||
public async Task<MediaSourceInfo> GetChannelStream(string channelId, string streamId, CancellationToken cancellationToken)
|
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);
|
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)
|
private MediaSourceInfo CloneMediaSource(MediaSourceInfo mediaSource, int consumerId, bool enableStreamSharing)
|
||||||
|
|
|
@ -227,10 +227,12 @@ namespace MediaBrowser.Server.Implementations.LiveTv
|
||||||
|
|
||||||
public async Task<MediaSourceInfo> GetRecordingStream(string id, CancellationToken cancellationToken)
|
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);
|
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));
|
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);
|
await _liveStreamSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
|
@ -294,6 +296,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv
|
||||||
MediaSourceInfo info;
|
MediaSourceInfo info;
|
||||||
bool isVideo;
|
bool isVideo;
|
||||||
ILiveTvService service;
|
ILiveTvService service;
|
||||||
|
IDirectStreamProvider directStreamProvider = null;
|
||||||
|
|
||||||
if (isChannel)
|
if (isChannel)
|
||||||
{
|
{
|
||||||
|
@ -301,7 +304,18 @@ namespace MediaBrowser.Server.Implementations.LiveTv
|
||||||
isVideo = channel.ChannelType == ChannelType.TV;
|
isVideo = channel.ChannelType == ChannelType.TV;
|
||||||
service = GetService(channel);
|
service = GetService(channel);
|
||||||
_logger.Info("Opening channel stream from {0}, external channel Id: {1}", service.Name, channel.ExternalId);
|
_logger.Info("Opening channel stream from {0}, external channel Id: {1}", service.Name, channel.ExternalId);
|
||||||
info = await service.GetChannelStream(channel.ExternalId, 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;
|
info.RequiresClosing = true;
|
||||||
|
|
||||||
if (info.RequiresClosing)
|
if (info.RequiresClosing)
|
||||||
|
@ -332,7 +346,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv
|
||||||
_logger.Info("Live stream info: {0}", _jsonSerializer.SerializeToString(info));
|
_logger.Info("Live stream info: {0}", _jsonSerializer.SerializeToString(info));
|
||||||
Normalize(info, service, isVideo);
|
Normalize(info, service, isVideo);
|
||||||
|
|
||||||
return info;
|
return new Tuple<MediaSourceInfo, IDirectStreamProvider>(info, directStreamProvider);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
@ -544,11 +558,13 @@ namespace MediaBrowser.Server.Implementations.LiveTv
|
||||||
return item;
|
return item;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<LiveTvProgram> GetProgram(ProgramInfo info, LiveTvChannel channel, ChannelType channelType, string serviceName, CancellationToken cancellationToken)
|
private async Task<LiveTvProgram> GetProgram(ProgramInfo info, Dictionary<Guid, LiveTvProgram> allExistingPrograms, LiveTvChannel channel, ChannelType channelType, string serviceName, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var id = _tvDtoService.GetInternalProgramId(serviceName, info.Id);
|
var id = _tvDtoService.GetInternalProgramId(serviceName, info.Id);
|
||||||
|
|
||||||
var item = _libraryManager.GetItemById(id) as LiveTvProgram;
|
LiveTvProgram item = null;
|
||||||
|
allExistingPrograms.TryGetValue(id, out item);
|
||||||
|
|
||||||
var isNew = false;
|
var isNew = false;
|
||||||
var forceUpdate = false;
|
var forceUpdate = false;
|
||||||
|
|
||||||
|
@ -877,7 +893,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(query.SeriesTimerId))
|
if (!string.IsNullOrWhiteSpace(query.SeriesTimerId))
|
||||||
{
|
{
|
||||||
var seriesTimers = await GetSeriesTimersInternal(new SeriesTimerQuery {}, cancellationToken).ConfigureAwait(false);
|
var seriesTimers = await GetSeriesTimersInternal(new SeriesTimerQuery { }, cancellationToken).ConfigureAwait(false);
|
||||||
var seriesTimer = seriesTimers.Items.FirstOrDefault(i => string.Equals(_tvDtoService.GetInternalSeriesTimerId(i.ServiceName, i.Id).ToString("N"), query.SeriesTimerId, StringComparison.OrdinalIgnoreCase));
|
var seriesTimer = seriesTimers.Items.FirstOrDefault(i => string.Equals(_tvDtoService.GetInternalSeriesTimerId(i.ServiceName, i.Id).ToString("N"), query.SeriesTimerId, StringComparison.OrdinalIgnoreCase));
|
||||||
if (seriesTimer != null)
|
if (seriesTimer != null)
|
||||||
{
|
{
|
||||||
|
@ -1265,9 +1281,17 @@ namespace MediaBrowser.Server.Implementations.LiveTv
|
||||||
|
|
||||||
var channelPrograms = await service.GetProgramsAsync(currentChannel.ExternalId, start, end, cancellationToken).ConfigureAwait(false);
|
var channelPrograms = await service.GetProgramsAsync(currentChannel.ExternalId, start, end, cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
|
var existingPrograms = _libraryManager.GetItemList(new InternalItemsQuery
|
||||||
|
{
|
||||||
|
|
||||||
|
IncludeItemTypes = new string[] { typeof(LiveTvProgram).Name },
|
||||||
|
ChannelIds = new string[] { currentChannel.Id.ToString("N") }
|
||||||
|
|
||||||
|
}).Cast<LiveTvProgram>().ToDictionary(i => i.Id);
|
||||||
|
|
||||||
foreach (var program in channelPrograms)
|
foreach (var program in channelPrograms)
|
||||||
{
|
{
|
||||||
var programItem = await GetProgram(program, currentChannel, currentChannel.ChannelType, service.Name, cancellationToken).ConfigureAwait(false);
|
var programItem = await GetProgram(program, existingPrograms, currentChannel, currentChannel.ChannelType, service.Name, cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
programs.Add(programItem.Id);
|
programs.Add(programItem.Id);
|
||||||
|
|
||||||
|
@ -1881,11 +1905,11 @@ namespace MediaBrowser.Server.Implementations.LiveTv
|
||||||
{
|
{
|
||||||
if (query.IsScheduled.Value)
|
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
|
else
|
||||||
{
|
{
|
||||||
timers = timers.Where(i => !(i.Item1.Status == RecordingStatus.New || i.Item1.Status == RecordingStatus.Scheduled));
|
timers = timers.Where(i => !(i.Item1.Status == RecordingStatus.New));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -116,17 +116,20 @@ namespace MediaBrowser.Server.Implementations.LiveTv
|
||||||
return list;
|
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;
|
const bool isAudio = false;
|
||||||
|
|
||||||
var keys = openToken.Split(new[] { StreamIdDelimeter }, 3);
|
var keys = openToken.Split(new[] { StreamIdDelimeter }, 3);
|
||||||
var mediaSourceId = keys.Length >= 3 ? keys[2] : null;
|
var mediaSourceId = keys.Length >= 3 ? keys[2] : null;
|
||||||
|
IDirectStreamProvider directStreamProvider = null;
|
||||||
|
|
||||||
if (string.Equals(keys[0], typeof(LiveTvChannel).Name, StringComparison.OrdinalIgnoreCase))
|
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
|
else
|
||||||
{
|
{
|
||||||
|
@ -142,7 +145,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv
|
||||||
_logger.ErrorException("Error probing live tv stream", ex);
|
_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)
|
private async Task AddMediaInfo(MediaSourceInfo mediaSource, bool isAudio, CancellationToken cancellationToken)
|
||||||
|
|
|
@ -6,14 +6,16 @@ using CommonIO;
|
||||||
using MediaBrowser.Common.Net;
|
using MediaBrowser.Common.Net;
|
||||||
using MediaBrowser.Controller;
|
using MediaBrowser.Controller;
|
||||||
using MediaBrowser.Controller.LiveTv;
|
using MediaBrowser.Controller.LiveTv;
|
||||||
|
using MediaBrowser.Controller.Library;
|
||||||
using MediaBrowser.Model.Dto;
|
using MediaBrowser.Model.Dto;
|
||||||
using MediaBrowser.Model.Logging;
|
using MediaBrowser.Model.Logging;
|
||||||
using MediaBrowser.Model.MediaInfo;
|
using MediaBrowser.Model.MediaInfo;
|
||||||
using MediaBrowser.Server.Implementations.LiveTv.EmbyTV;
|
using MediaBrowser.Server.Implementations.LiveTv.EmbyTV;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
||||||
{
|
{
|
||||||
public class HdHomerunLiveStream : LiveStream
|
public class HdHomerunLiveStream : LiveStream, IDirectStreamProvider
|
||||||
{
|
{
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger _logger;
|
||||||
private readonly IHttpClient _httpClient;
|
private readonly IHttpClient _httpClient;
|
||||||
|
@ -106,7 +108,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
||||||
{
|
{
|
||||||
onStarted = () => ResolveWhenExists(openTaskCompletionSource, tempFilePath, cancellationToken);
|
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);
|
}).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)
|
private async void ResolveWhenExists(TaskCompletionSource<bool> taskCompletionSource, string file, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
while (!File.Exists(file) && !cancellationToken.IsCancellationRequested)
|
while (!File.Exists(file) && !cancellationToken.IsCancellationRequested)
|
||||||
|
|
|
@ -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.
|
// Do not use a pipe here because Roku http requests to the server will fail, without any explicit error message.
|
||||||
private const string StreamIdDelimeterString = "_";
|
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);
|
var openKeys = openToken.Split(new[] { StreamIdDelimeterString[0] }, 3);
|
||||||
|
|
||||||
|
@ -137,7 +137,7 @@ namespace MediaBrowser.Server.Implementations.Sync
|
||||||
mediaSource.Protocol = dynamicInfo.Protocol;
|
mediaSource.Protocol = dynamicInfo.Protocol;
|
||||||
mediaSource.RequiredHttpHeaders = dynamicInfo.RequiredHttpHeaders;
|
mediaSource.RequiredHttpHeaders = dynamicInfo.RequiredHttpHeaders;
|
||||||
|
|
||||||
return mediaSource;
|
return new Tuple<MediaSourceInfo, IDirectStreamProvider>(mediaSource, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SetStaticMediaSourceInfo(LocalItem item, MediaSourceInfo mediaSource)
|
private void SetStaticMediaSourceInfo(LocalItem item, MediaSourceInfo mediaSource)
|
||||||
|
|
|
@ -826,7 +826,6 @@ namespace MediaBrowser.Server.Startup.Common
|
||||||
LiveTvManager.AddParts(GetExports<ILiveTvService>(), GetExports<ITunerHost>(), GetExports<IListingsProvider>());
|
LiveTvManager.AddParts(GetExports<ILiveTvService>(), GetExports<ITunerHost>(), GetExports<IListingsProvider>());
|
||||||
|
|
||||||
SubtitleManager.AddParts(GetExports<ISubtitleProvider>());
|
SubtitleManager.AddParts(GetExports<ISubtitleProvider>());
|
||||||
ChapterManager.AddParts(GetExports<IChapterProvider>());
|
|
||||||
|
|
||||||
SessionManager.AddParts(GetExports<ISessionControllerFactory>());
|
SessionManager.AddParts(GetExports<ISessionControllerFactory>());
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user