diff options
Diffstat (limited to 'Emby.Server.Implementations/Library/MediaSourceManager.cs')
| -rw-r--r-- | Emby.Server.Implementations/Library/MediaSourceManager.cs | 599 |
1 files changed, 466 insertions, 133 deletions
diff --git a/Emby.Server.Implementations/Library/MediaSourceManager.cs b/Emby.Server.Implementations/Library/MediaSourceManager.cs index 688da5764..0dc436800 100644 --- a/Emby.Server.Implementations/Library/MediaSourceManager.cs +++ b/Emby.Server.Implementations/Library/MediaSourceManager.cs @@ -1,5 +1,6 @@ using MediaBrowser.Common.Extensions; using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.MediaEncoding; using MediaBrowser.Controller.Persistence; @@ -16,6 +17,11 @@ using System.Threading.Tasks; using MediaBrowser.Model.IO; using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Threading; +using MediaBrowser.Model.Dlna; +using MediaBrowser.Model.Globalization; +using System.IO; +using System.Globalization; +using MediaBrowser.Common.Configuration; namespace Emby.Server.Implementations.Library { @@ -31,8 +37,11 @@ namespace Emby.Server.Implementations.Library private readonly ILogger _logger; private readonly IUserDataManager _userDataManager; private readonly ITimerFactory _timerFactory; + private readonly Func<IMediaEncoder> _mediaEncoder; + private ILocalizationManager _localizationManager; + private IApplicationPaths _appPaths; - public MediaSourceManager(IItemRepository itemRepo, IUserManager userManager, ILibraryManager libraryManager, ILogger logger, IJsonSerializer jsonSerializer, IFileSystem fileSystem, IUserDataManager userDataManager, ITimerFactory timerFactory) + public MediaSourceManager(IItemRepository itemRepo, IApplicationPaths applicationPaths, ILocalizationManager localizationManager, IUserManager userManager, ILibraryManager libraryManager, ILogger logger, IJsonSerializer jsonSerializer, IFileSystem fileSystem, IUserDataManager userDataManager, ITimerFactory timerFactory, Func<IMediaEncoder> mediaEncoder) { _itemRepo = itemRepo; _userManager = userManager; @@ -42,6 +51,9 @@ namespace Emby.Server.Implementations.Library _fileSystem = fileSystem; _userDataManager = userDataManager; _timerFactory = timerFactory; + _mediaEncoder = mediaEncoder; + _localizationManager = localizationManager; + _appPaths = applicationPaths; } public void AddParts(IEnumerable<IMediaSourceProvider> providers) @@ -109,20 +121,23 @@ namespace Emby.Server.Implementations.Library return streams; } - public async Task<IEnumerable<MediaSourceInfo>> GetPlayackMediaSources(string id, string userId, bool enablePathSubstitution, string[] supportedLiveMediaTypes, CancellationToken cancellationToken) + public async Task<List<MediaSourceInfo>> GetPlayackMediaSources(BaseItem item, User user, bool allowMediaProbe, bool enablePathSubstitution, CancellationToken cancellationToken) { - var item = _libraryManager.GetItemById(id); + var mediaSources = GetStaticMediaSources(item, enablePathSubstitution, user); - var hasMediaSources = (IHasMediaSources)item; - User user = null; - - if (!string.IsNullOrWhiteSpace(userId)) + if (allowMediaProbe && mediaSources[0].Type != MediaSourceType.Placeholder && !mediaSources[0].MediaStreams.Any(i => i.Type == MediaStreamType.Audio || i.Type == MediaStreamType.Video)) { - user = _userManager.GetUserById(userId); + await item.RefreshMetadata(new MediaBrowser.Controller.Providers.MetadataRefreshOptions(_fileSystem) + { + EnableRemoteContentProbe = true, + MetadataRefreshMode = MediaBrowser.Controller.Providers.MetadataRefreshMode.FullRefresh + + }, cancellationToken).ConfigureAwait(false); + + mediaSources = GetStaticMediaSources(item, enablePathSubstitution, user); } - var mediaSources = GetStaticMediaSources(hasMediaSources, enablePathSubstitution, user); - var dynamicMediaSources = await GetDynamicMediaSources(hasMediaSources, cancellationToken).ConfigureAwait(false); + var dynamicMediaSources = await GetDynamicMediaSources(item, cancellationToken).ConfigureAwait(false); var list = new List<MediaSourceInfo>(); @@ -132,24 +147,13 @@ namespace Emby.Server.Implementations.Library { if (user != null) { - SetUserProperties(hasMediaSources, source, user); - } - if (source.Protocol == MediaProtocol.File) - { - // TODO: Path substitution - if (!_fileSystem.FileExists(source.Path)) - { - source.SupportsDirectStream = false; - } - } - 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; + SetDefaultAudioAndSubtitleStreamIndexes(item, source, user); } - else + + // Validate that this is actually possible + if (source.SupportsDirectStream) { - source.SupportsDirectStream = false; + source.SupportsDirectStream = SupportsDirectStream(source.Path, source.Protocol); } list.Add(source); @@ -169,10 +173,63 @@ namespace Emby.Server.Implementations.Library } } - return SortMediaSources(list).Where(i => i.Type != MediaSourceType.Placeholder); + return SortMediaSources(list).Where(i => i.Type != MediaSourceType.Placeholder).ToList(); + } + + public MediaProtocol GetPathProtocol(string path) + { + if (path.StartsWith("Rtsp", StringComparison.OrdinalIgnoreCase)) + { + return MediaProtocol.Rtsp; + } + if (path.StartsWith("Rtmp", StringComparison.OrdinalIgnoreCase)) + { + return MediaProtocol.Rtmp; + } + if (path.StartsWith("Http", StringComparison.OrdinalIgnoreCase)) + { + return MediaProtocol.Http; + } + if (path.StartsWith("rtp", StringComparison.OrdinalIgnoreCase)) + { + return MediaProtocol.Rtp; + } + if (path.StartsWith("ftp", StringComparison.OrdinalIgnoreCase)) + { + return MediaProtocol.Ftp; + } + if (path.StartsWith("udp", StringComparison.OrdinalIgnoreCase)) + { + return MediaProtocol.Udp; + } + + return _fileSystem.IsPathFile(path) ? MediaProtocol.File : MediaProtocol.Http; } - private async Task<IEnumerable<MediaSourceInfo>> GetDynamicMediaSources(IHasMediaSources item, CancellationToken cancellationToken) + public bool SupportsDirectStream(string path, MediaProtocol protocol) + { + if (protocol == MediaProtocol.File) + { + return true; + } + + if (protocol == MediaProtocol.Http) + { + if (path != null) + { + if (path.IndexOf(".m3u", StringComparison.OrdinalIgnoreCase) != -1) + { + return false; + } + + return true; + } + } + + return false; + } + + private async Task<IEnumerable<MediaSourceInfo>> GetDynamicMediaSources(BaseItem item, CancellationToken cancellationToken) { var tasks = _providers.Select(i => GetDynamicMediaSources(item, i, cancellationToken)); var results = await Task.WhenAll(tasks).ConfigureAwait(false); @@ -180,7 +237,7 @@ namespace Emby.Server.Implementations.Library return results.SelectMany(i => i.ToList()); } - private async Task<IEnumerable<MediaSourceInfo>> GetDynamicMediaSources(IHasMediaSources item, IMediaSourceProvider provider, CancellationToken cancellationToken) + private async Task<IEnumerable<MediaSourceInfo>> GetDynamicMediaSources(BaseItem item, IMediaSourceProvider provider, CancellationToken cancellationToken) { try { @@ -207,78 +264,65 @@ namespace Emby.Server.Implementations.Library { var prefix = provider.GetType().FullName.GetMD5().ToString("N") + LiveStreamIdDelimeter; - if (!string.IsNullOrWhiteSpace(mediaSource.OpenToken) && !mediaSource.OpenToken.StartsWith(prefix, StringComparison.OrdinalIgnoreCase)) + if (!string.IsNullOrEmpty(mediaSource.OpenToken) && !mediaSource.OpenToken.StartsWith(prefix, StringComparison.OrdinalIgnoreCase)) { mediaSource.OpenToken = prefix + mediaSource.OpenToken; } - if (!string.IsNullOrWhiteSpace(mediaSource.LiveStreamId) && !mediaSource.LiveStreamId.StartsWith(prefix, StringComparison.OrdinalIgnoreCase)) + if (!string.IsNullOrEmpty(mediaSource.LiveStreamId) && !mediaSource.LiveStreamId.StartsWith(prefix, StringComparison.OrdinalIgnoreCase)) { mediaSource.LiveStreamId = prefix + mediaSource.LiveStreamId; } } - public async Task<MediaSourceInfo> GetMediaSource(IHasMediaSources item, string mediaSourceId, string liveStreamId, bool enablePathSubstitution, CancellationToken cancellationToken) + public async Task<MediaSourceInfo> GetMediaSource(BaseItem item, string mediaSourceId, string liveStreamId, bool enablePathSubstitution, CancellationToken cancellationToken) { - if (!string.IsNullOrWhiteSpace(liveStreamId)) + if (!string.IsNullOrEmpty(liveStreamId)) { return await GetLiveStream(liveStreamId, cancellationToken).ConfigureAwait(false); } - //await _liveStreamSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false); - - //try - //{ - // var stream = _openStreams.Values.FirstOrDefault(i => string.Equals(i.MediaSource.Id, mediaSourceId, StringComparison.OrdinalIgnoreCase)); - // if (stream != null) - // { - // return stream.MediaSource; - // } - //} - //finally - //{ - // _liveStreamSemaphore.Release(); - //} - - var sources = await GetPlayackMediaSources(item.Id.ToString("N"), null, enablePathSubstitution, new[] { MediaType.Audio, MediaType.Video }, - CancellationToken.None).ConfigureAwait(false); + var sources = await GetPlayackMediaSources(item, null, false, enablePathSubstitution, cancellationToken).ConfigureAwait(false); return sources.FirstOrDefault(i => string.Equals(i.Id, mediaSourceId, StringComparison.OrdinalIgnoreCase)); } - public List<MediaSourceInfo> GetStaticMediaSources(IHasMediaSources item, bool enablePathSubstitution, User user = null) + public List<MediaSourceInfo> GetStaticMediaSources(BaseItem item, bool enablePathSubstitution, User user = null) { if (item == null) { throw new ArgumentNullException("item"); } - if (!(item is Video)) - { - return item.GetMediaSources(enablePathSubstitution); - } + var hasMediaSources = (IHasMediaSources)item; - var sources = item.GetMediaSources(enablePathSubstitution); + var sources = hasMediaSources.GetMediaSources(enablePathSubstitution); if (user != null) { foreach (var source in sources) { - SetUserProperties(item, source, user); + SetDefaultAudioAndSubtitleStreamIndexes(item, source, user); } } return sources; } - private void SetUserProperties(IHasUserData item, MediaSourceInfo source, User user) + private string[] NormalizeLanguage(string language) { - var userData = item == null ? new UserItemData() : _userDataManager.GetUserData(user, item); + if (language != null) + { + var culture = _localizationManager.FindLanguageInfo(language); + if (culture != null) + { + return culture.ThreeLetterISOLanguageNames; + } - var allowRememberingSelection = item == null || item.EnableRememberingTrackSelections; + return new string[] { language }; + } - SetDefaultAudioStreamIndex(source, userData, user, allowRememberingSelection); - SetDefaultSubtitleStreamIndex(source, userData, user, allowRememberingSelection); + return Array.Empty<string>(); } private void SetDefaultSubtitleStreamIndex(MediaSourceInfo source, UserItemData userData, User user, bool allowRememberingSelection) @@ -293,9 +337,9 @@ namespace Emby.Server.Implementations.Library return; } } - + var preferredSubs = string.IsNullOrEmpty(user.Configuration.SubtitleLanguagePreference) - ? new List<string>() : new List<string> { user.Configuration.SubtitleLanguagePreference }; + ? Array.Empty<string>() : NormalizeLanguage(user.Configuration.SubtitleLanguagePreference); var defaultAudioIndex = source.DefaultAudioStreamIndex; var audioLangage = defaultAudioIndex == null @@ -325,12 +369,37 @@ namespace Emby.Server.Implementations.Library } var preferredAudio = string.IsNullOrEmpty(user.Configuration.AudioLanguagePreference) - ? new string[] { } - : new[] { user.Configuration.AudioLanguagePreference }; + ? Array.Empty<string>() + : NormalizeLanguage(user.Configuration.AudioLanguagePreference); source.DefaultAudioStreamIndex = MediaStreamSelector.GetDefaultAudioStreamIndex(source.MediaStreams, preferredAudio, user.Configuration.PlayDefaultAudioTrack); } + 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; + + if (string.Equals(mediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase)) + { + var userData = item == null ? new UserItemData() : _userDataManager.GetUserData(user, item); + + var allowRememberingSelection = item == null || item.EnableRememberingTrackSelections; + + SetDefaultAudioStreamIndex(source, userData, user, allowRememberingSelection); + SetDefaultSubtitleStreamIndex(source, userData, user, allowRememberingSelection); + } + else if (string.Equals(mediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase)) + { + var audio = source.MediaStreams.FirstOrDefault(i => i.Type == MediaStreamType.Audio); + + if (audio != null) + { + source.DefaultAudioStreamIndex = audio.Index; + } + } + } + private IEnumerable<MediaSourceInfo> SortMediaSources(IEnumerable<MediaSourceInfo> sources) { return sources.OrderBy(i => @@ -352,55 +421,157 @@ namespace Emby.Server.Implementations.Library .ToList(); } - private readonly Dictionary<string, LiveStreamInfo> _openStreams = new Dictionary<string, LiveStreamInfo>(StringComparer.OrdinalIgnoreCase); + private readonly Dictionary<string, ILiveStream> _openStreams = new Dictionary<string, ILiveStream>(StringComparer.OrdinalIgnoreCase); private readonly SemaphoreSlim _liveStreamSemaphore = new SemaphoreSlim(1, 1); - public async Task<LiveStreamResponse> OpenLiveStream(LiveStreamRequest request, CancellationToken cancellationToken) + public async Task<Tuple<LiveStreamResponse, IDirectStreamProvider>> OpenLiveStreamInternal(LiveStreamRequest request, CancellationToken cancellationToken) { await _liveStreamSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false); + MediaSourceInfo mediaSource; + ILiveStream liveStream; + try { var tuple = GetProvider(request.OpenToken); var provider = tuple.Item1; - var mediaSourceTuple = await provider.OpenMediaSource(tuple.Item2, request.EnableMediaProbe, cancellationToken).ConfigureAwait(false); + var currentLiveStreams = _openStreams.Values.ToList(); + + liveStream = await provider.OpenMediaSource(tuple.Item2, currentLiveStreams, cancellationToken).ConfigureAwait(false); - var mediaSource = mediaSourceTuple.Item1; + mediaSource = liveStream.MediaSource; - if (string.IsNullOrWhiteSpace(mediaSource.LiveStreamId)) + // Validate that this is actually possible + if (mediaSource.SupportsDirectStream) { - throw new InvalidOperationException(string.Format("{0} returned null LiveStreamId", provider.GetType().Name)); + mediaSource.SupportsDirectStream = SupportsDirectStream(mediaSource.Path, mediaSource.Protocol); } SetKeyProperties(provider, mediaSource); - var info = new LiveStreamInfo + _openStreams[mediaSource.LiveStreamId] = liveStream; + } + finally + { + _liveStreamSemaphore.Release(); + } + + // TODO: Don't hardcode this + var isAudio = false; + + try + { + if (mediaSource.MediaStreams.Any(i => i.Index != -1) || !mediaSource.SupportsProbing) { - Id = mediaSource.LiveStreamId, - MediaSource = mediaSource, - DirectStreamProvider = mediaSourceTuple.Item2 - }; - - _openStreams[mediaSource.LiveStreamId] = info; - - var json = _jsonSerializer.SerializeToString(mediaSource); - _logger.Debug("Live stream opened: " + json); - var clone = _jsonSerializer.DeserializeFromString<MediaSourceInfo>(json); - - if (!string.IsNullOrWhiteSpace(request.UserId)) - { - var user = _userManager.GetUserById(request.UserId); - var item = string.IsNullOrWhiteSpace(request.ItemId) - ? null - : _libraryManager.GetItemById(request.ItemId); - SetUserProperties(item, clone, user); + AddMediaInfo(mediaSource, isAudio); + } + else + { + // hack - these two values were taken from LiveTVMediaSourceProvider + var cacheKey = request.OpenToken; + + await new LiveStreamHelper(_mediaEncoder(), _logger, _jsonSerializer, _appPaths).AddMediaInfoWithProbe(mediaSource, isAudio, cacheKey, true, cancellationToken).ConfigureAwait(false); } + } + catch (Exception ex) + { + _logger.ErrorException("Error probing live tv stream", ex); + AddMediaInfo(mediaSource, isAudio); + } + + var json = _jsonSerializer.SerializeToString(mediaSource); + _logger.Info("Live stream opened: " + json); + var clone = _jsonSerializer.DeserializeFromString<MediaSourceInfo>(json); + + if (!request.UserId.Equals(Guid.Empty)) + { + var user = _userManager.GetUserById(request.UserId); + var item = request.ItemId.Equals(Guid.Empty) + ? null + : _libraryManager.GetItemById(request.ItemId); + SetDefaultAudioAndSubtitleStreamIndexes(item, clone, user); + } + + return new Tuple<LiveStreamResponse, IDirectStreamProvider>(new LiveStreamResponse + { + MediaSource = clone + + }, liveStream as IDirectStreamProvider); + } + + private void AddMediaInfo(MediaSourceInfo mediaSource, bool isAudio) + { + mediaSource.DefaultSubtitleStreamIndex = null; + + // Null this out so that it will be treated like a live stream + if (mediaSource.IsInfiniteStream) + { + mediaSource.RunTimeTicks = null; + } - return new LiveStreamResponse + var audioStream = mediaSource.MediaStreams.FirstOrDefault(i => i.Type == MediaBrowser.Model.Entities.MediaStreamType.Audio); + + if (audioStream == null || audioStream.Index == -1) + { + mediaSource.DefaultAudioStreamIndex = null; + } + else + { + mediaSource.DefaultAudioStreamIndex = audioStream.Index; + } + + var videoStream = mediaSource.MediaStreams.FirstOrDefault(i => i.Type == MediaBrowser.Model.Entities.MediaStreamType.Video); + if (videoStream != null) + { + if (!videoStream.BitRate.HasValue) { - MediaSource = clone - }; + var width = videoStream.Width ?? 1920; + + if (width >= 3000) + { + videoStream.BitRate = 30000000; + } + + else if (width >= 1900) + { + videoStream.BitRate = 20000000; + } + + else if (width >= 1200) + { + videoStream.BitRate = 8000000; + } + + else if (width >= 700) + { + videoStream.BitRate = 2000000; + } + } + } + + // Try to estimate this + mediaSource.InferTotalBitrate(); + } + + public async Task<IDirectStreamProvider> GetDirectStreamProviderByUniqueId(string uniqueId, CancellationToken cancellationToken) + { + await _liveStreamSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false); + + try + { + var info = _openStreams.Values.FirstOrDefault(i => + { + var liveStream = i as ILiveStream; + if (liveStream != null) + { + return string.Equals(liveStream.UniqueId, uniqueId, StringComparison.OrdinalIgnoreCase); + } + + return false; + }); + + return info as IDirectStreamProvider; } finally { @@ -408,23 +579,207 @@ namespace Emby.Server.Implementations.Library } } + public async Task<LiveStreamResponse> OpenLiveStream(LiveStreamRequest request, CancellationToken cancellationToken) + { + var result = await OpenLiveStreamInternal(request, cancellationToken).ConfigureAwait(false); + return result.Item1; + } + + public async Task<MediaSourceInfo> GetLiveStreamMediaInfo(string id, CancellationToken cancellationToken) + { + var liveStreamInfo = await GetLiveStreamInfo(id, cancellationToken).ConfigureAwait(false); + + var mediaSource = liveStreamInfo.MediaSource; + + if (liveStreamInfo is IDirectStreamProvider) + { + var info = await _mediaEncoder().GetMediaInfo(new MediaInfoRequest + { + MediaSource = mediaSource, + ExtractChapters = false, + MediaType = DlnaProfileType.Video + + }, cancellationToken).ConfigureAwait(false); + + mediaSource.MediaStreams = info.MediaStreams; + mediaSource.Container = info.Container; + mediaSource.Bitrate = info.Bitrate; + } + + return mediaSource; + } + + public async Task AddMediaInfoWithProbe(MediaSourceInfo mediaSource, bool isAudio, string cacheKey, bool addProbeDelay, bool isLiveStream, CancellationToken cancellationToken) + { + var originalRuntime = mediaSource.RunTimeTicks; + + var now = DateTime.UtcNow; + + MediaInfo mediaInfo = null; + var cacheFilePath = string.IsNullOrEmpty(cacheKey) ? null : Path.Combine(_appPaths.CachePath, "mediainfo", cacheKey.GetMD5().ToString("N") + ".json"); + + if (!string.IsNullOrEmpty(cacheKey)) + { + try + { + mediaInfo = _jsonSerializer.DeserializeFromFile<MediaInfo>(cacheFilePath); + + //_logger.Debug("Found cached media info"); + } + catch (Exception ex) + { + } + } + + if (mediaInfo == null) + { + if (addProbeDelay) + { + var delayMs = mediaSource.AnalyzeDurationMs ?? 0; + delayMs = Math.Max(3000, delayMs); + await Task.Delay(delayMs, cancellationToken).ConfigureAwait(false); + } + + if (isLiveStream) + { + mediaSource.AnalyzeDurationMs = 3000; + } + + mediaInfo = await _mediaEncoder().GetMediaInfo(new MediaInfoRequest + { + MediaSource = mediaSource, + MediaType = isAudio ? DlnaProfileType.Audio : DlnaProfileType.Video, + ExtractChapters = false + + }, cancellationToken).ConfigureAwait(false); + + if (cacheFilePath != null) + { + _fileSystem.CreateDirectory(_fileSystem.GetDirectoryName(cacheFilePath)); + _jsonSerializer.SerializeToFile(mediaInfo, cacheFilePath); + + //_logger.Debug("Saved media info to {0}", cacheFilePath); + } + } + + var mediaStreams = mediaInfo.MediaStreams; + + if (isLiveStream && !string.IsNullOrEmpty(cacheKey)) + { + var newList = new List<MediaStream>(); + newList.AddRange(mediaStreams.Where(i => i.Type == MediaStreamType.Video).Take(1)); + newList.AddRange(mediaStreams.Where(i => i.Type == MediaStreamType.Audio).Take(1)); + + foreach (var stream in newList) + { + stream.Index = -1; + stream.Language = null; + } + + mediaStreams = newList; + } + + _logger.Info("Live tv media info probe took {0} seconds", (DateTime.UtcNow - now).TotalSeconds.ToString(CultureInfo.InvariantCulture)); + + mediaSource.Bitrate = mediaInfo.Bitrate; + mediaSource.Container = mediaInfo.Container; + mediaSource.Formats = mediaInfo.Formats; + mediaSource.MediaStreams = mediaStreams; + mediaSource.RunTimeTicks = mediaInfo.RunTimeTicks; + mediaSource.Size = mediaInfo.Size; + mediaSource.Timestamp = mediaInfo.Timestamp; + mediaSource.Video3DFormat = mediaInfo.Video3DFormat; + mediaSource.VideoType = mediaInfo.VideoType; + + mediaSource.DefaultSubtitleStreamIndex = null; + + if (isLiveStream) + { + // Null this out so that it will be treated like a live stream + if (!originalRuntime.HasValue) + { + mediaSource.RunTimeTicks = null; + } + } + + var audioStream = mediaStreams.FirstOrDefault(i => i.Type == MediaStreamType.Audio); + + if (audioStream == null || audioStream.Index == -1) + { + mediaSource.DefaultAudioStreamIndex = null; + } + else + { + mediaSource.DefaultAudioStreamIndex = audioStream.Index; + } + + var videoStream = mediaStreams.FirstOrDefault(i => i.Type == MediaStreamType.Video); + if (videoStream != null) + { + if (!videoStream.BitRate.HasValue) + { + var width = videoStream.Width ?? 1920; + + if (width >= 3000) + { + videoStream.BitRate = 30000000; + } + + else if (width >= 1900) + { + videoStream.BitRate = 20000000; + } + + else if (width >= 1200) + { + videoStream.BitRate = 8000000; + } + + else if (width >= 700) + { + videoStream.BitRate = 2000000; + } + } + + // This is coming up false and preventing stream copy + videoStream.IsAVC = null; + } + + if (isLiveStream) + { + mediaSource.AnalyzeDurationMs = 3000; + } + + // Try to estimate this + mediaSource.InferTotalBitrate(true); + } + public async Task<Tuple<MediaSourceInfo, IDirectStreamProvider>> GetLiveStreamWithDirectStreamProvider(string id, CancellationToken cancellationToken) { - if (string.IsNullOrWhiteSpace(id)) + if (string.IsNullOrEmpty(id)) { throw new ArgumentNullException("id"); } - _logger.Debug("Getting already opened live stream {0}", id); + var info = await GetLiveStreamInfo(id, cancellationToken).ConfigureAwait(false); + return new Tuple<MediaSourceInfo, IDirectStreamProvider>(info.MediaSource, info as IDirectStreamProvider); + } + + private async Task<ILiveStream> GetLiveStreamInfo(string id, CancellationToken cancellationToken) + { + if (string.IsNullOrEmpty(id)) + { + throw new ArgumentNullException("id"); + } await _liveStreamSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false); try { - LiveStreamInfo info; + ILiveStream info; if (_openStreams.TryGetValue(id, out info)) { - return new Tuple<MediaSourceInfo, IDirectStreamProvider>(info.MediaSource, info.DirectStreamProvider); + return info; } else { @@ -443,26 +798,9 @@ namespace Emby.Server.Implementations.Library return result.Item1; } - private async Task CloseLiveStreamWithProvider(IMediaSourceProvider provider, string streamId) - { - _logger.Info("Closing live stream {0} with provider {1}", streamId, provider.GetType().Name); - - try - { - await provider.CloseMediaSource(streamId).ConfigureAwait(false); - } - catch (NotImplementedException) - { - } - catch (Exception ex) - { - _logger.ErrorException("Error closing live stream {0}", ex, streamId); - } - } - public async Task CloseLiveStream(string id) { - if (string.IsNullOrWhiteSpace(id)) + if (string.IsNullOrEmpty(id)) { throw new ArgumentNullException("id"); } @@ -471,18 +809,22 @@ namespace Emby.Server.Implementations.Library try { - LiveStreamInfo current; + ILiveStream liveStream; - if (_openStreams.TryGetValue(id, out current)) + if (_openStreams.TryGetValue(id, out liveStream)) { - _openStreams.Remove(id); - current.Closed = true; + liveStream.ConsumerCount--; - if (current.MediaSource.RequiresClosing) + _logger.Info("Live stream {0} consumer count is now {1}", liveStream.OriginalStreamId, liveStream.ConsumerCount); + + if (liveStream.ConsumerCount <= 0) { - var tuple = GetProvider(id); + _openStreams.Remove(id); + + _logger.Info("Closing live stream {0}", id); - await CloseLiveStreamWithProvider(tuple.Item1, tuple.Item2).ConfigureAwait(false); + await liveStream.Close().ConfigureAwait(false); + _logger.Info("Live stream {0} closed successfully", id); } } } @@ -497,7 +839,7 @@ namespace Emby.Server.Implementations.Library private Tuple<IMediaSourceProvider, string> GetProvider(string key) { - if (string.IsNullOrWhiteSpace(key)) + if (string.IsNullOrEmpty(key)) { throw new ArgumentException("key"); } @@ -518,7 +860,6 @@ namespace Emby.Server.Implementations.Library public void Dispose() { Dispose(true); - GC.SuppressFinalize(this); } private readonly object _disposeLock = new object(); @@ -541,13 +882,5 @@ namespace Emby.Server.Implementations.Library } } } - - private class LiveStreamInfo - { - public string Id; - public bool Closed; - public MediaSourceInfo MediaSource; - public IDirectStreamProvider DirectStreamProvider; - } } }
\ No newline at end of file |
