aboutsummaryrefslogtreecommitdiff
path: root/Emby.Server.Implementations/Library/MediaSourceManager.cs
diff options
context:
space:
mode:
authorstefan <stefan@hegedues.at>2018-09-12 19:26:21 +0200
committerstefan <stefan@hegedues.at>2018-09-12 19:26:21 +0200
commit48facb797ed912e4ea6b04b17d1ff190ac2daac4 (patch)
tree8dae77a31670a888d733484cb17dd4077d5444e8 /Emby.Server.Implementations/Library/MediaSourceManager.cs
parentc32d8656382a0eacb301692e0084377fc433ae9b (diff)
Update to 3.5.2 and .net core 2.1
Diffstat (limited to 'Emby.Server.Implementations/Library/MediaSourceManager.cs')
-rw-r--r--Emby.Server.Implementations/Library/MediaSourceManager.cs599
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