< Summary - Jellyfin

Information
Class: Emby.Server.Implementations.Library.MediaSourceManager
Assembly: Emby.Server.Implementations
File(s): /srv/git/jellyfin/Emby.Server.Implementations/Library/MediaSourceManager.cs
Line coverage
22%
Covered lines: 100
Uncovered lines: 341
Coverable lines: 441
Total lines: 972
Line coverage: 22.6%
Branch coverage
25%
Covered branches: 61
Total branches: 242
Branch coverage: 25.2%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Coverage history

Coverage history 0 25 50 75 100 2/13/2026 - 12:11:21 AM Line coverage: 18.2% (36/197) Branch coverage: 10.6% (13/122) Total lines: 9244/19/2026 - 12:14:27 AM Line coverage: 8.8% (36/409) Branch coverage: 6.1% (13/210) Total lines: 9245/8/2026 - 12:15:13 AM Line coverage: 22.6% (100/441) Branch coverage: 26% (63/242) Total lines: 9725/20/2026 - 12:15:44 AM Line coverage: 22.6% (100/441) Branch coverage: 25.2% (61/242) Total lines: 972 2/13/2026 - 12:11:21 AM Line coverage: 18.2% (36/197) Branch coverage: 10.6% (13/122) Total lines: 9244/19/2026 - 12:14:27 AM Line coverage: 8.8% (36/409) Branch coverage: 6.1% (13/210) Total lines: 9245/8/2026 - 12:15:13 AM Line coverage: 22.6% (100/441) Branch coverage: 26% (63/242) Total lines: 9725/20/2026 - 12:15:44 AM Line coverage: 22.6% (100/441) Branch coverage: 25.2% (61/242) Total lines: 972

Coverage delta

Coverage delta 20 -20

Metrics

File(s)

/srv/git/jellyfin/Emby.Server.Implementations/Library/MediaSourceManager.cs

#LineLine coverage
 1#nullable disable
 2
 3#pragma warning disable CS1591
 4
 5using System;
 6using System.Collections.Concurrent;
 7using System.Collections.Generic;
 8using System.Collections.Immutable;
 9using System.Globalization;
 10using System.IO;
 11using System.Linq;
 12using System.Text.Json;
 13using System.Threading;
 14using System.Threading.Tasks;
 15using AsyncKeyedLock;
 16using Jellyfin.Data;
 17using Jellyfin.Data.Enums;
 18using Jellyfin.Database.Implementations.Entities;
 19using Jellyfin.Database.Implementations.Enums;
 20using Jellyfin.Extensions;
 21using Jellyfin.Extensions.Json;
 22using MediaBrowser.Common.Configuration;
 23using MediaBrowser.Common.Extensions;
 24using MediaBrowser.Controller;
 25using MediaBrowser.Controller.Entities;
 26using MediaBrowser.Controller.Entities.TV;
 27using MediaBrowser.Controller.Library;
 28using MediaBrowser.Controller.LiveTv;
 29using MediaBrowser.Controller.MediaEncoding;
 30using MediaBrowser.Controller.Persistence;
 31using MediaBrowser.Controller.Providers;
 32using MediaBrowser.Model.Dlna;
 33using MediaBrowser.Model.Dto;
 34using MediaBrowser.Model.Entities;
 35using MediaBrowser.Model.Globalization;
 36using MediaBrowser.Model.IO;
 37using MediaBrowser.Model.MediaInfo;
 38using Microsoft.Extensions.Logging;
 39
 40namespace Emby.Server.Implementations.Library
 41{
 42    public class MediaSourceManager : IMediaSourceManager, IDisposable
 43    {
 44        // Do not use a pipe here because Roku http requests to the server will fail, without any explicit error message
 45        private const char LiveStreamIdDelimiter = '_';
 46
 47        private readonly IServerApplicationHost _appHost;
 48        private readonly IItemRepository _itemRepo;
 49        private readonly IUserManager _userManager;
 50        private readonly ILibraryManager _libraryManager;
 51        private readonly IFileSystem _fileSystem;
 52        private readonly ILogger<MediaSourceManager> _logger;
 53        private readonly IUserDataManager _userDataManager;
 54        private readonly IMediaEncoder _mediaEncoder;
 55        private readonly ILocalizationManager _localizationManager;
 56        private readonly IApplicationPaths _appPaths;
 57        private readonly IDirectoryService _directoryService;
 58        private readonly IMediaStreamRepository _mediaStreamRepository;
 59        private readonly IMediaAttachmentRepository _mediaAttachmentRepository;
 4960        private readonly ConcurrentDictionary<string, ILiveStream> _openStreams = new ConcurrentDictionary<string, ILive
 4961        private readonly AsyncNonKeyedLocker _liveStreamLocker = new(1);
 4962        private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.Options;
 63
 64        private IMediaSourceProvider[] _providers;
 65
 66        public MediaSourceManager(
 67            IServerApplicationHost appHost,
 68            IItemRepository itemRepo,
 69            IApplicationPaths applicationPaths,
 70            ILocalizationManager localizationManager,
 71            IUserManager userManager,
 72            ILibraryManager libraryManager,
 73            ILogger<MediaSourceManager> logger,
 74            IFileSystem fileSystem,
 75            IUserDataManager userDataManager,
 76            IMediaEncoder mediaEncoder,
 77            IDirectoryService directoryService,
 78            IMediaStreamRepository mediaStreamRepository,
 79            IMediaAttachmentRepository mediaAttachmentRepository)
 80        {
 4981            _appHost = appHost;
 4982            _itemRepo = itemRepo;
 4983            _userManager = userManager;
 4984            _libraryManager = libraryManager;
 4985            _logger = logger;
 4986            _fileSystem = fileSystem;
 4987            _userDataManager = userDataManager;
 4988            _mediaEncoder = mediaEncoder;
 4989            _localizationManager = localizationManager;
 4990            _appPaths = applicationPaths;
 4991            _directoryService = directoryService;
 4992            _mediaStreamRepository = mediaStreamRepository;
 4993            _mediaAttachmentRepository = mediaAttachmentRepository;
 4994        }
 95
 96        public void AddParts(IEnumerable<IMediaSourceProvider> providers)
 97        {
 2198            _providers = providers.ToArray();
 2199        }
 100
 101        public IReadOnlyList<MediaStream> GetMediaStreams(MediaStreamQuery query)
 102        {
 0103            var list = _mediaStreamRepository.GetMediaStreams(query);
 104
 0105            foreach (var stream in list)
 106            {
 0107                stream.SupportsExternalStream = StreamSupportsExternalStream(stream);
 108            }
 109
 0110            return list;
 111        }
 112
 113        private static bool StreamSupportsExternalStream(MediaStream stream)
 114        {
 0115            if (stream.IsExternal)
 116            {
 0117                return true;
 118            }
 119
 0120            if (stream.IsTextSubtitleStream)
 121            {
 0122                return true;
 123            }
 124
 0125            if (stream.IsPgsSubtitleStream)
 126            {
 0127                return true;
 128            }
 129
 0130            return false;
 131        }
 132
 133        public IReadOnlyList<MediaStream> GetMediaStreams(Guid itemId)
 134        {
 0135            var list = GetMediaStreams(new MediaStreamQuery
 0136            {
 0137                ItemId = itemId
 0138            });
 139
 0140            return GetMediaStreamsForItem(list);
 141        }
 142
 143        private IReadOnlyList<MediaStream> GetMediaStreamsForItem(IReadOnlyList<MediaStream> streams)
 144        {
 0145            foreach (var stream in streams)
 146            {
 0147                if (stream.Type == MediaStreamType.Subtitle)
 148                {
 0149                    stream.SupportsExternalStream = StreamSupportsExternalStream(stream);
 150                }
 151            }
 152
 0153            return streams;
 154        }
 155
 156        /// <inheritdoc />
 157        public IReadOnlyList<MediaAttachment> GetMediaAttachments(MediaAttachmentQuery query)
 158        {
 0159            return _mediaAttachmentRepository.GetMediaAttachments(query);
 160        }
 161
 162        /// <inheritdoc />
 163        public IReadOnlyList<MediaAttachment> GetMediaAttachments(Guid itemId)
 164        {
 0165            return GetMediaAttachments(new MediaAttachmentQuery
 0166            {
 0167                ItemId = itemId
 0168            });
 169        }
 170
 171        public async Task<IReadOnlyList<MediaSourceInfo>> GetPlaybackMediaSources(BaseItem item, User user, bool allowMe
 172        {
 0173            var mediaSources = GetStaticMediaSources(item, enablePathSubstitution, user);
 174
 175            // If file is strm or main media stream is missing, force a metadata refresh with remote probing
 0176            if (allowMediaProbe && mediaSources[0].Type != MediaSourceType.Placeholder
 0177                && (item.Path.EndsWith(".strm", StringComparison.OrdinalIgnoreCase)
 0178                    || (item.MediaType == MediaType.Video && mediaSources[0].MediaStreams.All(i => i.Type != MediaStream
 0179                    || (item.MediaType == MediaType.Audio && mediaSources[0].MediaStreams.All(i => i.Type != MediaStream
 180            {
 0181                await item.RefreshMetadata(
 0182                    new MetadataRefreshOptions(_directoryService)
 0183                    {
 0184                        EnableRemoteContentProbe = true,
 0185                        MetadataRefreshMode = MetadataRefreshMode.FullRefresh
 0186                    },
 0187                    cancellationToken).ConfigureAwait(false);
 188
 0189                mediaSources = GetStaticMediaSources(item, enablePathSubstitution, user);
 190            }
 191
 0192            var dynamicMediaSources = await GetDynamicMediaSources(item, cancellationToken).ConfigureAwait(false);
 193
 0194            var list = new List<MediaSourceInfo>();
 195
 0196            list.AddRange(mediaSources);
 197
 0198            foreach (var source in dynamicMediaSources)
 199            {
 200                // Validate that this is actually possible
 0201                if (source.SupportsDirectStream)
 202                {
 0203                    source.SupportsDirectStream = SupportsDirectStream(source.Path, source.Protocol);
 204                }
 205
 0206                if (user is not null)
 207                {
 0208                    SetDefaultAudioAndSubtitleStreamIndices(item, source, user);
 209
 0210                    if (item.MediaType == MediaType.Audio)
 211                    {
 0212                        source.SupportsTranscoding = user.HasPermission(PermissionKind.EnableAudioPlaybackTranscoding);
 213                    }
 0214                    else if (item.MediaType == MediaType.Video)
 215                    {
 0216                        source.SupportsTranscoding = user.HasPermission(PermissionKind.EnableVideoPlaybackTranscoding);
 0217                        source.SupportsDirectStream = user.HasPermission(PermissionKind.EnablePlaybackRemuxing);
 218                    }
 219                }
 220
 0221                list.Add(source);
 222            }
 223
 0224            return SortMediaSources(list).ToArray();
 0225        }
 226
 227        /// <inheritdoc />>
 228        public MediaProtocol GetPathProtocol(string path)
 229        {
 2202230            if (string.IsNullOrEmpty(path))
 231            {
 0232                return MediaProtocol.File;
 233            }
 234
 2202235            if (path.StartsWith("Rtsp", StringComparison.OrdinalIgnoreCase))
 236            {
 1237                return MediaProtocol.Rtsp;
 238            }
 239
 2201240            if (path.StartsWith("Rtmp", StringComparison.OrdinalIgnoreCase))
 241            {
 0242                return MediaProtocol.Rtmp;
 243            }
 244
 2201245            if (path.StartsWith("Http", StringComparison.OrdinalIgnoreCase))
 246            {
 2247                return MediaProtocol.Http;
 248            }
 249
 2199250            if (path.StartsWith("rtp", StringComparison.OrdinalIgnoreCase))
 251            {
 0252                return MediaProtocol.Rtp;
 253            }
 254
 2199255            if (path.StartsWith("ftp", StringComparison.OrdinalIgnoreCase))
 256            {
 0257                return MediaProtocol.Ftp;
 258            }
 259
 2199260            if (path.StartsWith("udp", StringComparison.OrdinalIgnoreCase))
 261            {
 0262                return MediaProtocol.Udp;
 263            }
 264
 2199265            return _fileSystem.IsPathFile(path) ? MediaProtocol.File : MediaProtocol.Http;
 266        }
 267
 268        public bool SupportsDirectStream(string path, MediaProtocol protocol)
 269        {
 0270            if (protocol == MediaProtocol.File)
 271            {
 0272                return true;
 273            }
 274
 0275            if (protocol == MediaProtocol.Http)
 276            {
 0277                if (path is not null)
 278                {
 0279                    if (path.Contains(".m3u", StringComparison.OrdinalIgnoreCase))
 280                    {
 0281                        return false;
 282                    }
 283
 0284                    return true;
 285                }
 286            }
 287
 0288            return false;
 289        }
 290
 291        private async Task<IEnumerable<MediaSourceInfo>> GetDynamicMediaSources(BaseItem item, CancellationToken cancell
 292        {
 0293            var tasks = _providers.Select(i => GetDynamicMediaSources(item, i, cancellationToken));
 0294            var results = await Task.WhenAll(tasks).ConfigureAwait(false);
 295
 0296            return results.SelectMany(i => i);
 0297        }
 298
 299        private async Task<IEnumerable<MediaSourceInfo>> GetDynamicMediaSources(BaseItem item, IMediaSourceProvider prov
 300        {
 301            try
 302            {
 0303                var sources = await provider.GetMediaSources(item, cancellationToken).ConfigureAwait(false);
 0304                var list = sources.ToList();
 305
 0306                foreach (var mediaSource in list)
 307                {
 0308                    mediaSource.InferTotalBitrate();
 309
 0310                    SetKeyProperties(provider, mediaSource);
 311                }
 312
 0313                return list;
 314            }
 0315            catch (Exception ex)
 316            {
 0317                _logger.LogError(ex, "Error getting media sources");
 0318                return [];
 319            }
 0320        }
 321
 322        private static void SetKeyProperties(IMediaSourceProvider provider, MediaSourceInfo mediaSource)
 323        {
 0324            var prefix = provider.GetType().FullName.GetMD5().ToString("N", CultureInfo.InvariantCulture) + LiveStreamId
 325
 0326            if (!string.IsNullOrEmpty(mediaSource.OpenToken) && !mediaSource.OpenToken.StartsWith(prefix, StringComparis
 327            {
 0328                mediaSource.OpenToken = prefix + mediaSource.OpenToken;
 329            }
 330
 0331            if (!string.IsNullOrEmpty(mediaSource.LiveStreamId) && !mediaSource.LiveStreamId.StartsWith(prefix, StringCo
 332            {
 0333                mediaSource.LiveStreamId = prefix + mediaSource.LiveStreamId;
 334            }
 0335        }
 336
 337        public async Task<MediaSourceInfo> GetMediaSource(BaseItem item, string mediaSourceId, string liveStreamId, bool
 338        {
 0339            if (!string.IsNullOrEmpty(liveStreamId))
 340            {
 0341                return await GetLiveStream(liveStreamId, cancellationToken).ConfigureAwait(false);
 342            }
 343
 0344            var sources = await GetPlaybackMediaSources(item, null, false, enablePathSubstitution, cancellationToken).Co
 345
 0346            return sources.FirstOrDefault(i => string.Equals(i.Id, mediaSourceId, StringComparison.OrdinalIgnoreCase));
 0347        }
 348
 349        public IReadOnlyList<MediaSourceInfo> GetStaticMediaSources(BaseItem item, bool enablePathSubstitution, User use
 350        {
 0351            ArgumentNullException.ThrowIfNull(item);
 352
 0353            var hasMediaSources = (IHasMediaSources)item;
 354
 0355            var sources = hasMediaSources.GetMediaSources(enablePathSubstitution);
 356
 0357            if (user is not null)
 358            {
 0359                foreach (var source in sources)
 360                {
 0361                    SetDefaultAudioAndSubtitleStreamIndices(item, source, user);
 362
 0363                    if (item.MediaType == MediaType.Audio)
 364                    {
 0365                        source.SupportsTranscoding = user.HasPermission(PermissionKind.EnableAudioPlaybackTranscoding);
 366                    }
 0367                    else if (item.MediaType == MediaType.Video)
 368                    {
 0369                        source.SupportsTranscoding = user.HasPermission(PermissionKind.EnableVideoPlaybackTranscoding);
 0370                        source.SupportsDirectStream = user.HasPermission(PermissionKind.EnablePlaybackRemuxing);
 371                    }
 372                }
 373            }
 374
 0375            return sources;
 376        }
 377
 378        private IReadOnlyList<string> NormalizeLanguage(string language)
 379        {
 49380            if (string.IsNullOrEmpty(language))
 381            {
 1382                return [];
 383            }
 384
 48385            var culture = _localizationManager.FindLanguageInfo(language);
 48386            if (culture is not null)
 387            {
 48388                return culture.Name.Contains('-', StringComparison.OrdinalIgnoreCase) ? [culture.Name] : culture.ThreeLe
 389            }
 390
 0391            return [language];
 392        }
 393
 394        private void SetDefaultSubtitleStreamIndex(MediaSourceInfo source, UserItemData userData, User user, bool allowR
 395        {
 22396            if (userData is not null
 22397                && userData.SubtitleStreamIndex.HasValue
 22398                && user.RememberSubtitleSelections
 22399                && user.SubtitleMode != SubtitlePlaybackMode.None
 22400                && allowRememberingSelection)
 401            {
 0402                var index = userData.SubtitleStreamIndex.Value;
 403                // Make sure the saved index is still valid
 0404                if (index == -1 || source.MediaStreams.Any(i => i.Type == MediaStreamType.Subtitle && i.Index == index))
 405                {
 0406                    source.DefaultSubtitleStreamIndex = index;
 0407                    return;
 408                }
 409            }
 410
 22411            var preferredSubs = NormalizeLanguage(user.SubtitleLanguagePreference);
 412
 22413            var defaultAudioIndex = source.DefaultAudioStreamIndex;
 22414            var audioLanguage = defaultAudioIndex is null
 22415                ? null
 22416                : source.MediaStreams.Where(i => i.Type == MediaStreamType.Audio && i.Index == defaultAudioIndex).Select
 417
 22418            source.DefaultSubtitleStreamIndex = MediaStreamSelector.GetDefaultSubtitleStreamIndex(
 22419                source.MediaStreams,
 22420                preferredSubs,
 22421                user.SubtitleMode,
 22422                audioLanguage);
 423
 22424            MediaStreamSelector.SetSubtitleStreamScores(source.MediaStreams, preferredSubs, user.SubtitleMode, audioLang
 22425        }
 426
 427        private void SetDefaultAudioStreamIndex(MediaSourceInfo source, UserItemData userData, User user, bool allowReme
 428        {
 22429            if (userData is not null && userData.AudioStreamIndex.HasValue && user.RememberAudioSelections && allowRemem
 430            {
 0431                var index = userData.AudioStreamIndex.Value;
 432                // Make sure the saved index is still valid
 0433                if (source.MediaStreams.Any(i => i.Type == MediaStreamType.Audio && i.Index == index))
 434                {
 0435                    source.DefaultAudioStreamIndex = index;
 0436                    source.DefaultAudioIndexSource = AudioIndexSource.User;
 0437                    return;
 438                }
 439            }
 440
 22441            if (string.Equals(user.AudioLanguagePreference, "OriginalLanguage", StringComparison.OrdinalIgnoreCase))
 442            {
 16443                originalLanguage = !string.IsNullOrWhiteSpace(originalLanguage)
 16444                    ? originalLanguage.Split(',').FirstOrDefault()
 16445                    : null;
 446
 16447                if (user.PlayDefaultAudioTrack)
 448                {
 6449                    source.DefaultAudioStreamIndex = MediaStreamSelector.GetDefaultAudioStreamIndex(
 6450                        source.MediaStreams,
 6451                        NormalizeLanguage(originalLanguage),
 6452                        user.PlayDefaultAudioTrack);
 6453                    return;
 454                }
 455
 10456                var originalIndex = source.MediaStreams.FindIndex(i => i.Type == MediaStreamType.Audio && i.IsOriginal);
 457
 10458                if (!string.IsNullOrWhiteSpace(originalLanguage) && originalIndex != -1)
 459                {
 4460                    var mediaLanguageOriginal = source.MediaStreams[originalIndex].Language;
 4461                    if (NormalizeLanguage(mediaLanguageOriginal).Contains(NormalizeLanguage(originalLanguage).FirstOrDef
 462                    {
 1463                        source.DefaultAudioStreamIndex = originalIndex;
 1464                        return;
 465                    }
 466                }
 6467                else if (originalIndex != -1)
 468                {
 2469                    source.DefaultAudioStreamIndex = originalIndex;
 2470                    return;
 471                }
 472            }
 473
 13474            var preferredAudio = string.Equals(user.AudioLanguagePreference, "OriginalLanguage", StringComparison.Ordina
 13475                ? NormalizeLanguage(originalLanguage)
 13476                : NormalizeLanguage(user.AudioLanguagePreference);
 477
 13478            source.DefaultAudioStreamIndex = MediaStreamSelector.GetDefaultAudioStreamIndex(source.MediaStreams, preferr
 13479            if (user.PlayDefaultAudioTrack)
 480            {
 3481                source.DefaultAudioIndexSource |= AudioIndexSource.Default;
 482            }
 483
 13484            if (preferredAudio.Count > 0)
 485            {
 13486                source.DefaultAudioIndexSource |= AudioIndexSource.Language;
 487            }
 13488        }
 489
 490        public void SetDefaultAudioAndSubtitleStreamIndices(BaseItem item, MediaSourceInfo source, User user)
 491        {
 492            // Item would only be null if the app didn't supply ItemId as part of the live stream open request
 22493            var mediaType = item?.MediaType ?? MediaType.Video;
 494
 22495            if (mediaType == MediaType.Video)
 496            {
 22497                var userData = item is null ? null : _userDataManager.GetUserData(user, item);
 498
 22499                var allowRememberingSelection = item is null || item.EnableRememberingTrackSelections;
 500
 22501                var originalLanguage = item?.OriginalLanguage ?? item switch
 22502                {
 0503                    Episode episode => episode.Series.OriginalLanguage,
 2504                    Video video => video.GetOwner() switch
 2505                    {
 0506                        Episode ownerEpisode => ownerEpisode.OriginalLanguage ?? ownerEpisode.Series.OriginalLanguage,
 0507                        BaseItem owner => owner.OriginalLanguage,
 2508                        null => null
 2509                    },
 0510                    _ => null
 22511                };
 512
 22513                SetDefaultAudioStreamIndex(source, userData, user, allowRememberingSelection, originalLanguage);
 22514                SetDefaultSubtitleStreamIndex(source, userData, user, allowRememberingSelection);
 515            }
 0516            else if (mediaType == MediaType.Audio)
 517            {
 0518                var audio = source.MediaStreams.FirstOrDefault(i => i.Type == MediaStreamType.Audio);
 519
 0520                if (audio is not null)
 521                {
 0522                    source.DefaultAudioStreamIndex = audio.Index;
 523                }
 524            }
 0525        }
 526
 527        private static IEnumerable<MediaSourceInfo> SortMediaSources(IEnumerable<MediaSourceInfo> sources)
 528        {
 0529            return sources.OrderBy(i =>
 0530            {
 0531                if (i.VideoType.HasValue && i.VideoType.Value == VideoType.VideoFile)
 0532                {
 0533                    return 0;
 0534                }
 0535
 0536                return 1;
 0537            }).ThenBy(i => i.Video3DFormat.HasValue ? 1 : 0)
 0538            .ThenByDescending(i =>
 0539            {
 0540                var stream = i.VideoStream;
 0541
 0542                return stream?.Width ?? 0;
 0543            })
 0544            .Where(i => i.Type != MediaSourceType.Placeholder);
 545        }
 546
 547        public async Task<Tuple<LiveStreamResponse, IDirectStreamProvider>> OpenLiveStreamInternal(LiveStreamRequest req
 548        {
 549            MediaSourceInfo mediaSource;
 550            ILiveStream liveStream;
 551
 0552            using (await _liveStreamLocker.LockAsync(cancellationToken).ConfigureAwait(false))
 553            {
 0554                var (provider, keyId) = GetProvider(request.OpenToken);
 555
 0556                var currentLiveStreams = _openStreams.Values.ToList();
 557
 0558                liveStream = await provider.OpenMediaSource(keyId, currentLiveStreams, cancellationToken).ConfigureAwait
 559
 0560                mediaSource = liveStream.MediaSource;
 561
 562                // Validate that this is actually possible
 0563                if (mediaSource.SupportsDirectStream)
 564                {
 0565                    mediaSource.SupportsDirectStream = SupportsDirectStream(mediaSource.Path, mediaSource.Protocol);
 566                }
 567
 0568                SetKeyProperties(provider, mediaSource);
 569
 0570                _openStreams[mediaSource.LiveStreamId] = liveStream;
 0571            }
 572
 573            try
 574            {
 0575                if (mediaSource.MediaStreams.Any(i => i.Index != -1) || !mediaSource.SupportsProbing)
 576                {
 0577                    AddMediaInfo(mediaSource);
 578                }
 579                else
 580                {
 581                    // hack - these two values were taken from LiveTVMediaSourceProvider
 0582                    string cacheKey = request.OpenToken;
 583
 0584                    await new LiveStreamHelper(_mediaEncoder, _logger, _appPaths)
 0585                        .AddMediaInfoWithProbe(mediaSource, false, cacheKey, true, cancellationToken)
 0586                        .ConfigureAwait(false);
 587                }
 0588            }
 0589            catch (Exception ex)
 590            {
 0591                _logger.LogError(ex, "Error probing live tv stream");
 0592                AddMediaInfo(mediaSource);
 0593            }
 594
 595            // TODO: @bond Fix
 0596            var json = JsonSerializer.SerializeToUtf8Bytes(mediaSource, _jsonOptions);
 0597            _logger.LogInformation("Live stream opened: {@MediaSource}", mediaSource);
 0598            var clone = JsonSerializer.Deserialize<MediaSourceInfo>(json, _jsonOptions);
 599
 0600            if (!request.UserId.IsEmpty())
 601            {
 0602                var user = _userManager.GetUserById(request.UserId);
 0603                var item = request.ItemId.IsEmpty()
 0604                    ? null
 0605                    : _libraryManager.GetItemById(request.ItemId);
 0606                SetDefaultAudioAndSubtitleStreamIndices(item, clone, user);
 607            }
 608
 0609            return new Tuple<LiveStreamResponse, IDirectStreamProvider>(new LiveStreamResponse(clone), liveStream as IDi
 0610        }
 611
 612        private static void AddMediaInfo(MediaSourceInfo mediaSource)
 613        {
 0614            mediaSource.DefaultSubtitleStreamIndex = null;
 615
 616            // Null this out so that it will be treated like a live stream
 0617            if (mediaSource.IsInfiniteStream)
 618            {
 0619                mediaSource.RunTimeTicks = null;
 620            }
 621
 0622            var audioStream = mediaSource.MediaStreams.FirstOrDefault(i => i.Type == MediaStreamType.Audio);
 623
 0624            if (audioStream is null || audioStream.Index == -1)
 625            {
 0626                mediaSource.DefaultAudioStreamIndex = null;
 627            }
 628            else
 629            {
 0630                mediaSource.DefaultAudioStreamIndex = audioStream.Index;
 631            }
 632
 0633            var videoStream = mediaSource.MediaStreams.FirstOrDefault(i => i.Type == MediaStreamType.Video);
 0634            if (videoStream is not null)
 635            {
 0636                if (!videoStream.BitRate.HasValue)
 637                {
 0638                    var width = videoStream.Width ?? 1920;
 639
 0640                    if (width >= 3000)
 641                    {
 0642                        videoStream.BitRate = 30000000;
 643                    }
 0644                    else if (width >= 1900)
 645                    {
 0646                        videoStream.BitRate = 20000000;
 647                    }
 0648                    else if (width >= 1200)
 649                    {
 0650                        videoStream.BitRate = 8000000;
 651                    }
 0652                    else if (width >= 700)
 653                    {
 0654                        videoStream.BitRate = 2000000;
 655                    }
 656                }
 657            }
 658
 659            // Try to estimate this
 0660            mediaSource.InferTotalBitrate();
 0661        }
 662
 663        public async Task<LiveStreamResponse> OpenLiveStream(LiveStreamRequest request, CancellationToken cancellationTo
 664        {
 0665            var result = await OpenLiveStreamInternal(request, cancellationToken).ConfigureAwait(false);
 0666            return result.Item1;
 0667        }
 668
 669        public async Task<MediaSourceInfo> GetLiveStreamMediaInfo(string id, CancellationToken cancellationToken)
 670        {
 671            // TODO probably shouldn't throw here but it is kept for "backwards compatibility"
 0672            var liveStreamInfo = GetLiveStreamInfo(id) ?? throw new ResourceNotFoundException();
 673
 0674            var mediaSource = liveStreamInfo.MediaSource;
 675
 0676            if (liveStreamInfo is IDirectStreamProvider)
 677            {
 0678                var info = await _mediaEncoder.GetMediaInfo(
 0679                    new MediaInfoRequest
 0680                    {
 0681                        MediaSource = mediaSource,
 0682                        ExtractChapters = false,
 0683                        MediaType = DlnaProfileType.Video
 0684                    },
 0685                    cancellationToken).ConfigureAwait(false);
 686
 0687                mediaSource.MediaStreams = info.MediaStreams;
 0688                mediaSource.Container = info.Container;
 0689                mediaSource.Bitrate = info.Bitrate;
 690            }
 691
 0692            return mediaSource;
 0693        }
 694
 695        public async Task AddMediaInfoWithProbe(MediaSourceInfo mediaSource, bool isAudio, string cacheKey, bool addProb
 696        {
 0697            var originalRuntime = mediaSource.RunTimeTicks;
 698
 0699            var now = DateTime.UtcNow;
 700
 0701            MediaInfo mediaInfo = null;
 0702            var cacheFilePath = string.IsNullOrEmpty(cacheKey) ? null : Path.Combine(_appPaths.CachePath, "mediainfo", c
 703
 0704            if (!string.IsNullOrEmpty(cacheKey))
 705            {
 0706                FileStream jsonStream = AsyncFile.OpenRead(cacheFilePath);
 707                try
 708                {
 0709                    mediaInfo = await JsonSerializer.DeserializeAsync<MediaInfo>(jsonStream, _jsonOptions, cancellationT
 0710                }
 0711                catch (Exception ex)
 712                {
 0713                    _logger.LogDebug(ex, "Error parsing cached media info.");
 0714                }
 715                finally
 716                {
 0717                    await jsonStream.DisposeAsync().ConfigureAwait(false);
 718                }
 0719            }
 720
 0721            if (mediaInfo is null)
 722            {
 0723                if (addProbeDelay)
 724                {
 0725                    var delayMs = mediaSource.AnalyzeDurationMs ?? 0;
 0726                    delayMs = Math.Max(3000, delayMs);
 0727                    await Task.Delay(delayMs, cancellationToken).ConfigureAwait(false);
 728                }
 729
 0730                if (isLiveStream)
 731                {
 0732                    mediaSource.AnalyzeDurationMs = 3000;
 733                }
 734
 0735                mediaInfo = await _mediaEncoder.GetMediaInfo(
 0736                    new MediaInfoRequest
 0737                    {
 0738                        MediaSource = mediaSource,
 0739                        MediaType = isAudio ? DlnaProfileType.Audio : DlnaProfileType.Video,
 0740                        ExtractChapters = false
 0741                    },
 0742                    cancellationToken).ConfigureAwait(false);
 743
 0744                if (cacheFilePath is not null)
 745                {
 0746                    Directory.CreateDirectory(Path.GetDirectoryName(cacheFilePath));
 0747                    FileStream createStream = AsyncFile.Create(cacheFilePath);
 0748                    await using (createStream.ConfigureAwait(false))
 749                    {
 0750                        await JsonSerializer.SerializeAsync(createStream, mediaInfo, _jsonOptions, cancellationToken).Co
 751                    }
 752
 753                    // _logger.LogDebug("Saved media info to {0}", cacheFilePath);
 754                }
 755            }
 756
 0757            var mediaStreams = mediaInfo.MediaStreams;
 758
 0759            if (isLiveStream && !string.IsNullOrEmpty(cacheKey))
 760            {
 0761                var newList = new List<MediaStream>();
 0762                newList.AddRange(mediaStreams.Where(i => i.Type == MediaStreamType.Video).Take(1));
 0763                newList.AddRange(mediaStreams.Where(i => i.Type == MediaStreamType.Audio).Take(1));
 764
 0765                foreach (var stream in newList)
 766                {
 0767                    stream.Index = -1;
 0768                    stream.Language = null;
 769                }
 770
 0771                mediaStreams = newList;
 772            }
 773
 0774            _logger.LogInformation("Live tv media info probe took {0} seconds", (DateTime.UtcNow - now).TotalSeconds.ToS
 775
 0776            mediaSource.Bitrate = mediaInfo.Bitrate;
 0777            mediaSource.Container = mediaInfo.Container;
 0778            mediaSource.Formats = mediaInfo.Formats;
 0779            mediaSource.MediaStreams = mediaStreams;
 0780            mediaSource.RunTimeTicks = mediaInfo.RunTimeTicks;
 0781            mediaSource.Size = mediaInfo.Size;
 0782            mediaSource.Timestamp = mediaInfo.Timestamp;
 0783            mediaSource.Video3DFormat = mediaInfo.Video3DFormat;
 0784            mediaSource.VideoType = mediaInfo.VideoType;
 785
 0786            mediaSource.DefaultSubtitleStreamIndex = null;
 787
 0788            if (isLiveStream)
 789            {
 790                // Null this out so that it will be treated like a live stream
 0791                if (!originalRuntime.HasValue)
 792                {
 0793                    mediaSource.RunTimeTicks = null;
 794                }
 795            }
 796
 0797            var audioStream = mediaStreams.FirstOrDefault(i => i.Type == MediaStreamType.Audio);
 798
 0799            if (audioStream is null || audioStream.Index == -1)
 800            {
 0801                mediaSource.DefaultAudioStreamIndex = null;
 802            }
 803            else
 804            {
 0805                mediaSource.DefaultAudioStreamIndex = audioStream.Index;
 806            }
 807
 0808            var videoStream = mediaStreams.FirstOrDefault(i => i.Type == MediaStreamType.Video);
 0809            if (videoStream is not null)
 810            {
 0811                if (!videoStream.BitRate.HasValue)
 812                {
 0813                    var width = videoStream.Width ?? 1920;
 814
 0815                    if (width >= 3000)
 816                    {
 0817                        videoStream.BitRate = 30000000;
 818                    }
 0819                    else if (width >= 1900)
 820                    {
 0821                        videoStream.BitRate = 20000000;
 822                    }
 0823                    else if (width >= 1200)
 824                    {
 0825                        videoStream.BitRate = 8000000;
 826                    }
 0827                    else if (width >= 700)
 828                    {
 0829                        videoStream.BitRate = 2000000;
 830                    }
 831                }
 832
 833                // This is coming up false and preventing stream copy
 0834                videoStream.IsAVC = null;
 835            }
 836
 0837            if (isLiveStream)
 838            {
 0839                mediaSource.AnalyzeDurationMs = 3000;
 840            }
 841
 842            // Try to estimate this
 0843            mediaSource.InferTotalBitrate(true);
 0844        }
 845
 846        public Task<Tuple<MediaSourceInfo, IDirectStreamProvider>> GetLiveStreamWithDirectStreamProvider(string id, Canc
 847        {
 0848            ArgumentException.ThrowIfNullOrEmpty(id);
 849
 0850            var info = GetLiveStreamInfo(id);
 0851            if (info is null)
 852            {
 0853                return Task.FromResult<Tuple<MediaSourceInfo, IDirectStreamProvider>>(new Tuple<MediaSourceInfo, IDirect
 854            }
 855
 0856            return Task.FromResult<Tuple<MediaSourceInfo, IDirectStreamProvider>>(new Tuple<MediaSourceInfo, IDirectStre
 857        }
 858
 859        public ILiveStream GetLiveStreamInfo(string id)
 860        {
 0861            ArgumentException.ThrowIfNullOrEmpty(id);
 862
 0863            if (_openStreams.TryGetValue(id, out ILiveStream info))
 864            {
 0865                return info;
 866            }
 867
 0868            return null;
 869        }
 870
 871        /// <inheritdoc />
 872        public ILiveStream GetLiveStreamInfoByUniqueId(string uniqueId)
 873        {
 0874            return _openStreams.Values.FirstOrDefault(stream => string.Equals(uniqueId, stream?.UniqueId, StringComparis
 875        }
 876
 877        public async Task<MediaSourceInfo> GetLiveStream(string id, CancellationToken cancellationToken)
 878        {
 0879            var result = await GetLiveStreamWithDirectStreamProvider(id, cancellationToken).ConfigureAwait(false);
 0880            return result.Item1;
 0881        }
 882
 883        public async Task<IReadOnlyList<MediaSourceInfo>> GetRecordingStreamMediaSources(ActiveRecordingInfo info, Cance
 884        {
 0885            var stream = new MediaSourceInfo
 0886            {
 0887                EncoderPath = _appHost.GetApiUrlForLocalAccess() + "/LiveTv/LiveRecordings/" + info.Id + "/stream",
 0888                EncoderProtocol = MediaProtocol.Http,
 0889                Path = info.Path,
 0890                Protocol = MediaProtocol.File,
 0891                Id = info.Id,
 0892                SupportsDirectPlay = false,
 0893                SupportsDirectStream = true,
 0894                SupportsTranscoding = true,
 0895                IsInfiniteStream = true,
 0896                RequiresOpening = false,
 0897                RequiresClosing = false,
 0898                BufferMs = 0,
 0899                IgnoreDts = true,
 0900                IgnoreIndex = true
 0901            };
 902
 0903            await new LiveStreamHelper(_mediaEncoder, _logger, _appPaths)
 0904                .AddMediaInfoWithProbe(stream, false, false, cancellationToken).ConfigureAwait(false);
 905
 0906            return [stream];
 0907        }
 908
 909        public async Task CloseLiveStream(string id)
 910        {
 0911            ArgumentException.ThrowIfNullOrEmpty(id);
 912
 0913            using (await _liveStreamLocker.LockAsync().ConfigureAwait(false))
 914            {
 0915                if (_openStreams.TryGetValue(id, out ILiveStream liveStream))
 916                {
 0917                    liveStream.ConsumerCount--;
 918
 0919                    _logger.LogInformation("Live stream {0} consumer count is now {1}", liveStream.OriginalStreamId, liv
 920
 0921                    if (liveStream.ConsumerCount <= 0)
 922                    {
 0923                        _openStreams.TryRemove(id, out _);
 924
 0925                        _logger.LogInformation("Closing live stream {0}", id);
 926
 0927                        await liveStream.Close().ConfigureAwait(false);
 0928                        _logger.LogInformation("Live stream {0} closed successfully", id);
 929                    }
 930                }
 0931            }
 0932        }
 933
 934        private (IMediaSourceProvider MediaSourceProvider, string KeyId) GetProvider(string key)
 935        {
 0936            ArgumentException.ThrowIfNullOrEmpty(key);
 937
 0938            var keys = key.Split(LiveStreamIdDelimiter, 2);
 939
 0940            var provider = _providers.FirstOrDefault(i => string.Equals(i.GetType().FullName.GetMD5().ToString("N", Cult
 941
 0942            var splitIndex = key.IndexOf(LiveStreamIdDelimiter, StringComparison.Ordinal);
 0943            var keyId = key.Substring(splitIndex + 1);
 944
 0945            return (provider, keyId);
 946        }
 947
 948        /// <inheritdoc />
 949        public void Dispose()
 950        {
 21951            Dispose(true);
 21952            GC.SuppressFinalize(this);
 21953        }
 954
 955        /// <summary>
 956        /// Releases unmanaged and - optionally - managed resources.
 957        /// </summary>
 958        /// <param name="dispose"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release o
 959        protected virtual void Dispose(bool dispose)
 960        {
 21961            if (dispose)
 962            {
 42963                foreach (var key in _openStreams.Keys.ToList())
 964                {
 0965                    CloseLiveStream(key).GetAwaiter().GetResult();
 966                }
 967
 21968                _liveStreamLocker.Dispose();
 969            }
 21970        }
 971    }
 972}

Methods/Properties

.ctor(MediaBrowser.Controller.IServerApplicationHost,MediaBrowser.Controller.Persistence.IItemRepository,MediaBrowser.Common.Configuration.IApplicationPaths,MediaBrowser.Model.Globalization.ILocalizationManager,MediaBrowser.Controller.Library.IUserManager,MediaBrowser.Controller.Library.ILibraryManager,Microsoft.Extensions.Logging.ILogger`1<Emby.Server.Implementations.Library.MediaSourceManager>,MediaBrowser.Model.IO.IFileSystem,MediaBrowser.Controller.Library.IUserDataManager,MediaBrowser.Controller.MediaEncoding.IMediaEncoder,MediaBrowser.Controller.Providers.IDirectoryService,MediaBrowser.Controller.Persistence.IMediaStreamRepository,MediaBrowser.Controller.Persistence.IMediaAttachmentRepository)
AddParts(System.Collections.Generic.IEnumerable`1<MediaBrowser.Controller.Library.IMediaSourceProvider>)
GetMediaStreams(MediaBrowser.Controller.Persistence.MediaStreamQuery)
StreamSupportsExternalStream(MediaBrowser.Model.Entities.MediaStream)
GetMediaStreams(System.Guid)
GetMediaStreamsForItem(System.Collections.Generic.IReadOnlyList`1<MediaBrowser.Model.Entities.MediaStream>)
GetMediaAttachments(MediaBrowser.Controller.Persistence.MediaAttachmentQuery)
GetMediaAttachments(System.Guid)
GetPlaybackMediaSources()
GetPathProtocol(System.String)
SupportsDirectStream(System.String,MediaBrowser.Model.MediaInfo.MediaProtocol)
GetDynamicMediaSources()
GetDynamicMediaSources()
SetKeyProperties(MediaBrowser.Controller.Library.IMediaSourceProvider,MediaBrowser.Model.Dto.MediaSourceInfo)
GetMediaSource()
GetStaticMediaSources(MediaBrowser.Controller.Entities.BaseItem,System.Boolean,Jellyfin.Database.Implementations.Entities.User)
NormalizeLanguage(System.String)
SetDefaultSubtitleStreamIndex(MediaBrowser.Model.Dto.MediaSourceInfo,MediaBrowser.Controller.Entities.UserItemData,Jellyfin.Database.Implementations.Entities.User,System.Boolean)
SetDefaultAudioStreamIndex(MediaBrowser.Model.Dto.MediaSourceInfo,MediaBrowser.Controller.Entities.UserItemData,Jellyfin.Database.Implementations.Entities.User,System.Boolean,System.String)
SetDefaultAudioAndSubtitleStreamIndices(MediaBrowser.Controller.Entities.BaseItem,MediaBrowser.Model.Dto.MediaSourceInfo,Jellyfin.Database.Implementations.Entities.User)
SortMediaSources(System.Collections.Generic.IEnumerable`1<MediaBrowser.Model.Dto.MediaSourceInfo>)
OpenLiveStreamInternal()
AddMediaInfo(MediaBrowser.Model.Dto.MediaSourceInfo)
OpenLiveStream()
GetLiveStreamMediaInfo()
AddMediaInfoWithProbe()
GetLiveStreamWithDirectStreamProvider(System.String,System.Threading.CancellationToken)
GetLiveStreamInfo(System.String)
GetLiveStreamInfoByUniqueId(System.String)
GetLiveStream()
GetRecordingStreamMediaSources()
CloseLiveStream()
GetProvider(System.String)
Dispose()
Dispose(System.Boolean)