mirror of
https://github.com/jellyfin/jellyfin.git
synced 2026-03-17 23:56:44 +00:00
implement modular media sources
This commit is contained in:
@@ -169,7 +169,7 @@ namespace MediaBrowser.Server.Implementations.Channels
|
||||
|
||||
foreach (var item in result.Items)
|
||||
{
|
||||
var channelItem = (IChannelItem)item;
|
||||
var channelItem = (IChannelMediaItem)item;
|
||||
|
||||
var channelFeatures = _manager.GetChannelFeatures(channelItem.ChannelId);
|
||||
|
||||
@@ -179,7 +179,7 @@ namespace MediaBrowser.Server.Implementations.Channels
|
||||
{
|
||||
try
|
||||
{
|
||||
await DownloadChannelItem(item, options, cancellationToken, path);
|
||||
await DownloadChannelItem(channelItem, options, cancellationToken, path);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
@@ -210,13 +210,13 @@ namespace MediaBrowser.Server.Implementations.Channels
|
||||
return channelOptions.DownloadSizeLimit;
|
||||
}
|
||||
|
||||
private async Task DownloadChannelItem(BaseItem item,
|
||||
private async Task DownloadChannelItem(IChannelMediaItem item,
|
||||
ChannelOptions channelOptions,
|
||||
CancellationToken cancellationToken,
|
||||
string path)
|
||||
{
|
||||
var itemId = item.Id.ToString("N");
|
||||
var sources = await _manager.GetChannelItemMediaSources(itemId, false, cancellationToken)
|
||||
var sources = await _manager.GetStaticMediaSources(item, true, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
var cachedVersions = sources.Where(i => i.Protocol == MediaProtocol.File).ToList();
|
||||
@@ -237,11 +237,9 @@ namespace MediaBrowser.Server.Implementations.Channels
|
||||
}
|
||||
}
|
||||
|
||||
var channelItem = (IChannelMediaItem)item;
|
||||
var destination = Path.Combine(path, item.ChannelId, itemId);
|
||||
|
||||
var destination = Path.Combine(path, channelItem.ChannelId, itemId);
|
||||
|
||||
await _manager.DownloadChannelItem(channelItem, destination, new Progress<double>(), cancellationToken)
|
||||
await _manager.DownloadChannelItem(item, destination, new Progress<double>(), cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
await RefreshMediaSourceItem(destination, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
using MediaBrowser.Controller.Channels;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Model.Dto;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MediaBrowser.Server.Implementations.Channels
|
||||
{
|
||||
public class ChannelDynamicMediaSourceProvider : IMediaSourceProvider
|
||||
{
|
||||
private readonly ChannelManager _channelManager;
|
||||
|
||||
public ChannelDynamicMediaSourceProvider(IChannelManager channelManager)
|
||||
{
|
||||
_channelManager = (ChannelManager)channelManager;
|
||||
}
|
||||
|
||||
public Task<IEnumerable<MediaSourceInfo>> GetMediaSources(IHasMediaSources item, CancellationToken cancellationToken)
|
||||
{
|
||||
var channelItem = item as IChannelMediaItem;
|
||||
|
||||
if (channelItem != null)
|
||||
{
|
||||
return _channelManager.GetDynamicMediaSources(channelItem, cancellationToken);
|
||||
}
|
||||
|
||||
return Task.FromResult<IEnumerable<MediaSourceInfo>>(new List<MediaSourceInfo>());
|
||||
}
|
||||
|
||||
public Task<MediaSourceInfo> OpenMediaSource(string openKey, CancellationToken cancellationToken)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Task CloseMediaSource(string closeKey, CancellationToken cancellationToken)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -241,10 +241,25 @@ namespace MediaBrowser.Server.Implementations.Channels
|
||||
return item;
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<MediaSourceInfo>> GetChannelItemMediaSources(string id, bool includeDynamicSources, CancellationToken cancellationToken)
|
||||
public async Task<IEnumerable<MediaSourceInfo>> GetStaticMediaSources(IChannelMediaItem item, bool includeCachedVersions, CancellationToken cancellationToken)
|
||||
{
|
||||
var item = (IChannelMediaItem)_libraryManager.GetItemById(id);
|
||||
IEnumerable<ChannelMediaInfo> results = item.ChannelMediaSources;
|
||||
|
||||
var sources = SortMediaInfoResults(results)
|
||||
.Select(i => GetMediaSource(item, i))
|
||||
.ToList();
|
||||
|
||||
if (includeCachedVersions)
|
||||
{
|
||||
var cachedVersions = GetCachedChannelItemMediaSources(item);
|
||||
sources.InsertRange(0, cachedVersions);
|
||||
}
|
||||
|
||||
return sources.Where(IsValidMediaSource);
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<MediaSourceInfo>> GetDynamicMediaSources(IChannelMediaItem item, CancellationToken cancellationToken)
|
||||
{
|
||||
var channel = GetChannel(item.ChannelId);
|
||||
var channelPlugin = GetChannelProvider(channel);
|
||||
|
||||
@@ -252,24 +267,25 @@ namespace MediaBrowser.Server.Implementations.Channels
|
||||
|
||||
IEnumerable<ChannelMediaInfo> results;
|
||||
|
||||
if (requiresCallback != null && includeDynamicSources)
|
||||
if (requiresCallback != null)
|
||||
{
|
||||
results = await GetChannelItemMediaSourcesInternal(requiresCallback, item.ExternalId, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
results = item.ChannelMediaSources;
|
||||
results = new List<ChannelMediaInfo>();
|
||||
}
|
||||
|
||||
var sources = SortMediaInfoResults(results).Select(i => GetMediaSource(item, i))
|
||||
var list = SortMediaInfoResults(results)
|
||||
.Select(i => GetMediaSource(item, i))
|
||||
.Where(IsValidMediaSource)
|
||||
.ToList();
|
||||
|
||||
var cachedVersions = GetCachedChannelItemMediaSources(item);
|
||||
list.InsertRange(0, cachedVersions);
|
||||
|
||||
sources.InsertRange(0, cachedVersions);
|
||||
|
||||
return sources.Where(IsValidMediaSource);
|
||||
return list;
|
||||
}
|
||||
|
||||
private readonly ConcurrentDictionary<string, Tuple<DateTime, List<ChannelMediaInfo>>> _channelItemMediaInfo =
|
||||
@@ -297,14 +313,7 @@ namespace MediaBrowser.Server.Implementations.Channels
|
||||
return list;
|
||||
}
|
||||
|
||||
public IEnumerable<MediaSourceInfo> GetCachedChannelItemMediaSources(string id)
|
||||
{
|
||||
var item = (IChannelMediaItem)_libraryManager.GetItemById(id);
|
||||
|
||||
return GetCachedChannelItemMediaSources(item);
|
||||
}
|
||||
|
||||
public IEnumerable<MediaSourceInfo> GetCachedChannelItemMediaSources(IChannelMediaItem item)
|
||||
private IEnumerable<MediaSourceInfo> GetCachedChannelItemMediaSources(IChannelMediaItem item)
|
||||
{
|
||||
var filenamePrefix = item.Id.ToString("N");
|
||||
var parentPath = Path.Combine(ChannelDownloadPath, item.ChannelId);
|
||||
@@ -339,7 +348,6 @@ namespace MediaBrowser.Server.Implementations.Channels
|
||||
|
||||
if (source != null)
|
||||
{
|
||||
source.Type = MediaSourceType.Cache;
|
||||
return new[] { source };
|
||||
}
|
||||
}
|
||||
@@ -1408,8 +1416,7 @@ namespace MediaBrowser.Server.Implementations.Channels
|
||||
public async Task DownloadChannelItem(IChannelMediaItem item, string destination,
|
||||
IProgress<double> progress, CancellationToken cancellationToken)
|
||||
{
|
||||
var itemId = item.Id.ToString("N");
|
||||
var sources = await GetChannelItemMediaSources(itemId, true, cancellationToken)
|
||||
var sources = await GetDynamicMediaSources(item, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
var list = sources.Where(i => i.Protocol == MediaProtocol.Http).ToList();
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using MediaBrowser.Controller.Channels;
|
||||
using System.Collections.Concurrent;
|
||||
using MediaBrowser.Common.Extensions;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Controller.MediaEncoding;
|
||||
@@ -13,25 +14,24 @@ using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Server.Implementations.LiveTv;
|
||||
|
||||
namespace MediaBrowser.Server.Implementations.Library
|
||||
{
|
||||
public class MediaSourceManager : IMediaSourceManager
|
||||
public class MediaSourceManager : IMediaSourceManager, IDisposable
|
||||
{
|
||||
private readonly IItemRepository _itemRepo;
|
||||
private readonly IUserManager _userManager;
|
||||
private readonly ILibraryManager _libraryManager;
|
||||
private readonly IChannelManager _channelManager;
|
||||
|
||||
private IMediaSourceProvider[] _providers;
|
||||
private readonly ILogger _logger;
|
||||
|
||||
public MediaSourceManager(IItemRepository itemRepo, IUserManager userManager, ILibraryManager libraryManager, IChannelManager channelManager, ILogger logger)
|
||||
public MediaSourceManager(IItemRepository itemRepo, IUserManager userManager, ILibraryManager libraryManager, ILogger logger)
|
||||
{
|
||||
_itemRepo = itemRepo;
|
||||
_userManager = userManager;
|
||||
_libraryManager = libraryManager;
|
||||
_channelManager = channelManager;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
@@ -133,24 +133,15 @@ namespace MediaBrowser.Server.Implementations.Library
|
||||
IEnumerable<MediaSourceInfo> mediaSources;
|
||||
|
||||
var hasMediaSources = (IHasMediaSources)item;
|
||||
var channelItem = item as IChannelMediaItem;
|
||||
|
||||
if (channelItem != null)
|
||||
if (string.IsNullOrWhiteSpace(userId))
|
||||
{
|
||||
mediaSources = await _channelManager.GetChannelItemMediaSources(id, true, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
mediaSources = hasMediaSources.GetMediaSources(enablePathSubstitution);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(userId))
|
||||
{
|
||||
mediaSources = hasMediaSources.GetMediaSources(enablePathSubstitution);
|
||||
}
|
||||
else
|
||||
{
|
||||
var user = _userManager.GetUserById(userId);
|
||||
mediaSources = GetStaticMediaSources(hasMediaSources, enablePathSubstitution, user);
|
||||
}
|
||||
var user = _userManager.GetUserById(userId);
|
||||
mediaSources = GetStaticMediaSources(hasMediaSources, enablePathSubstitution, user);
|
||||
}
|
||||
|
||||
var dynamicMediaSources = await GetDynamicMediaSources(hasMediaSources, cancellationToken).ConfigureAwait(false);
|
||||
@@ -161,11 +152,16 @@ namespace MediaBrowser.Server.Implementations.Library
|
||||
|
||||
foreach (var source in dynamicMediaSources)
|
||||
{
|
||||
source.SupportsTranscoding = false;
|
||||
|
||||
if (source.Protocol == MediaProtocol.File)
|
||||
{
|
||||
source.SupportsDirectStream = File.Exists(source.Path);
|
||||
|
||||
// TODO: Path substitution
|
||||
}
|
||||
else if (source.Protocol == MediaProtocol.Http)
|
||||
{
|
||||
// TODO: Allow this when the source is plain http, e.g. not HLS or Mpeg Dash
|
||||
source.SupportsDirectStream = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -175,7 +171,7 @@ namespace MediaBrowser.Server.Implementations.Library
|
||||
list.Add(source);
|
||||
}
|
||||
|
||||
return SortMediaSources(list);
|
||||
return SortMediaSources(list).Where(i => i.Type != MediaSourceType.Placeholder);
|
||||
}
|
||||
|
||||
private async Task<IEnumerable<MediaSourceInfo>> GetDynamicMediaSources(IHasMediaSources item, CancellationToken cancellationToken)
|
||||
@@ -190,7 +186,15 @@ namespace MediaBrowser.Server.Implementations.Library
|
||||
{
|
||||
try
|
||||
{
|
||||
return await provider.GetMediaSources(item, cancellationToken).ConfigureAwait(false);
|
||||
var sources = await provider.GetMediaSources(item, cancellationToken).ConfigureAwait(false);
|
||||
var list = sources.ToList();
|
||||
|
||||
foreach (var mediaSource in list)
|
||||
{
|
||||
SetKeyProperties(provider, mediaSource);
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -199,6 +203,21 @@ namespace MediaBrowser.Server.Implementations.Library
|
||||
}
|
||||
}
|
||||
|
||||
private void SetKeyProperties(IMediaSourceProvider provider, MediaSourceInfo mediaSource)
|
||||
{
|
||||
var prefix = provider.GetType().FullName.GetMD5().ToString("N") + "|";
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(mediaSource.OpenKey) && !mediaSource.OpenKey.StartsWith(prefix, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
mediaSource.OpenKey = prefix + mediaSource.OpenKey;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(mediaSource.CloseKey) && !mediaSource.CloseKey.StartsWith(prefix, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
mediaSource.CloseKey = prefix + mediaSource.CloseKey;
|
||||
}
|
||||
}
|
||||
|
||||
public Task<IEnumerable<MediaSourceInfo>> GetPlayackMediaSources(string id, bool enablePathSubstitution, CancellationToken cancellationToken)
|
||||
{
|
||||
return GetPlayackMediaSources(id, null, enablePathSubstitution, cancellationToken);
|
||||
@@ -294,5 +313,90 @@ namespace MediaBrowser.Server.Implementations.Library
|
||||
{
|
||||
return GetStaticMediaSources(item, enablePathSubstitution).FirstOrDefault(i => string.Equals(i.Id, mediaSourceId, StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
|
||||
private readonly ConcurrentDictionary<string, string> _openStreams =
|
||||
new ConcurrentDictionary<string, string>();
|
||||
private readonly SemaphoreSlim _liveStreamSemaphore = new SemaphoreSlim(1, 1);
|
||||
public async Task<MediaSourceInfo> OpenMediaSource(string openKey, CancellationToken cancellationToken)
|
||||
{
|
||||
await _liveStreamSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
try
|
||||
{
|
||||
var tuple = GetProvider(openKey);
|
||||
var provider = tuple.Item1;
|
||||
|
||||
var mediaSource = await provider.OpenMediaSource(tuple.Item2, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
SetKeyProperties(provider, mediaSource);
|
||||
|
||||
_openStreams.AddOrUpdate(mediaSource.CloseKey, mediaSource.CloseKey, (key, i) => mediaSource.CloseKey);
|
||||
|
||||
return mediaSource;
|
||||
}
|
||||
finally
|
||||
{
|
||||
_liveStreamSemaphore.Release();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task CloseMediaSource(string closeKey, CancellationToken cancellationToken)
|
||||
{
|
||||
await _liveStreamSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
try
|
||||
{
|
||||
var tuple = GetProvider(closeKey);
|
||||
|
||||
await tuple.Item1.OpenMediaSource(tuple.Item2, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
string removedKey;
|
||||
_openStreams.TryRemove(closeKey, out removedKey);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_liveStreamSemaphore.Release();
|
||||
}
|
||||
}
|
||||
|
||||
private Tuple<IMediaSourceProvider, string> GetProvider(string key)
|
||||
{
|
||||
var keys = key.Split(new[] { '|' }, 2);
|
||||
|
||||
var provider = _providers.FirstOrDefault(i => string.Equals(i.GetType().FullName.GetMD5().ToString("N"), keys[0], StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
return new Tuple<IMediaSourceProvider, string>(provider, keys[1]);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
}
|
||||
|
||||
private readonly object _disposeLock = new object();
|
||||
/// <summary>
|
||||
/// Releases unmanaged and - optionally - managed resources.
|
||||
/// </summary>
|
||||
/// <param name="dispose"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
|
||||
protected virtual void Dispose(bool dispose)
|
||||
{
|
||||
if (dispose)
|
||||
{
|
||||
lock (_disposeLock)
|
||||
{
|
||||
foreach (var key in _openStreams.Keys.ToList())
|
||||
{
|
||||
var task = CloseMediaSource(key, CancellationToken.None);
|
||||
|
||||
Task.WaitAll(task);
|
||||
}
|
||||
|
||||
_openStreams.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
using System.Globalization;
|
||||
using MediaBrowser.Common;
|
||||
using MediaBrowser.Common;
|
||||
using MediaBrowser.Common.Configuration;
|
||||
using MediaBrowser.Common.Extensions;
|
||||
using MediaBrowser.Common.Progress;
|
||||
using MediaBrowser.Common.ScheduledTasks;
|
||||
using MediaBrowser.Controller.Channels;
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
using MediaBrowser.Controller.Drawing;
|
||||
using MediaBrowser.Controller.Dto;
|
||||
@@ -15,7 +13,6 @@ using MediaBrowser.Controller.Localization;
|
||||
using MediaBrowser.Controller.Persistence;
|
||||
using MediaBrowser.Controller.Providers;
|
||||
using MediaBrowser.Controller.Sorting;
|
||||
using MediaBrowser.Model.Dlna;
|
||||
using MediaBrowser.Model.Dto;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.LiveTv;
|
||||
@@ -342,6 +339,8 @@ namespace MediaBrowser.Server.Implementations.LiveTv
|
||||
var service = GetService(channel);
|
||||
_logger.Info("Opening channel stream from {0}, external channel Id: {1}", service.Name, channel.ExternalId);
|
||||
info = await service.GetChannelStream(channel.ExternalId, null, cancellationToken).ConfigureAwait(false);
|
||||
info.RequiresClosing = true;
|
||||
info.CloseKey = info.Id;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -351,6 +350,8 @@ namespace MediaBrowser.Server.Implementations.LiveTv
|
||||
|
||||
_logger.Info("Opening recording stream from {0}, external recording Id: {1}", service.Name, recording.RecordingInfo.Id);
|
||||
info = await service.GetRecordingStream(recording.RecordingInfo.Id, null, cancellationToken).ConfigureAwait(false);
|
||||
info.RequiresClosing = true;
|
||||
info.CloseKey = info.Id;
|
||||
}
|
||||
|
||||
_logger.Info("Live stream info: {0}", _jsonSerializer.SerializeToString(info));
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Controller.LiveTv;
|
||||
using MediaBrowser.Model.Dto;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MediaBrowser.Server.Implementations.LiveTv
|
||||
{
|
||||
public class LiveTvMediaSourceProvider : IMediaSourceProvider
|
||||
{
|
||||
private readonly ILiveTvManager _liveTvManager;
|
||||
|
||||
public LiveTvMediaSourceProvider(ILiveTvManager liveTvManager)
|
||||
{
|
||||
_liveTvManager = liveTvManager;
|
||||
}
|
||||
|
||||
public Task<IEnumerable<MediaSourceInfo>> GetMediaSources(IHasMediaSources item, CancellationToken cancellationToken)
|
||||
{
|
||||
var channelItem = item as ILiveTvItem;
|
||||
|
||||
if (channelItem != null)
|
||||
{
|
||||
var hasMetadata = (IHasMetadata)channelItem;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(hasMetadata.Path))
|
||||
{
|
||||
return GetMediaSourcesInternal(channelItem, cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
return Task.FromResult<IEnumerable<MediaSourceInfo>>(new List<MediaSourceInfo>());
|
||||
}
|
||||
|
||||
private async Task<IEnumerable<MediaSourceInfo>> GetMediaSourcesInternal(ILiveTvItem item, CancellationToken cancellationToken)
|
||||
{
|
||||
var hasMediaSources = (IHasMediaSources)item;
|
||||
|
||||
var sources = hasMediaSources.GetMediaSources(false)
|
||||
.ToList();
|
||||
|
||||
foreach (var source in sources)
|
||||
{
|
||||
source.Type = MediaSourceType.Default;
|
||||
source.RequiresOpening = true;
|
||||
|
||||
var openKeys = new List<string>();
|
||||
openKeys.Add(item.GetType().Name);
|
||||
openKeys.Add(item.Id.ToString("N"));
|
||||
source.OpenKey = string.Join("|", openKeys.ToArray());
|
||||
}
|
||||
|
||||
return sources;
|
||||
}
|
||||
|
||||
public async Task<MediaSourceInfo> OpenMediaSource(string openKey, CancellationToken cancellationToken)
|
||||
{
|
||||
var keys = openKey.Split(new[] { '|' }, 2);
|
||||
|
||||
if (string.Equals(keys[0], typeof(LiveTvChannel).Name, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return await _liveTvManager.GetChannelStream(keys[1], cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
return await _liveTvManager.GetRecordingStream(keys[1], cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public Task CloseMediaSource(string closeKey, CancellationToken cancellationToken)
|
||||
{
|
||||
return _liveTvManager.CloseLiveStream(closeKey, cancellationToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -111,6 +111,7 @@
|
||||
<Compile Include="Branding\BrandingConfigurationFactory.cs" />
|
||||
<Compile Include="Channels\ChannelConfigurations.cs" />
|
||||
<Compile Include="Channels\ChannelDownloadScheduledTask.cs" />
|
||||
<Compile Include="Channels\ChannelDynamicMediaSourceProvider.cs" />
|
||||
<Compile Include="Channels\ChannelImageProvider.cs" />
|
||||
<Compile Include="Channels\ChannelItemImageProvider.cs" />
|
||||
<Compile Include="Channels\ChannelManager.cs" />
|
||||
@@ -225,6 +226,7 @@
|
||||
<Compile Include="LiveTv\LiveTvConfigurationFactory.cs" />
|
||||
<Compile Include="LiveTv\LiveTvDtoService.cs" />
|
||||
<Compile Include="LiveTv\LiveTvManager.cs" />
|
||||
<Compile Include="LiveTv\LiveTvMediaSourceProvider.cs" />
|
||||
<Compile Include="LiveTv\ProgramImageProvider.cs" />
|
||||
<Compile Include="LiveTv\RecordingImageProvider.cs" />
|
||||
<Compile Include="LiveTv\RefreshChannelsScheduledTask.cs" />
|
||||
|
||||
@@ -161,6 +161,7 @@ namespace MediaBrowser.Server.Implementations.Sync
|
||||
{
|
||||
mediaSource.Path = sendFileResult.Path;
|
||||
mediaSource.Protocol = sendFileResult.Protocol;
|
||||
mediaSource.RequiredHttpHeaders = sendFileResult.RequiredHttpHeaders;
|
||||
mediaSource.SupportsTranscoding = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using MediaBrowser.Controller;
|
||||
using MediaBrowser.Common.Extensions;
|
||||
using MediaBrowser.Controller;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Controller.Sync;
|
||||
@@ -61,7 +62,7 @@ namespace MediaBrowser.Server.Implementations.Sync
|
||||
{
|
||||
foreach (var mediaSource in localItem.Item.MediaSources)
|
||||
{
|
||||
await TryAddMediaSource(list, localItem, mediaSource, syncProvider, syncTarget, cancellationToken).ConfigureAwait(false);
|
||||
AddMediaSource(list, localItem, mediaSource, syncProvider, syncTarget);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -71,41 +72,70 @@ namespace MediaBrowser.Server.Implementations.Sync
|
||||
return list;
|
||||
}
|
||||
|
||||
private async Task TryAddMediaSource(List<MediaSourceInfo> list,
|
||||
private void AddMediaSource(List<MediaSourceInfo> list,
|
||||
LocalItem item,
|
||||
MediaSourceInfo mediaSource,
|
||||
IServerSyncProvider provider,
|
||||
SyncTarget target,
|
||||
CancellationToken cancellationToken)
|
||||
SyncTarget target)
|
||||
{
|
||||
SetStaticMediaSourceInfo(item, mediaSource);
|
||||
|
||||
var requiresDynamicAccess = provider as IHasDynamicAccess;
|
||||
|
||||
if (requiresDynamicAccess == null)
|
||||
if (requiresDynamicAccess != null)
|
||||
{
|
||||
list.Add(mediaSource);
|
||||
return;
|
||||
mediaSource.RequiresOpening = true;
|
||||
|
||||
var keyList = new List<string>();
|
||||
keyList.Add(provider.GetType().FullName.GetMD5().ToString("N"));
|
||||
keyList.Add(target.Id.GetMD5().ToString("N"));
|
||||
keyList.Add(item.Id);
|
||||
mediaSource.OpenKey = string.Join("|", keyList.ToArray());
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<MediaSourceInfo> OpenMediaSource(string openKey, CancellationToken cancellationToken)
|
||||
{
|
||||
var openKeys = openKey.Split(new[] { '|' }, 3);
|
||||
|
||||
var provider = _syncManager.ServerSyncProviders
|
||||
.FirstOrDefault(i => string.Equals(openKeys[0], i.GetType().FullName.GetMD5().ToString("N"), StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
var target = provider.GetAllSyncTargets()
|
||||
.FirstOrDefault(i => string.Equals(openKeys[1], i.Id.GetMD5().ToString("N"), StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
var dataProvider = _syncManager.GetDataProvider(provider, target);
|
||||
var localItem = await dataProvider.Get(target, openKeys[2]).ConfigureAwait(false);
|
||||
|
||||
var requiresDynamicAccess = (IHasDynamicAccess)provider;
|
||||
var dynamicInfo = await requiresDynamicAccess.GetSyncedFileInfo(localItem.LocalPath, target, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
var mediaSource = localItem.Item.MediaSources.First();
|
||||
SetStaticMediaSourceInfo(localItem, mediaSource);
|
||||
|
||||
foreach (var stream in mediaSource.MediaStreams)
|
||||
{
|
||||
var dynamicStreamInfo = await requiresDynamicAccess.GetSyncedFileInfo(stream.ExternalId, target, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
stream.Path = dynamicStreamInfo.Path;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var dynamicInfo = await requiresDynamicAccess.GetSyncedFileInfo(item.LocalPath, target, cancellationToken).ConfigureAwait(false);
|
||||
mediaSource.Path = dynamicInfo.Path;
|
||||
mediaSource.Protocol = dynamicInfo.Protocol;
|
||||
mediaSource.RequiredHttpHeaders = dynamicInfo.RequiredHttpHeaders;
|
||||
|
||||
foreach (var stream in mediaSource.MediaStreams)
|
||||
{
|
||||
var dynamicStreamInfo = await requiresDynamicAccess.GetSyncedFileInfo(stream.ExternalId, target, cancellationToken).ConfigureAwait(false);
|
||||
return mediaSource;
|
||||
}
|
||||
|
||||
stream.Path = dynamicStreamInfo.Path;
|
||||
}
|
||||
private void SetStaticMediaSourceInfo(LocalItem item, MediaSourceInfo mediaSource)
|
||||
{
|
||||
mediaSource.Id = item.Id;
|
||||
mediaSource.SupportsTranscoding = false;
|
||||
}
|
||||
|
||||
mediaSource.Path = dynamicInfo.Path;
|
||||
mediaSource.Protocol = dynamicInfo.Protocol;
|
||||
|
||||
list.Add(mediaSource);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.ErrorException("Error getting dynamic media source info", ex);
|
||||
}
|
||||
public Task CloseMediaSource(string closeKey, CancellationToken cancellationToken)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user