< Summary - Jellyfin

Information
Class: Jellyfin.LiveTv.TunerHosts.BaseTunerHost
Assembly: Jellyfin.LiveTv
File(s): /srv/git/jellyfin/src/Jellyfin.LiveTv/TunerHosts/BaseTunerHost.cs
Line coverage
33%
Covered lines: 3
Uncovered lines: 6
Coverable lines: 9
Total lines: 232
Line coverage: 33.3%
Branch coverage
N/A
Covered branches: 0
Total branches: 0
Branch coverage: N/A
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%
get_IsSupported()100%11100%
get_ChannelIdPrefix()100%210%
GetTunerHosts()100%210%
IsValidChannelId(...)100%210%

File(s)

/srv/git/jellyfin/src/Jellyfin.LiveTv/TunerHosts/BaseTunerHost.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.IO;
 9using System.Linq;
 10using System.Text.Json;
 11using System.Threading;
 12using System.Threading.Tasks;
 13using Jellyfin.LiveTv.Configuration;
 14using MediaBrowser.Controller.Configuration;
 15using MediaBrowser.Controller.Library;
 16using MediaBrowser.Controller.LiveTv;
 17using MediaBrowser.Model.Dto;
 18using MediaBrowser.Model.IO;
 19using MediaBrowser.Model.LiveTv;
 20using Microsoft.Extensions.Logging;
 21
 22namespace Jellyfin.LiveTv.TunerHosts
 23{
 24    public abstract class BaseTunerHost
 25    {
 26        private readonly ConcurrentDictionary<string, List<ChannelInfo>> _cache;
 27
 28        protected BaseTunerHost(IServerConfigurationManager config, ILogger<BaseTunerHost> logger, IFileSystem fileSyste
 29        {
 30            Config = config;
 31            Logger = logger;
 32            FileSystem = fileSystem;
 5133            _cache = new ConcurrentDictionary<string, List<ChannelInfo>>();
 5134        }
 35
 36        protected IServerConfigurationManager Config { get; }
 37
 38        protected ILogger<BaseTunerHost> Logger { get; }
 39
 40        protected IFileSystem FileSystem { get; }
 41
 4442        public virtual bool IsSupported => true;
 43
 44        public abstract string Type { get; }
 45
 046        protected virtual string ChannelIdPrefix => Type + "_";
 47
 48        protected abstract Task<List<ChannelInfo>> GetChannelsInternal(TunerHostInfo tuner, CancellationToken cancellati
 49
 50        public async Task<List<ChannelInfo>> GetChannels(TunerHostInfo tuner, bool enableCache, CancellationToken cancel
 51        {
 52            var key = tuner.Id;
 53
 54            if (enableCache && !string.IsNullOrEmpty(key) && _cache.TryGetValue(key, out List<ChannelInfo> cache))
 55            {
 56                return cache;
 57            }
 58
 59            var list = await GetChannelsInternal(tuner, cancellationToken).ConfigureAwait(false);
 60            // logger.LogInformation("Channels from {0}: {1}", tuner.Url, JsonSerializer.SerializeToString(list));
 61
 62            if (!string.IsNullOrEmpty(key) && list.Count > 0)
 63            {
 64                _cache[key] = list;
 65            }
 66
 67            return list;
 68        }
 69
 70        protected virtual IList<TunerHostInfo> GetTunerHosts()
 71        {
 072            return Config.GetLiveTvConfiguration().TunerHosts
 073                .Where(i => string.Equals(i.Type, Type, StringComparison.OrdinalIgnoreCase))
 074                .ToList();
 75        }
 76
 77        public async Task<List<ChannelInfo>> GetChannels(bool enableCache, CancellationToken cancellationToken)
 78        {
 79            var list = new List<ChannelInfo>();
 80
 81            var hosts = GetTunerHosts();
 82
 83            foreach (var host in hosts)
 84            {
 85                var channelCacheFile = Path.Combine(Config.ApplicationPaths.CachePath, host.Id + "_channels");
 86
 87                try
 88                {
 89                    var channels = await GetChannels(host, enableCache, cancellationToken).ConfigureAwait(false);
 90                    var newChannels = channels.Where(i => !list.Any(l => string.Equals(i.Id, l.Id, StringComparison.Ordi
 91
 92                    list.AddRange(newChannels);
 93
 94                    if (!enableCache)
 95                    {
 96                        try
 97                        {
 98                            Directory.CreateDirectory(Path.GetDirectoryName(channelCacheFile));
 99                            var writeStream = AsyncFile.OpenWrite(channelCacheFile);
 100                            await using (writeStream.ConfigureAwait(false))
 101                            {
 102                                await JsonSerializer.SerializeAsync(writeStream, channels, cancellationToken: cancellati
 103                            }
 104                        }
 105                        catch (IOException)
 106                        {
 107                        }
 108                    }
 109                }
 110                catch (Exception ex)
 111                {
 112                    Logger.LogError(ex, "Error getting channel list");
 113
 114                    if (enableCache)
 115                    {
 116                        try
 117                        {
 118                            var readStream = AsyncFile.OpenRead(channelCacheFile);
 119                            await using (readStream.ConfigureAwait(false))
 120                            {
 121                                var channels = await JsonSerializer
 122                                    .DeserializeAsync<List<ChannelInfo>>(readStream, cancellationToken: cancellationToke
 123                                    .ConfigureAwait(false);
 124                                list.AddRange(channels);
 125                            }
 126                        }
 127                        catch (IOException)
 128                        {
 129                        }
 130                    }
 131                }
 132            }
 133
 134            return list;
 135        }
 136
 137        protected abstract Task<List<MediaSourceInfo>> GetChannelStreamMediaSources(TunerHostInfo tuner, ChannelInfo cha
 138
 139        public async Task<List<MediaSourceInfo>> GetChannelStreamMediaSources(string channelId, CancellationToken cancel
 140        {
 141            ArgumentException.ThrowIfNullOrEmpty(channelId);
 142
 143            if (IsValidChannelId(channelId))
 144            {
 145                var hosts = GetTunerHosts();
 146
 147                foreach (var host in hosts)
 148                {
 149                    try
 150                    {
 151                        var channels = await GetChannels(host, true, cancellationToken).ConfigureAwait(false);
 152                        var channelInfo = channels.FirstOrDefault(i => string.Equals(i.Id, channelId, StringComparison.O
 153
 154                        if (channelInfo is not null)
 155                        {
 156                            return await GetChannelStreamMediaSources(host, channelInfo, cancellationToken).ConfigureAwa
 157                        }
 158                    }
 159                    catch (Exception ex)
 160                    {
 161                        Logger.LogError(ex, "Error getting channels");
 162                    }
 163                }
 164            }
 165
 166            return new List<MediaSourceInfo>();
 167        }
 168
 169        protected abstract Task<ILiveStream> GetChannelStream(TunerHostInfo tunerHost, ChannelInfo channel, string strea
 170
 171        public async Task<ILiveStream> GetChannelStream(string channelId, string streamId, IList<ILiveStream> currentLiv
 172        {
 173            ArgumentException.ThrowIfNullOrEmpty(channelId);
 174
 175            if (!IsValidChannelId(channelId))
 176            {
 177                throw new FileNotFoundException();
 178            }
 179
 180            var hosts = GetTunerHosts();
 181
 182            var hostsWithChannel = new List<Tuple<TunerHostInfo, ChannelInfo>>();
 183
 184            foreach (var host in hosts)
 185            {
 186                try
 187                {
 188                    var channels = await GetChannels(host, true, cancellationToken).ConfigureAwait(false);
 189                    var channelInfo = channels.FirstOrDefault(i => string.Equals(i.Id, channelId, StringComparison.Ordin
 190
 191                    if (channelInfo is not null)
 192                    {
 193                        hostsWithChannel.Add(new Tuple<TunerHostInfo, ChannelInfo>(host, channelInfo));
 194                    }
 195                }
 196                catch (Exception ex)
 197                {
 198                    Logger.LogError(ex, "Error getting channels");
 199                }
 200            }
 201
 202            foreach (var hostTuple in hostsWithChannel)
 203            {
 204                var host = hostTuple.Item1;
 205                var channelInfo = hostTuple.Item2;
 206
 207                try
 208                {
 209                    var liveStream = await GetChannelStream(host, channelInfo, streamId, currentLiveStreams, cancellatio
 210                    var startTime = DateTime.UtcNow;
 211                    await liveStream.Open(cancellationToken).ConfigureAwait(false);
 212                    var endTime = DateTime.UtcNow;
 213                    Logger.LogInformation("Live stream opened after {0}ms", (endTime - startTime).TotalMilliseconds);
 214                    return liveStream;
 215                }
 216                catch (Exception ex)
 217                {
 218                    Logger.LogError(ex, "Error opening tuner");
 219                }
 220            }
 221
 222            throw new LiveTvConflictException("Unable to find host to play channel");
 223        }
 224
 225        protected virtual bool IsValidChannelId(string channelId)
 226        {
 0227            ArgumentException.ThrowIfNullOrEmpty(channelId);
 228
 0229            return channelId.StartsWith(ChannelIdPrefix, StringComparison.OrdinalIgnoreCase);
 230        }
 231    }
 232}