diff options
Diffstat (limited to 'Emby.Server.Implementations/Library/MediaSourceManager.cs')
| -rw-r--r-- | Emby.Server.Implementations/Library/MediaSourceManager.cs | 174 |
1 files changed, 77 insertions, 97 deletions
diff --git a/Emby.Server.Implementations/Library/MediaSourceManager.cs b/Emby.Server.Implementations/Library/MediaSourceManager.cs index b812b6b61..c9a26a30f 100644 --- a/Emby.Server.Implementations/Library/MediaSourceManager.cs +++ b/Emby.Server.Implementations/Library/MediaSourceManager.cs @@ -13,9 +13,9 @@ using System.Threading; using System.Threading.Tasks; using Jellyfin.Data.Entities; using Jellyfin.Data.Enums; +using Jellyfin.Extensions.Json; using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Extensions; -using MediaBrowser.Common.Json; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.MediaEncoding; @@ -45,6 +45,7 @@ namespace Emby.Server.Implementations.Library private readonly IMediaEncoder _mediaEncoder; private readonly ILocalizationManager _localizationManager; private readonly IApplicationPaths _appPaths; + private readonly IDirectoryService _directoryService; private readonly ConcurrentDictionary<string, ILiveStream> _openStreams = new ConcurrentDictionary<string, ILiveStream>(StringComparer.OrdinalIgnoreCase); private readonly SemaphoreSlim _liveStreamSemaphore = new SemaphoreSlim(1, 1); @@ -61,7 +62,8 @@ namespace Emby.Server.Implementations.Library ILogger<MediaSourceManager> logger, IFileSystem fileSystem, IUserDataManager userDataManager, - IMediaEncoder mediaEncoder) + IMediaEncoder mediaEncoder, + IDirectoryService directoryService) { _itemRepo = itemRepo; _userManager = userManager; @@ -72,6 +74,7 @@ namespace Emby.Server.Implementations.Library _mediaEncoder = mediaEncoder; _localizationManager = localizationManager; _appPaths = applicationPaths; + _directoryService = directoryService; } public void AddParts(IEnumerable<IMediaSourceProvider> providers) @@ -106,16 +109,6 @@ namespace Emby.Server.Implementations.Library return false; } - public List<MediaStream> GetMediaStreams(string mediaSourceId) - { - var list = GetMediaStreams(new MediaStreamQuery - { - ItemId = new Guid(mediaSourceId) - }); - - return GetMediaStreamsForItem(list); - } - public List<MediaStream> GetMediaStreams(Guid itemId) { var list = GetMediaStreams(new MediaStreamQuery @@ -158,10 +151,14 @@ namespace Emby.Server.Implementations.Library { var mediaSources = GetStaticMediaSources(item, enablePathSubstitution, user); - if (allowMediaProbe && mediaSources[0].Type != MediaSourceType.Placeholder && !mediaSources[0].MediaStreams.Any(i => i.Type == MediaStreamType.Audio || i.Type == MediaStreamType.Video)) + // If file is strm or main media stream is missing, force a metadata refresh with remote probing + if (allowMediaProbe && mediaSources[0].Type != MediaSourceType.Placeholder + && (item.Path.EndsWith(".strm", StringComparison.OrdinalIgnoreCase) + || (item.MediaType == MediaType.Video && mediaSources[0].MediaStreams.All(i => i.Type != MediaStreamType.Video)) + || (item.MediaType == MediaType.Audio && mediaSources[0].MediaStreams.All(i => i.Type != MediaStreamType.Audio)))) { await item.RefreshMetadata( - new MetadataRefreshOptions(new DirectoryService(_fileSystem)) + new MetadataRefreshOptions(_directoryService) { EnableRemoteContentProbe = true, MetadataRefreshMode = MetadataRefreshMode.FullRefresh @@ -179,24 +176,16 @@ namespace Emby.Server.Implementations.Library foreach (var source in dynamicMediaSources) { - if (user != null) - { - SetDefaultAudioAndSubtitleStreamIndexes(item, source, user); - } - // Validate that this is actually possible if (source.SupportsDirectStream) { source.SupportsDirectStream = SupportsDirectStream(source.Path, source.Protocol); } - list.Add(source); - } - - if (user != null) - { - foreach (var source in list) + if (user is not null) { + SetDefaultAudioAndSubtitleStreamIndexes(item, source, user); + if (string.Equals(item.MediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase)) { source.SupportsTranscoding = user.HasPermission(PermissionKind.EnableAudioPlaybackTranscoding); @@ -207,11 +196,14 @@ namespace Emby.Server.Implementations.Library source.SupportsDirectStream = user.HasPermission(PermissionKind.EnablePlaybackRemuxing); } } + + list.Add(source); } return SortMediaSources(list); } + /// <inheritdoc />> public MediaProtocol GetPathProtocol(string path) { if (path.StartsWith("Rtsp", StringComparison.OrdinalIgnoreCase)) @@ -256,9 +248,9 @@ namespace Emby.Server.Implementations.Library if (protocol == MediaProtocol.Http) { - if (path != null) + if (path is not null) { - if (path.IndexOf(".m3u", StringComparison.OrdinalIgnoreCase) != -1) + if (path.Contains(".m3u", StringComparison.OrdinalIgnoreCase)) { return false; } @@ -297,7 +289,7 @@ namespace Emby.Server.Implementations.Library catch (Exception ex) { _logger.LogError(ex, "Error getting media sources"); - return new List<MediaSourceInfo>(); + return Enumerable.Empty<MediaSourceInfo>(); } } @@ -330,27 +322,34 @@ namespace Emby.Server.Implementations.Library public List<MediaSourceInfo> GetStaticMediaSources(BaseItem item, bool enablePathSubstitution, User user = null) { - if (item == null) - { - throw new ArgumentNullException(nameof(item)); - } + ArgumentNullException.ThrowIfNull(item); var hasMediaSources = (IHasMediaSources)item; var sources = hasMediaSources.GetMediaSources(enablePathSubstitution); - if (user != null) + if (user is not null) { foreach (var source in sources) { SetDefaultAudioAndSubtitleStreamIndexes(item, source, user); + + if (string.Equals(item.MediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase)) + { + source.SupportsTranscoding = user.HasPermission(PermissionKind.EnableAudioPlaybackTranscoding); + } + else if (string.Equals(item.MediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase)) + { + source.SupportsTranscoding = user.HasPermission(PermissionKind.EnableVideoPlaybackTranscoding); + source.SupportsDirectStream = user.HasPermission(PermissionKind.EnablePlaybackRemuxing); + } } } return sources; } - private string[] NormalizeLanguage(string language) + private IReadOnlyList<string> NormalizeLanguage(string language) { if (string.IsNullOrEmpty(language)) { @@ -358,7 +357,7 @@ namespace Emby.Server.Implementations.Library } var culture = _localizationManager.FindLanguageInfo(language); - if (culture != null) + if (culture is not null) { return culture.ThreeLetterISOLanguageNames; } @@ -384,7 +383,7 @@ namespace Emby.Server.Implementations.Library var preferredSubs = NormalizeLanguage(user.SubtitleLanguagePreference); var defaultAudioIndex = source.DefaultAudioStreamIndex; - var audioLangage = defaultAudioIndex == null + var audioLangage = defaultAudioIndex is null ? null : source.MediaStreams.Where(i => i.Type == MediaStreamType.Audio && i.Index == defaultAudioIndex).Select(i => i.Language).FirstOrDefault(); @@ -418,13 +417,13 @@ namespace Emby.Server.Implementations.Library public void SetDefaultAudioAndSubtitleStreamIndexes(BaseItem item, MediaSourceInfo source, User user) { // Item would only be null if the app didn't supply ItemId as part of the live stream open request - var mediaType = item == null ? MediaType.Video : item.MediaType; + var mediaType = item is null ? MediaType.Video : item.MediaType; if (string.Equals(mediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase)) { - var userData = item == null ? new UserItemData() : _userDataManager.GetUserData(user, item); + var userData = item is null ? new UserItemData() : _userDataManager.GetUserData(user, item); - var allowRememberingSelection = item == null || item.EnableRememberingTrackSelections; + var allowRememberingSelection = item is null || item.EnableRememberingTrackSelections; SetDefaultAudioStreamIndex(source, userData, user, allowRememberingSelection); SetDefaultSubtitleStreamIndex(source, userData, user, allowRememberingSelection); @@ -433,7 +432,7 @@ namespace Emby.Server.Implementations.Library { var audio = source.MediaStreams.FirstOrDefault(i => i.Type == MediaStreamType.Audio); - if (audio != null) + if (audio is not null) { source.DefaultAudioStreamIndex = audio.Index; } @@ -470,12 +469,11 @@ namespace Emby.Server.Implementations.Library try { - var tuple = GetProvider(request.OpenToken); - var provider = tuple.Item1; + var (provider, keyId) = GetProvider(request.OpenToken); var currentLiveStreams = _openStreams.Values.ToList(); - liveStream = await provider.OpenMediaSource(tuple.Item2, currentLiveStreams, cancellationToken).ConfigureAwait(false); + liveStream = await provider.OpenMediaSource(keyId, currentLiveStreams, cancellationToken).ConfigureAwait(false); mediaSource = liveStream.MediaSource; @@ -494,14 +492,11 @@ namespace Emby.Server.Implementations.Library _liveStreamSemaphore.Release(); } - // TODO: Don't hardcode this - const bool isAudio = false; - try { if (mediaSource.MediaStreams.Any(i => i.Index != -1) || !mediaSource.SupportsProbing) { - AddMediaInfo(mediaSource, isAudio); + AddMediaInfo(mediaSource); } else { @@ -509,25 +504,25 @@ namespace Emby.Server.Implementations.Library string cacheKey = request.OpenToken; await new LiveStreamHelper(_mediaEncoder, _logger, _appPaths) - .AddMediaInfoWithProbe(mediaSource, isAudio, cacheKey, true, cancellationToken) + .AddMediaInfoWithProbe(mediaSource, false, cacheKey, true, cancellationToken) .ConfigureAwait(false); } } catch (Exception ex) { _logger.LogError(ex, "Error probing live tv stream"); - AddMediaInfo(mediaSource, isAudio); + AddMediaInfo(mediaSource); } // TODO: @bond Fix var json = JsonSerializer.SerializeToUtf8Bytes(mediaSource, _jsonOptions); - _logger.LogInformation("Live stream opened: " + json); + _logger.LogInformation("Live stream opened: {@MediaSource}", mediaSource); var clone = JsonSerializer.Deserialize<MediaSourceInfo>(json, _jsonOptions); - if (!request.UserId.Equals(Guid.Empty)) + if (!request.UserId.Equals(default)) { var user = _userManager.GetUserById(request.UserId); - var item = request.ItemId.Equals(Guid.Empty) + var item = request.ItemId.Equals(default) ? null : _libraryManager.GetItemById(request.ItemId); SetDefaultAudioAndSubtitleStreamIndexes(item, clone, user); @@ -536,7 +531,7 @@ namespace Emby.Server.Implementations.Library return new Tuple<LiveStreamResponse, IDirectStreamProvider>(new LiveStreamResponse(clone), liveStream as IDirectStreamProvider); } - private static void AddMediaInfo(MediaSourceInfo mediaSource, bool isAudio) + private static void AddMediaInfo(MediaSourceInfo mediaSource) { mediaSource.DefaultSubtitleStreamIndex = null; @@ -548,7 +543,7 @@ namespace Emby.Server.Implementations.Library var audioStream = mediaSource.MediaStreams.FirstOrDefault(i => i.Type == MediaStreamType.Audio); - if (audioStream == null || audioStream.Index == -1) + if (audioStream is null || audioStream.Index == -1) { mediaSource.DefaultAudioStreamIndex = null; } @@ -558,7 +553,7 @@ namespace Emby.Server.Implementations.Library } var videoStream = mediaSource.MediaStreams.FirstOrDefault(i => i.Type == MediaStreamType.Video); - if (videoStream != null) + if (videoStream is not null) { if (!videoStream.BitRate.HasValue) { @@ -587,13 +582,6 @@ namespace Emby.Server.Implementations.Library mediaSource.InferTotalBitrate(); } - public Task<IDirectStreamProvider> GetDirectStreamProviderByUniqueId(string uniqueId, CancellationToken cancellationToken) - { - var info = _openStreams.FirstOrDefault(i => i.Value != null && string.Equals(i.Value.UniqueId, uniqueId, StringComparison.OrdinalIgnoreCase)); - - return Task.FromResult(info.Value as IDirectStreamProvider); - } - public async Task<LiveStreamResponse> OpenLiveStream(LiveStreamRequest request, CancellationToken cancellationToken) { var result = await OpenLiveStreamInternal(request, cancellationToken).ConfigureAwait(false); @@ -602,7 +590,8 @@ namespace Emby.Server.Implementations.Library public async Task<MediaSourceInfo> GetLiveStreamMediaInfo(string id, CancellationToken cancellationToken) { - var liveStreamInfo = await GetLiveStreamInfo(id, cancellationToken).ConfigureAwait(false); + // TODO probably shouldn't throw here but it is kept for "backwards compatibility" + var liveStreamInfo = GetLiveStreamInfo(id) ?? throw new ResourceNotFoundException(); var mediaSource = liveStreamInfo.MediaSource; @@ -638,7 +627,7 @@ namespace Emby.Server.Implementations.Library { try { - await using FileStream jsonStream = File.OpenRead(cacheFilePath); + await using FileStream jsonStream = AsyncFile.OpenRead(cacheFilePath); mediaInfo = await JsonSerializer.DeserializeAsync<MediaInfo>(jsonStream, _jsonOptions, cancellationToken).ConfigureAwait(false); // _logger.LogDebug("Found cached media info"); @@ -649,7 +638,7 @@ namespace Emby.Server.Implementations.Library } } - if (mediaInfo == null) + if (mediaInfo is null) { if (addProbeDelay) { @@ -672,7 +661,7 @@ namespace Emby.Server.Implementations.Library }, cancellationToken).ConfigureAwait(false); - if (cacheFilePath != null) + if (cacheFilePath is not null) { Directory.CreateDirectory(Path.GetDirectoryName(cacheFilePath)); await using FileStream createStream = File.Create(cacheFilePath); @@ -724,7 +713,7 @@ namespace Emby.Server.Implementations.Library var audioStream = mediaStreams.FirstOrDefault(i => i.Type == MediaStreamType.Audio); - if (audioStream == null || audioStream.Index == -1) + if (audioStream is null || audioStream.Index == -1) { mediaSource.DefaultAudioStreamIndex = null; } @@ -734,7 +723,7 @@ namespace Emby.Server.Implementations.Library } var videoStream = mediaStreams.FirstOrDefault(i => i.Type == MediaStreamType.Video); - if (videoStream != null) + if (videoStream is not null) { if (!videoStream.BitRate.HasValue) { @@ -771,32 +760,31 @@ namespace Emby.Server.Implementations.Library mediaSource.InferTotalBitrate(true); } - public async Task<Tuple<MediaSourceInfo, IDirectStreamProvider>> GetLiveStreamWithDirectStreamProvider(string id, CancellationToken cancellationToken) + public Task<Tuple<MediaSourceInfo, IDirectStreamProvider>> GetLiveStreamWithDirectStreamProvider(string id, CancellationToken cancellationToken) { - if (string.IsNullOrEmpty(id)) - { - throw new ArgumentNullException(nameof(id)); - } + ArgumentException.ThrowIfNullOrEmpty(id); - var info = await GetLiveStreamInfo(id, cancellationToken).ConfigureAwait(false); - return new Tuple<MediaSourceInfo, IDirectStreamProvider>(info.MediaSource, info as IDirectStreamProvider); + // TODO probably shouldn't throw here but it is kept for "backwards compatibility" + var info = GetLiveStreamInfo(id) ?? throw new ResourceNotFoundException(); + return Task.FromResult(new Tuple<MediaSourceInfo, IDirectStreamProvider>(info.MediaSource, info as IDirectStreamProvider)); } - private Task<ILiveStream> GetLiveStreamInfo(string id, CancellationToken cancellationToken) + public ILiveStream GetLiveStreamInfo(string id) { - if (string.IsNullOrEmpty(id)) - { - throw new ArgumentNullException(nameof(id)); - } + ArgumentException.ThrowIfNullOrEmpty(id); if (_openStreams.TryGetValue(id, out ILiveStream info)) { - return Task.FromResult(info); - } - else - { - return Task.FromException<ILiveStream>(new ResourceNotFoundException()); + return info; } + + return null; + } + + /// <inheritdoc /> + public ILiveStream GetLiveStreamInfoByUniqueId(string uniqueId) + { + return _openStreams.Values.FirstOrDefault(stream => string.Equals(uniqueId, stream?.UniqueId, StringComparison.OrdinalIgnoreCase)); } public async Task<MediaSourceInfo> GetLiveStream(string id, CancellationToken cancellationToken) @@ -807,10 +795,7 @@ namespace Emby.Server.Implementations.Library public async Task CloseLiveStream(string id) { - if (string.IsNullOrEmpty(id)) - { - throw new ArgumentNullException(nameof(id)); - } + ArgumentException.ThrowIfNullOrEmpty(id); await _liveStreamSemaphore.WaitAsync().ConfigureAwait(false); @@ -839,12 +824,9 @@ namespace Emby.Server.Implementations.Library } } - private (IMediaSourceProvider, string) GetProvider(string key) + private (IMediaSourceProvider MediaSourceProvider, string KeyId) GetProvider(string key) { - if (string.IsNullOrEmpty(key)) - { - throw new ArgumentException("Key can't be empty.", nameof(key)); - } + ArgumentException.ThrowIfNullOrEmpty(key); var keys = key.Split(LiveStreamIdDelimeter, 2); @@ -856,9 +838,7 @@ namespace Emby.Server.Implementations.Library return (provider, keyId); } - /// <summary> - /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. - /// </summary> + /// <inheritdoc /> public void Dispose() { Dispose(true); |
