< 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
21%
Covered lines: 91
Uncovered lines: 339
Coverable lines: 430
Total lines: 963
Line coverage: 21.1%
Branch coverage
23%
Covered branches: 53
Total branches: 230
Branch coverage: 23%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Coverage history

Coverage history 0 25 50 75 100 2/19/2026 - 12:13:41 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: 9725/27/2026 - 12:15:38 AM Line coverage: 21.2% (91/428) Branch coverage: 23.2% (53/228) Total lines: 9586/1/2026 - 12:16:05 AM Line coverage: 21.1% (91/430) Branch coverage: 23% (53/230) Total lines: 963 2/19/2026 - 12:13:41 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: 9725/27/2026 - 12:15:38 AM Line coverage: 21.2% (91/428) Branch coverage: 23.2% (53/228) Total lines: 9586/1/2026 - 12:16:05 AM Line coverage: 21.1% (91/430) Branch coverage: 23% (53/230) Total lines: 963

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            if (stream.IsVobSubSubtitleStream)
 131            {
 0132                return true;
 133            }
 134
 0135            return false;
 136        }
 137
 138        public IReadOnlyList<MediaStream> GetMediaStreams(Guid itemId)
 139        {
 0140            var list = GetMediaStreams(new MediaStreamQuery
 0141            {
 0142                ItemId = itemId
 0143            });
 144
 0145            return GetMediaStreamsForItem(list);
 146        }
 147
 148        private IReadOnlyList<MediaStream> GetMediaStreamsForItem(IReadOnlyList<MediaStream> streams)
 149        {
 0150            foreach (var stream in streams)
 151            {
 0152                if (stream.Type == MediaStreamType.Subtitle)
 153                {
 0154                    stream.SupportsExternalStream = StreamSupportsExternalStream(stream);
 155                }
 156            }
 157
 0158            return streams;
 159        }
 160
 161        /// <inheritdoc />
 162        public IReadOnlyList<MediaAttachment> GetMediaAttachments(MediaAttachmentQuery query)
 163        {
 0164            return _mediaAttachmentRepository.GetMediaAttachments(query);
 165        }
 166
 167        /// <inheritdoc />
 168        public IReadOnlyList<MediaAttachment> GetMediaAttachments(Guid itemId)
 169        {
 0170            return GetMediaAttachments(new MediaAttachmentQuery
 0171            {
 0172                ItemId = itemId
 0173            });
 174        }
 175
 176        public async Task<IReadOnlyList<MediaSourceInfo>> GetPlaybackMediaSources(BaseItem item, User user, bool allowMe
 177        {
 0178            var mediaSources = GetStaticMediaSources(item, enablePathSubstitution, user);
 179
 180            // If file is strm or main media stream is missing, force a metadata refresh with remote probing
 0181            if (allowMediaProbe && mediaSources[0].Type != MediaSourceType.Placeholder
 0182                && (item.Path.EndsWith(".strm", StringComparison.OrdinalIgnoreCase)
 0183                    || (item.MediaType == MediaType.Video && mediaSources[0].MediaStreams.All(i => i.Type != MediaStream
 0184                    || (item.MediaType == MediaType.Audio && mediaSources[0].MediaStreams.All(i => i.Type != MediaStream
 185            {
 0186                await item.RefreshMetadata(
 0187                    new MetadataRefreshOptions(_directoryService)
 0188                    {
 0189                        EnableRemoteContentProbe = true,
 0190                        MetadataRefreshMode = MetadataRefreshMode.FullRefresh
 0191                    },
 0192                    cancellationToken).ConfigureAwait(false);
 193
 0194                mediaSources = GetStaticMediaSources(item, enablePathSubstitution, user);
 195            }
 196
 0197            var dynamicMediaSources = await GetDynamicMediaSources(item, cancellationToken).ConfigureAwait(false);
 198
 0199            var list = new List<MediaSourceInfo>();
 200
 0201            list.AddRange(mediaSources);
 202
 0203            foreach (var source in dynamicMediaSources)
 204            {
 205                // Validate that this is actually possible
 0206                if (source.SupportsDirectStream)
 207                {
 0208                    source.SupportsDirectStream = SupportsDirectStream(source.Path, source.Protocol);
 209                }
 210
 0211                if (user is not null)
 212                {
 0213                    SetDefaultAudioAndSubtitleStreamIndices(item, source, user);
 214
 0215                    if (item.MediaType == MediaType.Audio)
 216                    {
 0217                        source.SupportsTranscoding = user.HasPermission(PermissionKind.EnableAudioPlaybackTranscoding);
 218                    }
 0219                    else if (item.MediaType == MediaType.Video)
 220                    {
 0221                        source.SupportsTranscoding = user.HasPermission(PermissionKind.EnableVideoPlaybackTranscoding);
 0222                        source.SupportsDirectStream = user.HasPermission(PermissionKind.EnablePlaybackRemuxing);
 223                    }
 224                }
 225
 0226                list.Add(source);
 227            }
 228
 0229            return SortMediaSources(list).ToArray();
 0230        }
 231
 232        /// <inheritdoc />>
 233        public MediaProtocol GetPathProtocol(string path)
 234        {
 2193235            if (string.IsNullOrEmpty(path))
 236            {
 0237                return MediaProtocol.File;
 238            }
 239
 2193240            if (path.StartsWith("Rtsp", StringComparison.OrdinalIgnoreCase))
 241            {
 1242                return MediaProtocol.Rtsp;
 243            }
 244
 2192245            if (path.StartsWith("Rtmp", StringComparison.OrdinalIgnoreCase))
 246            {
 0247                return MediaProtocol.Rtmp;
 248            }
 249
 2192250            if (path.StartsWith("Http", StringComparison.OrdinalIgnoreCase))
 251            {
 2252                return MediaProtocol.Http;
 253            }
 254
 2190255            if (path.StartsWith("rtp", StringComparison.OrdinalIgnoreCase))
 256            {
 0257                return MediaProtocol.Rtp;
 258            }
 259
 2190260            if (path.StartsWith("ftp", StringComparison.OrdinalIgnoreCase))
 261            {
 0262                return MediaProtocol.Ftp;
 263            }
 264
 2190265            if (path.StartsWith("udp", StringComparison.OrdinalIgnoreCase))
 266            {
 0267                return MediaProtocol.Udp;
 268            }
 269
 2190270            return _fileSystem.IsPathFile(path) ? MediaProtocol.File : MediaProtocol.Http;
 271        }
 272
 273        public bool SupportsDirectStream(string path, MediaProtocol protocol)
 274        {
 0275            if (protocol == MediaProtocol.File)
 276            {
 0277                return true;
 278            }
 279
 0280            if (protocol == MediaProtocol.Http)
 281            {
 0282                if (path is not null)
 283                {
 0284                    if (path.Contains(".m3u", StringComparison.OrdinalIgnoreCase))
 285                    {
 0286                        return false;
 287                    }
 288
 0289                    return true;
 290                }
 291            }
 292
 0293            return false;
 294        }
 295
 296        private async Task<IEnumerable<MediaSourceInfo>> GetDynamicMediaSources(BaseItem item, CancellationToken cancell
 297        {
 0298            var tasks = _providers.Select(i => GetDynamicMediaSources(item, i, cancellationToken));
 0299            var results = await Task.WhenAll(tasks).ConfigureAwait(false);
 300
 0301            return results.SelectMany(i => i);
 0302        }
 303
 304        private async Task<IEnumerable<MediaSourceInfo>> GetDynamicMediaSources(BaseItem item, IMediaSourceProvider prov
 305        {
 306            try
 307            {
 0308                var sources = await provider.GetMediaSources(item, cancellationToken).ConfigureAwait(false);
 0309                var list = sources.ToList();
 310
 0311                foreach (var mediaSource in list)
 312                {
 0313                    mediaSource.InferTotalBitrate();
 314
 0315                    SetKeyProperties(provider, mediaSource);
 316                }
 317
 0318                return list;
 319            }
 0320            catch (Exception ex)
 321            {
 0322                _logger.LogError(ex, "Error getting media sources");
 0323                return [];
 324            }
 0325        }
 326
 327        private static void SetKeyProperties(IMediaSourceProvider provider, MediaSourceInfo mediaSource)
 328        {
 0329            var prefix = provider.GetType().FullName.GetMD5().ToString("N", CultureInfo.InvariantCulture) + LiveStreamId
 330
 0331            if (!string.IsNullOrEmpty(mediaSource.OpenToken) && !mediaSource.OpenToken.StartsWith(prefix, StringComparis
 332            {
 0333                mediaSource.OpenToken = prefix + mediaSource.OpenToken;
 334            }
 335
 0336            if (!string.IsNullOrEmpty(mediaSource.LiveStreamId) && !mediaSource.LiveStreamId.StartsWith(prefix, StringCo
 337            {
 0338                mediaSource.LiveStreamId = prefix + mediaSource.LiveStreamId;
 339            }
 0340        }
 341
 342        public async Task<MediaSourceInfo> GetMediaSource(BaseItem item, string mediaSourceId, string liveStreamId, bool
 343        {
 0344            if (!string.IsNullOrEmpty(liveStreamId))
 345            {
 0346                return await GetLiveStream(liveStreamId, cancellationToken).ConfigureAwait(false);
 347            }
 348
 0349            var sources = await GetPlaybackMediaSources(item, null, false, enablePathSubstitution, cancellationToken).Co
 350
 0351            return sources.FirstOrDefault(i => string.Equals(i.Id, mediaSourceId, StringComparison.OrdinalIgnoreCase));
 0352        }
 353
 354        public IReadOnlyList<MediaSourceInfo> GetStaticMediaSources(BaseItem item, bool enablePathSubstitution, User use
 355        {
 0356            ArgumentNullException.ThrowIfNull(item);
 357
 0358            var hasMediaSources = (IHasMediaSources)item;
 359
 0360            var sources = hasMediaSources.GetMediaSources(enablePathSubstitution);
 361
 0362            if (user is not null)
 363            {
 0364                foreach (var source in sources)
 365                {
 0366                    SetDefaultAudioAndSubtitleStreamIndices(item, source, user);
 367
 0368                    if (item.MediaType == MediaType.Audio)
 369                    {
 0370                        source.SupportsTranscoding = user.HasPermission(PermissionKind.EnableAudioPlaybackTranscoding);
 371                    }
 0372                    else if (item.MediaType == MediaType.Video)
 373                    {
 0374                        source.SupportsTranscoding = user.HasPermission(PermissionKind.EnableVideoPlaybackTranscoding);
 0375                        source.SupportsDirectStream = user.HasPermission(PermissionKind.EnablePlaybackRemuxing);
 376                    }
 377                }
 378            }
 379
 0380            return sources;
 381        }
 382
 383        private IReadOnlyList<string> NormalizeLanguage(string language)
 384        {
 49385            if (string.IsNullOrEmpty(language))
 386            {
 1387                return [];
 388            }
 389
 48390            var culture = _localizationManager.FindLanguageInfo(language);
 48391            if (culture is not null)
 392            {
 48393                return culture.Name.Contains('-', StringComparison.OrdinalIgnoreCase) ? [culture.Name] : culture.ThreeLe
 394            }
 395
 0396            return [language];
 397        }
 398
 399        private void SetDefaultSubtitleStreamIndex(MediaSourceInfo source, UserItemData userData, User user, bool allowR
 400        {
 22401            if (userData is not null
 22402                && userData.SubtitleStreamIndex.HasValue
 22403                && user.RememberSubtitleSelections
 22404                && user.SubtitleMode != SubtitlePlaybackMode.None
 22405                && allowRememberingSelection)
 406            {
 0407                var index = userData.SubtitleStreamIndex.Value;
 408                // Make sure the saved index is still valid
 0409                if (index == -1 || source.MediaStreams.Any(i => i.Type == MediaStreamType.Subtitle && i.Index == index))
 410                {
 0411                    source.DefaultSubtitleStreamIndex = index;
 0412                    return;
 413                }
 414            }
 415
 22416            var preferredSubs = NormalizeLanguage(user.SubtitleLanguagePreference);
 417
 22418            var defaultAudioIndex = source.DefaultAudioStreamIndex;
 22419            var audioLanguage = defaultAudioIndex is null
 22420                ? null
 22421                : source.MediaStreams.Where(i => i.Type == MediaStreamType.Audio && i.Index == defaultAudioIndex).Select
 422
 22423            source.DefaultSubtitleStreamIndex = MediaStreamSelector.GetDefaultSubtitleStreamIndex(
 22424                source.MediaStreams,
 22425                preferredSubs,
 22426                user.SubtitleMode,
 22427                audioLanguage);
 428
 22429            MediaStreamSelector.SetSubtitleStreamScores(source.MediaStreams, preferredSubs, user.SubtitleMode, audioLang
 22430        }
 431
 432        private void SetDefaultAudioStreamIndex(MediaSourceInfo source, UserItemData userData, User user, bool allowReme
 433        {
 22434            if (userData is not null && userData.AudioStreamIndex.HasValue && user.RememberAudioSelections && allowRemem
 435            {
 0436                var index = userData.AudioStreamIndex.Value;
 437                // Make sure the saved index is still valid
 0438                if (source.MediaStreams.Any(i => i.Type == MediaStreamType.Audio && i.Index == index))
 439                {
 0440                    source.DefaultAudioStreamIndex = index;
 0441                    source.DefaultAudioIndexSource = AudioIndexSource.User;
 0442                    return;
 443                }
 444            }
 445
 22446            if (string.Equals(user.AudioLanguagePreference, "OriginalLanguage", StringComparison.OrdinalIgnoreCase))
 447            {
 16448                if (user.PlayDefaultAudioTrack)
 449                {
 6450                    source.DefaultAudioStreamIndex = MediaStreamSelector.GetDefaultAudioStreamIndex(
 6451                        source.MediaStreams,
 6452                        NormalizeLanguage(originalLanguage),
 6453                        user.PlayDefaultAudioTrack);
 6454                    return;
 455                }
 456
 10457                var originalIndex = source.MediaStreams.FindIndex(i => i.Type == MediaStreamType.Audio && i.IsOriginal);
 458
 10459                if (!string.IsNullOrWhiteSpace(originalLanguage) && originalIndex != -1)
 460                {
 4461                    var mediaLanguageOriginal = source.MediaStreams[originalIndex].Language;
 4462                    if (NormalizeLanguage(mediaLanguageOriginal).Contains(NormalizeLanguage(originalLanguage).FirstOrDef
 463                    {
 1464                        source.DefaultAudioStreamIndex = originalIndex;
 1465                        return;
 466                    }
 467                }
 6468                else if (originalIndex != -1)
 469                {
 2470                    source.DefaultAudioStreamIndex = originalIndex;
 2471                    return;
 472                }
 473            }
 474
 13475            var preferredAudio = string.Equals(user.AudioLanguagePreference, "OriginalLanguage", StringComparison.Ordina
 13476                ? NormalizeLanguage(originalLanguage)
 13477                : NormalizeLanguage(user.AudioLanguagePreference);
 478
 13479            source.DefaultAudioStreamIndex = MediaStreamSelector.GetDefaultAudioStreamIndex(source.MediaStreams, preferr
 13480            if (user.PlayDefaultAudioTrack)
 481            {
 3482                source.DefaultAudioIndexSource |= AudioIndexSource.Default;
 483            }
 484
 13485            if (preferredAudio.Count > 0)
 486            {
 13487                source.DefaultAudioIndexSource |= AudioIndexSource.Language;
 488            }
 13489        }
 490
 491        public void SetDefaultAudioAndSubtitleStreamIndices(BaseItem item, MediaSourceInfo source, User user)
 492        {
 493            // Item would only be null if the app didn't supply ItemId as part of the live stream open request
 22494            var mediaType = item?.MediaType ?? MediaType.Video;
 495
 22496            if (mediaType == MediaType.Video)
 497            {
 22498                var userData = item is null ? null : _userDataManager.GetUserData(user, item);
 499
 22500                var allowRememberingSelection = item is null || item.EnableRememberingTrackSelections;
 501
 22502                var originalLanguage = item?.GetInheritedOriginalLanguage();
 503
 22504                SetDefaultAudioStreamIndex(source, userData, user, allowRememberingSelection, originalLanguage);
 22505                SetDefaultSubtitleStreamIndex(source, userData, user, allowRememberingSelection);
 506            }
 0507            else if (mediaType == MediaType.Audio)
 508            {
 0509                var audio = source.MediaStreams.FirstOrDefault(i => i.Type == MediaStreamType.Audio);
 510
 0511                if (audio is not null)
 512                {
 0513                    source.DefaultAudioStreamIndex = audio.Index;
 514                }
 515            }
 0516        }
 517
 518        private static IEnumerable<MediaSourceInfo> SortMediaSources(IEnumerable<MediaSourceInfo> sources)
 519        {
 0520            return sources.OrderBy(i =>
 0521            {
 0522                if (i.VideoType.HasValue && i.VideoType.Value == VideoType.VideoFile)
 0523                {
 0524                    return 0;
 0525                }
 0526
 0527                return 1;
 0528            }).ThenBy(i => i.Video3DFormat.HasValue ? 1 : 0)
 0529            .ThenByDescending(i =>
 0530            {
 0531                var stream = i.VideoStream;
 0532
 0533                return stream?.Width ?? 0;
 0534            })
 0535            .Where(i => i.Type != MediaSourceType.Placeholder);
 536        }
 537
 538        public async Task<Tuple<LiveStreamResponse, IDirectStreamProvider>> OpenLiveStreamInternal(LiveStreamRequest req
 539        {
 540            MediaSourceInfo mediaSource;
 541            ILiveStream liveStream;
 542
 0543            using (await _liveStreamLocker.LockAsync(cancellationToken).ConfigureAwait(false))
 544            {
 0545                var (provider, keyId) = GetProvider(request.OpenToken);
 546
 0547                var currentLiveStreams = _openStreams.Values.ToList();
 548
 0549                liveStream = await provider.OpenMediaSource(keyId, currentLiveStreams, cancellationToken).ConfigureAwait
 550
 0551                mediaSource = liveStream.MediaSource;
 552
 553                // Validate that this is actually possible
 0554                if (mediaSource.SupportsDirectStream)
 555                {
 0556                    mediaSource.SupportsDirectStream = SupportsDirectStream(mediaSource.Path, mediaSource.Protocol);
 557                }
 558
 0559                SetKeyProperties(provider, mediaSource);
 560
 0561                _openStreams[mediaSource.LiveStreamId] = liveStream;
 0562            }
 563
 564            try
 565            {
 0566                if (mediaSource.MediaStreams.Any(i => i.Index != -1) || !mediaSource.SupportsProbing)
 567                {
 0568                    AddMediaInfo(mediaSource);
 569                }
 570                else
 571                {
 572                    // hack - these two values were taken from LiveTVMediaSourceProvider
 0573                    string cacheKey = request.OpenToken;
 574
 0575                    await new LiveStreamHelper(_mediaEncoder, _logger, _appPaths)
 0576                        .AddMediaInfoWithProbe(mediaSource, false, cacheKey, true, cancellationToken)
 0577                        .ConfigureAwait(false);
 578                }
 0579            }
 0580            catch (Exception ex)
 581            {
 0582                _logger.LogError(ex, "Error probing live tv stream");
 0583                AddMediaInfo(mediaSource);
 0584            }
 585
 586            // TODO: @bond Fix
 0587            var json = JsonSerializer.SerializeToUtf8Bytes(mediaSource, _jsonOptions);
 0588            _logger.LogInformation("Live stream opened: {@MediaSource}", mediaSource);
 0589            var clone = JsonSerializer.Deserialize<MediaSourceInfo>(json, _jsonOptions);
 590
 0591            if (!request.UserId.IsEmpty())
 592            {
 0593                var user = _userManager.GetUserById(request.UserId);
 0594                var item = request.ItemId.IsEmpty()
 0595                    ? null
 0596                    : _libraryManager.GetItemById(request.ItemId);
 0597                SetDefaultAudioAndSubtitleStreamIndices(item, clone, user);
 598            }
 599
 0600            return new Tuple<LiveStreamResponse, IDirectStreamProvider>(new LiveStreamResponse(clone), liveStream as IDi
 0601        }
 602
 603        private static void AddMediaInfo(MediaSourceInfo mediaSource)
 604        {
 0605            mediaSource.DefaultSubtitleStreamIndex = null;
 606
 607            // Null this out so that it will be treated like a live stream
 0608            if (mediaSource.IsInfiniteStream)
 609            {
 0610                mediaSource.RunTimeTicks = null;
 611            }
 612
 0613            var audioStream = mediaSource.MediaStreams.FirstOrDefault(i => i.Type == MediaStreamType.Audio);
 614
 0615            if (audioStream is null || audioStream.Index == -1)
 616            {
 0617                mediaSource.DefaultAudioStreamIndex = null;
 618            }
 619            else
 620            {
 0621                mediaSource.DefaultAudioStreamIndex = audioStream.Index;
 622            }
 623
 0624            var videoStream = mediaSource.MediaStreams.FirstOrDefault(i => i.Type == MediaStreamType.Video);
 0625            if (videoStream is not null)
 626            {
 0627                if (!videoStream.BitRate.HasValue)
 628                {
 0629                    var width = videoStream.Width ?? 1920;
 630
 0631                    if (width >= 3000)
 632                    {
 0633                        videoStream.BitRate = 30000000;
 634                    }
 0635                    else if (width >= 1900)
 636                    {
 0637                        videoStream.BitRate = 20000000;
 638                    }
 0639                    else if (width >= 1200)
 640                    {
 0641                        videoStream.BitRate = 8000000;
 642                    }
 0643                    else if (width >= 700)
 644                    {
 0645                        videoStream.BitRate = 2000000;
 646                    }
 647                }
 648            }
 649
 650            // Try to estimate this
 0651            mediaSource.InferTotalBitrate();
 0652        }
 653
 654        public async Task<LiveStreamResponse> OpenLiveStream(LiveStreamRequest request, CancellationToken cancellationTo
 655        {
 0656            var result = await OpenLiveStreamInternal(request, cancellationToken).ConfigureAwait(false);
 0657            return result.Item1;
 0658        }
 659
 660        public async Task<MediaSourceInfo> GetLiveStreamMediaInfo(string id, CancellationToken cancellationToken)
 661        {
 662            // TODO probably shouldn't throw here but it is kept for "backwards compatibility"
 0663            var liveStreamInfo = GetLiveStreamInfo(id) ?? throw new ResourceNotFoundException();
 664
 0665            var mediaSource = liveStreamInfo.MediaSource;
 666
 0667            if (liveStreamInfo is IDirectStreamProvider)
 668            {
 0669                var info = await _mediaEncoder.GetMediaInfo(
 0670                    new MediaInfoRequest
 0671                    {
 0672                        MediaSource = mediaSource,
 0673                        ExtractChapters = false,
 0674                        MediaType = DlnaProfileType.Video
 0675                    },
 0676                    cancellationToken).ConfigureAwait(false);
 677
 0678                mediaSource.MediaStreams = info.MediaStreams;
 0679                mediaSource.Container = info.Container;
 0680                mediaSource.Bitrate = info.Bitrate;
 681            }
 682
 0683            return mediaSource;
 0684        }
 685
 686        public async Task AddMediaInfoWithProbe(MediaSourceInfo mediaSource, bool isAudio, string cacheKey, bool addProb
 687        {
 0688            var originalRuntime = mediaSource.RunTimeTicks;
 689
 0690            var now = DateTime.UtcNow;
 691
 0692            MediaInfo mediaInfo = null;
 0693            var cacheFilePath = string.IsNullOrEmpty(cacheKey) ? null : Path.Combine(_appPaths.CachePath, "mediainfo", c
 694
 0695            if (!string.IsNullOrEmpty(cacheKey))
 696            {
 0697                FileStream jsonStream = AsyncFile.OpenRead(cacheFilePath);
 698                try
 699                {
 0700                    mediaInfo = await JsonSerializer.DeserializeAsync<MediaInfo>(jsonStream, _jsonOptions, cancellationT
 0701                }
 0702                catch (Exception ex)
 703                {
 0704                    _logger.LogDebug(ex, "Error parsing cached media info.");
 0705                }
 706                finally
 707                {
 0708                    await jsonStream.DisposeAsync().ConfigureAwait(false);
 709                }
 0710            }
 711
 0712            if (mediaInfo is null)
 713            {
 0714                if (addProbeDelay)
 715                {
 0716                    var delayMs = mediaSource.AnalyzeDurationMs ?? 0;
 0717                    delayMs = Math.Max(3000, delayMs);
 0718                    await Task.Delay(delayMs, cancellationToken).ConfigureAwait(false);
 719                }
 720
 0721                if (isLiveStream)
 722                {
 0723                    mediaSource.AnalyzeDurationMs = 3000;
 724                }
 725
 0726                mediaInfo = await _mediaEncoder.GetMediaInfo(
 0727                    new MediaInfoRequest
 0728                    {
 0729                        MediaSource = mediaSource,
 0730                        MediaType = isAudio ? DlnaProfileType.Audio : DlnaProfileType.Video,
 0731                        ExtractChapters = false
 0732                    },
 0733                    cancellationToken).ConfigureAwait(false);
 734
 0735                if (cacheFilePath is not null)
 736                {
 0737                    Directory.CreateDirectory(Path.GetDirectoryName(cacheFilePath));
 0738                    FileStream createStream = AsyncFile.Create(cacheFilePath);
 0739                    await using (createStream.ConfigureAwait(false))
 740                    {
 0741                        await JsonSerializer.SerializeAsync(createStream, mediaInfo, _jsonOptions, cancellationToken).Co
 742                    }
 743
 744                    // _logger.LogDebug("Saved media info to {0}", cacheFilePath);
 745                }
 746            }
 747
 0748            var mediaStreams = mediaInfo.MediaStreams;
 749
 0750            if (isLiveStream && !string.IsNullOrEmpty(cacheKey))
 751            {
 0752                var newList = new List<MediaStream>();
 0753                newList.AddRange(mediaStreams.Where(i => i.Type == MediaStreamType.Video).Take(1));
 0754                newList.AddRange(mediaStreams.Where(i => i.Type == MediaStreamType.Audio).Take(1));
 755
 0756                foreach (var stream in newList)
 757                {
 0758                    stream.Index = -1;
 0759                    stream.Language = null;
 760                }
 761
 0762                mediaStreams = newList;
 763            }
 764
 0765            _logger.LogInformation("Live tv media info probe took {0} seconds", (DateTime.UtcNow - now).TotalSeconds.ToS
 766
 0767            mediaSource.Bitrate = mediaInfo.Bitrate;
 0768            mediaSource.Container = mediaInfo.Container;
 0769            mediaSource.Formats = mediaInfo.Formats;
 0770            mediaSource.MediaStreams = mediaStreams;
 0771            mediaSource.RunTimeTicks = mediaInfo.RunTimeTicks;
 0772            mediaSource.Size = mediaInfo.Size;
 0773            mediaSource.Timestamp = mediaInfo.Timestamp;
 0774            mediaSource.Video3DFormat = mediaInfo.Video3DFormat;
 0775            mediaSource.VideoType = mediaInfo.VideoType;
 776
 0777            mediaSource.DefaultSubtitleStreamIndex = null;
 778
 0779            if (isLiveStream)
 780            {
 781                // Null this out so that it will be treated like a live stream
 0782                if (!originalRuntime.HasValue)
 783                {
 0784                    mediaSource.RunTimeTicks = null;
 785                }
 786            }
 787
 0788            var audioStream = mediaStreams.FirstOrDefault(i => i.Type == MediaStreamType.Audio);
 789
 0790            if (audioStream is null || audioStream.Index == -1)
 791            {
 0792                mediaSource.DefaultAudioStreamIndex = null;
 793            }
 794            else
 795            {
 0796                mediaSource.DefaultAudioStreamIndex = audioStream.Index;
 797            }
 798
 0799            var videoStream = mediaStreams.FirstOrDefault(i => i.Type == MediaStreamType.Video);
 0800            if (videoStream is not null)
 801            {
 0802                if (!videoStream.BitRate.HasValue)
 803                {
 0804                    var width = videoStream.Width ?? 1920;
 805
 0806                    if (width >= 3000)
 807                    {
 0808                        videoStream.BitRate = 30000000;
 809                    }
 0810                    else if (width >= 1900)
 811                    {
 0812                        videoStream.BitRate = 20000000;
 813                    }
 0814                    else if (width >= 1200)
 815                    {
 0816                        videoStream.BitRate = 8000000;
 817                    }
 0818                    else if (width >= 700)
 819                    {
 0820                        videoStream.BitRate = 2000000;
 821                    }
 822                }
 823
 824                // This is coming up false and preventing stream copy
 0825                videoStream.IsAVC = null;
 826            }
 827
 0828            if (isLiveStream)
 829            {
 0830                mediaSource.AnalyzeDurationMs = 3000;
 831            }
 832
 833            // Try to estimate this
 0834            mediaSource.InferTotalBitrate(true);
 0835        }
 836
 837        public Task<Tuple<MediaSourceInfo, IDirectStreamProvider>> GetLiveStreamWithDirectStreamProvider(string id, Canc
 838        {
 0839            ArgumentException.ThrowIfNullOrEmpty(id);
 840
 0841            var info = GetLiveStreamInfo(id);
 0842            if (info is null)
 843            {
 0844                return Task.FromResult<Tuple<MediaSourceInfo, IDirectStreamProvider>>(new Tuple<MediaSourceInfo, IDirect
 845            }
 846
 0847            return Task.FromResult<Tuple<MediaSourceInfo, IDirectStreamProvider>>(new Tuple<MediaSourceInfo, IDirectStre
 848        }
 849
 850        public ILiveStream GetLiveStreamInfo(string id)
 851        {
 0852            ArgumentException.ThrowIfNullOrEmpty(id);
 853
 0854            if (_openStreams.TryGetValue(id, out ILiveStream info))
 855            {
 0856                return info;
 857            }
 858
 0859            return null;
 860        }
 861
 862        /// <inheritdoc />
 863        public ILiveStream GetLiveStreamInfoByUniqueId(string uniqueId)
 864        {
 0865            return _openStreams.Values.FirstOrDefault(stream => string.Equals(uniqueId, stream?.UniqueId, StringComparis
 866        }
 867
 868        public async Task<MediaSourceInfo> GetLiveStream(string id, CancellationToken cancellationToken)
 869        {
 0870            var result = await GetLiveStreamWithDirectStreamProvider(id, cancellationToken).ConfigureAwait(false);
 0871            return result.Item1;
 0872        }
 873
 874        public async Task<IReadOnlyList<MediaSourceInfo>> GetRecordingStreamMediaSources(ActiveRecordingInfo info, Cance
 875        {
 0876            var stream = new MediaSourceInfo
 0877            {
 0878                EncoderPath = _appHost.GetApiUrlForLocalAccess() + "/LiveTv/LiveRecordings/" + info.Id + "/stream",
 0879                EncoderProtocol = MediaProtocol.Http,
 0880                Path = info.Path,
 0881                Protocol = MediaProtocol.File,
 0882                Id = info.Id,
 0883                SupportsDirectPlay = false,
 0884                SupportsDirectStream = true,
 0885                SupportsTranscoding = true,
 0886                IsInfiniteStream = true,
 0887                RequiresOpening = false,
 0888                RequiresClosing = false,
 0889                BufferMs = 0,
 0890                IgnoreDts = true,
 0891                IgnoreIndex = true
 0892            };
 893
 0894            await new LiveStreamHelper(_mediaEncoder, _logger, _appPaths)
 0895                .AddMediaInfoWithProbe(stream, false, false, cancellationToken).ConfigureAwait(false);
 896
 0897            return [stream];
 0898        }
 899
 900        public async Task CloseLiveStream(string id)
 901        {
 0902            ArgumentException.ThrowIfNullOrEmpty(id);
 903
 0904            using (await _liveStreamLocker.LockAsync().ConfigureAwait(false))
 905            {
 0906                if (_openStreams.TryGetValue(id, out ILiveStream liveStream))
 907                {
 0908                    liveStream.ConsumerCount--;
 909
 0910                    _logger.LogInformation("Live stream {0} consumer count is now {1}", liveStream.OriginalStreamId, liv
 911
 0912                    if (liveStream.ConsumerCount <= 0)
 913                    {
 0914                        _openStreams.TryRemove(id, out _);
 915
 0916                        _logger.LogInformation("Closing live stream {0}", id);
 917
 0918                        await liveStream.Close().ConfigureAwait(false);
 0919                        _logger.LogInformation("Live stream {0} closed successfully", id);
 920                    }
 921                }
 0922            }
 0923        }
 924
 925        private (IMediaSourceProvider MediaSourceProvider, string KeyId) GetProvider(string key)
 926        {
 0927            ArgumentException.ThrowIfNullOrEmpty(key);
 928
 0929            var keys = key.Split(LiveStreamIdDelimiter, 2);
 930
 0931            var provider = _providers.FirstOrDefault(i => string.Equals(i.GetType().FullName.GetMD5().ToString("N", Cult
 932
 0933            var splitIndex = key.IndexOf(LiveStreamIdDelimiter, StringComparison.Ordinal);
 0934            var keyId = key.Substring(splitIndex + 1);
 935
 0936            return (provider, keyId);
 937        }
 938
 939        /// <inheritdoc />
 940        public void Dispose()
 941        {
 21942            Dispose(true);
 21943            GC.SuppressFinalize(this);
 21944        }
 945
 946        /// <summary>
 947        /// Releases unmanaged and - optionally - managed resources.
 948        /// </summary>
 949        /// <param name="dispose"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release o
 950        protected virtual void Dispose(bool dispose)
 951        {
 21952            if (dispose)
 953            {
 42954                foreach (var key in _openStreams.Keys.ToList())
 955                {
 0956                    CloseLiveStream(key).GetAwaiter().GetResult();
 957                }
 958
 21959                _liveStreamLocker.Dispose();
 960            }
 21961        }
 962    }
 963}

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)