| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865 | #nullable disable#pragma warning disable CS1591using System;using System.Collections.Concurrent;using System.Collections.Generic;using System.Globalization;using System.IO;using System.Linq;using System.Text.Json;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.Controller.Entities;using MediaBrowser.Controller.Library;using MediaBrowser.Controller.MediaEncoding;using MediaBrowser.Controller.Persistence;using MediaBrowser.Controller.Providers;using MediaBrowser.Model.Dlna;using MediaBrowser.Model.Dto;using MediaBrowser.Model.Entities;using MediaBrowser.Model.Globalization;using MediaBrowser.Model.IO;using MediaBrowser.Model.MediaInfo;using Microsoft.Extensions.Logging;namespace Emby.Server.Implementations.Library{    public class MediaSourceManager : IMediaSourceManager, IDisposable    {        // Do not use a pipe here because Roku http requests to the server will fail, without any explicit error message.        private const char LiveStreamIdDelimeter = '_';        private readonly IItemRepository _itemRepo;        private readonly IUserManager _userManager;        private readonly ILibraryManager _libraryManager;        private readonly IFileSystem _fileSystem;        private readonly ILogger<MediaSourceManager> _logger;        private readonly IUserDataManager _userDataManager;        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);        private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.Options;        private IMediaSourceProvider[] _providers;        public MediaSourceManager(            IItemRepository itemRepo,            IApplicationPaths applicationPaths,            ILocalizationManager localizationManager,            IUserManager userManager,            ILibraryManager libraryManager,            ILogger<MediaSourceManager> logger,            IFileSystem fileSystem,            IUserDataManager userDataManager,            IMediaEncoder mediaEncoder,            IDirectoryService directoryService)        {            _itemRepo = itemRepo;            _userManager = userManager;            _libraryManager = libraryManager;            _logger = logger;            _fileSystem = fileSystem;            _userDataManager = userDataManager;            _mediaEncoder = mediaEncoder;            _localizationManager = localizationManager;            _appPaths = applicationPaths;            _directoryService = directoryService;        }        public void AddParts(IEnumerable<IMediaSourceProvider> providers)        {            _providers = providers.ToArray();        }        public List<MediaStream> GetMediaStreams(MediaStreamQuery query)        {            var list = _itemRepo.GetMediaStreams(query);            foreach (var stream in list)            {                stream.SupportsExternalStream = StreamSupportsExternalStream(stream);            }            return list;        }        private static bool StreamSupportsExternalStream(MediaStream stream)        {            if (stream.IsExternal)            {                return true;            }            if (stream.IsTextSubtitleStream)            {                return true;            }            return false;        }        public List<MediaStream> GetMediaStreams(Guid itemId)        {            var list = GetMediaStreams(new MediaStreamQuery            {                ItemId = itemId            });            return GetMediaStreamsForItem(list);        }        private List<MediaStream> GetMediaStreamsForItem(List<MediaStream> streams)        {            foreach (var stream in streams)            {                if (stream.Type == MediaStreamType.Subtitle)                {                    stream.SupportsExternalStream = StreamSupportsExternalStream(stream);                }            }            return streams;        }        /// <inheritdoc />        public List<MediaAttachment> GetMediaAttachments(MediaAttachmentQuery query)        {            return _itemRepo.GetMediaAttachments(query);        }        /// <inheritdoc />        public List<MediaAttachment> GetMediaAttachments(Guid itemId)        {            return GetMediaAttachments(new MediaAttachmentQuery            {                ItemId = itemId            });        }        public async Task<List<MediaSourceInfo>> GetPlaybackMediaSources(BaseItem item, User user, bool allowMediaProbe, bool enablePathSubstitution, CancellationToken cancellationToken)        {            var mediaSources = GetStaticMediaSources(item, enablePathSubstitution, user);            // 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.Any(i => i.Type == MediaStreamType.Video))                    || (item.MediaType == MediaType.Audio && !mediaSources[0].MediaStreams.Any(i => i.Type == MediaStreamType.Audio))))            {                await item.RefreshMetadata(                    new MetadataRefreshOptions(_directoryService)                    {                        EnableRemoteContentProbe = true,                        MetadataRefreshMode = MetadataRefreshMode.FullRefresh                    },                    cancellationToken).ConfigureAwait(false);                mediaSources = GetStaticMediaSources(item, enablePathSubstitution, user);            }            var dynamicMediaSources = await GetDynamicMediaSources(item, cancellationToken).ConfigureAwait(false);            var list = new List<MediaSourceInfo>();            list.AddRange(mediaSources);            foreach (var source in dynamicMediaSources)            {                // Validate that this is actually possible                if (source.SupportsDirectStream)                {                    source.SupportsDirectStream = SupportsDirectStream(source.Path, source.Protocol);                }                if (user is not null)                {                    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);                    }                }                list.Add(source);            }            return SortMediaSources(list);        }        /// <inheritdoc />>        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 is not null)                {                    if (path.Contains(".m3u", StringComparison.OrdinalIgnoreCase))                    {                        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);            return results.SelectMany(i => i.ToList());        }        private async Task<IEnumerable<MediaSourceInfo>> GetDynamicMediaSources(BaseItem item, IMediaSourceProvider provider, CancellationToken cancellationToken)        {            try            {                var sources = await provider.GetMediaSources(item, cancellationToken).ConfigureAwait(false);                var list = sources.ToList();                foreach (var mediaSource in list)                {                    mediaSource.InferTotalBitrate();                    SetKeyProperties(provider, mediaSource);                }                return list;            }            catch (Exception ex)            {                _logger.LogError(ex, "Error getting media sources");                return Enumerable.Empty<MediaSourceInfo>();            }        }        private static void SetKeyProperties(IMediaSourceProvider provider, MediaSourceInfo mediaSource)        {            var prefix = provider.GetType().FullName.GetMD5().ToString("N", CultureInfo.InvariantCulture) + LiveStreamIdDelimeter;            if (!string.IsNullOrEmpty(mediaSource.OpenToken) && !mediaSource.OpenToken.StartsWith(prefix, StringComparison.OrdinalIgnoreCase))            {                mediaSource.OpenToken = prefix + mediaSource.OpenToken;            }            if (!string.IsNullOrEmpty(mediaSource.LiveStreamId) && !mediaSource.LiveStreamId.StartsWith(prefix, StringComparison.OrdinalIgnoreCase))            {                mediaSource.LiveStreamId = prefix + mediaSource.LiveStreamId;            }        }        public async Task<MediaSourceInfo> GetMediaSource(BaseItem item, string mediaSourceId, string liveStreamId, bool enablePathSubstitution, CancellationToken cancellationToken)        {            if (!string.IsNullOrEmpty(liveStreamId))            {                return await GetLiveStream(liveStreamId, cancellationToken).ConfigureAwait(false);            }            var sources = await GetPlaybackMediaSources(item, null, false, enablePathSubstitution, cancellationToken).ConfigureAwait(false);            return sources.FirstOrDefault(i => string.Equals(i.Id, mediaSourceId, StringComparison.OrdinalIgnoreCase));        }        public List<MediaSourceInfo> GetStaticMediaSources(BaseItem item, bool enablePathSubstitution, User user = null)        {            ArgumentNullException.ThrowIfNull(item);            var hasMediaSources = (IHasMediaSources)item;            var sources = hasMediaSources.GetMediaSources(enablePathSubstitution);            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 IReadOnlyList<string> NormalizeLanguage(string language)        {            if (string.IsNullOrEmpty(language))            {                return Array.Empty<string>();            }            var culture = _localizationManager.FindLanguageInfo(language);            if (culture is not null)            {                return culture.ThreeLetterISOLanguageNames;            }            return new string[] { language };        }        private void SetDefaultSubtitleStreamIndex(MediaSourceInfo source, UserItemData userData, User user, bool allowRememberingSelection)        {            if (userData.SubtitleStreamIndex.HasValue                && user.RememberSubtitleSelections                && user.SubtitleMode != SubtitlePlaybackMode.None && allowRememberingSelection)            {                var index = userData.SubtitleStreamIndex.Value;                // Make sure the saved index is still valid                if (index == -1 || source.MediaStreams.Any(i => i.Type == MediaStreamType.Subtitle && i.Index == index))                {                    source.DefaultSubtitleStreamIndex = index;                    return;                }            }            var preferredSubs = NormalizeLanguage(user.SubtitleLanguagePreference);            var defaultAudioIndex = source.DefaultAudioStreamIndex;            var audioLangage = defaultAudioIndex is null                ? null                : source.MediaStreams.Where(i => i.Type == MediaStreamType.Audio && i.Index == defaultAudioIndex).Select(i => i.Language).FirstOrDefault();            source.DefaultSubtitleStreamIndex = MediaStreamSelector.GetDefaultSubtitleStreamIndex(                source.MediaStreams,                preferredSubs,                user.SubtitleMode,                audioLangage);            MediaStreamSelector.SetSubtitleStreamScores(source.MediaStreams, preferredSubs, user.SubtitleMode, audioLangage);        }        private void SetDefaultAudioStreamIndex(MediaSourceInfo source, UserItemData userData, User user, bool allowRememberingSelection)        {            if (userData.AudioStreamIndex.HasValue && user.RememberAudioSelections && allowRememberingSelection)            {                var index = userData.AudioStreamIndex.Value;                // Make sure the saved index is still valid                if (source.MediaStreams.Any(i => i.Type == MediaStreamType.Audio && i.Index == index))                {                    source.DefaultAudioStreamIndex = index;                    return;                }            }            var preferredAudio = NormalizeLanguage(user.AudioLanguagePreference);            source.DefaultAudioStreamIndex = MediaStreamSelector.GetDefaultAudioStreamIndex(source.MediaStreams, preferredAudio, user.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 is null ? MediaType.Video : item.MediaType;            if (string.Equals(mediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase))            {                var userData = item is null ? new UserItemData() : _userDataManager.GetUserData(user, item);                var allowRememberingSelection = item is 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 is not null)                {                    source.DefaultAudioStreamIndex = audio.Index;                }            }        }        private static List<MediaSourceInfo> SortMediaSources(IEnumerable<MediaSourceInfo> sources)        {            return sources.OrderBy(i =>            {                if (i.VideoType.HasValue && i.VideoType.Value == VideoType.VideoFile)                {                    return 0;                }                return 1;            }).ThenBy(i => i.Video3DFormat.HasValue ? 1 : 0)            .ThenByDescending(i =>            {                var stream = i.VideoStream;                return stream?.Width ?? 0;            })            .Where(i => i.Type != MediaSourceType.Placeholder)            .ToList();        }        public async Task<Tuple<LiveStreamResponse, IDirectStreamProvider>> OpenLiveStreamInternal(LiveStreamRequest request, CancellationToken cancellationToken)        {            await _liveStreamSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false);            MediaSourceInfo mediaSource;            ILiveStream liveStream;            try            {                var (provider, keyId) = GetProvider(request.OpenToken);                var currentLiveStreams = _openStreams.Values.ToList();                liveStream = await provider.OpenMediaSource(keyId, currentLiveStreams, cancellationToken).ConfigureAwait(false);                mediaSource = liveStream.MediaSource;                // Validate that this is actually possible                if (mediaSource.SupportsDirectStream)                {                    mediaSource.SupportsDirectStream = SupportsDirectStream(mediaSource.Path, mediaSource.Protocol);                }                SetKeyProperties(provider, mediaSource);                _openStreams[mediaSource.LiveStreamId] = liveStream;            }            finally            {                _liveStreamSemaphore.Release();            }            try            {                if (mediaSource.MediaStreams.Any(i => i.Index != -1) || !mediaSource.SupportsProbing)                {                    AddMediaInfo(mediaSource);                }                else                {                    // hack - these two values were taken from LiveTVMediaSourceProvider                    string cacheKey = request.OpenToken;                    await new LiveStreamHelper(_mediaEncoder, _logger, _appPaths)                        .AddMediaInfoWithProbe(mediaSource, false, cacheKey, true, cancellationToken)                        .ConfigureAwait(false);                }            }            catch (Exception ex)            {                _logger.LogError(ex, "Error probing live tv stream");                AddMediaInfo(mediaSource);            }            // TODO: @bond Fix            var json = JsonSerializer.SerializeToUtf8Bytes(mediaSource, _jsonOptions);            _logger.LogInformation("Live stream opened: {@MediaSource}", mediaSource);            var clone = JsonSerializer.Deserialize<MediaSourceInfo>(json, _jsonOptions);            if (!request.UserId.Equals(default))            {                var user = _userManager.GetUserById(request.UserId);                var item = request.ItemId.Equals(default)                    ? null                    : _libraryManager.GetItemById(request.ItemId);                SetDefaultAudioAndSubtitleStreamIndexes(item, clone, user);            }            return new Tuple<LiveStreamResponse, IDirectStreamProvider>(new LiveStreamResponse(clone), liveStream as IDirectStreamProvider);        }        private static void AddMediaInfo(MediaSourceInfo mediaSource)        {            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 == MediaStreamType.Audio);            if (audioStream is null || audioStream.Index == -1)            {                mediaSource.DefaultAudioStreamIndex = null;            }            else            {                mediaSource.DefaultAudioStreamIndex = audioStream.Index;            }            var videoStream = mediaSource.MediaStreams.FirstOrDefault(i => i.Type == MediaStreamType.Video);            if (videoStream is not 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<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)        {            // TODO probably shouldn't throw here but it is kept for "backwards compatibility"            var liveStreamInfo = GetLiveStreamInfo(id) ?? throw new ResourceNotFoundException();            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", CultureInfo.InvariantCulture) + ".json");            if (!string.IsNullOrEmpty(cacheKey))            {                try                {                    await using FileStream jsonStream = AsyncFile.OpenRead(cacheFilePath);                    mediaInfo = await JsonSerializer.DeserializeAsync<MediaInfo>(jsonStream, _jsonOptions, cancellationToken).ConfigureAwait(false);                    // _logger.LogDebug("Found cached media info");                }                catch (Exception ex)                {                    _logger.LogDebug(ex, "_jsonSerializer.DeserializeFromFile threw an exception.");                }            }            if (mediaInfo is 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 is not null)                {                    Directory.CreateDirectory(Path.GetDirectoryName(cacheFilePath));                    await using FileStream createStream = File.Create(cacheFilePath);                    await JsonSerializer.SerializeAsync(createStream, mediaInfo, _jsonOptions, cancellationToken).ConfigureAwait(false);                    // _logger.LogDebug("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.LogInformation("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 is null || audioStream.Index == -1)            {                mediaSource.DefaultAudioStreamIndex = null;            }            else            {                mediaSource.DefaultAudioStreamIndex = audioStream.Index;            }            var videoStream = mediaStreams.FirstOrDefault(i => i.Type == MediaStreamType.Video);            if (videoStream is not 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 Task<Tuple<MediaSourceInfo, IDirectStreamProvider>> GetLiveStreamWithDirectStreamProvider(string id, CancellationToken cancellationToken)        {            ArgumentException.ThrowIfNullOrEmpty(id);            // 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));        }        public ILiveStream GetLiveStreamInfo(string id)        {            ArgumentException.ThrowIfNullOrEmpty(id);            if (_openStreams.TryGetValue(id, out ILiveStream info))            {                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)        {            var result = await GetLiveStreamWithDirectStreamProvider(id, cancellationToken).ConfigureAwait(false);            return result.Item1;        }        public async Task CloseLiveStream(string id)        {            ArgumentException.ThrowIfNullOrEmpty(id);            await _liveStreamSemaphore.WaitAsync().ConfigureAwait(false);            try            {                if (_openStreams.TryGetValue(id, out ILiveStream liveStream))                {                    liveStream.ConsumerCount--;                    _logger.LogInformation("Live stream {0} consumer count is now {1}", liveStream.OriginalStreamId, liveStream.ConsumerCount);                    if (liveStream.ConsumerCount <= 0)                    {                        _openStreams.TryRemove(id, out _);                        _logger.LogInformation("Closing live stream {0}", id);                        await liveStream.Close().ConfigureAwait(false);                        _logger.LogInformation("Live stream {0} closed successfully", id);                    }                }            }            finally            {                _liveStreamSemaphore.Release();            }        }        private (IMediaSourceProvider MediaSourceProvider, string KeyId) GetProvider(string key)        {            ArgumentException.ThrowIfNullOrEmpty(key);            var keys = key.Split(LiveStreamIdDelimeter, 2);            var provider = _providers.FirstOrDefault(i => string.Equals(i.GetType().FullName.GetMD5().ToString("N", CultureInfo.InvariantCulture), keys[0], StringComparison.OrdinalIgnoreCase));            var splitIndex = key.IndexOf(LiveStreamIdDelimeter, StringComparison.Ordinal);            var keyId = key.Substring(splitIndex + 1);            return (provider, keyId);        }        /// <inheritdoc />        public void Dispose()        {            Dispose(true);            GC.SuppressFinalize(this);        }        /// <summary>        /// Releases unmanaged and - optionally - managed resources.        /// </summary>        /// <param name="dispose"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>        protected virtual void Dispose(bool dispose)        {            if (dispose)            {                foreach (var key in _openStreams.Keys.ToList())                {                    CloseLiveStream(key).GetAwaiter().GetResult();                }                _liveStreamSemaphore.Dispose();            }        }    }}
 |