update live streams

pull/702/head
Luke Pulverenti 8 years ago
parent c4e137e6cf
commit b9cacd8076

@ -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);
outputHeaders["Content-Type"] = MimeTypes.GetMimeType(filePath);
outputHeaders["Content-Type"] = MediaBrowser.Model.Net.MimeTypes.GetMimeType(filePath);
long startPosition = 0;

@ -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" />

@ -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;

@ -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);
}
}

@ -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);

@ -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; }

@ -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 MediaBrowser.Model.Chapters;
using System.Collections.Generic;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Model.Configuration;
@ -13,12 +11,6 @@ namespace MediaBrowser.Controller.Chapters
/// </summary>
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>
@ -35,43 +27,6 @@ namespace MediaBrowser.Controller.Chapters
/// <returns>Task.</returns>
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>

@ -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.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);
}
}

@ -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.

@ -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.

@ -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);
}
}

@ -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" />

@ -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>

@ -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>

@ -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 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; }
}
}

@ -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" />

@ -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));

@ -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>

@ -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()

@ -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();
}

@ -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;
}

@ -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;
}
}
}

@ -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;
@ -828,10 +828,17 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
private readonly Dictionary<string, LiveStream> _liveStreams = new Dictionary<string, LiveStream>();
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)

@ -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));
}
}

@ -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)

@ -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)

@ -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)

@ -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>());

Loading…
Cancel
Save