< Summary - Jellyfin

Information
Class: Jellyfin.LiveTv.LiveTvManager
Assembly: Jellyfin.LiveTv
File(s): /srv/git/jellyfin/src/Jellyfin.LiveTv/LiveTvManager.cs
Line coverage
2%
Covered lines: 19
Uncovered lines: 683
Coverable lines: 702
Total lines: 1297
Line coverage: 2.7%
Branch coverage
0%
Covered branches: 2
Total branches: 228
Branch coverage: 0.8%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Coverage history

Coverage history 0 25 50 75 100 2/15/2026 - 12:13:43 AM Line coverage: 7.6% (19/250) Branch coverage: 2% (2/100) Total lines: 12924/19/2026 - 12:14:27 AM Line coverage: 2.7% (19/700) Branch coverage: 0.8% (2/226) Total lines: 12925/18/2026 - 12:15:49 AM Line coverage: 2.7% (19/702) Branch coverage: 0.8% (2/228) Total lines: 1297 2/15/2026 - 12:13:43 AM Line coverage: 7.6% (19/250) Branch coverage: 2% (2/100) Total lines: 12924/19/2026 - 12:14:27 AM Line coverage: 2.7% (19/700) Branch coverage: 0.8% (2/226) Total lines: 12925/18/2026 - 12:15:49 AM Line coverage: 2.7% (19/702) Branch coverage: 0.8% (2/228) Total lines: 1297

Coverage delta

Coverage delta 5 -5

Metrics

File(s)

/srv/git/jellyfin/src/Jellyfin.LiveTv/LiveTvManager.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.Data;
 12using Jellyfin.Data.Enums;
 13using Jellyfin.Data.Events;
 14using Jellyfin.Database.Implementations.Entities;
 15using Jellyfin.Database.Implementations.Enums;
 16using Jellyfin.LiveTv.Configuration;
 17using MediaBrowser.Common.Extensions;
 18using MediaBrowser.Controller.Channels;
 19using MediaBrowser.Controller.Configuration;
 20using MediaBrowser.Controller.Dto;
 21using MediaBrowser.Controller.Entities;
 22using MediaBrowser.Controller.Library;
 23using MediaBrowser.Controller.LiveTv;
 24using MediaBrowser.Controller.Sorting;
 25using MediaBrowser.Model.Dto;
 26using MediaBrowser.Model.Entities;
 27using MediaBrowser.Model.Globalization;
 28using MediaBrowser.Model.LiveTv;
 29using MediaBrowser.Model.Querying;
 30using Microsoft.Extensions.Logging;
 31
 32namespace Jellyfin.LiveTv
 33{
 34    /// <summary>
 35    /// Class LiveTvManager.
 36    /// </summary>
 37    public class LiveTvManager : ILiveTvManager
 38    {
 39        private readonly IServerConfigurationManager _config;
 40        private readonly ILogger<LiveTvManager> _logger;
 41        private readonly IUserManager _userManager;
 42        private readonly IDtoService _dtoService;
 43        private readonly IUserDataManager _userDataManager;
 44        private readonly ILibraryManager _libraryManager;
 45        private readonly ILocalizationManager _localization;
 46        private readonly IChannelManager _channelManager;
 47        private readonly IRecordingsManager _recordingsManager;
 48        private readonly LiveTvDtoService _tvDtoService;
 49        private readonly ILiveTvService[] _services;
 50
 51        public LiveTvManager(
 52            IServerConfigurationManager config,
 53            ILogger<LiveTvManager> logger,
 54            IUserDataManager userDataManager,
 55            IDtoService dtoService,
 56            IUserManager userManager,
 57            ILibraryManager libraryManager,
 58            ILocalizationManager localization,
 59            IChannelManager channelManager,
 60            IRecordingsManager recordingsManager,
 61            LiveTvDtoService liveTvDtoService,
 62            IEnumerable<ILiveTvService> services)
 63        {
 2164            _config = config;
 2165            _logger = logger;
 2166            _userManager = userManager;
 2167            _libraryManager = libraryManager;
 2168            _localization = localization;
 2169            _dtoService = dtoService;
 2170            _userDataManager = userDataManager;
 2171            _channelManager = channelManager;
 2172            _tvDtoService = liveTvDtoService;
 2173            _recordingsManager = recordingsManager;
 2174            _services = services.ToArray();
 75
 2176            var defaultService = _services.OfType<DefaultLiveTvService>().First();
 2177            defaultService.TimerCreated += OnEmbyTvTimerCreated;
 2178            defaultService.TimerCancelled += OnEmbyTvTimerCancelled;
 2179        }
 80
 81        public event EventHandler<GenericEventArgs<TimerEventInfo>> SeriesTimerCancelled;
 82
 83        public event EventHandler<GenericEventArgs<TimerEventInfo>> TimerCancelled;
 84
 85        public event EventHandler<GenericEventArgs<TimerEventInfo>> TimerCreated;
 86
 87        public event EventHandler<GenericEventArgs<TimerEventInfo>> SeriesTimerCreated;
 88
 89        /// <summary>
 90        /// Gets the services.
 91        /// </summary>
 92        /// <value>The services.</value>
 493        public IReadOnlyList<ILiveTvService> Services => _services;
 94
 95        private void OnEmbyTvTimerCancelled(object sender, GenericEventArgs<string> e)
 96        {
 097            var timerId = e.Argument;
 98
 099            TimerCancelled?.Invoke(this, new GenericEventArgs<TimerEventInfo>(new TimerEventInfo(timerId)));
 0100        }
 101
 102        private void OnEmbyTvTimerCreated(object sender, GenericEventArgs<TimerInfo> e)
 103        {
 0104            var timer = e.Argument;
 105
 0106            TimerCreated?.Invoke(this, new GenericEventArgs<TimerEventInfo>(
 0107                new TimerEventInfo(timer.Id)
 0108                {
 0109                    ProgramId = _tvDtoService.GetInternalProgramId(timer.ProgramId)
 0110                }));
 0111        }
 112
 113        public QueryResult<BaseItem> GetInternalChannels(LiveTvChannelQuery query, DtoOptions dtoOptions, CancellationTo
 114        {
 0115            var user = query.UserId.Equals(default)
 0116                ? null
 0117                : _userManager.GetUserById(query.UserId);
 118
 0119            var topFolder = GetInternalLiveTvFolder(cancellationToken);
 120
 0121            var internalQuery = new InternalItemsQuery(user)
 0122            {
 0123                IsMovie = query.IsMovie,
 0124                IsNews = query.IsNews,
 0125                IsKids = query.IsKids,
 0126                IsSports = query.IsSports,
 0127                IsSeries = query.IsSeries,
 0128                IncludeItemTypes = [BaseItemKind.LiveTvChannel],
 0129                TopParentIds = [topFolder.Id],
 0130                IsFavorite = query.IsFavorite,
 0131                IsLiked = query.IsLiked,
 0132                StartIndex = query.StartIndex,
 0133                Limit = query.Limit,
 0134                DtoOptions = dtoOptions
 0135            };
 136
 0137            var orderBy = internalQuery.OrderBy.ToList();
 138
 0139            orderBy.AddRange(query.SortBy.Select(i => (i, query.SortOrder ?? SortOrder.Ascending)));
 140
 0141            if (query.EnableFavoriteSorting)
 142            {
 0143                orderBy.Insert(0, (ItemSortBy.IsFavoriteOrLiked, SortOrder.Descending));
 144            }
 145
 0146            if (internalQuery.OrderBy.All(i => i.OrderBy != ItemSortBy.SortName))
 147            {
 0148                orderBy.Add((ItemSortBy.SortName, SortOrder.Ascending));
 149            }
 150
 0151            internalQuery.OrderBy = orderBy.ToArray();
 152
 0153            return _libraryManager.GetItemsResult(internalQuery);
 154        }
 155
 156        private ILiveTvService GetService(LiveTvChannel item)
 157        {
 0158            var name = item.ServiceName;
 0159            return GetService(name);
 160        }
 161
 162        private ILiveTvService GetService(LiveTvProgram item)
 163        {
 0164            var channel = _libraryManager.GetItemById(item.ChannelId) as LiveTvChannel;
 165
 0166            return GetService(channel);
 167        }
 168
 169        private ILiveTvService GetService(string name)
 0170            => Array.Find(_services, x => string.Equals(x.Name, name, StringComparison.OrdinalIgnoreCase))
 0171                ?? throw new KeyNotFoundException(
 0172                    string.Format(
 0173                        CultureInfo.InvariantCulture,
 0174                        "No service with the name '{0}' can be found.",
 0175                        name));
 176
 177        public async Task<BaseItemDto> GetProgram(string id, CancellationToken cancellationToken, User user = null)
 178        {
 0179            var program = _libraryManager.GetItemById(id);
 180
 0181            if (program is null)
 182            {
 0183                return null;
 184            }
 185
 0186            var dto = _dtoService.GetBaseItemDto(program, new DtoOptions(), user);
 187
 0188            var list = new List<(BaseItemDto ItemDto, string ExternalId, string ExternalSeriesId)>
 0189            {
 0190                (dto, program.ExternalId, program.ExternalSeriesId)
 0191            };
 192
 0193            await AddRecordingInfo(list, cancellationToken).ConfigureAwait(false);
 194
 0195            return dto;
 0196        }
 197
 198        public async Task<QueryResult<BaseItemDto>> GetPrograms(InternalItemsQuery query, DtoOptions options, Cancellati
 199        {
 0200            var user = query.User;
 201
 0202            var topFolder = GetInternalLiveTvFolder(cancellationToken);
 203
 0204            if (query.OrderBy.Count == 0)
 205            {
 206                // Unless something else was specified, order by start date to take advantage of a specialized index
 0207                query.OrderBy =
 0208                [
 0209                    (ItemSortBy.StartDate, SortOrder.Ascending)
 0210                ];
 211            }
 212
 0213            RemoveFields(options);
 214
 0215            var internalQuery = new InternalItemsQuery(user)
 0216            {
 0217                IncludeItemTypes = [BaseItemKind.LiveTvProgram],
 0218                MinEndDate = query.MinEndDate,
 0219                MinStartDate = query.MinStartDate,
 0220                MaxEndDate = query.MaxEndDate,
 0221                MaxStartDate = query.MaxStartDate,
 0222                ChannelIds = query.ChannelIds,
 0223                IsMovie = query.IsMovie,
 0224                IsSeries = query.IsSeries,
 0225                IsSports = query.IsSports,
 0226                IsKids = query.IsKids,
 0227                IsNews = query.IsNews,
 0228                Genres = query.Genres,
 0229                GenreIds = query.GenreIds,
 0230                StartIndex = query.StartIndex,
 0231                Limit = query.Limit,
 0232                OrderBy = query.OrderBy,
 0233                EnableTotalRecordCount = query.EnableTotalRecordCount,
 0234                TopParentIds = [topFolder.Id],
 0235                Name = query.Name,
 0236                DtoOptions = options,
 0237                HasAired = query.HasAired,
 0238                IsAiring = query.IsAiring
 0239            };
 240
 0241            if (!string.IsNullOrWhiteSpace(query.SeriesTimerId))
 242            {
 0243                var seriesTimers = await GetSeriesTimersInternal(new SeriesTimerQuery(), cancellationToken).ConfigureAwa
 0244                var seriesTimer = seriesTimers.Items.FirstOrDefault(i => string.Equals(_tvDtoService.GetInternalSeriesTi
 0245                if (seriesTimer is not null)
 246                {
 0247                    internalQuery.ExternalSeriesId = seriesTimer.SeriesId;
 248
 0249                    if (string.IsNullOrWhiteSpace(seriesTimer.SeriesId))
 250                    {
 251                        // Better to return nothing than every program in the database
 0252                        return new QueryResult<BaseItemDto>();
 253                    }
 254                }
 255                else
 256                {
 257                    // Better to return nothing than every program in the database
 0258                    return new QueryResult<BaseItemDto>();
 259                }
 260            }
 261
 0262            var queryResult = _libraryManager.QueryItems(internalQuery);
 263
 0264            var returnArray = _dtoService.GetBaseItemDtos(queryResult.Items, options, user);
 265
 0266            return new QueryResult<BaseItemDto>(
 0267                query.StartIndex,
 0268                queryResult.TotalRecordCount,
 0269                returnArray);
 0270        }
 271
 272        public QueryResult<BaseItem> GetRecommendedProgramsInternal(InternalItemsQuery query, DtoOptions options, Cancel
 273        {
 0274            var user = query.User;
 275
 0276            var topFolder = GetInternalLiveTvFolder(cancellationToken);
 277
 0278            var internalQuery = new InternalItemsQuery(user)
 0279            {
 0280                IncludeItemTypes = [BaseItemKind.LiveTvProgram],
 0281                IsAiring = query.IsAiring,
 0282                HasAired = query.HasAired,
 0283                IsNews = query.IsNews,
 0284                IsMovie = query.IsMovie,
 0285                IsSeries = query.IsSeries,
 0286                IsSports = query.IsSports,
 0287                IsKids = query.IsKids,
 0288                EnableTotalRecordCount = query.EnableTotalRecordCount,
 0289                OrderBy = [(ItemSortBy.StartDate, SortOrder.Ascending)],
 0290                TopParentIds = [topFolder.Id],
 0291                DtoOptions = options,
 0292                GenreIds = query.GenreIds
 0293            };
 294
 0295            if (query.Limit.HasValue && query.Limit.Value > 0)
 296            {
 0297                internalQuery.Limit = Math.Max(query.Limit.Value * 4, 200);
 298            }
 299
 0300            var programList = _libraryManager.QueryItems(internalQuery).Items;
 0301            var totalCount = programList.Count;
 302
 0303            var orderedPrograms = programList.Cast<LiveTvProgram>().OrderBy(i => i.StartDate.Date);
 304
 0305            if (query.IsAiring ?? false)
 306            {
 0307                orderedPrograms = orderedPrograms
 0308                    .ThenByDescending(i => GetRecommendationScore(i, user, true));
 309            }
 310
 0311            IEnumerable<BaseItem> programs = orderedPrograms;
 312
 0313            if (query.Limit.HasValue && query.Limit.Value > 0)
 314            {
 0315                programs = programs.Take(query.Limit.Value);
 316            }
 317
 0318            return new QueryResult<BaseItem>(
 0319                query.StartIndex,
 0320                totalCount,
 0321                programs.ToArray());
 322        }
 323
 324        public Task<QueryResult<BaseItemDto>> GetRecommendedProgramsAsync(InternalItemsQuery query, DtoOptions options, 
 325        {
 0326            if (!(query.IsAiring ?? false))
 327            {
 0328                return GetPrograms(query, options, cancellationToken);
 329            }
 330
 0331            RemoveFields(options);
 332
 0333            var internalResult = GetRecommendedProgramsInternal(query, options, cancellationToken);
 334
 0335            return Task.FromResult(new QueryResult<BaseItemDto>(
 0336                query.StartIndex,
 0337                internalResult.TotalRecordCount,
 0338                _dtoService.GetBaseItemDtos(internalResult.Items, options, query.User)));
 339        }
 340
 341        private int GetRecommendationScore(LiveTvProgram program, User user, bool factorChannelWatchCount)
 342        {
 0343            var score = 0;
 344
 0345            if (program.IsLive)
 346            {
 0347                score++;
 348            }
 349
 0350            if (program.IsSeries && !program.IsRepeat)
 351            {
 0352                score++;
 353            }
 354
 0355            var channel = _libraryManager.GetItemById(program.ChannelId);
 356
 0357            if (channel is null)
 358            {
 0359                return score;
 360            }
 361
 0362            var channelUserdata = _userDataManager.GetUserData(user, channel);
 363
 0364            if (channelUserdata.Likes.HasValue)
 365            {
 0366                score += channelUserdata.Likes.Value ? 2 : -2;
 367            }
 368
 0369            if (channelUserdata.IsFavorite)
 370            {
 0371                score += 3;
 372            }
 373
 0374            if (factorChannelWatchCount)
 375            {
 0376                score += channelUserdata.PlayCount;
 377            }
 378
 0379            return score;
 380        }
 381
 382        private async Task AddRecordingInfo(IEnumerable<(BaseItemDto ItemDto, string ExternalId, string ExternalSeriesId
 383        {
 0384            IReadOnlyList<TimerInfo> timerList = null;
 0385            IReadOnlyList<SeriesTimerInfo> seriesTimerList = null;
 386
 0387            foreach (var programTuple in programs)
 388            {
 0389                var program = programTuple.ItemDto;
 0390                var externalProgramId = programTuple.ExternalId;
 0391                string externalSeriesId = programTuple.ExternalSeriesId;
 392
 0393                timerList ??= (await GetTimersInternal(new TimerQuery(), cancellationToken).ConfigureAwait(false)).Items
 394
 0395                var timer = timerList.FirstOrDefault(i => string.Equals(i.ProgramId, externalProgramId, StringComparison
 0396                var foundSeriesTimer = false;
 397
 0398                if (timer is not null)
 399                {
 0400                    if (timer.Status != RecordingStatus.Cancelled && timer.Status != RecordingStatus.Error)
 401                    {
 0402                        program.TimerId = _tvDtoService.GetInternalTimerId(timer.Id);
 403
 0404                        program.Status = timer.Status.ToString();
 405                    }
 406
 0407                    if (!string.IsNullOrEmpty(timer.SeriesTimerId))
 408                    {
 0409                        program.SeriesTimerId = _tvDtoService.GetInternalSeriesTimerId(timer.SeriesTimerId)
 0410                            .ToString("N", CultureInfo.InvariantCulture);
 411
 0412                        foundSeriesTimer = true;
 413                    }
 414                }
 415
 0416                if (foundSeriesTimer || string.IsNullOrWhiteSpace(externalSeriesId))
 417                {
 418                    continue;
 419                }
 420
 0421                seriesTimerList ??= (await GetSeriesTimersInternal(new SeriesTimerQuery(), cancellationToken).ConfigureA
 422
 0423                var seriesTimer = seriesTimerList.FirstOrDefault(i => string.Equals(i.SeriesId, externalSeriesId, String
 424
 0425                if (seriesTimer is not null)
 426                {
 0427                    program.SeriesTimerId = _tvDtoService.GetInternalSeriesTimerId(seriesTimer.Id)
 0428                        .ToString("N", CultureInfo.InvariantCulture);
 429                }
 0430            }
 0431        }
 432
 433        private async Task<QueryResult<BaseItem>> GetEmbyRecordingsAsync(RecordingQuery query, DtoOptions dtoOptions, Us
 434        {
 0435            if (user is null)
 436            {
 0437                return new QueryResult<BaseItem>();
 438            }
 439
 0440            var folders = await GetRecordingFoldersAsync(user, true).ConfigureAwait(false);
 0441            var folderIds = Array.ConvertAll(folders, x => x.Id);
 442
 0443            var excludeItemTypes = new List<BaseItemKind>();
 444
 0445            if (folderIds.Length == 0)
 446            {
 0447                return new QueryResult<BaseItem>();
 448            }
 449
 0450            var includeItemTypes = new List<BaseItemKind>();
 0451            var genres = new List<string>();
 452
 0453            if (query.IsMovie.HasValue)
 454            {
 0455                if (query.IsMovie.Value)
 456                {
 0457                    includeItemTypes.Add(BaseItemKind.Movie);
 458                }
 459                else
 460                {
 0461                    excludeItemTypes.Add(BaseItemKind.Movie);
 462                }
 463            }
 464
 0465            if (query.IsSeries.HasValue)
 466            {
 0467                if (query.IsSeries.Value)
 468                {
 0469                    includeItemTypes.Add(BaseItemKind.Episode);
 470                }
 471                else
 472                {
 0473                    excludeItemTypes.Add(BaseItemKind.Episode);
 474                }
 475            }
 476
 0477            if (query.IsSports ?? false)
 478            {
 0479                genres.Add("Sports");
 480            }
 481
 0482            if (query.IsKids ?? false)
 483            {
 0484                genres.Add("Kids");
 0485                genres.Add("Children");
 0486                genres.Add("Family");
 487            }
 488
 0489            var limit = query.Limit;
 490
 0491            if (query.IsInProgress ?? false)
 492            {
 493                // limit = (query.Limit ?? 10) * 2;
 0494                limit = null;
 495
 496                // var allActivePaths = EmbyTV.EmbyTV.Current.GetAllActiveRecordings().Select(i => i.Path).ToArray();
 497                // var items = allActivePaths.Select(i => _libraryManager.FindByPath(i, false)).Where(i => i is not null
 498
 499                // return new QueryResult<BaseItem>
 500                // {
 501                //    Items = items,
 502                //    TotalRecordCount = items.Length
 503                // };
 504
 0505                dtoOptions.Fields = dtoOptions.Fields.Concat([ItemFields.Tags]).Distinct().ToArray();
 506            }
 507
 0508            var result = _libraryManager.GetItemsResult(new InternalItemsQuery(user)
 0509            {
 0510                MediaTypes = [MediaType.Video],
 0511                Recursive = true,
 0512                AncestorIds = folderIds,
 0513                IsFolder = false,
 0514                IsVirtualItem = false,
 0515                Limit = limit,
 0516                StartIndex = query.StartIndex,
 0517                OrderBy = [(ItemSortBy.DateCreated, SortOrder.Descending)],
 0518                EnableTotalRecordCount = query.EnableTotalRecordCount,
 0519                IncludeItemTypes = includeItemTypes.ToArray(),
 0520                ExcludeItemTypes = excludeItemTypes.ToArray(),
 0521                Genres = genres.ToArray(),
 0522                DtoOptions = dtoOptions
 0523            });
 524
 0525            if (query.IsInProgress ?? false)
 526            {
 527                // TODO: Fix The co-variant conversion between Video[] and BaseItem[], this can generate runtime issues.
 0528                result.Items = result
 0529                    .Items
 0530                    .OfType<Video>()
 0531                    .Where(i => !i.IsCompleteMedia)
 0532                    .ToArray();
 533
 0534                result.TotalRecordCount = result.Items.Count;
 535            }
 536
 0537            return result;
 0538        }
 539
 540        public Task AddInfoToProgramDto(IReadOnlyCollection<(BaseItem Item, BaseItemDto ItemDto)> programs, IReadOnlyLis
 541        {
 0542            var programTuples = new List<(BaseItemDto Dto, string ExternalId, string ExternalSeriesId)>();
 0543            var hasChannelImage = fields.Contains(ItemFields.ChannelImage);
 0544            var hasChannelInfo = fields.Contains(ItemFields.ChannelInfo);
 545
 0546            foreach (var (item, dto) in programs)
 547            {
 0548                var program = (LiveTvProgram)item;
 549
 0550                dto.StartDate = program.StartDate;
 0551                dto.EpisodeTitle = program.EpisodeTitle;
 0552                dto.IsRepeat |= program.IsRepeat;
 0553                dto.IsMovie |= program.IsMovie;
 0554                dto.IsSeries |= program.IsSeries;
 0555                dto.IsSports |= program.IsSports;
 0556                dto.IsLive |= program.IsLive;
 0557                dto.IsNews |= program.IsNews;
 0558                dto.IsKids |= program.IsKids;
 0559                dto.IsPremiere |= program.IsPremiere;
 560
 0561                if (hasChannelInfo || hasChannelImage)
 562                {
 0563                    var channel = _libraryManager.GetItemById(program.ChannelId);
 564
 0565                    if (channel is LiveTvChannel liveChannel)
 566                    {
 0567                        dto.ChannelName = liveChannel.Name;
 0568                        dto.MediaType = liveChannel.MediaType;
 0569                        dto.ChannelNumber = liveChannel.Number;
 570
 0571                        if (hasChannelImage && liveChannel.HasImage(ImageType.Primary))
 572                        {
 0573                            dto.ChannelPrimaryImageTag = _tvDtoService.GetImageTag(liveChannel);
 574                        }
 575                    }
 576                }
 577
 0578                programTuples.Add((dto, program.ExternalId, program.ExternalSeriesId));
 579            }
 580
 0581            return AddRecordingInfo(programTuples, CancellationToken.None);
 582        }
 583
 584        public void AddInfoToRecordingDto(BaseItem item, BaseItemDto dto, ActiveRecordingInfo activeRecordingInfo, User 
 585        {
 0586            var info = activeRecordingInfo.Timer;
 587
 0588            var channel = string.IsNullOrWhiteSpace(info.ChannelId)
 0589                ? null
 0590                : _libraryManager.GetItemById(_tvDtoService.GetInternalChannelId(DefaultLiveTvService.ServiceName, info.
 591
 0592            dto.SeriesTimerId = string.IsNullOrEmpty(info.SeriesTimerId)
 0593                ? null
 0594                : _tvDtoService.GetInternalSeriesTimerId(info.SeriesTimerId).ToString("N", CultureInfo.InvariantCulture)
 595
 0596            dto.TimerId = string.IsNullOrEmpty(info.Id)
 0597                ? null
 0598                : _tvDtoService.GetInternalTimerId(info.Id);
 599
 0600            var startDate = info.StartDate;
 0601            var endDate = info.EndDate;
 602
 0603            dto.StartDate = startDate;
 0604            dto.EndDate = endDate;
 0605            dto.Status = info.Status.ToString();
 0606            dto.IsRepeat = info.IsRepeat;
 0607            dto.EpisodeTitle = info.EpisodeTitle;
 0608            dto.IsMovie = info.IsMovie;
 0609            dto.IsSeries = info.IsSeries;
 0610            dto.IsSports = info.IsSports;
 0611            dto.IsLive = info.IsLive;
 0612            dto.IsNews = info.IsNews;
 0613            dto.IsKids = info.IsKids;
 0614            dto.IsPremiere = info.IsPremiere;
 615
 0616            if (info.Status == RecordingStatus.InProgress)
 617            {
 0618                startDate = info.StartDate.AddSeconds(0 - info.PrePaddingSeconds);
 0619                endDate = info.EndDate.AddSeconds(info.PostPaddingSeconds);
 620
 0621                var now = DateTime.UtcNow.Ticks;
 0622                var start = startDate.Ticks;
 0623                var end = endDate.Ticks;
 624
 0625                var pct = now - start;
 626
 0627                pct /= end;
 0628                pct *= 100;
 0629                dto.CompletionPercentage = pct;
 630            }
 631
 0632            if (channel is not null)
 633            {
 0634                dto.ChannelName = channel.Name;
 635
 0636                if (channel.HasImage(ImageType.Primary))
 637                {
 0638                    dto.ChannelPrimaryImageTag = _tvDtoService.GetImageTag(channel);
 639                }
 640            }
 0641        }
 642
 643        public async Task<QueryResult<BaseItemDto>> GetRecordingsAsync(RecordingQuery query, DtoOptions options)
 644        {
 0645            var user = query.UserId.Equals(default)
 0646                ? null
 0647                : _userManager.GetUserById(query.UserId);
 648
 0649            RemoveFields(options);
 650
 0651            var internalResult = await GetEmbyRecordingsAsync(query, options, user).ConfigureAwait(false);
 652
 0653            var returnArray = _dtoService.GetBaseItemDtos(internalResult.Items, options, user);
 654
 0655            return new QueryResult<BaseItemDto>(
 0656                query.StartIndex,
 0657                internalResult.TotalRecordCount,
 0658                returnArray);
 0659        }
 660
 661        private async Task<QueryResult<TimerInfo>> GetTimersInternal(TimerQuery query, CancellationToken cancellationTok
 662        {
 0663            var tasks = _services.Select(async i =>
 0664            {
 0665                try
 0666                {
 0667                    var recs = await i.GetTimersAsync(cancellationToken).ConfigureAwait(false);
 0668                    return recs.Select(r => new Tuple<TimerInfo, ILiveTvService>(r, i));
 0669                }
 0670                catch (Exception ex)
 0671                {
 0672                    _logger.LogError(ex, "Error getting recordings");
 0673                    return new List<Tuple<TimerInfo, ILiveTvService>>();
 0674                }
 0675            });
 0676            var results = await Task.WhenAll(tasks).ConfigureAwait(false);
 0677            var timers = results.SelectMany(i => i.ToList());
 678
 0679            if (query.IsActive.HasValue)
 680            {
 0681                if (query.IsActive.Value)
 682                {
 0683                    timers = timers.Where(i => i.Item1.Status == RecordingStatus.InProgress);
 684                }
 685                else
 686                {
 0687                    timers = timers.Where(i => i.Item1.Status != RecordingStatus.InProgress);
 688                }
 689            }
 690
 0691            if (query.IsScheduled.HasValue)
 692            {
 0693                if (query.IsScheduled.Value)
 694                {
 0695                    timers = timers.Where(i => i.Item1.Status == RecordingStatus.New);
 696                }
 697                else
 698                {
 0699                    timers = timers.Where(i => i.Item1.Status != RecordingStatus.New);
 700                }
 701            }
 702
 0703            if (!string.IsNullOrEmpty(query.ChannelId))
 704            {
 0705                var guid = new Guid(query.ChannelId);
 0706                timers = timers.Where(i => _tvDtoService.GetInternalChannelId(i.Item2.Name, i.Item1.ChannelId).Equals(gu
 707            }
 708
 0709            if (!string.IsNullOrEmpty(query.SeriesTimerId))
 710            {
 0711                var guid = new Guid(query.SeriesTimerId);
 712
 0713                timers = timers
 0714                    .Where(i => _tvDtoService.GetInternalSeriesTimerId(i.Item1.SeriesTimerId).Equals(guid));
 715            }
 716
 0717            if (!string.IsNullOrEmpty(query.Id))
 718            {
 0719                timers = timers
 0720                    .Where(i => string.Equals(_tvDtoService.GetInternalTimerId(i.Item1.Id), query.Id, StringComparison.O
 721            }
 722
 0723            var returnArray = timers
 0724                .Select(i => i.Item1)
 0725                .OrderBy(i => i.StartDate)
 0726                .ToArray();
 727
 0728            return new QueryResult<TimerInfo>(returnArray);
 0729        }
 730
 731        public async Task<QueryResult<TimerInfoDto>> GetTimers(TimerQuery query, CancellationToken cancellationToken)
 732        {
 0733            var tasks = _services.Select(async i =>
 0734            {
 0735                try
 0736                {
 0737                    var recs = await i.GetTimersAsync(cancellationToken).ConfigureAwait(false);
 0738                    return recs.Select(r => new Tuple<TimerInfo, ILiveTvService>(r, i));
 0739                }
 0740                catch (Exception ex)
 0741                {
 0742                    _logger.LogError(ex, "Error getting recordings");
 0743                    return new List<Tuple<TimerInfo, ILiveTvService>>();
 0744                }
 0745            });
 0746            var results = await Task.WhenAll(tasks).ConfigureAwait(false);
 0747            var timers = results.SelectMany(i => i.ToList());
 748
 0749            if (query.IsActive.HasValue)
 750            {
 0751                if (query.IsActive.Value)
 752                {
 0753                    timers = timers.Where(i => i.Item1.Status == RecordingStatus.InProgress);
 754                }
 755                else
 756                {
 0757                    timers = timers.Where(i => i.Item1.Status != RecordingStatus.InProgress);
 758                }
 759            }
 760
 0761            if (query.IsScheduled.HasValue)
 762            {
 0763                if (query.IsScheduled.Value)
 764                {
 0765                    timers = timers.Where(i => i.Item1.Status == RecordingStatus.New);
 766                }
 767                else
 768                {
 0769                    timers = timers.Where(i => i.Item1.Status != RecordingStatus.New);
 770                }
 771            }
 772
 0773            if (!string.IsNullOrEmpty(query.ChannelId))
 774            {
 0775                var guid = new Guid(query.ChannelId);
 0776                timers = timers.Where(i => _tvDtoService.GetInternalChannelId(i.Item2.Name, i.Item1.ChannelId).Equals(gu
 777            }
 778
 0779            if (!string.IsNullOrEmpty(query.SeriesTimerId))
 780            {
 0781                var guid = new Guid(query.SeriesTimerId);
 782
 0783                timers = timers
 0784                    .Where(i => _tvDtoService.GetInternalSeriesTimerId(i.Item1.SeriesTimerId).Equals(guid));
 785            }
 786
 0787            if (!string.IsNullOrEmpty(query.Id))
 788            {
 0789                timers = timers
 0790                    .Where(i => string.Equals(_tvDtoService.GetInternalTimerId(i.Item1.Id), query.Id, StringComparison.O
 791            }
 792
 0793            var returnList = new List<TimerInfoDto>();
 794
 0795            foreach (var i in timers)
 796            {
 0797                var program = string.IsNullOrEmpty(i.Item1.ProgramId) ?
 0798                    null :
 0799                    _libraryManager.GetItemById(_tvDtoService.GetInternalProgramId(i.Item1.ProgramId)) as LiveTvProgram;
 800
 0801                var channel = string.IsNullOrEmpty(i.Item1.ChannelId) ? null : _libraryManager.GetItemById(_tvDtoService
 802
 0803                returnList.Add(_tvDtoService.GetTimerInfoDto(i.Item1, i.Item2, program, channel));
 804            }
 805
 0806            var returnArray = returnList
 0807                .OrderBy(i => i.StartDate)
 0808                .ToArray();
 809
 0810            return new QueryResult<TimerInfoDto>(returnArray);
 0811        }
 812
 813        public async Task CancelTimer(string id)
 814        {
 0815            var timer = await GetTimer(id, CancellationToken.None).ConfigureAwait(false);
 816
 0817            if (timer is null)
 818            {
 0819                throw new ResourceNotFoundException(string.Format(CultureInfo.InvariantCulture, "Timer with Id {0} not f
 820            }
 821
 0822            var service = GetService(timer.ServiceName);
 823
 0824            await service.CancelTimerAsync(timer.ExternalId, CancellationToken.None).ConfigureAwait(false);
 825
 0826            if (service is not DefaultLiveTvService)
 827            {
 0828                TimerCancelled?.Invoke(this, new GenericEventArgs<TimerEventInfo>(new TimerEventInfo(id)));
 829            }
 0830        }
 831
 832        public async Task CancelSeriesTimer(string id)
 833        {
 0834            var timer = await GetSeriesTimer(id, CancellationToken.None).ConfigureAwait(false);
 835
 0836            if (timer is null)
 837            {
 0838                throw new ResourceNotFoundException(string.Format(CultureInfo.InvariantCulture, "SeriesTimer with Id {0}
 839            }
 840
 0841            var service = GetService(timer.ServiceName);
 842
 0843            await service.CancelSeriesTimerAsync(timer.ExternalId, CancellationToken.None).ConfigureAwait(false);
 844
 0845            SeriesTimerCancelled?.Invoke(this, new GenericEventArgs<TimerEventInfo>(new TimerEventInfo(id)));
 0846        }
 847
 848        public async Task<TimerInfoDto> GetTimer(string id, CancellationToken cancellationToken)
 849        {
 0850            var results = await GetTimers(
 0851                new TimerQuery
 0852                {
 0853                    Id = id
 0854                },
 0855                cancellationToken).ConfigureAwait(false);
 856
 0857            return results.Items.FirstOrDefault(i => string.Equals(i.Id, id, StringComparison.OrdinalIgnoreCase));
 0858        }
 859
 860        public async Task<SeriesTimerInfoDto> GetSeriesTimer(string id, CancellationToken cancellationToken)
 861        {
 0862            var results = await GetSeriesTimers(new SeriesTimerQuery(), cancellationToken).ConfigureAwait(false);
 863
 0864            return results.Items.FirstOrDefault(i => string.Equals(i.Id, id, StringComparison.OrdinalIgnoreCase));
 0865        }
 866
 867        private async Task<QueryResult<SeriesTimerInfo>> GetSeriesTimersInternal(SeriesTimerQuery query, CancellationTok
 868        {
 0869            var tasks = _services.Select(async i =>
 0870            {
 0871                try
 0872                {
 0873                    var recs = await i.GetSeriesTimersAsync(cancellationToken).ConfigureAwait(false);
 0874                    return recs.Select(r =>
 0875                    {
 0876                        r.ServiceName = i.Name;
 0877                        return new Tuple<SeriesTimerInfo, ILiveTvService>(r, i);
 0878                    });
 0879                }
 0880                catch (Exception ex)
 0881                {
 0882                    _logger.LogError(ex, "Error getting recordings");
 0883                    return new List<Tuple<SeriesTimerInfo, ILiveTvService>>();
 0884                }
 0885            });
 0886            var results = await Task.WhenAll(tasks).ConfigureAwait(false);
 0887            var timers = results.SelectMany(i => i.ToList());
 888
 0889            if (string.Equals(query.SortBy, "Priority", StringComparison.OrdinalIgnoreCase))
 890            {
 0891                timers = query.SortOrder == SortOrder.Descending ?
 0892                    timers.OrderBy(i => i.Item1.Priority).ThenByStringDescending(i => i.Item1.Name) :
 0893                    timers.OrderByDescending(i => i.Item1.Priority).ThenByString(i => i.Item1.Name);
 894            }
 895            else
 896            {
 0897                timers = query.SortOrder == SortOrder.Descending ?
 0898                    timers.OrderByStringDescending(i => i.Item1.Name) :
 0899                    timers.OrderByString(i => i.Item1.Name);
 900            }
 901
 0902            var returnArray = timers
 0903                .Select(i => i.Item1)
 0904                .ToArray();
 905
 0906            return new QueryResult<SeriesTimerInfo>(returnArray);
 0907        }
 908
 909        public async Task<QueryResult<SeriesTimerInfoDto>> GetSeriesTimers(SeriesTimerQuery query, CancellationToken can
 910        {
 0911            var tasks = _services.Select(async i =>
 0912            {
 0913                try
 0914                {
 0915                    var recs = await i.GetSeriesTimersAsync(cancellationToken).ConfigureAwait(false);
 0916                    return recs.Select(r => new Tuple<SeriesTimerInfo, ILiveTvService>(r, i));
 0917                }
 0918                catch (Exception ex)
 0919                {
 0920                    _logger.LogError(ex, "Error getting recordings");
 0921                    return new List<Tuple<SeriesTimerInfo, ILiveTvService>>();
 0922                }
 0923            });
 0924            var results = await Task.WhenAll(tasks).ConfigureAwait(false);
 0925            var timers = results.SelectMany(i => i.ToList());
 926
 0927            if (string.Equals(query.SortBy, "Priority", StringComparison.OrdinalIgnoreCase))
 928            {
 0929                timers = query.SortOrder == SortOrder.Descending ?
 0930                    timers.OrderBy(i => i.Item1.Priority).ThenByStringDescending(i => i.Item1.Name) :
 0931                    timers.OrderByDescending(i => i.Item1.Priority).ThenByString(i => i.Item1.Name);
 932            }
 933            else
 934            {
 0935                timers = query.SortOrder == SortOrder.Descending ?
 0936                    timers.OrderByStringDescending(i => i.Item1.Name) :
 0937                    timers.OrderByString(i => i.Item1.Name);
 938            }
 939
 0940            var returnArray = timers
 0941                .Select(i =>
 0942                {
 0943                    string channelName = null;
 0944
 0945                    if (!string.IsNullOrEmpty(i.Item1.ChannelId))
 0946                    {
 0947                        var internalChannelId = _tvDtoService.GetInternalChannelId(i.Item2.Name, i.Item1.ChannelId);
 0948                        var channel = _libraryManager.GetItemById(internalChannelId);
 0949                        channelName = channel?.Name;
 0950                    }
 0951
 0952                    return _tvDtoService.GetSeriesTimerInfoDto(i.Item1, i.Item2, channelName);
 0953                })
 0954                .ToArray();
 955
 0956            return new QueryResult<SeriesTimerInfoDto>(returnArray);
 0957        }
 958
 959        public void AddChannelInfo(IReadOnlyCollection<(BaseItemDto ItemDto, LiveTvChannel Channel)> items, DtoOptions o
 960        {
 0961            var now = DateTime.UtcNow;
 962
 0963            var channelIds = items.Select(i => i.Channel.Id).Distinct().ToArray();
 964
 0965            var programs = options.AddCurrentProgram ? _libraryManager.GetItemList(new InternalItemsQuery(user)
 0966            {
 0967                IncludeItemTypes = [BaseItemKind.LiveTvProgram],
 0968                ChannelIds = channelIds,
 0969                MaxStartDate = now,
 0970                MinEndDate = now,
 0971                Limit = channelIds.Length,
 0972                OrderBy = [(ItemSortBy.StartDate, SortOrder.Ascending)],
 0973                TopParentIds = [GetInternalLiveTvFolder(CancellationToken.None).Id],
 0974                DtoOptions = options
 0975            }) : new List<BaseItem>();
 976
 0977            RemoveFields(options);
 978
 0979            var currentProgramsList = new List<BaseItem>();
 0980            var currentChannelsDict = new Dictionary<Guid, BaseItemDto>();
 981
 0982            var addCurrentProgram = options.AddCurrentProgram;
 983
 0984            foreach (var (dto, channel) in items)
 985            {
 0986                dto.Number = channel.Number;
 0987                dto.ChannelNumber = channel.Number;
 0988                dto.ChannelType = channel.ChannelType;
 989
 0990                currentChannelsDict[dto.Id] = dto;
 991
 0992                if (addCurrentProgram)
 993                {
 0994                    var currentProgram = programs.FirstOrDefault(i => channel.Id.Equals(i.ChannelId));
 995
 0996                    if (currentProgram is not null)
 997                    {
 0998                        currentProgramsList.Add(currentProgram);
 999                    }
 1000                }
 1001            }
 1002
 01003            if (addCurrentProgram)
 1004            {
 01005                var currentProgramDtos = _dtoService.GetBaseItemDtos(currentProgramsList, options, user);
 1006
 01007                foreach (var programDto in currentProgramDtos)
 1008                {
 01009                    if (programDto.ChannelId.HasValue && currentChannelsDict.TryGetValue(programDto.ChannelId.Value, out
 1010                    {
 01011                        channelDto.CurrentProgram = programDto;
 1012                    }
 1013                }
 1014            }
 01015        }
 1016
 1017        private async Task<Tuple<SeriesTimerInfo, ILiveTvService>> GetNewTimerDefaultsInternal(CancellationToken cancell
 1018        {
 01019            ILiveTvService service = null;
 01020            ProgramInfo programInfo = null;
 1021
 01022            if (program is not null)
 1023            {
 01024                service = GetService(program);
 1025
 01026                var channel = _libraryManager.GetItemById(program.ChannelId);
 1027
 01028                programInfo = new ProgramInfo
 01029                {
 01030                    Audio = program.Audio,
 01031                    ChannelId = channel.ExternalId,
 01032                    CommunityRating = program.CommunityRating,
 01033                    EndDate = program.EndDate ?? DateTime.MinValue,
 01034                    EpisodeTitle = program.EpisodeTitle,
 01035                    Genres = program.Genres.ToList(),
 01036                    Id = program.ExternalId,
 01037                    IsHD = program.IsHD,
 01038                    IsKids = program.IsKids,
 01039                    IsLive = program.IsLive,
 01040                    IsMovie = program.IsMovie,
 01041                    IsNews = program.IsNews,
 01042                    IsPremiere = program.IsPremiere,
 01043                    IsRepeat = program.IsRepeat,
 01044                    IsSeries = program.IsSeries,
 01045                    IsSports = program.IsSports,
 01046                    OriginalAirDate = program.PremiereDate,
 01047                    Overview = program.Overview,
 01048                    StartDate = program.StartDate,
 01049                    // ImagePath = program.ExternalImagePath,
 01050                    Name = program.Name,
 01051                    OfficialRating = program.OfficialRating
 01052                };
 1053            }
 1054
 01055            service ??= _services[0];
 1056
 01057            var info = await service.GetNewTimerDefaultsAsync(cancellationToken, programInfo).ConfigureAwait(false);
 1058
 01059            info.RecordAnyTime = true;
 01060            info.Days = new List<DayOfWeek>
 01061            {
 01062                DayOfWeek.Sunday,
 01063                DayOfWeek.Monday,
 01064                DayOfWeek.Tuesday,
 01065                DayOfWeek.Wednesday,
 01066                DayOfWeek.Thursday,
 01067                DayOfWeek.Friday,
 01068                DayOfWeek.Saturday
 01069            };
 1070
 01071            info.Id = null;
 1072
 01073            return new Tuple<SeriesTimerInfo, ILiveTvService>(info, service);
 01074        }
 1075
 1076        public async Task<SeriesTimerInfoDto> GetNewTimerDefaults(CancellationToken cancellationToken)
 1077        {
 01078            var info = await GetNewTimerDefaultsInternal(cancellationToken).ConfigureAwait(false);
 1079
 01080            return _tvDtoService.GetSeriesTimerInfoDto(info.Item1, info.Item2, null);
 01081        }
 1082
 1083        public async Task<SeriesTimerInfoDto> GetNewTimerDefaults(string programId, CancellationToken cancellationToken)
 1084        {
 01085            var program = (LiveTvProgram)_libraryManager.GetItemById(programId);
 01086            var programDto = await GetProgram(programId, cancellationToken).ConfigureAwait(false);
 1087
 01088            var defaults = await GetNewTimerDefaultsInternal(cancellationToken, program).ConfigureAwait(false);
 01089            var info = _tvDtoService.GetSeriesTimerInfoDto(defaults.Item1, defaults.Item2, null);
 1090
 01091            info.Days = defaults.Item1.Days.ToArray();
 1092
 01093            info.DayPattern = _tvDtoService.GetDayPattern(info.Days);
 1094
 01095            info.Name = program.Name;
 01096            info.ChannelId = programDto.ChannelId ?? Guid.Empty;
 01097            info.ChannelName = programDto.ChannelName;
 01098            info.StartDate = program.StartDate;
 01099            info.Name = program.Name;
 01100            info.Overview = program.Overview;
 01101            info.ProgramId = programDto.Id.ToString("N", CultureInfo.InvariantCulture);
 01102            info.ExternalProgramId = program.ExternalId;
 1103
 01104            if (program.EndDate.HasValue)
 1105            {
 01106                info.EndDate = program.EndDate.Value;
 1107            }
 1108
 01109            return info;
 01110        }
 1111
 1112        public async Task CreateTimer(TimerInfoDto timer, CancellationToken cancellationToken)
 1113        {
 01114            var service = GetService(timer.ServiceName);
 1115
 01116            var info = await _tvDtoService.GetTimerInfo(timer, true, this, cancellationToken).ConfigureAwait(false);
 1117
 1118            // Set priority from default values
 01119            var defaultValues = await service.GetNewTimerDefaultsAsync(cancellationToken).ConfigureAwait(false);
 01120            info.Priority = defaultValues.Priority;
 1121
 01122            string newTimerId = null;
 01123            if (service is ISupportsNewTimerIds supportsNewTimerIds)
 1124            {
 01125                newTimerId = await supportsNewTimerIds.CreateTimer(info, cancellationToken).ConfigureAwait(false);
 01126                newTimerId = _tvDtoService.GetInternalTimerId(newTimerId);
 1127            }
 1128            else
 1129            {
 01130                await service.CreateTimerAsync(info, cancellationToken).ConfigureAwait(false);
 1131            }
 1132
 01133            _logger.LogInformation("New recording scheduled");
 1134
 01135            if (service is not DefaultLiveTvService)
 1136            {
 01137                TimerCreated?.Invoke(this, new GenericEventArgs<TimerEventInfo>(
 01138                    new TimerEventInfo(newTimerId)
 01139                    {
 01140                        ProgramId = _tvDtoService.GetInternalProgramId(info.ProgramId)
 01141                    }));
 1142            }
 01143        }
 1144
 1145        public async Task CreateSeriesTimer(SeriesTimerInfoDto timer, CancellationToken cancellationToken)
 1146        {
 01147            var service = GetService(timer.ServiceName);
 1148
 01149            var info = await _tvDtoService.GetSeriesTimerInfo(timer, true, this, cancellationToken).ConfigureAwait(false
 1150
 1151            // Set priority from default values
 01152            var defaultValues = await service.GetNewTimerDefaultsAsync(cancellationToken).ConfigureAwait(false);
 01153            info.Priority = defaultValues.Priority;
 1154
 01155            string newTimerId = null;
 01156            if (service is ISupportsNewTimerIds supportsNewTimerIds)
 1157            {
 01158                newTimerId = await supportsNewTimerIds.CreateSeriesTimer(info, cancellationToken).ConfigureAwait(false);
 01159                newTimerId = _tvDtoService.GetInternalSeriesTimerId(newTimerId).ToString("N", CultureInfo.InvariantCultu
 1160            }
 1161            else
 1162            {
 01163                await service.CreateSeriesTimerAsync(info, cancellationToken).ConfigureAwait(false);
 1164            }
 1165
 01166            SeriesTimerCreated?.Invoke(this, new GenericEventArgs<TimerEventInfo>(
 01167                new TimerEventInfo(newTimerId)
 01168                {
 01169                    ProgramId = _tvDtoService.GetInternalProgramId(info.ProgramId)
 01170                }));
 01171        }
 1172
 1173        public async Task UpdateTimer(TimerInfoDto timer, CancellationToken cancellationToken)
 1174        {
 01175            var info = await _tvDtoService.GetTimerInfo(timer, false, this, cancellationToken).ConfigureAwait(false);
 1176
 01177            var service = GetService(timer.ServiceName);
 1178
 01179            await service.UpdateTimerAsync(info, cancellationToken).ConfigureAwait(false);
 01180        }
 1181
 1182        public async Task UpdateSeriesTimer(SeriesTimerInfoDto timer, CancellationToken cancellationToken)
 1183        {
 01184            var info = await _tvDtoService.GetSeriesTimerInfo(timer, false, this, cancellationToken).ConfigureAwait(fals
 1185
 01186            var service = GetService(timer.ServiceName);
 1187
 01188            await service.UpdateSeriesTimerAsync(info, cancellationToken).ConfigureAwait(false);
 01189        }
 1190
 1191        private LiveTvServiceInfo[] GetServiceInfos()
 1192        {
 01193            return Services.Select(GetServiceInfo).ToArray();
 1194        }
 1195
 1196        private static LiveTvServiceInfo GetServiceInfo(ILiveTvService service)
 1197        {
 01198            return new LiveTvServiceInfo
 01199            {
 01200                Name = service.Name
 01201            };
 1202        }
 1203
 1204        public LiveTvInfo GetLiveTvInfo(CancellationToken cancellationToken)
 1205        {
 01206            var services = GetServiceInfos();
 1207
 01208            var info = new LiveTvInfo
 01209            {
 01210                Services = services,
 01211                IsEnabled = services.Length > 0,
 01212                EnabledUsers = _userManager.GetUsers()
 01213                    .Where(IsLiveTvEnabled)
 01214                    .Select(i => i.Id.ToString("N", CultureInfo.InvariantCulture))
 01215                    .ToArray()
 01216            };
 1217
 01218            return info;
 1219        }
 1220
 1221        private bool IsLiveTvEnabled(User user)
 1222        {
 11223            return user.HasPermission(PermissionKind.EnableLiveTvAccess) && (Services.Count > 1 || _config.GetLiveTvConf
 1224        }
 1225
 1226        public IEnumerable<User> GetEnabledUsers()
 1227        {
 11228            return _userManager.GetUsers()
 11229                .Where(IsLiveTvEnabled);
 1230        }
 1231
 1232        /// <summary>
 1233        /// Resets the tuner.
 1234        /// </summary>
 1235        /// <param name="id">The identifier.</param>
 1236        /// <param name="cancellationToken">The cancellation token.</param>
 1237        /// <returns>Task.</returns>
 1238        public Task ResetTuner(string id, CancellationToken cancellationToken)
 1239        {
 01240            var parts = id.Split('_', 2);
 1241
 01242            var service = _services.FirstOrDefault(i => string.Equals(i.GetType().FullName.GetMD5().ToString("N", Cultur
 1243
 01244            if (service is null)
 1245            {
 01246                throw new ArgumentException("Service not found.");
 1247            }
 1248
 01249            return service.ResetTuner(parts[1], cancellationToken);
 1250        }
 1251
 1252        private static void RemoveFields(DtoOptions options)
 1253        {
 01254            var fields = options.Fields.ToList();
 1255
 01256            fields.Remove(ItemFields.CanDelete);
 01257            fields.Remove(ItemFields.CanDownload);
 01258            fields.Remove(ItemFields.DisplayPreferencesId);
 01259            fields.Remove(ItemFields.Etag);
 01260            options.Fields = fields.ToArray();
 01261        }
 1262
 1263        public Folder GetInternalLiveTvFolder(CancellationToken cancellationToken)
 1264        {
 01265            var name = _localization.GetLocalizedString("HeaderLiveTV");
 01266            return _libraryManager.GetNamedView(name, CollectionType.livetv, name);
 1267        }
 1268
 1269        /// <inheritdoc />
 1270        public Task<BaseItem[]> GetRecordingFoldersAsync(User user)
 01271            => GetRecordingFoldersAsync(user, false);
 1272
 1273        private async Task<BaseItem[]> GetRecordingFoldersAsync(User user, bool refreshChannels)
 1274        {
 01275            var folders = _recordingsManager.GetRecordingFolders()
 01276                .SelectMany(i => i.Locations)
 01277                .Distinct()
 01278                .Select(i => _libraryManager.FindByPath(i, true))
 01279                .Where(i => i is not null && i.IsVisibleStandalone(user))
 01280                .SelectMany(i => _libraryManager.GetCollectionFolders(i))
 01281                .DistinctBy(x => x.Id)
 01282                .OrderBy(i => i.SortName)
 01283                .ToList();
 1284
 01285            var channels = await _channelManager.GetChannelsInternalAsync(new MediaBrowser.Model.Channels.ChannelQuery
 01286            {
 01287                UserId = user.Id,
 01288                IsRecordingsFolder = true,
 01289                RefreshLatestChannelItems = refreshChannels
 01290            }).ConfigureAwait(false);
 1291
 01292            folders.AddRange(channels.Items);
 1293
 01294            return folders.Cast<BaseItem>().ToArray();
 01295        }
 1296    }
 1297}

Methods/Properties

.ctor(MediaBrowser.Controller.Configuration.IServerConfigurationManager,Microsoft.Extensions.Logging.ILogger`1<Jellyfin.LiveTv.LiveTvManager>,MediaBrowser.Controller.Library.IUserDataManager,MediaBrowser.Controller.Dto.IDtoService,MediaBrowser.Controller.Library.IUserManager,MediaBrowser.Controller.Library.ILibraryManager,MediaBrowser.Model.Globalization.ILocalizationManager,MediaBrowser.Controller.Channels.IChannelManager,MediaBrowser.Controller.LiveTv.IRecordingsManager,Jellyfin.LiveTv.LiveTvDtoService,System.Collections.Generic.IEnumerable`1<MediaBrowser.Controller.LiveTv.ILiveTvService>)
get_Services()
OnEmbyTvTimerCancelled(System.Object,Jellyfin.Data.Events.GenericEventArgs`1<System.String>)
OnEmbyTvTimerCreated(System.Object,Jellyfin.Data.Events.GenericEventArgs`1<MediaBrowser.Controller.LiveTv.TimerInfo>)
GetInternalChannels(MediaBrowser.Model.LiveTv.LiveTvChannelQuery,MediaBrowser.Controller.Dto.DtoOptions,System.Threading.CancellationToken)
GetService(MediaBrowser.Controller.LiveTv.LiveTvChannel)
GetService(MediaBrowser.Controller.LiveTv.LiveTvProgram)
GetService(System.String)
GetProgram()
GetPrograms()
GetRecommendedProgramsInternal(MediaBrowser.Controller.Entities.InternalItemsQuery,MediaBrowser.Controller.Dto.DtoOptions,System.Threading.CancellationToken)
GetRecommendedProgramsAsync(MediaBrowser.Controller.Entities.InternalItemsQuery,MediaBrowser.Controller.Dto.DtoOptions,System.Threading.CancellationToken)
GetRecommendationScore(MediaBrowser.Controller.LiveTv.LiveTvProgram,Jellyfin.Database.Implementations.Entities.User,System.Boolean)
AddRecordingInfo()
GetEmbyRecordingsAsync()
AddInfoToProgramDto(System.Collections.Generic.IReadOnlyCollection`1<System.ValueTuple`2<MediaBrowser.Controller.Entities.BaseItem,MediaBrowser.Model.Dto.BaseItemDto>>,System.Collections.Generic.IReadOnlyList`1<MediaBrowser.Model.Querying.ItemFields>,Jellyfin.Database.Implementations.Entities.User)
AddInfoToRecordingDto(MediaBrowser.Controller.Entities.BaseItem,MediaBrowser.Model.Dto.BaseItemDto,MediaBrowser.Controller.LiveTv.ActiveRecordingInfo,Jellyfin.Database.Implementations.Entities.User)
GetRecordingsAsync()
GetTimersInternal()
GetTimers()
CancelTimer()
CancelSeriesTimer()
GetTimer()
GetSeriesTimer()
GetSeriesTimersInternal()
GetSeriesTimers()
AddChannelInfo(System.Collections.Generic.IReadOnlyCollection`1<System.ValueTuple`2<MediaBrowser.Model.Dto.BaseItemDto,MediaBrowser.Controller.LiveTv.LiveTvChannel>>,MediaBrowser.Controller.Dto.DtoOptions,Jellyfin.Database.Implementations.Entities.User)
GetNewTimerDefaultsInternal()
GetNewTimerDefaults()
GetNewTimerDefaults()
CreateTimer()
CreateSeriesTimer()
UpdateTimer()
UpdateSeriesTimer()
GetServiceInfos()
GetServiceInfo(MediaBrowser.Controller.LiveTv.ILiveTvService)
GetLiveTvInfo(System.Threading.CancellationToken)
IsLiveTvEnabled(Jellyfin.Database.Implementations.Entities.User)
GetEnabledUsers()
ResetTuner(System.String,System.Threading.CancellationToken)
RemoveFields(MediaBrowser.Controller.Dto.DtoOptions)
GetInternalLiveTvFolder(System.Threading.CancellationToken)
GetRecordingFoldersAsync(Jellyfin.Database.Implementations.Entities.User)
GetRecordingFoldersAsync()