Update to 3.5.2 and .net core 2.1

This commit is contained in:
stefan
2018-09-12 19:26:21 +02:00
parent c32d865638
commit 48facb797e
1419 changed files with 27525 additions and 88927 deletions

View File

@@ -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);
SetDefaultAudioAndSubtitleStreamIndexes(item, source, user);
}
if (source.Protocol == MediaProtocol.File)
// Validate that this is actually possible
if (source.SupportsDirectStream)
{
// 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;
}
else
{
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();
}
private async Task<IEnumerable<MediaSourceInfo>> GetDynamicMediaSources(IHasMediaSources item, CancellationToken cancellationToken)
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;
}
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();
var mediaSource = mediaSourceTuple.Item1;
liveStream = await provider.OpenMediaSource(tuple.Item2, currentLiveStreams, cancellationToken).ConfigureAwait(false);
if (string.IsNullOrWhiteSpace(mediaSource.LiveStreamId))
mediaSource = liveStream.MediaSource;
// 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
{
Id = mediaSource.LiveStreamId,
MediaSource = mediaSource,
DirectStreamProvider = mediaSourceTuple.Item2
};
_openStreams[mediaSource.LiveStreamId] = liveStream;
}
finally
{
_liveStreamSemaphore.Release();
}
_openStreams[mediaSource.LiveStreamId] = info;
// TODO: Don't hardcode this
var isAudio = false;
var json = _jsonSerializer.SerializeToString(mediaSource);
_logger.Debug("Live stream opened: " + json);
var clone = _jsonSerializer.DeserializeFromString<MediaSourceInfo>(json);
if (!string.IsNullOrWhiteSpace(request.UserId))
try
{
if (mediaSource.MediaStreams.Any(i => i.Index != -1) || !mediaSource.SupportsProbing)
{
var user = _userManager.GetUserById(request.UserId);
var item = string.IsNullOrWhiteSpace(request.ItemId)
? null
: _libraryManager.GetItemById(request.ItemId);
SetUserProperties(item, clone, user);
AddMediaInfo(mediaSource, isAudio);
}
return new LiveStreamResponse
else
{
MediaSource = clone
};
// 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;
}
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)
{
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);
await CloseLiveStreamWithProvider(tuple.Item1, tuple.Item2).ConfigureAwait(false);
_logger.Info("Closing live stream {0}", id);
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;
}
}
}