< Summary - Jellyfin

Information
Class: Jellyfin.LiveTv.LiveTvMediaSourceProvider
Assembly: Jellyfin.LiveTv
File(s): /srv/git/jellyfin/src/Jellyfin.LiveTv/LiveTvMediaSourceProvider.cs
Line coverage
9%
Covered lines: 7
Uncovered lines: 65
Coverable lines: 72
Total lines: 332
Line coverage: 9.7%
Branch coverage
0%
Covered branches: 0
Total branches: 58
Branch coverage: 0%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Coverage history

Coverage history 0 25 50 75 100

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%
GetMediaSources(...)0%4260%
Normalize(...)0%2756520%
GetService(...)100%210%

File(s)

/srv/git/jellyfin/src/Jellyfin.LiveTv/LiveTvMediaSourceProvider.cs

#LineLine coverage
 1#nullable disable
 2
 3#pragma warning disable CS1591
 4
 5using System;
 6using System.Collections.Generic;
 7using System.Globalization;
 8using System.Linq;
 9using System.Threading;
 10using System.Threading.Tasks;
 11using Jellyfin.LiveTv.IO;
 12using MediaBrowser.Common.Extensions;
 13using MediaBrowser.Controller;
 14using MediaBrowser.Controller.Entities;
 15using MediaBrowser.Controller.Library;
 16using MediaBrowser.Controller.LiveTv;
 17using MediaBrowser.Model.Dto;
 18using MediaBrowser.Model.Entities;
 19using MediaBrowser.Model.LiveTv;
 20using MediaBrowser.Model.MediaInfo;
 21using Microsoft.Extensions.Logging;
 22
 23namespace Jellyfin.LiveTv
 24{
 25    public class LiveTvMediaSourceProvider : IMediaSourceProvider
 26    {
 27        // Do not use a pipe here because Roku http requests to the server will fail, without any explicit error message
 28        private const char StreamIdDelimiter = '_';
 29
 30        private readonly ILogger<LiveTvMediaSourceProvider> _logger;
 31        private readonly IServerApplicationHost _appHost;
 32        private readonly IRecordingsManager _recordingsManager;
 33        private readonly IMediaSourceManager _mediaSourceManager;
 34        private readonly ILibraryManager _libraryManager;
 35        private readonly ILiveTvService[] _services;
 36
 37        public LiveTvMediaSourceProvider(
 38            ILogger<LiveTvMediaSourceProvider> logger,
 39            IServerApplicationHost appHost,
 40            IRecordingsManager recordingsManager,
 41            IMediaSourceManager mediaSourceManager,
 42            ILibraryManager libraryManager,
 43            IEnumerable<ILiveTvService> services)
 44        {
 2245            _logger = logger;
 2246            _appHost = appHost;
 2247            _recordingsManager = recordingsManager;
 2248            _mediaSourceManager = mediaSourceManager;
 2249            _libraryManager = libraryManager;
 2250            _services = services.ToArray();
 2251        }
 52
 53        public Task<IEnumerable<MediaSourceInfo>> GetMediaSources(BaseItem item, CancellationToken cancellationToken)
 54        {
 055            if (item.SourceType == SourceType.LiveTV)
 56            {
 057                var activeRecordingInfo = _recordingsManager.GetActiveRecordingInfo(item.Path);
 58
 059                if (string.IsNullOrEmpty(item.Path) || activeRecordingInfo is not null)
 60                {
 061                    return GetMediaSourcesInternal(item, activeRecordingInfo, cancellationToken);
 62                }
 63            }
 64
 065            return Task.FromResult(Enumerable.Empty<MediaSourceInfo>());
 66        }
 67
 68        private async Task<IEnumerable<MediaSourceInfo>> GetMediaSourcesInternal(BaseItem item, ActiveRecordingInfo acti
 69        {
 70            IEnumerable<MediaSourceInfo> sources;
 71
 72            var forceRequireOpening = false;
 73
 74            try
 75            {
 76                if (activeRecordingInfo is not null)
 77                {
 78                    sources = await _mediaSourceManager.GetRecordingStreamMediaSources(activeRecordingInfo, cancellation
 79                        .ConfigureAwait(false);
 80                }
 81                else
 82                {
 83                    sources = await GetChannelMediaSources(item, cancellationToken)
 84                        .ConfigureAwait(false);
 85                }
 86            }
 87            catch (NotImplementedException)
 88            {
 89                sources = _mediaSourceManager.GetStaticMediaSources(item, false);
 90
 91                forceRequireOpening = true;
 92            }
 93
 94            var list = sources.ToList();
 95
 96            foreach (var source in list)
 97            {
 98                source.Type = MediaSourceType.Default;
 99                source.BufferMs ??= 1500;
 100
 101                if (source.RequiresOpening || forceRequireOpening)
 102                {
 103                    source.RequiresOpening = true;
 104                }
 105
 106                if (source.RequiresOpening)
 107                {
 108                    var openKeys = new List<string>
 109                    {
 110                        item.GetType().Name,
 111                        item.Id.ToString("N", CultureInfo.InvariantCulture),
 112                        source.Id ?? string.Empty
 113                    };
 114
 115                    source.OpenToken = string.Join(StreamIdDelimiter, openKeys);
 116                }
 117
 118                // Dummy this up so that direct play checks can still run
 119                if (string.IsNullOrEmpty(source.Path) && source.Protocol == MediaProtocol.Http)
 120                {
 121                    source.Path = _appHost.GetApiUrlForLocalAccess();
 122                }
 123            }
 124
 125            _logger.LogDebug("MediaSources: {@MediaSources}", list);
 126
 127            return list;
 128        }
 129
 130        /// <inheritdoc />
 131        public async Task<ILiveStream> OpenMediaSource(string openToken, List<ILiveStream> currentLiveStreams, Cancellat
 132        {
 133            var keys = openToken.Split(StreamIdDelimiter, 3);
 134            var mediaSourceId = keys.Length >= 3 ? keys[2] : null;
 135
 136            var info = await GetChannelStream(keys[1], mediaSourceId, currentLiveStreams, cancellationToken).ConfigureAw
 137            var liveStream = info.Item2;
 138
 139            return liveStream;
 140        }
 141
 142        private static void Normalize(MediaSourceInfo mediaSource, ILiveTvService service, bool isVideo)
 143        {
 144            // Not all of the plugins are setting this
 0145            mediaSource.IsInfiniteStream = true;
 146
 0147            if (mediaSource.MediaStreams.Count == 0)
 148            {
 0149                if (isVideo)
 150                {
 0151                    mediaSource.MediaStreams = new[]
 0152                    {
 0153                        new MediaStream
 0154                        {
 0155                            Type = MediaStreamType.Video,
 0156                            // Set the index to -1 because we don't know the exact index of the video stream within the 
 0157                            Index = -1,
 0158                            // Set to true if unknown to enable deinterlacing
 0159                            IsInterlaced = true
 0160                        },
 0161                        new MediaStream
 0162                        {
 0163                            Type = MediaStreamType.Audio,
 0164                            // Set the index to -1 because we don't know the exact index of the audio stream within the 
 0165                            Index = -1
 0166                        }
 0167                    };
 168                }
 169                else
 170                {
 0171                    mediaSource.MediaStreams = new[]
 0172                    {
 0173                        new MediaStream
 0174                        {
 0175                            Type = MediaStreamType.Audio,
 0176                            // Set the index to -1 because we don't know the exact index of the audio stream within the 
 0177                            Index = -1
 0178                        }
 0179                    };
 180                }
 181            }
 182
 183            // Clean some bad data coming from providers
 0184            foreach (var stream in mediaSource.MediaStreams)
 185            {
 0186                if (stream.BitRate is <= 0)
 187                {
 0188                    stream.BitRate = null;
 189                }
 190
 0191                if (stream.Channels is <= 0)
 192                {
 0193                    stream.Channels = null;
 194                }
 195
 0196                if (stream.AverageFrameRate is <= 0)
 197                {
 0198                    stream.AverageFrameRate = null;
 199                }
 200
 0201                if (stream.RealFrameRate is <= 0)
 202                {
 0203                    stream.RealFrameRate = null;
 204                }
 205
 0206                if (stream.Width is <= 0)
 207                {
 0208                    stream.Width = null;
 209                }
 210
 0211                if (stream.Height is <= 0)
 212                {
 0213                    stream.Height = null;
 214                }
 215
 0216                if (stream.SampleRate is <= 0)
 217                {
 0218                    stream.SampleRate = null;
 219                }
 220
 0221                if (stream.Level is <= 0)
 222                {
 0223                    stream.Level = null;
 224                }
 225            }
 226
 0227            var indexCount = mediaSource.MediaStreams.Select(i => i.Index).Distinct().Count();
 228
 229            // If there are duplicate stream indexes, set them all to unknown
 0230            if (indexCount != mediaSource.MediaStreams.Count)
 231            {
 0232                foreach (var stream in mediaSource.MediaStreams)
 233                {
 0234                    stream.Index = -1;
 235                }
 236            }
 237
 238            // Set the total bitrate if not already supplied
 0239            mediaSource.InferTotalBitrate();
 240
 0241            if (service is not DefaultLiveTvService)
 242            {
 0243                mediaSource.SupportsTranscoding = true;
 0244                foreach (var stream in mediaSource.MediaStreams)
 245                {
 0246                    if (stream.Type == MediaStreamType.Video && string.IsNullOrWhiteSpace(stream.NalLengthSize))
 247                    {
 0248                        stream.NalLengthSize = "0";
 249                    }
 250
 0251                    if (stream.Type == MediaStreamType.Video)
 252                    {
 0253                        stream.IsInterlaced = true;
 254                    }
 255                }
 256            }
 0257        }
 258
 259        private async Task<Tuple<MediaSourceInfo, ILiveStream>> GetChannelStream(
 260            string id,
 261            string mediaSourceId,
 262            List<ILiveStream> currentLiveStreams,
 263            CancellationToken cancellationToken)
 264        {
 265            if (string.Equals(id, mediaSourceId, StringComparison.OrdinalIgnoreCase))
 266            {
 267                mediaSourceId = null;
 268            }
 269
 270            var channel = (LiveTvChannel)_libraryManager.GetItemById(id);
 271
 272            bool isVideo = channel.ChannelType == ChannelType.TV;
 273            var service = GetService(channel.ServiceName);
 274            _logger.LogInformation("Opening channel stream from {0}, external channel Id: {1}", service.Name, channel.Ex
 275
 276            MediaSourceInfo info;
 277#pragma warning disable CA1859 // TODO: Analyzer bug?
 278            ILiveStream liveStream;
 279#pragma warning restore CA1859
 280            if (service is ISupportsDirectStreamProvider supportsManagedStream)
 281            {
 282                liveStream = await supportsManagedStream.GetChannelStreamWithDirectStreamProvider(channel.ExternalId, me
 283                info = liveStream.MediaSource;
 284            }
 285            else
 286            {
 287                info = await service.GetChannelStream(channel.ExternalId, mediaSourceId, cancellationToken).ConfigureAwa
 288                var openedId = info.Id;
 289                Func<Task> closeFn = () => service.CloseLiveStream(openedId, CancellationToken.None);
 290
 291                liveStream = new ExclusiveLiveStream(info, closeFn);
 292
 293                var startTime = DateTime.UtcNow;
 294                await liveStream.Open(cancellationToken).ConfigureAwait(false);
 295                var endTime = DateTime.UtcNow;
 296                _logger.LogInformation("Live stream opened after {0}ms", (endTime - startTime).TotalMilliseconds);
 297            }
 298
 299            info.RequiresClosing = true;
 300
 301            var idPrefix = service.GetType().FullName!.GetMD5().ToString("N", CultureInfo.InvariantCulture) + "_";
 302
 303            info.LiveStreamId = idPrefix + info.Id;
 304
 305            Normalize(info, service, isVideo);
 306
 307            return new Tuple<MediaSourceInfo, ILiveStream>(info, liveStream);
 308        }
 309
 310        private async Task<List<MediaSourceInfo>> GetChannelMediaSources(BaseItem item, CancellationToken cancellationTo
 311        {
 312            var baseItem = (LiveTvChannel)item;
 313            var service = GetService(baseItem.ServiceName);
 314
 315            var sources = await service.GetChannelStreamMediaSources(baseItem.ExternalId, cancellationToken).ConfigureAw
 316            if (sources.Count == 0)
 317            {
 318                throw new NotImplementedException();
 319            }
 320
 321            foreach (var source in sources)
 322            {
 323                Normalize(source, service, baseItem.ChannelType == ChannelType.TV);
 324            }
 325
 326            return sources;
 327        }
 328
 329        private ILiveTvService GetService(string name)
 0330            => _services.First(service => string.Equals(service.Name, name, StringComparison.OrdinalIgnoreCase));
 331    }
 332}