< 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
28%
Covered lines: 135
Uncovered lines: 344
Coverable lines: 479
Total lines: 1070
Line coverage: 28.1%
Branch coverage
32%
Covered branches: 87
Total branches: 268
Branch coverage: 32.4%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Coverage history

Coverage history 0 25 50 75 100 5/16/2026 - 12:15:55 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: 9636/2/2026 - 12:15:49 AM Line coverage: 20.7% (91/439) Branch coverage: 22% (53/240) Total lines: 9886/9/2026 - 12:16:23 AM Line coverage: 20.2% (91/450) Branch coverage: 21.9% (53/242) Total lines: 10027/6/2026 - 12:16:28 AM Line coverage: 27.1% (130/478) Branch coverage: 30.3% (82/270) Total lines: 10697/18/2026 - 12:15:19 AM Line coverage: 26.4% (125/473) Branch coverage: 28.2% (74/262) Total lines: 105910/29/2026 - 12:17:11 AM Line coverage: 28.1% (135/479) Branch coverage: 32.4% (87/268) Total lines: 1070 5/16/2026 - 12:15:55 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: 9636/2/2026 - 12:15:49 AM Line coverage: 20.7% (91/439) Branch coverage: 22% (53/240) Total lines: 9886/9/2026 - 12:16:23 AM Line coverage: 20.2% (91/450) Branch coverage: 21.9% (53/242) Total lines: 10027/6/2026 - 12:16:28 AM Line coverage: 27.1% (130/478) Branch coverage: 30.3% (82/270) Total lines: 10697/18/2026 - 12:15:19 AM Line coverage: 26.4% (125/473) Branch coverage: 28.2% (74/262) Total lines: 105910/29/2026 - 12:17:11 AM Line coverage: 28.1% (135/479) Branch coverage: 32.4% (87/268) Total lines: 1070

Coverage delta

Coverage delta 9 -9

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.IO;
 28using MediaBrowser.Controller.Library;
 29using MediaBrowser.Controller.LiveTv;
 30using MediaBrowser.Controller.MediaEncoding;
 31using MediaBrowser.Controller.Persistence;
 32using MediaBrowser.Controller.Providers;
 33using MediaBrowser.Model.Dlna;
 34using MediaBrowser.Model.Dto;
 35using MediaBrowser.Model.Entities;
 36using MediaBrowser.Model.Globalization;
 37using MediaBrowser.Model.IO;
 38using MediaBrowser.Model.MediaInfo;
 39using Microsoft.Extensions.Logging;
 40
 41namespace Emby.Server.Implementations.Library
 42{
 43    public class MediaSourceManager : IMediaSourceManager, IDisposable
 44    {
 45        // Do not use a pipe here because Roku http requests to the server will fail, without any explicit error message
 46        private const char LiveStreamIdDelimiter = '_';
 47
 48        private readonly IServerApplicationHost _appHost;
 49        private readonly IItemRepository _itemRepo;
 50        private readonly IUserManager _userManager;
 51        private readonly ILibraryManager _libraryManager;
 52        private readonly IFileSystem _fileSystem;
 53        private readonly ILogger<MediaSourceManager> _logger;
 54        private readonly IUserDataManager _userDataManager;
 55        private readonly IMediaEncoder _mediaEncoder;
 56        private readonly ILocalizationManager _localizationManager;
 57        private readonly IApplicationPaths _appPaths;
 58        private readonly IDirectoryService _directoryService;
 59        private readonly IMediaStreamRepository _mediaStreamRepository;
 60        private readonly IMediaAttachmentRepository _mediaAttachmentRepository;
 6861        private readonly ConcurrentDictionary<string, ILiveStream> _openStreams = new ConcurrentDictionary<string, ILive
 6862        private readonly AsyncNonKeyedLocker _liveStreamLocker = new(1);
 6863        private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.Options;
 64
 65        private IMediaSourceProvider[] _providers;
 66
 67        public MediaSourceManager(
 68            IServerApplicationHost appHost,
 69            IItemRepository itemRepo,
 70            IApplicationPaths applicationPaths,
 71            ILocalizationManager localizationManager,
 72            IUserManager userManager,
 73            ILibraryManager libraryManager,
 74            ILogger<MediaSourceManager> logger,
 75            IFileSystem fileSystem,
 76            IUserDataManager userDataManager,
 77            IMediaEncoder mediaEncoder,
 78            IDirectoryService directoryService,
 79            IMediaStreamRepository mediaStreamRepository,
 80            IMediaAttachmentRepository mediaAttachmentRepository)
 81        {
 6882            _appHost = appHost;
 6883            _itemRepo = itemRepo;
 6884            _userManager = userManager;
 6885            _libraryManager = libraryManager;
 6886            _logger = logger;
 6887            _fileSystem = fileSystem;
 6888            _userDataManager = userDataManager;
 6889            _mediaEncoder = mediaEncoder;
 6890            _localizationManager = localizationManager;
 6891            _appPaths = applicationPaths;
 6892            _directoryService = directoryService;
 6893            _mediaStreamRepository = mediaStreamRepository;
 6894            _mediaAttachmentRepository = mediaAttachmentRepository;
 6895        }
 96
 97        public void AddParts(IEnumerable<IMediaSourceProvider> providers)
 98        {
 2799            _providers = providers.ToArray();
 27100        }
 101
 102        public IReadOnlyList<MediaStream> GetMediaStreams(MediaStreamQuery query)
 103        {
 0104            var list = _mediaStreamRepository.GetMediaStreams(query);
 105
 0106            foreach (var stream in list)
 107            {
 0108                stream.SupportsExternalStream = StreamSupportsExternalStream(stream);
 109            }
 110
 0111            return list;
 112        }
 113
 114        private static bool StreamSupportsExternalStream(MediaStream stream)
 115        {
 0116            if (stream.IsExternal)
 117            {
 0118                return true;
 119            }
 120
 0121            if (stream.IsTextSubtitleStream)
 122            {
 0123                return true;
 124            }
 125
 0126            if (stream.IsPgsSubtitleStream)
 127            {
 0128                return true;
 129            }
 130
 0131            if (stream.IsVobSubSubtitleStream)
 132            {
 0133                return true;
 134            }
 135
 0136            return false;
 137        }
 138
 139        public IReadOnlyList<MediaStream> GetMediaStreams(Guid itemId)
 140        {
 0141            var list = GetMediaStreams(new MediaStreamQuery
 0142            {
 0143                ItemId = itemId
 0144            });
 145
 0146            return GetMediaStreamsForItem(list);
 147        }
 148
 149        private IReadOnlyList<MediaStream> GetMediaStreamsForItem(IReadOnlyList<MediaStream> streams)
 150        {
 0151            foreach (var stream in streams)
 152            {
 0153                if (stream.Type == MediaStreamType.Subtitle)
 154                {
 0155                    stream.SupportsExternalStream = StreamSupportsExternalStream(stream);
 156                }
 157            }
 158
 0159            return streams;
 160        }
 161
 162        /// <inheritdoc />
 163        public IReadOnlyList<MediaAttachment> GetMediaAttachments(MediaAttachmentQuery query)
 164        {
 0165            return _mediaAttachmentRepository.GetMediaAttachments(query);
 166        }
 167
 168        /// <inheritdoc />
 169        public IReadOnlyList<MediaAttachment> GetMediaAttachments(Guid itemId)
 170        {
 0171            return GetMediaAttachments(new MediaAttachmentQuery
 0172            {
 0173                ItemId = itemId
 0174            });
 175        }
 176
 177        public async Task<IReadOnlyList<MediaSourceInfo>> GetPlaybackMediaSources(BaseItem item, User user, bool allowMe
 178        {
 0179            var mediaSources = GetStaticMediaSources(item, enablePathSubstitution, user);
 0180            ResolveSymlinkPaths(mediaSources, enablePathSubstitution);
 181
 182            // If file is strm or main media stream is missing, force a metadata refresh with remote probing
 0183            if (allowMediaProbe && mediaSources[0].Type != MediaSourceType.Placeholder
 0184                && (item.Path.EndsWith(".strm", StringComparison.OrdinalIgnoreCase)
 0185                    || (item.MediaType == MediaType.Video && mediaSources[0].MediaStreams.All(i => i.Type != MediaStream
 0186                    || (item.MediaType == MediaType.Audio && mediaSources[0].MediaStreams.All(i => i.Type != MediaStream
 187            {
 0188                await item.RefreshMetadata(
 0189                    new MetadataRefreshOptions(_directoryService)
 0190                    {
 0191                        EnableRemoteContentProbe = true,
 0192                        MetadataRefreshMode = MetadataRefreshMode.FullRefresh
 0193                    },
 0194                    cancellationToken).ConfigureAwait(false);
 195
 0196                mediaSources = GetStaticMediaSources(item, enablePathSubstitution, user);
 0197                ResolveSymlinkPaths(mediaSources, enablePathSubstitution);
 198            }
 199
 0200            var dynamicMediaSources = await GetDynamicMediaSources(item, cancellationToken).ConfigureAwait(false);
 201
 0202            var list = new List<MediaSourceInfo>();
 203
 0204            list.AddRange(mediaSources);
 205
 0206            foreach (var source in dynamicMediaSources)
 207            {
 208                // Validate that this is actually possible
 0209                if (source.SupportsDirectStream)
 210                {
 0211                    source.SupportsDirectStream = SupportsDirectStream(source.Path, source.Protocol);
 212                }
 213
 0214                if (user is not null)
 215                {
 0216                    SetDefaultAudioAndSubtitleStreamIndices(item, source, user);
 217
 0218                    if (item.MediaType == MediaType.Audio)
 219                    {
 0220                        source.SupportsTranscoding = user.HasPermission(PermissionKind.EnableAudioPlaybackTranscoding);
 221                    }
 0222                    else if (item.MediaType == MediaType.Video)
 223                    {
 0224                        source.SupportsTranscoding = user.HasPermission(PermissionKind.EnableVideoPlaybackTranscoding);
 0225                        source.SupportsDirectStream = user.HasPermission(PermissionKind.EnablePlaybackRemuxing);
 226                    }
 227                }
 228
 0229                list.Add(source);
 230            }
 231
 0232            var preferredId = mediaSources.Count > 0 && Guid.TryParse(mediaSources[0].Id, out var topSourceId)
 0233                ? topSourceId
 0234                : item.Id;
 235
 0236            return SortMediaSources(list, preferredId).ToArray();
 0237        }
 238
 239        /// <inheritdoc />>
 240        public MediaProtocol GetPathProtocol(string path)
 241        {
 3508242            if (string.IsNullOrEmpty(path))
 243            {
 0244                return MediaProtocol.File;
 245            }
 246
 3508247            if (path.StartsWith("Rtsp", StringComparison.OrdinalIgnoreCase))
 248            {
 1249                return MediaProtocol.Rtsp;
 250            }
 251
 3507252            if (path.StartsWith("Rtmp", StringComparison.OrdinalIgnoreCase))
 253            {
 0254                return MediaProtocol.Rtmp;
 255            }
 256
 3507257            if (path.StartsWith("Http", StringComparison.OrdinalIgnoreCase))
 258            {
 2259                return MediaProtocol.Http;
 260            }
 261
 3505262            if (path.StartsWith("rtp", StringComparison.OrdinalIgnoreCase))
 263            {
 0264                return MediaProtocol.Rtp;
 265            }
 266
 3505267            if (path.StartsWith("ftp", StringComparison.OrdinalIgnoreCase))
 268            {
 0269                return MediaProtocol.Ftp;
 270            }
 271
 3505272            if (path.StartsWith("udp", StringComparison.OrdinalIgnoreCase))
 273            {
 0274                return MediaProtocol.Udp;
 275            }
 276
 3505277            return _fileSystem.IsPathFile(path) ? MediaProtocol.File : MediaProtocol.Http;
 278        }
 279
 280        public bool SupportsDirectStream(string path, MediaProtocol protocol)
 281        {
 0282            if (protocol == MediaProtocol.File)
 283            {
 0284                return true;
 285            }
 286
 0287            if (protocol == MediaProtocol.Http)
 288            {
 0289                if (path is not null)
 290                {
 0291                    if (path.Contains(".m3u", StringComparison.OrdinalIgnoreCase))
 292                    {
 0293                        return false;
 294                    }
 295
 0296                    return true;
 297                }
 298            }
 299
 0300            return false;
 301        }
 302
 303        private async Task<IEnumerable<MediaSourceInfo>> GetDynamicMediaSources(BaseItem item, CancellationToken cancell
 304        {
 0305            var tasks = _providers.Select(i => GetDynamicMediaSources(item, i, cancellationToken));
 0306            var results = await Task.WhenAll(tasks).ConfigureAwait(false);
 307
 0308            return results.SelectMany(i => i);
 0309        }
 310
 311        private async Task<IEnumerable<MediaSourceInfo>> GetDynamicMediaSources(BaseItem item, IMediaSourceProvider prov
 312        {
 313            try
 314            {
 0315                var sources = await provider.GetMediaSources(item, cancellationToken).ConfigureAwait(false);
 0316                var list = sources.ToList();
 317
 0318                foreach (var mediaSource in list)
 319                {
 0320                    mediaSource.InferTotalBitrate();
 321
 0322                    SetKeyProperties(provider, mediaSource);
 323                }
 324
 0325                return list;
 326            }
 0327            catch (Exception ex)
 328            {
 0329                _logger.LogError(ex, "Error getting media sources");
 0330                return [];
 331            }
 0332        }
 333
 334        /// <summary>
 335        /// Resolves symlinked file paths on the supplied sources to the real on-disk target.
 336        /// Skipped when <paramref name="enablePathSubstitution"/> is set because the path may
 337        /// already have been rewritten to a UNC/URL meant for the client to consume directly.
 338        /// </summary>
 339        private static void ResolveSymlinkPaths(IReadOnlyList<MediaSourceInfo> sources, bool enablePathSubstitution)
 340        {
 0341            if (enablePathSubstitution)
 342            {
 0343                return;
 344            }
 345
 0346            foreach (var source in sources)
 347            {
 0348                if (source.Protocol == MediaProtocol.File
 0349                    && FileSystemHelper.ResolveLinkTarget(source.Path, returnFinalTarget: true) is { Exists: true } targ
 350                {
 0351                    source.Path = target.FullName;
 352                }
 353            }
 0354        }
 355
 356        private static void SetKeyProperties(IMediaSourceProvider provider, MediaSourceInfo mediaSource)
 357        {
 0358            var prefix = provider.GetType().FullName.GetMD5().ToString("N", CultureInfo.InvariantCulture) + LiveStreamId
 359
 0360            if (!string.IsNullOrEmpty(mediaSource.OpenToken) && !mediaSource.OpenToken.StartsWith(prefix, StringComparis
 361            {
 0362                mediaSource.OpenToken = prefix + mediaSource.OpenToken;
 363            }
 364
 0365            if (!string.IsNullOrEmpty(mediaSource.LiveStreamId) && !mediaSource.LiveStreamId.StartsWith(prefix, StringCo
 366            {
 0367                mediaSource.LiveStreamId = prefix + mediaSource.LiveStreamId;
 368            }
 0369        }
 370
 371        public async Task<MediaSourceInfo> GetMediaSource(BaseItem item, string mediaSourceId, string liveStreamId, bool
 372        {
 0373            if (!string.IsNullOrEmpty(liveStreamId))
 374            {
 0375                return await GetLiveStream(liveStreamId, cancellationToken).ConfigureAwait(false);
 376            }
 377
 0378            var sources = await GetPlaybackMediaSources(item, null, false, enablePathSubstitution, cancellationToken).Co
 379
 0380            return sources.FirstOrDefault(i => string.Equals(i.Id, mediaSourceId, StringComparison.OrdinalIgnoreCase));
 0381        }
 382
 383        public IReadOnlyList<MediaSourceInfo> GetStaticMediaSources(BaseItem item, bool enablePathSubstitution, User use
 384        {
 5385            ArgumentNullException.ThrowIfNull(item);
 386
 387            // Clients can ask for the sources of an item that has none (a container queued by mistake).
 5388            if (item is not IHasMediaSources hasMediaSources)
 389            {
 1390                throw new ArgumentException(
 1391                    string.Format(CultureInfo.InvariantCulture, "{0} {1} has no media sources and cannot be played.", it
 1392                    nameof(item));
 393            }
 394
 4395            var sources = hasMediaSources.GetMediaSources(enablePathSubstitution);
 396
 4397            if (user is not null)
 398            {
 3399                sources = sources
 3400                    .Where(source => !Guid.TryParse(source.Id, out var sourceId)
 3401                        || sourceId.Equals(item.Id)
 3402                        || _libraryManager.GetItemById<BaseItem>(sourceId, user) is not null)
 3403                    .ToArray();
 404
 24405                foreach (var source in sources)
 406                {
 9407                    SetDefaultAudioAndSubtitleStreamIndices(item, source, user);
 408
 9409                    if (item.MediaType == MediaType.Audio)
 410                    {
 0411                        source.SupportsTranscoding = user.HasPermission(PermissionKind.EnableAudioPlaybackTranscoding);
 412                    }
 9413                    else if (item.MediaType == MediaType.Video)
 414                    {
 9415                        source.SupportsTranscoding = user.HasPermission(PermissionKind.EnableVideoPlaybackTranscoding);
 9416                        source.SupportsDirectStream = user.HasPermission(PermissionKind.EnablePlaybackRemuxing);
 417                    }
 418                }
 419
 3420                sources = SetAlternateVersionResumeStates(item, sources, user);
 421            }
 422
 4423            return sources;
 424        }
 425
 426        /// <summary>
 427        /// When the queried item is a primary, moves the most recently played version to the front so
 428        /// that resuming without an explicit source selection plays the version that was last watched.
 429        /// A directly queried alternate version keeps its own source first. Per-user playback position
 430        /// is not surfaced on the source itself; it is carried by each version's own UserData.
 431        /// </summary>
 432        /// <param name="item">The queried item.</param>
 433        /// <param name="sources">The item's media sources.</param>
 434        /// <param name="user">The user.</param>
 435        /// <returns>The media sources, reordered when a version drives resume.</returns>
 436        private IReadOnlyList<MediaSourceInfo> SetAlternateVersionResumeStates(BaseItem item, IReadOnlyList<MediaSourceI
 437        {
 438            // For a video, multiple sources means alternate versions.
 3439            if (item is not Video video || sources.Count < 2)
 440            {
 0441                return sources;
 442            }
 443
 3444            var versions = video.GetAllVersions();
 3445            if (versions.Count < 2)
 446            {
 0447                return sources;
 448            }
 449
 3450            var userDataByVersion = _userDataManager.GetUserDataBatch(versions, user);
 3451            var dataBySourceId = new Dictionary<string, UserItemData>(versions.Count, StringComparer.OrdinalIgnoreCase);
 24452            foreach (var version in versions)
 453            {
 9454                if (userDataByVersion.TryGetValue(version.Id, out var data))
 455                {
 3456                    dataBySourceId[version.Id.ToString("N", CultureInfo.InvariantCulture)] = data;
 457                }
 458            }
 459
 460            // Reorder only for a resumable (in-progress) version;
 461            // a completed version has no position to resume, so it must not be pulled to the front here.
 3462            var resumeSource = VersionPlaybackSelector.SelectMostRecentlyPlayed(
 3463                sources,
 3464                source => source.Id is not null ? dataBySourceId.GetValueOrDefault(source.Id) : null,
 3465                data => data.PlaybackPositionTicks > 0);
 466
 3467            if (resumeSource is not null && !video.PrimaryVersionId.HasValue && !ReferenceEquals(sources[0], resumeSourc
 468            {
 1469                var reordered = new List<MediaSourceInfo>(sources.Count) { resumeSource };
 1470                reordered.AddRange(sources.Where(s => !ReferenceEquals(s, resumeSource)));
 1471                return reordered;
 472            }
 473
 2474            return sources;
 475        }
 476
 477        private IReadOnlyList<string> NormalizeLanguage(string language)
 478        {
 78479            if (string.IsNullOrEmpty(language))
 480            {
 12481                return [];
 482            }
 483
 66484            var culture = _localizationManager.FindLanguageInfo(language);
 66485            if (culture is not null)
 486            {
 66487                return culture.Name.Contains('-', StringComparison.OrdinalIgnoreCase) ? [culture.Name] : culture.ThreeLe
 488            }
 489
 0490            return [language];
 491        }
 492
 493        private void SetDefaultSubtitleStreamIndex(MediaSourceInfo source, UserItemData userData, User user, bool allowR
 494        {
 39495            if (userData is not null
 39496                && userData.SubtitleStreamIndex.HasValue
 39497                && user.RememberSubtitleSelections
 39498                && user.SubtitleMode != SubtitlePlaybackMode.None
 39499                && allowRememberingSelection)
 500            {
 7501                var index = userData.SubtitleStreamIndex.Value;
 502                // Make sure the saved index is still valid
 7503                var savedStream = source.MediaStreams.FirstOrDefault(i => i.Type == MediaStreamType.Subtitle && i.Index 
 504                // "Only forced" rules out full tracks entirely, so a remembered one must not resurrect them.
 505                // The client reports whatever is playing, so an index remembered under another mode sticks forever othe
 7506                if (index == -1
 7507                    || (savedStream is not null
 7508                        && (user.SubtitleMode != SubtitlePlaybackMode.OnlyForced || savedStream.IsForced)))
 509                {
 5510                    source.DefaultSubtitleStreamIndex = index;
 5511                    return;
 512                }
 513            }
 514
 34515            var preferredSubs = NormalizeLanguage(user.SubtitleLanguagePreference);
 516
 34517            var defaultAudioIndex = source.DefaultAudioStreamIndex;
 34518            var audioLanguage = defaultAudioIndex is null
 34519                ? null
 34520                : source.MediaStreams.Where(i => i.Type == MediaStreamType.Audio && i.Index == defaultAudioIndex).Select
 521
 34522            source.DefaultSubtitleStreamIndex = MediaStreamSelector.GetDefaultSubtitleStreamIndex(
 34523                source.MediaStreams,
 34524                preferredSubs,
 34525                user.SubtitleMode,
 34526                audioLanguage);
 527
 34528            MediaStreamSelector.SetSubtitleStreamScores(source.MediaStreams, preferredSubs, user.SubtitleMode, audioLang
 34529        }
 530
 531        private void SetDefaultAudioStreamIndex(MediaSourceInfo source, UserItemData userData, User user, bool allowReme
 532        {
 39533            if (userData is not null && userData.AudioStreamIndex.HasValue && user.RememberAudioSelections && allowRemem
 534            {
 0535                var index = userData.AudioStreamIndex.Value;
 536                // Make sure the saved index is still valid
 0537                if (source.MediaStreams.Any(i => i.Type == MediaStreamType.Audio && i.Index == index))
 538                {
 0539                    source.DefaultAudioStreamIndex = index;
 0540                    source.DefaultAudioIndexSource = AudioIndexSource.User;
 0541                    return;
 542                }
 543            }
 544
 39545            if (string.Equals(user.AudioLanguagePreference, "OriginalLanguage", StringComparison.OrdinalIgnoreCase))
 546            {
 16547                if (user.PlayDefaultAudioTrack)
 548                {
 6549                    source.DefaultAudioStreamIndex = MediaStreamSelector.GetDefaultAudioStreamIndex(
 6550                        source.MediaStreams,
 6551                        NormalizeLanguage(originalLanguage),
 6552                        user.PlayDefaultAudioTrack);
 6553                    return;
 554                }
 555
 10556                var originalIndex = source.MediaStreams.FindIndex(i => i.Type == MediaStreamType.Audio && i.IsOriginal);
 557
 10558                if (!string.IsNullOrWhiteSpace(originalLanguage) && originalIndex != -1)
 559                {
 4560                    var mediaLanguageOriginal = source.MediaStreams[originalIndex].Language;
 4561                    if (NormalizeLanguage(mediaLanguageOriginal).Contains(NormalizeLanguage(originalLanguage).FirstOrDef
 562                    {
 1563                        source.DefaultAudioStreamIndex = originalIndex;
 1564                        return;
 565                    }
 566                }
 6567                else if (originalIndex != -1)
 568                {
 2569                    source.DefaultAudioStreamIndex = originalIndex;
 2570                    return;
 571                }
 572            }
 573
 30574            var preferredAudio = string.Equals(user.AudioLanguagePreference, "OriginalLanguage", StringComparison.Ordina
 30575                ? NormalizeLanguage(originalLanguage)
 30576                : NormalizeLanguage(user.AudioLanguagePreference);
 577
 30578            source.DefaultAudioStreamIndex = MediaStreamSelector.GetDefaultAudioStreamIndex(source.MediaStreams, preferr
 30579            if (user.PlayDefaultAudioTrack)
 580            {
 20581                source.DefaultAudioIndexSource |= AudioIndexSource.Default;
 582            }
 583
 30584            if (preferredAudio.Count > 0)
 585            {
 22586                source.DefaultAudioIndexSource |= AudioIndexSource.Language;
 587            }
 30588        }
 589
 590        public void SetDefaultAudioAndSubtitleStreamIndices(BaseItem item, MediaSourceInfo source, User user)
 591        {
 592            // Item would only be null if the app didn't supply ItemId as part of the live stream open request
 39593            var mediaType = item?.MediaType ?? MediaType.Video;
 594
 39595            if (mediaType == MediaType.Video)
 596            {
 39597                var userData = item is null ? null : _userDataManager.GetUserData(user, item);
 598
 39599                var allowRememberingSelection = item is null || item.EnableRememberingTrackSelections;
 600
 39601                var originalLanguage = item?.GetInheritedOriginalLanguage();
 602
 39603                SetDefaultAudioStreamIndex(source, userData, user, allowRememberingSelection, originalLanguage);
 39604                SetDefaultSubtitleStreamIndex(source, userData, user, allowRememberingSelection);
 605            }
 0606            else if (mediaType == MediaType.Audio)
 607            {
 0608                var audio = source.MediaStreams.FirstOrDefault(i => i.Type == MediaStreamType.Audio);
 609
 0610                if (audio is not null)
 611                {
 0612                    source.DefaultAudioStreamIndex = audio.Index;
 613                }
 614            }
 0615        }
 616
 617        private static IEnumerable<MediaSourceInfo> SortMediaSources(IEnumerable<MediaSourceInfo> sources, Guid preferre
 618        {
 619            // The source belonging to the queried item sorts first so it stays the default that gets played.
 0620            var preferredId = preferredItemId.IsEmpty()
 0621                ? null
 0622                : preferredItemId.ToString("N", CultureInfo.InvariantCulture);
 623
 0624            return sources
 0625                .OrderByDescending(i => preferredId is not null && string.Equals(i.Id, preferredId, StringComparison.Ord
 0626                .ThenBy(i =>
 0627                {
 0628                    if (i.VideoType.HasValue && i.VideoType.Value == VideoType.VideoFile)
 0629                    {
 0630                        return 0;
 0631                    }
 0632
 0633                    return 1;
 0634                })
 0635                .ThenBy(i => i.Video3DFormat.HasValue ? 1 : 0)
 0636                .ThenByDescending(i =>
 0637                {
 0638                    var stream = i.VideoStream;
 0639
 0640                    return stream?.Width ?? 0;
 0641                })
 0642                .Where(i => i.Type != MediaSourceType.Placeholder);
 643        }
 644
 645        public async Task<Tuple<LiveStreamResponse, IDirectStreamProvider>> OpenLiveStreamInternal(LiveStreamRequest req
 646        {
 647            MediaSourceInfo mediaSource;
 648            ILiveStream liveStream;
 649
 0650            using (await _liveStreamLocker.LockAsync(cancellationToken).ConfigureAwait(false))
 651            {
 0652                var (provider, keyId) = GetProvider(request.OpenToken);
 653
 0654                var currentLiveStreams = _openStreams.Values.ToList();
 655
 0656                liveStream = await provider.OpenMediaSource(keyId, currentLiveStreams, cancellationToken).ConfigureAwait
 657
 0658                mediaSource = liveStream.MediaSource;
 659
 660                // Validate that this is actually possible
 0661                if (mediaSource.SupportsDirectStream)
 662                {
 0663                    mediaSource.SupportsDirectStream = SupportsDirectStream(mediaSource.Path, mediaSource.Protocol);
 664                }
 665
 0666                SetKeyProperties(provider, mediaSource);
 667
 0668                _openStreams[mediaSource.LiveStreamId] = liveStream;
 0669            }
 670
 671            try
 672            {
 0673                if (mediaSource.MediaStreams.Any(i => i.Index != -1) || !mediaSource.SupportsProbing)
 674                {
 0675                    AddMediaInfo(mediaSource);
 676                }
 677                else
 678                {
 679                    // hack - these two values were taken from LiveTVMediaSourceProvider
 0680                    string cacheKey = request.OpenToken;
 681
 0682                    await new LiveStreamHelper(_mediaEncoder, _logger, _appPaths)
 0683                        .AddMediaInfoWithProbe(mediaSource, false, cacheKey, true, cancellationToken)
 0684                        .ConfigureAwait(false);
 685                }
 0686            }
 0687            catch (Exception ex)
 688            {
 0689                _logger.LogError(ex, "Error probing live tv stream");
 0690                AddMediaInfo(mediaSource);
 0691            }
 692
 693            // TODO: @bond Fix
 0694            var json = JsonSerializer.SerializeToUtf8Bytes(mediaSource, _jsonOptions);
 0695            _logger.LogInformation("Live stream opened: {@MediaSource}", mediaSource);
 0696            var clone = JsonSerializer.Deserialize<MediaSourceInfo>(json, _jsonOptions);
 697
 0698            if (!request.UserId.IsEmpty())
 699            {
 0700                var user = _userManager.GetUserById(request.UserId);
 0701                var item = request.ItemId.IsEmpty()
 0702                    ? null
 0703                    : _libraryManager.GetItemById(request.ItemId);
 0704                SetDefaultAudioAndSubtitleStreamIndices(item, clone, user);
 705            }
 706
 0707            return new Tuple<LiveStreamResponse, IDirectStreamProvider>(new LiveStreamResponse(clone), liveStream as IDi
 0708        }
 709
 710        private static void AddMediaInfo(MediaSourceInfo mediaSource)
 711        {
 0712            mediaSource.DefaultSubtitleStreamIndex = null;
 713
 714            // Null this out so that it will be treated like a live stream
 0715            if (mediaSource.IsInfiniteStream)
 716            {
 0717                mediaSource.RunTimeTicks = null;
 718            }
 719
 0720            var audioStream = mediaSource.MediaStreams.FirstOrDefault(i => i.Type == MediaStreamType.Audio);
 721
 0722            if (audioStream is null || audioStream.Index == -1)
 723            {
 0724                mediaSource.DefaultAudioStreamIndex = null;
 725            }
 726            else
 727            {
 0728                mediaSource.DefaultAudioStreamIndex = audioStream.Index;
 729            }
 730
 0731            var videoStream = mediaSource.MediaStreams.FirstOrDefault(i => i.Type == MediaStreamType.Video);
 0732            if (videoStream is not null)
 733            {
 0734                if (!videoStream.BitRate.HasValue)
 735                {
 0736                    var width = videoStream.Width ?? 1920;
 737
 0738                    if (width >= 3000)
 739                    {
 0740                        videoStream.BitRate = 30000000;
 741                    }
 0742                    else if (width >= 1900)
 743                    {
 0744                        videoStream.BitRate = 20000000;
 745                    }
 0746                    else if (width >= 1200)
 747                    {
 0748                        videoStream.BitRate = 8000000;
 749                    }
 0750                    else if (width >= 700)
 751                    {
 0752                        videoStream.BitRate = 2000000;
 753                    }
 754                }
 755            }
 756
 757            // Try to estimate this
 0758            mediaSource.InferTotalBitrate();
 0759        }
 760
 761        public async Task<LiveStreamResponse> OpenLiveStream(LiveStreamRequest request, CancellationToken cancellationTo
 762        {
 0763            var result = await OpenLiveStreamInternal(request, cancellationToken).ConfigureAwait(false);
 0764            return result.Item1;
 0765        }
 766
 767        public async Task<MediaSourceInfo> GetLiveStreamMediaInfo(string id, CancellationToken cancellationToken)
 768        {
 769            // TODO probably shouldn't throw here but it is kept for "backwards compatibility"
 0770            var liveStreamInfo = GetLiveStreamInfo(id) ?? throw new ResourceNotFoundException();
 771
 0772            var mediaSource = liveStreamInfo.MediaSource;
 773
 0774            if (liveStreamInfo is IDirectStreamProvider)
 775            {
 0776                var info = await _mediaEncoder.GetMediaInfo(
 0777                    new MediaInfoRequest
 0778                    {
 0779                        MediaSource = mediaSource,
 0780                        ExtractChapters = false,
 0781                        MediaType = DlnaProfileType.Video
 0782                    },
 0783                    cancellationToken).ConfigureAwait(false);
 784
 0785                mediaSource.MediaStreams = info.MediaStreams;
 0786                mediaSource.Container = info.Container;
 0787                mediaSource.Bitrate = info.Bitrate;
 788            }
 789
 0790            return mediaSource;
 0791        }
 792
 793        public async Task AddMediaInfoWithProbe(MediaSourceInfo mediaSource, bool isAudio, string cacheKey, bool addProb
 794        {
 0795            var originalRuntime = mediaSource.RunTimeTicks;
 796
 0797            var now = DateTime.UtcNow;
 798
 0799            MediaInfo mediaInfo = null;
 0800            var cacheFilePath = string.IsNullOrEmpty(cacheKey) ? null : Path.Combine(_appPaths.CachePath, "mediainfo", c
 801
 0802            if (!string.IsNullOrEmpty(cacheKey))
 803            {
 0804                FileStream jsonStream = AsyncFile.OpenRead(cacheFilePath);
 805                try
 806                {
 0807                    mediaInfo = await JsonSerializer.DeserializeAsync<MediaInfo>(jsonStream, _jsonOptions, cancellationT
 0808                }
 0809                catch (Exception ex)
 810                {
 0811                    _logger.LogDebug(ex, "Error parsing cached media info.");
 0812                }
 813                finally
 814                {
 0815                    await jsonStream.DisposeAsync().ConfigureAwait(false);
 816                }
 0817            }
 818
 0819            if (mediaInfo is null)
 820            {
 0821                if (addProbeDelay)
 822                {
 0823                    var delayMs = mediaSource.AnalyzeDurationMs ?? 0;
 0824                    delayMs = Math.Max(3000, delayMs);
 0825                    await Task.Delay(delayMs, cancellationToken).ConfigureAwait(false);
 826                }
 827
 0828                if (isLiveStream)
 829                {
 0830                    mediaSource.AnalyzeDurationMs = 3000;
 831                }
 832
 0833                mediaInfo = await _mediaEncoder.GetMediaInfo(
 0834                    new MediaInfoRequest
 0835                    {
 0836                        MediaSource = mediaSource,
 0837                        MediaType = isAudio ? DlnaProfileType.Audio : DlnaProfileType.Video,
 0838                        ExtractChapters = false
 0839                    },
 0840                    cancellationToken).ConfigureAwait(false);
 841
 0842                if (cacheFilePath is not null)
 843                {
 0844                    Directory.CreateDirectory(Path.GetDirectoryName(cacheFilePath));
 0845                    FileStream createStream = AsyncFile.Create(cacheFilePath);
 0846                    await using (createStream.ConfigureAwait(false))
 847                    {
 0848                        await JsonSerializer.SerializeAsync(createStream, mediaInfo, _jsonOptions, cancellationToken).Co
 849                    }
 850
 851                    // _logger.LogDebug("Saved media info to {0}", cacheFilePath);
 852                }
 853            }
 854
 0855            var mediaStreams = mediaInfo.MediaStreams;
 856
 0857            if (isLiveStream && !string.IsNullOrEmpty(cacheKey))
 858            {
 0859                var newList = new List<MediaStream>();
 0860                newList.AddRange(mediaStreams.Where(i => i.Type == MediaStreamType.Video).Take(1));
 0861                newList.AddRange(mediaStreams.Where(i => i.Type == MediaStreamType.Audio).Take(1));
 862
 0863                foreach (var stream in newList)
 864                {
 0865                    stream.Index = -1;
 0866                    stream.Language = null;
 867                }
 868
 0869                mediaStreams = newList;
 870            }
 871
 0872            _logger.LogInformation("Live tv media info probe took {0} seconds", (DateTime.UtcNow - now).TotalSeconds.ToS
 873
 0874            mediaSource.Bitrate = mediaInfo.Bitrate;
 0875            mediaSource.Container = mediaInfo.Container;
 0876            mediaSource.Formats = mediaInfo.Formats;
 0877            mediaSource.MediaStreams = mediaStreams;
 0878            mediaSource.RunTimeTicks = mediaInfo.RunTimeTicks;
 0879            mediaSource.Size = mediaInfo.Size;
 0880            mediaSource.Timestamp = mediaInfo.Timestamp;
 0881            mediaSource.Video3DFormat = mediaInfo.Video3DFormat;
 0882            mediaSource.VideoType = mediaInfo.VideoType;
 883
 0884            mediaSource.DefaultSubtitleStreamIndex = null;
 885
 0886            if (isLiveStream)
 887            {
 888                // Null this out so that it will be treated like a live stream
 0889                if (!originalRuntime.HasValue)
 890                {
 0891                    mediaSource.RunTimeTicks = null;
 892                }
 893            }
 894
 0895            var audioStream = mediaStreams.FirstOrDefault(i => i.Type == MediaStreamType.Audio);
 896
 0897            if (audioStream is null || audioStream.Index == -1)
 898            {
 0899                mediaSource.DefaultAudioStreamIndex = null;
 900            }
 901            else
 902            {
 0903                mediaSource.DefaultAudioStreamIndex = audioStream.Index;
 904            }
 905
 0906            var videoStream = mediaStreams.FirstOrDefault(i => i.Type == MediaStreamType.Video);
 0907            if (videoStream is not null)
 908            {
 0909                if (!videoStream.BitRate.HasValue)
 910                {
 0911                    var width = videoStream.Width ?? 1920;
 912
 0913                    if (width >= 3000)
 914                    {
 0915                        videoStream.BitRate = 30000000;
 916                    }
 0917                    else if (width >= 1900)
 918                    {
 0919                        videoStream.BitRate = 20000000;
 920                    }
 0921                    else if (width >= 1200)
 922                    {
 0923                        videoStream.BitRate = 8000000;
 924                    }
 0925                    else if (width >= 700)
 926                    {
 0927                        videoStream.BitRate = 2000000;
 928                    }
 929                }
 930
 931                // This is coming up false and preventing stream copy
 0932                videoStream.IsAVC = null;
 933            }
 934
 0935            if (isLiveStream)
 936            {
 0937                mediaSource.AnalyzeDurationMs = 3000;
 938            }
 939
 940            // Try to estimate this
 0941            mediaSource.InferTotalBitrate(true);
 0942        }
 943
 944        public Task<Tuple<MediaSourceInfo, IDirectStreamProvider>> GetLiveStreamWithDirectStreamProvider(string id, Canc
 945        {
 0946            ArgumentException.ThrowIfNullOrEmpty(id);
 947
 0948            var info = GetLiveStreamInfo(id);
 0949            if (info is null)
 950            {
 0951                return Task.FromResult<Tuple<MediaSourceInfo, IDirectStreamProvider>>(new Tuple<MediaSourceInfo, IDirect
 952            }
 953
 0954            return Task.FromResult<Tuple<MediaSourceInfo, IDirectStreamProvider>>(new Tuple<MediaSourceInfo, IDirectStre
 955        }
 956
 957        public ILiveStream GetLiveStreamInfo(string id)
 958        {
 0959            ArgumentException.ThrowIfNullOrEmpty(id);
 960
 0961            if (_openStreams.TryGetValue(id, out ILiveStream info))
 962            {
 0963                return info;
 964            }
 965
 0966            return null;
 967        }
 968
 969        /// <inheritdoc />
 970        public ILiveStream GetLiveStreamInfoByUniqueId(string uniqueId)
 971        {
 0972            return _openStreams.Values.FirstOrDefault(stream => string.Equals(uniqueId, stream?.UniqueId, StringComparis
 973        }
 974
 975        public async Task<MediaSourceInfo> GetLiveStream(string id, CancellationToken cancellationToken)
 976        {
 0977            var result = await GetLiveStreamWithDirectStreamProvider(id, cancellationToken).ConfigureAwait(false);
 0978            return result.Item1;
 0979        }
 980
 981        public async Task<IReadOnlyList<MediaSourceInfo>> GetRecordingStreamMediaSources(ActiveRecordingInfo info, Cance
 982        {
 0983            var stream = new MediaSourceInfo
 0984            {
 0985                EncoderPath = _appHost.GetApiUrlForLocalAccess() + "/LiveTv/LiveRecordings/" + info.Id + "/stream",
 0986                EncoderProtocol = MediaProtocol.Http,
 0987                Path = info.Path,
 0988                Protocol = MediaProtocol.File,
 0989                Id = info.Id,
 0990                SupportsDirectPlay = false,
 0991                SupportsDirectStream = true,
 0992                SupportsTranscoding = true,
 0993                IsInfiniteStream = true,
 0994                RequiresOpening = false,
 0995                RequiresClosing = false,
 0996                BufferMs = 0,
 0997                IgnoreDts = true,
 0998                IgnoreIndex = true
 0999            };
 1000
 01001            await new LiveStreamHelper(_mediaEncoder, _logger, _appPaths)
 01002                .AddMediaInfoWithProbe(stream, false, false, cancellationToken).ConfigureAwait(false);
 1003
 01004            return [stream];
 01005        }
 1006
 1007        public async Task CloseLiveStream(string id)
 1008        {
 01009            ArgumentException.ThrowIfNullOrEmpty(id);
 1010
 01011            using (await _liveStreamLocker.LockAsync().ConfigureAwait(false))
 1012            {
 01013                if (_openStreams.TryGetValue(id, out ILiveStream liveStream))
 1014                {
 01015                    liveStream.ConsumerCount--;
 1016
 01017                    _logger.LogInformation("Live stream {0} consumer count is now {1}", liveStream.OriginalStreamId, liv
 1018
 01019                    if (liveStream.ConsumerCount <= 0)
 1020                    {
 01021                        _openStreams.TryRemove(id, out _);
 1022
 01023                        _logger.LogInformation("Closing live stream {0}", id);
 1024
 01025                        await liveStream.Close().ConfigureAwait(false);
 01026                        _logger.LogInformation("Live stream {0} closed successfully", id);
 1027                    }
 1028                }
 01029            }
 01030        }
 1031
 1032        private (IMediaSourceProvider MediaSourceProvider, string KeyId) GetProvider(string key)
 1033        {
 01034            ArgumentException.ThrowIfNullOrEmpty(key);
 1035
 01036            var keys = key.Split(LiveStreamIdDelimiter, 2);
 1037
 01038            var provider = _providers.FirstOrDefault(i => string.Equals(i.GetType().FullName.GetMD5().ToString("N", Cult
 1039
 01040            var splitIndex = key.IndexOf(LiveStreamIdDelimiter, StringComparison.Ordinal);
 01041            var keyId = key.Substring(splitIndex + 1);
 1042
 01043            return (provider, keyId);
 1044        }
 1045
 1046        /// <inheritdoc />
 1047        public void Dispose()
 1048        {
 271049            Dispose(true);
 271050            GC.SuppressFinalize(this);
 271051        }
 1052
 1053        /// <summary>
 1054        /// Releases unmanaged and - optionally - managed resources.
 1055        /// </summary>
 1056        /// <param name="dispose"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release o
 1057        protected virtual void Dispose(bool dispose)
 1058        {
 271059            if (dispose)
 1060            {
 541061                foreach (var key in _openStreams.Keys.ToList())
 1062                {
 01063                    CloseLiveStream(key).GetAwaiter().GetResult();
 1064                }
 1065
 271066                _liveStreamLocker.Dispose();
 1067            }
 271068        }
 1069    }
 1070}

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()
ResolveSymlinkPaths(System.Collections.Generic.IReadOnlyList`1<MediaBrowser.Model.Dto.MediaSourceInfo>,System.Boolean)
SetKeyProperties(MediaBrowser.Controller.Library.IMediaSourceProvider,MediaBrowser.Model.Dto.MediaSourceInfo)
GetMediaSource()
GetStaticMediaSources(MediaBrowser.Controller.Entities.BaseItem,System.Boolean,Jellyfin.Database.Implementations.Entities.User)
SetAlternateVersionResumeStates(MediaBrowser.Controller.Entities.BaseItem,System.Collections.Generic.IReadOnlyList`1<MediaBrowser.Model.Dto.MediaSourceInfo>,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>,System.Guid)
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)