< Summary - Jellyfin

Information
Class: Jellyfin.LiveTv.LiveTvManager
Assembly: Jellyfin.LiveTv
File(s): /srv/git/jellyfin/src/Jellyfin.LiveTv/LiveTvManager.cs
Line coverage
7%
Covered lines: 19
Uncovered lines: 231
Coverable lines: 250
Total lines: 1290
Line coverage: 7.6%
Branch coverage
2%
Covered branches: 2
Total branches: 96
Branch coverage: 2%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Coverage history

Coverage history 0 25 50 75 100

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.Entities;
 12using Jellyfin.Data.Enums;
 13using Jellyfin.Data.Events;
 14using Jellyfin.LiveTv.Configuration;
 15using MediaBrowser.Common.Extensions;
 16using MediaBrowser.Controller.Channels;
 17using MediaBrowser.Controller.Configuration;
 18using MediaBrowser.Controller.Dto;
 19using MediaBrowser.Controller.Entities;
 20using MediaBrowser.Controller.Library;
 21using MediaBrowser.Controller.LiveTv;
 22using MediaBrowser.Controller.Sorting;
 23using MediaBrowser.Model.Dto;
 24using MediaBrowser.Model.Entities;
 25using MediaBrowser.Model.Globalization;
 26using MediaBrowser.Model.LiveTv;
 27using MediaBrowser.Model.Querying;
 28using Microsoft.Extensions.Logging;
 29
 30namespace Jellyfin.LiveTv
 31{
 32    /// <summary>
 33    /// Class LiveTvManager.
 34    /// </summary>
 35    public class LiveTvManager : ILiveTvManager
 36    {
 37        private readonly IServerConfigurationManager _config;
 38        private readonly ILogger<LiveTvManager> _logger;
 39        private readonly IUserManager _userManager;
 40        private readonly IDtoService _dtoService;
 41        private readonly IUserDataManager _userDataManager;
 42        private readonly ILibraryManager _libraryManager;
 43        private readonly ILocalizationManager _localization;
 44        private readonly IChannelManager _channelManager;
 45        private readonly IRecordingsManager _recordingsManager;
 46        private readonly LiveTvDtoService _tvDtoService;
 47        private readonly ILiveTvService[] _services;
 48
 49        public LiveTvManager(
 50            IServerConfigurationManager config,
 51            ILogger<LiveTvManager> logger,
 52            IUserDataManager userDataManager,
 53            IDtoService dtoService,
 54            IUserManager userManager,
 55            ILibraryManager libraryManager,
 56            ILocalizationManager localization,
 57            IChannelManager channelManager,
 58            IRecordingsManager recordingsManager,
 59            LiveTvDtoService liveTvDtoService,
 60            IEnumerable<ILiveTvService> services)
 61        {
 2262            _config = config;
 2263            _logger = logger;
 2264            _userManager = userManager;
 2265            _libraryManager = libraryManager;
 2266            _localization = localization;
 2267            _dtoService = dtoService;
 2268            _userDataManager = userDataManager;
 2269            _channelManager = channelManager;
 2270            _tvDtoService = liveTvDtoService;
 2271            _recordingsManager = recordingsManager;
 2272            _services = services.ToArray();
 73
 2274            var defaultService = _services.OfType<DefaultLiveTvService>().First();
 2275            defaultService.TimerCreated += OnEmbyTvTimerCreated;
 2276            defaultService.TimerCancelled += OnEmbyTvTimerCancelled;
 2277        }
 78
 79        public event EventHandler<GenericEventArgs<TimerEventInfo>> SeriesTimerCancelled;
 80
 81        public event EventHandler<GenericEventArgs<TimerEventInfo>> TimerCancelled;
 82
 83        public event EventHandler<GenericEventArgs<TimerEventInfo>> TimerCreated;
 84
 85        public event EventHandler<GenericEventArgs<TimerEventInfo>> SeriesTimerCreated;
 86
 87        /// <summary>
 88        /// Gets the services.
 89        /// </summary>
 90        /// <value>The services.</value>
 491        public IReadOnlyList<ILiveTvService> Services => _services;
 92
 93        private void OnEmbyTvTimerCancelled(object sender, GenericEventArgs<string> e)
 94        {
 095            var timerId = e.Argument;
 96
 097            TimerCancelled?.Invoke(this, new GenericEventArgs<TimerEventInfo>(new TimerEventInfo(timerId)));
 098        }
 99
 100        private void OnEmbyTvTimerCreated(object sender, GenericEventArgs<TimerInfo> e)
 101        {
 0102            var timer = e.Argument;
 103
 0104            TimerCreated?.Invoke(this, new GenericEventArgs<TimerEventInfo>(
 0105                new TimerEventInfo(timer.Id)
 0106                {
 0107                    ProgramId = _tvDtoService.GetInternalProgramId(timer.ProgramId)
 0108                }));
 0109        }
 110
 111        public QueryResult<BaseItem> GetInternalChannels(LiveTvChannelQuery query, DtoOptions dtoOptions, CancellationTo
 112        {
 0113            var user = query.UserId.Equals(default)
 0114                ? null
 0115                : _userManager.GetUserById(query.UserId);
 116
 0117            var topFolder = GetInternalLiveTvFolder(cancellationToken);
 118
 0119            var internalQuery = new InternalItemsQuery(user)
 0120            {
 0121                IsMovie = query.IsMovie,
 0122                IsNews = query.IsNews,
 0123                IsKids = query.IsKids,
 0124                IsSports = query.IsSports,
 0125                IsSeries = query.IsSeries,
 0126                IncludeItemTypes = new[] { BaseItemKind.LiveTvChannel },
 0127                TopParentIds = new[] { topFolder.Id },
 0128                IsFavorite = query.IsFavorite,
 0129                IsLiked = query.IsLiked,
 0130                StartIndex = query.StartIndex,
 0131                Limit = query.Limit,
 0132                DtoOptions = dtoOptions
 0133            };
 134
 0135            var orderBy = internalQuery.OrderBy.ToList();
 136
 0137            orderBy.AddRange(query.SortBy.Select(i => (i, query.SortOrder ?? SortOrder.Ascending)));
 138
 0139            if (query.EnableFavoriteSorting)
 140            {
 0141                orderBy.Insert(0, (ItemSortBy.IsFavoriteOrLiked, SortOrder.Descending));
 142            }
 143
 0144            if (internalQuery.OrderBy.All(i => i.OrderBy != ItemSortBy.SortName))
 145            {
 0146                orderBy.Add((ItemSortBy.SortName, SortOrder.Ascending));
 147            }
 148
 0149            internalQuery.OrderBy = orderBy.ToArray();
 150
 0151            return _libraryManager.GetItemsResult(internalQuery);
 152        }
 153
 154        private ILiveTvService GetService(LiveTvChannel item)
 155        {
 0156            var name = item.ServiceName;
 0157            return GetService(name);
 158        }
 159
 160        private ILiveTvService GetService(LiveTvProgram item)
 161        {
 0162            var channel = _libraryManager.GetItemById(item.ChannelId) as LiveTvChannel;
 163
 0164            return GetService(channel);
 165        }
 166
 167        private ILiveTvService GetService(string name)
 0168            => Array.Find(_services, x => string.Equals(x.Name, name, StringComparison.OrdinalIgnoreCase))
 0169                ?? throw new KeyNotFoundException(
 0170                    string.Format(
 0171                        CultureInfo.InvariantCulture,
 0172                        "No service with the name '{0}' can be found.",
 0173                        name));
 174
 175        public async Task<BaseItemDto> GetProgram(string id, CancellationToken cancellationToken, User user = null)
 176        {
 177            var program = _libraryManager.GetItemById(id);
 178
 179            var dto = _dtoService.GetBaseItemDto(program, new DtoOptions(), user);
 180
 181            var list = new List<(BaseItemDto ItemDto, string ExternalId, string ExternalSeriesId)>
 182            {
 183                (dto, program.ExternalId, program.ExternalSeriesId)
 184            };
 185
 186            await AddRecordingInfo(list, cancellationToken).ConfigureAwait(false);
 187
 188            return dto;
 189        }
 190
 191        public async Task<QueryResult<BaseItemDto>> GetPrograms(InternalItemsQuery query, DtoOptions options, Cancellati
 192        {
 193            var user = query.User;
 194
 195            var topFolder = GetInternalLiveTvFolder(cancellationToken);
 196
 197            if (query.OrderBy.Count == 0)
 198            {
 199                // Unless something else was specified, order by start date to take advantage of a specialized index
 200                query.OrderBy = new[]
 201                {
 202                    (ItemSortBy.StartDate, SortOrder.Ascending)
 203                };
 204            }
 205
 206            RemoveFields(options);
 207
 208            var internalQuery = new InternalItemsQuery(user)
 209            {
 210                IncludeItemTypes = new[] { BaseItemKind.LiveTvProgram },
 211                MinEndDate = query.MinEndDate,
 212                MinStartDate = query.MinStartDate,
 213                MaxEndDate = query.MaxEndDate,
 214                MaxStartDate = query.MaxStartDate,
 215                ChannelIds = query.ChannelIds,
 216                IsMovie = query.IsMovie,
 217                IsSeries = query.IsSeries,
 218                IsSports = query.IsSports,
 219                IsKids = query.IsKids,
 220                IsNews = query.IsNews,
 221                Genres = query.Genres,
 222                GenreIds = query.GenreIds,
 223                StartIndex = query.StartIndex,
 224                Limit = query.Limit,
 225                OrderBy = query.OrderBy,
 226                EnableTotalRecordCount = query.EnableTotalRecordCount,
 227                TopParentIds = new[] { topFolder.Id },
 228                Name = query.Name,
 229                DtoOptions = options,
 230                HasAired = query.HasAired,
 231                IsAiring = query.IsAiring
 232            };
 233
 234            if (!string.IsNullOrWhiteSpace(query.SeriesTimerId))
 235            {
 236                var seriesTimers = await GetSeriesTimersInternal(new SeriesTimerQuery(), cancellationToken).ConfigureAwa
 237                var seriesTimer = seriesTimers.Items.FirstOrDefault(i => string.Equals(_tvDtoService.GetInternalSeriesTi
 238                if (seriesTimer is not null)
 239                {
 240                    internalQuery.ExternalSeriesId = seriesTimer.SeriesId;
 241
 242                    if (string.IsNullOrWhiteSpace(seriesTimer.SeriesId))
 243                    {
 244                        // Better to return nothing than every program in the database
 245                        return new QueryResult<BaseItemDto>();
 246                    }
 247                }
 248                else
 249                {
 250                    // Better to return nothing than every program in the database
 251                    return new QueryResult<BaseItemDto>();
 252                }
 253            }
 254
 255            var queryResult = _libraryManager.QueryItems(internalQuery);
 256
 257            var returnArray = _dtoService.GetBaseItemDtos(queryResult.Items, options, user);
 258
 259            return new QueryResult<BaseItemDto>(
 260                query.StartIndex,
 261                queryResult.TotalRecordCount,
 262                returnArray);
 263        }
 264
 265        public QueryResult<BaseItem> GetRecommendedProgramsInternal(InternalItemsQuery query, DtoOptions options, Cancel
 266        {
 0267            var user = query.User;
 268
 0269            var topFolder = GetInternalLiveTvFolder(cancellationToken);
 270
 0271            var internalQuery = new InternalItemsQuery(user)
 0272            {
 0273                IncludeItemTypes = new[] { BaseItemKind.LiveTvProgram },
 0274                IsAiring = query.IsAiring,
 0275                HasAired = query.HasAired,
 0276                IsNews = query.IsNews,
 0277                IsMovie = query.IsMovie,
 0278                IsSeries = query.IsSeries,
 0279                IsSports = query.IsSports,
 0280                IsKids = query.IsKids,
 0281                EnableTotalRecordCount = query.EnableTotalRecordCount,
 0282                OrderBy = new[] { (ItemSortBy.StartDate, SortOrder.Ascending) },
 0283                TopParentIds = new[] { topFolder.Id },
 0284                DtoOptions = options,
 0285                GenreIds = query.GenreIds
 0286            };
 287
 0288            if (query.Limit.HasValue)
 289            {
 0290                internalQuery.Limit = Math.Max(query.Limit.Value * 4, 200);
 291            }
 292
 0293            var programList = _libraryManager.QueryItems(internalQuery).Items;
 0294            var totalCount = programList.Count;
 295
 0296            var orderedPrograms = programList.Cast<LiveTvProgram>().OrderBy(i => i.StartDate.Date);
 297
 0298            if (query.IsAiring ?? false)
 299            {
 0300                orderedPrograms = orderedPrograms
 0301                    .ThenByDescending(i => GetRecommendationScore(i, user, true));
 302            }
 303
 0304            IEnumerable<BaseItem> programs = orderedPrograms;
 305
 0306            if (query.Limit.HasValue)
 307            {
 0308                programs = programs.Take(query.Limit.Value);
 309            }
 310
 0311            return new QueryResult<BaseItem>(
 0312                query.StartIndex,
 0313                totalCount,
 0314                programs.ToArray());
 315        }
 316
 317        public Task<QueryResult<BaseItemDto>> GetRecommendedProgramsAsync(InternalItemsQuery query, DtoOptions options, 
 318        {
 0319            if (!(query.IsAiring ?? false))
 320            {
 0321                return GetPrograms(query, options, cancellationToken);
 322            }
 323
 0324            RemoveFields(options);
 325
 0326            var internalResult = GetRecommendedProgramsInternal(query, options, cancellationToken);
 327
 0328            return Task.FromResult(new QueryResult<BaseItemDto>(
 0329                query.StartIndex,
 0330                internalResult.TotalRecordCount,
 0331                _dtoService.GetBaseItemDtos(internalResult.Items, options, query.User)));
 332        }
 333
 334        private int GetRecommendationScore(LiveTvProgram program, User user, bool factorChannelWatchCount)
 335        {
 0336            var score = 0;
 337
 0338            if (program.IsLive)
 339            {
 0340                score++;
 341            }
 342
 0343            if (program.IsSeries && !program.IsRepeat)
 344            {
 0345                score++;
 346            }
 347
 0348            var channel = _libraryManager.GetItemById(program.ChannelId);
 349
 0350            if (channel is null)
 351            {
 0352                return score;
 353            }
 354
 0355            var channelUserdata = _userDataManager.GetUserData(user, channel);
 356
 0357            if (channelUserdata.Likes.HasValue)
 358            {
 0359                score += channelUserdata.Likes.Value ? 2 : -2;
 360            }
 361
 0362            if (channelUserdata.IsFavorite)
 363            {
 0364                score += 3;
 365            }
 366
 0367            if (factorChannelWatchCount)
 368            {
 0369                score += channelUserdata.PlayCount;
 370            }
 371
 0372            return score;
 373        }
 374
 375        private async Task AddRecordingInfo(IEnumerable<(BaseItemDto ItemDto, string ExternalId, string ExternalSeriesId
 376        {
 377            IReadOnlyList<TimerInfo> timerList = null;
 378            IReadOnlyList<SeriesTimerInfo> seriesTimerList = null;
 379
 380            foreach (var programTuple in programs)
 381            {
 382                var program = programTuple.ItemDto;
 383                var externalProgramId = programTuple.ExternalId;
 384                string externalSeriesId = programTuple.ExternalSeriesId;
 385
 386                timerList ??= (await GetTimersInternal(new TimerQuery(), cancellationToken).ConfigureAwait(false)).Items
 387
 388                var timer = timerList.FirstOrDefault(i => string.Equals(i.ProgramId, externalProgramId, StringComparison
 389                var foundSeriesTimer = false;
 390
 391                if (timer is not null)
 392                {
 393                    if (timer.Status != RecordingStatus.Cancelled && timer.Status != RecordingStatus.Error)
 394                    {
 395                        program.TimerId = _tvDtoService.GetInternalTimerId(timer.Id);
 396
 397                        program.Status = timer.Status.ToString();
 398                    }
 399
 400                    if (!string.IsNullOrEmpty(timer.SeriesTimerId))
 401                    {
 402                        program.SeriesTimerId = _tvDtoService.GetInternalSeriesTimerId(timer.SeriesTimerId)
 403                            .ToString("N", CultureInfo.InvariantCulture);
 404
 405                        foundSeriesTimer = true;
 406                    }
 407                }
 408
 409                if (foundSeriesTimer || string.IsNullOrWhiteSpace(externalSeriesId))
 410                {
 411                    continue;
 412                }
 413
 414                seriesTimerList ??= (await GetSeriesTimersInternal(new SeriesTimerQuery(), cancellationToken).ConfigureA
 415
 416                var seriesTimer = seriesTimerList.FirstOrDefault(i => string.Equals(i.SeriesId, externalSeriesId, String
 417
 418                if (seriesTimer is not null)
 419                {
 420                    program.SeriesTimerId = _tvDtoService.GetInternalSeriesTimerId(seriesTimer.Id)
 421                        .ToString("N", CultureInfo.InvariantCulture);
 422                }
 423            }
 424        }
 425
 426        private async Task<QueryResult<BaseItem>> GetEmbyRecordingsAsync(RecordingQuery query, DtoOptions dtoOptions, Us
 427        {
 428            if (user is null)
 429            {
 430                return new QueryResult<BaseItem>();
 431            }
 432
 433            var folders = await GetRecordingFoldersAsync(user, true).ConfigureAwait(false);
 434            var folderIds = Array.ConvertAll(folders, x => x.Id);
 435
 436            var excludeItemTypes = new List<BaseItemKind>();
 437
 438            if (folderIds.Length == 0)
 439            {
 440                return new QueryResult<BaseItem>();
 441            }
 442
 443            var includeItemTypes = new List<BaseItemKind>();
 444            var genres = new List<string>();
 445
 446            if (query.IsMovie.HasValue)
 447            {
 448                if (query.IsMovie.Value)
 449                {
 450                    includeItemTypes.Add(BaseItemKind.Movie);
 451                }
 452                else
 453                {
 454                    excludeItemTypes.Add(BaseItemKind.Movie);
 455                }
 456            }
 457
 458            if (query.IsSeries.HasValue)
 459            {
 460                if (query.IsSeries.Value)
 461                {
 462                    includeItemTypes.Add(BaseItemKind.Episode);
 463                }
 464                else
 465                {
 466                    excludeItemTypes.Add(BaseItemKind.Episode);
 467                }
 468            }
 469
 470            if (query.IsSports ?? false)
 471            {
 472                genres.Add("Sports");
 473            }
 474
 475            if (query.IsKids ?? false)
 476            {
 477                genres.Add("Kids");
 478                genres.Add("Children");
 479                genres.Add("Family");
 480            }
 481
 482            var limit = query.Limit;
 483
 484            if (query.IsInProgress ?? false)
 485            {
 486                // limit = (query.Limit ?? 10) * 2;
 487                limit = null;
 488
 489                // var allActivePaths = EmbyTV.EmbyTV.Current.GetAllActiveRecordings().Select(i => i.Path).ToArray();
 490                // var items = allActivePaths.Select(i => _libraryManager.FindByPath(i, false)).Where(i => i is not null
 491
 492                // return new QueryResult<BaseItem>
 493                // {
 494                //    Items = items,
 495                //    TotalRecordCount = items.Length
 496                // };
 497
 498                dtoOptions.Fields = dtoOptions.Fields.Concat(new[] { ItemFields.Tags }).Distinct().ToArray();
 499            }
 500
 501            var result = _libraryManager.GetItemsResult(new InternalItemsQuery(user)
 502            {
 503                MediaTypes = new[] { MediaType.Video },
 504                Recursive = true,
 505                AncestorIds = folderIds,
 506                IsFolder = false,
 507                IsVirtualItem = false,
 508                Limit = limit,
 509                StartIndex = query.StartIndex,
 510                OrderBy = new[] { (ItemSortBy.DateCreated, SortOrder.Descending) },
 511                EnableTotalRecordCount = query.EnableTotalRecordCount,
 512                IncludeItemTypes = includeItemTypes.ToArray(),
 513                ExcludeItemTypes = excludeItemTypes.ToArray(),
 514                Genres = genres.ToArray(),
 515                DtoOptions = dtoOptions
 516            });
 517
 518            if (query.IsInProgress ?? false)
 519            {
 520                // TODO: Fix The co-variant conversion between Video[] and BaseItem[], this can generate runtime issues.
 521                result.Items = result
 522                    .Items
 523                    .OfType<Video>()
 524                    .Where(i => !i.IsCompleteMedia)
 525                    .ToArray();
 526
 527                result.TotalRecordCount = result.Items.Count;
 528            }
 529
 530            return result;
 531        }
 532
 533        public Task AddInfoToProgramDto(IReadOnlyCollection<(BaseItem Item, BaseItemDto ItemDto)> programs, IReadOnlyLis
 534        {
 0535            var programTuples = new List<(BaseItemDto Dto, string ExternalId, string ExternalSeriesId)>();
 0536            var hasChannelImage = fields.Contains(ItemFields.ChannelImage);
 0537            var hasChannelInfo = fields.Contains(ItemFields.ChannelInfo);
 538
 0539            foreach (var (item, dto) in programs)
 540            {
 0541                var program = (LiveTvProgram)item;
 542
 0543                dto.StartDate = program.StartDate;
 0544                dto.EpisodeTitle = program.EpisodeTitle;
 0545                dto.IsRepeat |= program.IsRepeat;
 0546                dto.IsMovie |= program.IsMovie;
 0547                dto.IsSeries |= program.IsSeries;
 0548                dto.IsSports |= program.IsSports;
 0549                dto.IsLive |= program.IsLive;
 0550                dto.IsNews |= program.IsNews;
 0551                dto.IsKids |= program.IsKids;
 0552                dto.IsPremiere |= program.IsPremiere;
 553
 0554                if (hasChannelInfo || hasChannelImage)
 555                {
 0556                    var channel = _libraryManager.GetItemById(program.ChannelId);
 557
 0558                    if (channel is LiveTvChannel liveChannel)
 559                    {
 0560                        dto.ChannelName = liveChannel.Name;
 0561                        dto.MediaType = liveChannel.MediaType;
 0562                        dto.ChannelNumber = liveChannel.Number;
 563
 0564                        if (hasChannelImage && liveChannel.HasImage(ImageType.Primary))
 565                        {
 0566                            dto.ChannelPrimaryImageTag = _tvDtoService.GetImageTag(liveChannel);
 567                        }
 568                    }
 569                }
 570
 0571                programTuples.Add((dto, program.ExternalId, program.ExternalSeriesId));
 572            }
 573
 0574            return AddRecordingInfo(programTuples, CancellationToken.None);
 575        }
 576
 577        public void AddInfoToRecordingDto(BaseItem item, BaseItemDto dto, ActiveRecordingInfo activeRecordingInfo, User 
 578        {
 0579            var info = activeRecordingInfo.Timer;
 580
 0581            var channel = string.IsNullOrWhiteSpace(info.ChannelId)
 0582                ? null
 0583                : _libraryManager.GetItemById(_tvDtoService.GetInternalChannelId(DefaultLiveTvService.ServiceName, info.
 584
 0585            dto.SeriesTimerId = string.IsNullOrEmpty(info.SeriesTimerId)
 0586                ? null
 0587                : _tvDtoService.GetInternalSeriesTimerId(info.SeriesTimerId).ToString("N", CultureInfo.InvariantCulture)
 588
 0589            dto.TimerId = string.IsNullOrEmpty(info.Id)
 0590                ? null
 0591                : _tvDtoService.GetInternalTimerId(info.Id);
 592
 0593            var startDate = info.StartDate;
 0594            var endDate = info.EndDate;
 595
 0596            dto.StartDate = startDate;
 0597            dto.EndDate = endDate;
 0598            dto.Status = info.Status.ToString();
 0599            dto.IsRepeat = info.IsRepeat;
 0600            dto.EpisodeTitle = info.EpisodeTitle;
 0601            dto.IsMovie = info.IsMovie;
 0602            dto.IsSeries = info.IsSeries;
 0603            dto.IsSports = info.IsSports;
 0604            dto.IsLive = info.IsLive;
 0605            dto.IsNews = info.IsNews;
 0606            dto.IsKids = info.IsKids;
 0607            dto.IsPremiere = info.IsPremiere;
 608
 0609            if (info.Status == RecordingStatus.InProgress)
 610            {
 0611                startDate = info.StartDate.AddSeconds(0 - info.PrePaddingSeconds);
 0612                endDate = info.EndDate.AddSeconds(info.PostPaddingSeconds);
 613
 0614                var now = DateTime.UtcNow.Ticks;
 0615                var start = startDate.Ticks;
 0616                var end = endDate.Ticks;
 617
 0618                var pct = now - start;
 619
 0620                pct /= end;
 0621                pct *= 100;
 0622                dto.CompletionPercentage = pct;
 623            }
 624
 0625            if (channel is not null)
 626            {
 0627                dto.ChannelName = channel.Name;
 628
 0629                if (channel.HasImage(ImageType.Primary))
 630                {
 0631                    dto.ChannelPrimaryImageTag = _tvDtoService.GetImageTag(channel);
 632                }
 633            }
 0634        }
 635
 636        public async Task<QueryResult<BaseItemDto>> GetRecordingsAsync(RecordingQuery query, DtoOptions options)
 637        {
 638            var user = query.UserId.Equals(default)
 639                ? null
 640                : _userManager.GetUserById(query.UserId);
 641
 642            RemoveFields(options);
 643
 644            var internalResult = await GetEmbyRecordingsAsync(query, options, user).ConfigureAwait(false);
 645
 646            var returnArray = _dtoService.GetBaseItemDtos(internalResult.Items, options, user);
 647
 648            return new QueryResult<BaseItemDto>(
 649                query.StartIndex,
 650                internalResult.TotalRecordCount,
 651                returnArray);
 652        }
 653
 654        private async Task<QueryResult<TimerInfo>> GetTimersInternal(TimerQuery query, CancellationToken cancellationTok
 655        {
 656            var tasks = _services.Select(async i =>
 657            {
 658                try
 659                {
 660                    var recs = await i.GetTimersAsync(cancellationToken).ConfigureAwait(false);
 661                    return recs.Select(r => new Tuple<TimerInfo, ILiveTvService>(r, i));
 662                }
 663                catch (Exception ex)
 664                {
 665                    _logger.LogError(ex, "Error getting recordings");
 666                    return new List<Tuple<TimerInfo, ILiveTvService>>();
 667                }
 668            });
 669            var results = await Task.WhenAll(tasks).ConfigureAwait(false);
 670            var timers = results.SelectMany(i => i.ToList());
 671
 672            if (query.IsActive.HasValue)
 673            {
 674                if (query.IsActive.Value)
 675                {
 676                    timers = timers.Where(i => i.Item1.Status == RecordingStatus.InProgress);
 677                }
 678                else
 679                {
 680                    timers = timers.Where(i => i.Item1.Status != RecordingStatus.InProgress);
 681                }
 682            }
 683
 684            if (query.IsScheduled.HasValue)
 685            {
 686                if (query.IsScheduled.Value)
 687                {
 688                    timers = timers.Where(i => i.Item1.Status == RecordingStatus.New);
 689                }
 690                else
 691                {
 692                    timers = timers.Where(i => i.Item1.Status != RecordingStatus.New);
 693                }
 694            }
 695
 696            if (!string.IsNullOrEmpty(query.ChannelId))
 697            {
 698                var guid = new Guid(query.ChannelId);
 699                timers = timers.Where(i => _tvDtoService.GetInternalChannelId(i.Item2.Name, i.Item1.ChannelId).Equals(gu
 700            }
 701
 702            if (!string.IsNullOrEmpty(query.SeriesTimerId))
 703            {
 704                var guid = new Guid(query.SeriesTimerId);
 705
 706                timers = timers
 707                    .Where(i => _tvDtoService.GetInternalSeriesTimerId(i.Item1.SeriesTimerId).Equals(guid));
 708            }
 709
 710            if (!string.IsNullOrEmpty(query.Id))
 711            {
 712                timers = timers
 713                    .Where(i => string.Equals(_tvDtoService.GetInternalTimerId(i.Item1.Id), query.Id, StringComparison.O
 714            }
 715
 716            var returnArray = timers
 717                .Select(i => i.Item1)
 718                .OrderBy(i => i.StartDate)
 719                .ToArray();
 720
 721            return new QueryResult<TimerInfo>(returnArray);
 722        }
 723
 724        public async Task<QueryResult<TimerInfoDto>> GetTimers(TimerQuery query, CancellationToken cancellationToken)
 725        {
 726            var tasks = _services.Select(async i =>
 727            {
 728                try
 729                {
 730                    var recs = await i.GetTimersAsync(cancellationToken).ConfigureAwait(false);
 731                    return recs.Select(r => new Tuple<TimerInfo, ILiveTvService>(r, i));
 732                }
 733                catch (Exception ex)
 734                {
 735                    _logger.LogError(ex, "Error getting recordings");
 736                    return new List<Tuple<TimerInfo, ILiveTvService>>();
 737                }
 738            });
 739            var results = await Task.WhenAll(tasks).ConfigureAwait(false);
 740            var timers = results.SelectMany(i => i.ToList());
 741
 742            if (query.IsActive.HasValue)
 743            {
 744                if (query.IsActive.Value)
 745                {
 746                    timers = timers.Where(i => i.Item1.Status == RecordingStatus.InProgress);
 747                }
 748                else
 749                {
 750                    timers = timers.Where(i => i.Item1.Status != RecordingStatus.InProgress);
 751                }
 752            }
 753
 754            if (query.IsScheduled.HasValue)
 755            {
 756                if (query.IsScheduled.Value)
 757                {
 758                    timers = timers.Where(i => i.Item1.Status == RecordingStatus.New);
 759                }
 760                else
 761                {
 762                    timers = timers.Where(i => i.Item1.Status != RecordingStatus.New);
 763                }
 764            }
 765
 766            if (!string.IsNullOrEmpty(query.ChannelId))
 767            {
 768                var guid = new Guid(query.ChannelId);
 769                timers = timers.Where(i => _tvDtoService.GetInternalChannelId(i.Item2.Name, i.Item1.ChannelId).Equals(gu
 770            }
 771
 772            if (!string.IsNullOrEmpty(query.SeriesTimerId))
 773            {
 774                var guid = new Guid(query.SeriesTimerId);
 775
 776                timers = timers
 777                    .Where(i => _tvDtoService.GetInternalSeriesTimerId(i.Item1.SeriesTimerId).Equals(guid));
 778            }
 779
 780            if (!string.IsNullOrEmpty(query.Id))
 781            {
 782                timers = timers
 783                    .Where(i => string.Equals(_tvDtoService.GetInternalTimerId(i.Item1.Id), query.Id, StringComparison.O
 784            }
 785
 786            var returnList = new List<TimerInfoDto>();
 787
 788            foreach (var i in timers)
 789            {
 790                var program = string.IsNullOrEmpty(i.Item1.ProgramId) ?
 791                    null :
 792                    _libraryManager.GetItemById(_tvDtoService.GetInternalProgramId(i.Item1.ProgramId)) as LiveTvProgram;
 793
 794                var channel = string.IsNullOrEmpty(i.Item1.ChannelId) ? null : _libraryManager.GetItemById(_tvDtoService
 795
 796                returnList.Add(_tvDtoService.GetTimerInfoDto(i.Item1, i.Item2, program, channel));
 797            }
 798
 799            var returnArray = returnList
 800                .OrderBy(i => i.StartDate)
 801                .ToArray();
 802
 803            return new QueryResult<TimerInfoDto>(returnArray);
 804        }
 805
 806        public async Task CancelTimer(string id)
 807        {
 808            var timer = await GetTimer(id, CancellationToken.None).ConfigureAwait(false);
 809
 810            if (timer is null)
 811            {
 812                throw new ResourceNotFoundException(string.Format(CultureInfo.InvariantCulture, "Timer with Id {0} not f
 813            }
 814
 815            var service = GetService(timer.ServiceName);
 816
 817            await service.CancelTimerAsync(timer.ExternalId, CancellationToken.None).ConfigureAwait(false);
 818
 819            if (service is not DefaultLiveTvService)
 820            {
 821                TimerCancelled?.Invoke(this, new GenericEventArgs<TimerEventInfo>(new TimerEventInfo(id)));
 822            }
 823        }
 824
 825        public async Task CancelSeriesTimer(string id)
 826        {
 827            var timer = await GetSeriesTimer(id, CancellationToken.None).ConfigureAwait(false);
 828
 829            if (timer is null)
 830            {
 831                throw new ResourceNotFoundException(string.Format(CultureInfo.InvariantCulture, "SeriesTimer with Id {0}
 832            }
 833
 834            var service = GetService(timer.ServiceName);
 835
 836            await service.CancelSeriesTimerAsync(timer.ExternalId, CancellationToken.None).ConfigureAwait(false);
 837
 838            SeriesTimerCancelled?.Invoke(this, new GenericEventArgs<TimerEventInfo>(new TimerEventInfo(id)));
 839        }
 840
 841        public async Task<TimerInfoDto> GetTimer(string id, CancellationToken cancellationToken)
 842        {
 843            var results = await GetTimers(
 844                new TimerQuery
 845                {
 846                    Id = id
 847                },
 848                cancellationToken).ConfigureAwait(false);
 849
 850            return results.Items.FirstOrDefault(i => string.Equals(i.Id, id, StringComparison.OrdinalIgnoreCase));
 851        }
 852
 853        public async Task<SeriesTimerInfoDto> GetSeriesTimer(string id, CancellationToken cancellationToken)
 854        {
 855            var results = await GetSeriesTimers(new SeriesTimerQuery(), cancellationToken).ConfigureAwait(false);
 856
 857            return results.Items.FirstOrDefault(i => string.Equals(i.Id, id, StringComparison.OrdinalIgnoreCase));
 858        }
 859
 860        private async Task<QueryResult<SeriesTimerInfo>> GetSeriesTimersInternal(SeriesTimerQuery query, CancellationTok
 861        {
 862            var tasks = _services.Select(async i =>
 863            {
 864                try
 865                {
 866                    var recs = await i.GetSeriesTimersAsync(cancellationToken).ConfigureAwait(false);
 867                    return recs.Select(r =>
 868                    {
 869                        r.ServiceName = i.Name;
 870                        return new Tuple<SeriesTimerInfo, ILiveTvService>(r, i);
 871                    });
 872                }
 873                catch (Exception ex)
 874                {
 875                    _logger.LogError(ex, "Error getting recordings");
 876                    return new List<Tuple<SeriesTimerInfo, ILiveTvService>>();
 877                }
 878            });
 879            var results = await Task.WhenAll(tasks).ConfigureAwait(false);
 880            var timers = results.SelectMany(i => i.ToList());
 881
 882            if (string.Equals(query.SortBy, "Priority", StringComparison.OrdinalIgnoreCase))
 883            {
 884                timers = query.SortOrder == SortOrder.Descending ?
 885                    timers.OrderBy(i => i.Item1.Priority).ThenByStringDescending(i => i.Item1.Name) :
 886                    timers.OrderByDescending(i => i.Item1.Priority).ThenByString(i => i.Item1.Name);
 887            }
 888            else
 889            {
 890                timers = query.SortOrder == SortOrder.Descending ?
 891                    timers.OrderByStringDescending(i => i.Item1.Name) :
 892                    timers.OrderByString(i => i.Item1.Name);
 893            }
 894
 895            var returnArray = timers
 896                .Select(i => i.Item1)
 897                .ToArray();
 898
 899            return new QueryResult<SeriesTimerInfo>(returnArray);
 900        }
 901
 902        public async Task<QueryResult<SeriesTimerInfoDto>> GetSeriesTimers(SeriesTimerQuery query, CancellationToken can
 903        {
 904            var tasks = _services.Select(async i =>
 905            {
 906                try
 907                {
 908                    var recs = await i.GetSeriesTimersAsync(cancellationToken).ConfigureAwait(false);
 909                    return recs.Select(r => new Tuple<SeriesTimerInfo, ILiveTvService>(r, i));
 910                }
 911                catch (Exception ex)
 912                {
 913                    _logger.LogError(ex, "Error getting recordings");
 914                    return new List<Tuple<SeriesTimerInfo, ILiveTvService>>();
 915                }
 916            });
 917            var results = await Task.WhenAll(tasks).ConfigureAwait(false);
 918            var timers = results.SelectMany(i => i.ToList());
 919
 920            if (string.Equals(query.SortBy, "Priority", StringComparison.OrdinalIgnoreCase))
 921            {
 922                timers = query.SortOrder == SortOrder.Descending ?
 923                    timers.OrderBy(i => i.Item1.Priority).ThenByStringDescending(i => i.Item1.Name) :
 924                    timers.OrderByDescending(i => i.Item1.Priority).ThenByString(i => i.Item1.Name);
 925            }
 926            else
 927            {
 928                timers = query.SortOrder == SortOrder.Descending ?
 929                    timers.OrderByStringDescending(i => i.Item1.Name) :
 930                    timers.OrderByString(i => i.Item1.Name);
 931            }
 932
 933            var returnArray = timers
 934                .Select(i =>
 935                {
 936                    string channelName = null;
 937
 938                    if (!string.IsNullOrEmpty(i.Item1.ChannelId))
 939                    {
 940                        var internalChannelId = _tvDtoService.GetInternalChannelId(i.Item2.Name, i.Item1.ChannelId);
 941                        var channel = _libraryManager.GetItemById(internalChannelId);
 942                        channelName = channel?.Name;
 943                    }
 944
 945                    return _tvDtoService.GetSeriesTimerInfoDto(i.Item1, i.Item2, channelName);
 946                })
 947                .ToArray();
 948
 949            return new QueryResult<SeriesTimerInfoDto>(returnArray);
 950        }
 951
 952        public void AddChannelInfo(IReadOnlyCollection<(BaseItemDto ItemDto, LiveTvChannel Channel)> items, DtoOptions o
 953        {
 0954            var now = DateTime.UtcNow;
 955
 0956            var channelIds = items.Select(i => i.Channel.Id).Distinct().ToArray();
 957
 0958            var programs = options.AddCurrentProgram ? _libraryManager.GetItemList(new InternalItemsQuery(user)
 0959            {
 0960                IncludeItemTypes = new[] { BaseItemKind.LiveTvProgram },
 0961                ChannelIds = channelIds,
 0962                MaxStartDate = now,
 0963                MinEndDate = now,
 0964                Limit = channelIds.Length,
 0965                OrderBy = new[] { (ItemSortBy.StartDate, SortOrder.Ascending) },
 0966                TopParentIds = new[] { GetInternalLiveTvFolder(CancellationToken.None).Id },
 0967                DtoOptions = options
 0968            }) : new List<BaseItem>();
 969
 0970            RemoveFields(options);
 971
 0972            var currentProgramsList = new List<BaseItem>();
 0973            var currentChannelsDict = new Dictionary<Guid, BaseItemDto>();
 974
 0975            var addCurrentProgram = options.AddCurrentProgram;
 976
 0977            foreach (var (dto, channel) in items)
 978            {
 0979                dto.Number = channel.Number;
 0980                dto.ChannelNumber = channel.Number;
 0981                dto.ChannelType = channel.ChannelType;
 982
 0983                currentChannelsDict[dto.Id] = dto;
 984
 0985                if (addCurrentProgram)
 986                {
 0987                    var currentProgram = programs.FirstOrDefault(i => channel.Id.Equals(i.ChannelId));
 988
 0989                    if (currentProgram is not null)
 990                    {
 0991                        currentProgramsList.Add(currentProgram);
 992                    }
 993                }
 994            }
 995
 0996            if (addCurrentProgram)
 997            {
 0998                var currentProgramDtos = _dtoService.GetBaseItemDtos(currentProgramsList, options, user);
 999
 01000                foreach (var programDto in currentProgramDtos)
 1001                {
 01002                    if (programDto.ChannelId.HasValue && currentChannelsDict.TryGetValue(programDto.ChannelId.Value, out
 1003                    {
 01004                        channelDto.CurrentProgram = programDto;
 1005                    }
 1006                }
 1007            }
 01008        }
 1009
 1010        private async Task<Tuple<SeriesTimerInfo, ILiveTvService>> GetNewTimerDefaultsInternal(CancellationToken cancell
 1011        {
 1012            ILiveTvService service = null;
 1013            ProgramInfo programInfo = null;
 1014
 1015            if (program is not null)
 1016            {
 1017                service = GetService(program);
 1018
 1019                var channel = _libraryManager.GetItemById(program.ChannelId);
 1020
 1021                programInfo = new ProgramInfo
 1022                {
 1023                    Audio = program.Audio,
 1024                    ChannelId = channel.ExternalId,
 1025                    CommunityRating = program.CommunityRating,
 1026                    EndDate = program.EndDate ?? DateTime.MinValue,
 1027                    EpisodeTitle = program.EpisodeTitle,
 1028                    Genres = program.Genres.ToList(),
 1029                    Id = program.ExternalId,
 1030                    IsHD = program.IsHD,
 1031                    IsKids = program.IsKids,
 1032                    IsLive = program.IsLive,
 1033                    IsMovie = program.IsMovie,
 1034                    IsNews = program.IsNews,
 1035                    IsPremiere = program.IsPremiere,
 1036                    IsRepeat = program.IsRepeat,
 1037                    IsSeries = program.IsSeries,
 1038                    IsSports = program.IsSports,
 1039                    OriginalAirDate = program.PremiereDate,
 1040                    Overview = program.Overview,
 1041                    StartDate = program.StartDate,
 1042                    // ImagePath = program.ExternalImagePath,
 1043                    Name = program.Name,
 1044                    OfficialRating = program.OfficialRating
 1045                };
 1046            }
 1047
 1048            service ??= _services[0];
 1049
 1050            var info = await service.GetNewTimerDefaultsAsync(cancellationToken, programInfo).ConfigureAwait(false);
 1051
 1052            info.RecordAnyTime = true;
 1053            info.Days = new List<DayOfWeek>
 1054            {
 1055                DayOfWeek.Sunday,
 1056                DayOfWeek.Monday,
 1057                DayOfWeek.Tuesday,
 1058                DayOfWeek.Wednesday,
 1059                DayOfWeek.Thursday,
 1060                DayOfWeek.Friday,
 1061                DayOfWeek.Saturday
 1062            };
 1063
 1064            info.Id = null;
 1065
 1066            return new Tuple<SeriesTimerInfo, ILiveTvService>(info, service);
 1067        }
 1068
 1069        public async Task<SeriesTimerInfoDto> GetNewTimerDefaults(CancellationToken cancellationToken)
 1070        {
 1071            var info = await GetNewTimerDefaultsInternal(cancellationToken).ConfigureAwait(false);
 1072
 1073            return _tvDtoService.GetSeriesTimerInfoDto(info.Item1, info.Item2, null);
 1074        }
 1075
 1076        public async Task<SeriesTimerInfoDto> GetNewTimerDefaults(string programId, CancellationToken cancellationToken)
 1077        {
 1078            var program = (LiveTvProgram)_libraryManager.GetItemById(programId);
 1079            var programDto = await GetProgram(programId, cancellationToken).ConfigureAwait(false);
 1080
 1081            var defaults = await GetNewTimerDefaultsInternal(cancellationToken, program).ConfigureAwait(false);
 1082            var info = _tvDtoService.GetSeriesTimerInfoDto(defaults.Item1, defaults.Item2, null);
 1083
 1084            info.Days = defaults.Item1.Days.ToArray();
 1085
 1086            info.DayPattern = _tvDtoService.GetDayPattern(info.Days);
 1087
 1088            info.Name = program.Name;
 1089            info.ChannelId = programDto.ChannelId ?? Guid.Empty;
 1090            info.ChannelName = programDto.ChannelName;
 1091            info.StartDate = program.StartDate;
 1092            info.Name = program.Name;
 1093            info.Overview = program.Overview;
 1094            info.ProgramId = programDto.Id.ToString("N", CultureInfo.InvariantCulture);
 1095            info.ExternalProgramId = program.ExternalId;
 1096
 1097            if (program.EndDate.HasValue)
 1098            {
 1099                info.EndDate = program.EndDate.Value;
 1100            }
 1101
 1102            return info;
 1103        }
 1104
 1105        public async Task CreateTimer(TimerInfoDto timer, CancellationToken cancellationToken)
 1106        {
 1107            var service = GetService(timer.ServiceName);
 1108
 1109            var info = await _tvDtoService.GetTimerInfo(timer, true, this, cancellationToken).ConfigureAwait(false);
 1110
 1111            // Set priority from default values
 1112            var defaultValues = await service.GetNewTimerDefaultsAsync(cancellationToken).ConfigureAwait(false);
 1113            info.Priority = defaultValues.Priority;
 1114
 1115            string newTimerId = null;
 1116            if (service is ISupportsNewTimerIds supportsNewTimerIds)
 1117            {
 1118                newTimerId = await supportsNewTimerIds.CreateTimer(info, cancellationToken).ConfigureAwait(false);
 1119                newTimerId = _tvDtoService.GetInternalTimerId(newTimerId);
 1120            }
 1121            else
 1122            {
 1123                await service.CreateTimerAsync(info, cancellationToken).ConfigureAwait(false);
 1124            }
 1125
 1126            _logger.LogInformation("New recording scheduled");
 1127
 1128            if (service is not DefaultLiveTvService)
 1129            {
 1130                TimerCreated?.Invoke(this, new GenericEventArgs<TimerEventInfo>(
 1131                    new TimerEventInfo(newTimerId)
 1132                    {
 1133                        ProgramId = _tvDtoService.GetInternalProgramId(info.ProgramId)
 1134                    }));
 1135            }
 1136        }
 1137
 1138        public async Task CreateSeriesTimer(SeriesTimerInfoDto timer, CancellationToken cancellationToken)
 1139        {
 1140            var service = GetService(timer.ServiceName);
 1141
 1142            var info = await _tvDtoService.GetSeriesTimerInfo(timer, true, this, cancellationToken).ConfigureAwait(false
 1143
 1144            // Set priority from default values
 1145            var defaultValues = await service.GetNewTimerDefaultsAsync(cancellationToken).ConfigureAwait(false);
 1146            info.Priority = defaultValues.Priority;
 1147
 1148            string newTimerId = null;
 1149            if (service is ISupportsNewTimerIds supportsNewTimerIds)
 1150            {
 1151                newTimerId = await supportsNewTimerIds.CreateSeriesTimer(info, cancellationToken).ConfigureAwait(false);
 1152                newTimerId = _tvDtoService.GetInternalSeriesTimerId(newTimerId).ToString("N", CultureInfo.InvariantCultu
 1153            }
 1154            else
 1155            {
 1156                await service.CreateSeriesTimerAsync(info, cancellationToken).ConfigureAwait(false);
 1157            }
 1158
 1159            SeriesTimerCreated?.Invoke(this, new GenericEventArgs<TimerEventInfo>(
 1160                new TimerEventInfo(newTimerId)
 1161                {
 1162                    ProgramId = _tvDtoService.GetInternalProgramId(info.ProgramId)
 1163                }));
 1164        }
 1165
 1166        public async Task UpdateTimer(TimerInfoDto timer, CancellationToken cancellationToken)
 1167        {
 1168            var info = await _tvDtoService.GetTimerInfo(timer, false, this, cancellationToken).ConfigureAwait(false);
 1169
 1170            var service = GetService(timer.ServiceName);
 1171
 1172            await service.UpdateTimerAsync(info, cancellationToken).ConfigureAwait(false);
 1173        }
 1174
 1175        public async Task UpdateSeriesTimer(SeriesTimerInfoDto timer, CancellationToken cancellationToken)
 1176        {
 1177            var info = await _tvDtoService.GetSeriesTimerInfo(timer, false, this, cancellationToken).ConfigureAwait(fals
 1178
 1179            var service = GetService(timer.ServiceName);
 1180
 1181            await service.UpdateSeriesTimerAsync(info, cancellationToken).ConfigureAwait(false);
 1182        }
 1183
 1184        private LiveTvServiceInfo[] GetServiceInfos()
 1185        {
 01186            return Services.Select(GetServiceInfo).ToArray();
 1187        }
 1188
 1189        private static LiveTvServiceInfo GetServiceInfo(ILiveTvService service)
 1190        {
 01191            return new LiveTvServiceInfo
 01192            {
 01193                Name = service.Name
 01194            };
 1195        }
 1196
 1197        public LiveTvInfo GetLiveTvInfo(CancellationToken cancellationToken)
 1198        {
 01199            var services = GetServiceInfos();
 1200
 01201            var info = new LiveTvInfo
 01202            {
 01203                Services = services,
 01204                IsEnabled = services.Length > 0,
 01205                EnabledUsers = _userManager.Users
 01206                    .Where(IsLiveTvEnabled)
 01207                    .Select(i => i.Id.ToString("N", CultureInfo.InvariantCulture))
 01208                    .ToArray()
 01209            };
 1210
 01211            return info;
 1212        }
 1213
 1214        private bool IsLiveTvEnabled(User user)
 1215        {
 11216            return user.HasPermission(PermissionKind.EnableLiveTvAccess) && (Services.Count > 1 || _config.GetLiveTvConf
 1217        }
 1218
 1219        public IEnumerable<User> GetEnabledUsers()
 1220        {
 11221            return _userManager.Users
 11222                .Where(IsLiveTvEnabled);
 1223        }
 1224
 1225        /// <summary>
 1226        /// Resets the tuner.
 1227        /// </summary>
 1228        /// <param name="id">The identifier.</param>
 1229        /// <param name="cancellationToken">The cancellation token.</param>
 1230        /// <returns>Task.</returns>
 1231        public Task ResetTuner(string id, CancellationToken cancellationToken)
 1232        {
 01233            var parts = id.Split('_', 2);
 1234
 01235            var service = _services.FirstOrDefault(i => string.Equals(i.GetType().FullName.GetMD5().ToString("N", Cultur
 1236
 01237            if (service is null)
 1238            {
 01239                throw new ArgumentException("Service not found.");
 1240            }
 1241
 01242            return service.ResetTuner(parts[1], cancellationToken);
 1243        }
 1244
 1245        private static void RemoveFields(DtoOptions options)
 1246        {
 01247            var fields = options.Fields.ToList();
 1248
 01249            fields.Remove(ItemFields.CanDelete);
 01250            fields.Remove(ItemFields.CanDownload);
 01251            fields.Remove(ItemFields.DisplayPreferencesId);
 01252            fields.Remove(ItemFields.Etag);
 01253            options.Fields = fields.ToArray();
 01254        }
 1255
 1256        public Folder GetInternalLiveTvFolder(CancellationToken cancellationToken)
 1257        {
 01258            var name = _localization.GetLocalizedString("HeaderLiveTV");
 01259            return _libraryManager.GetNamedView(name, CollectionType.livetv, name);
 1260        }
 1261
 1262        /// <inheritdoc />
 1263        public Task<BaseItem[]> GetRecordingFoldersAsync(User user)
 01264            => GetRecordingFoldersAsync(user, false);
 1265
 1266        private async Task<BaseItem[]> GetRecordingFoldersAsync(User user, bool refreshChannels)
 1267        {
 1268            var folders = _recordingsManager.GetRecordingFolders()
 1269                .SelectMany(i => i.Locations)
 1270                .Distinct(StringComparer.OrdinalIgnoreCase)
 1271                .Select(i => _libraryManager.FindByPath(i, true))
 1272                .Where(i => i is not null && i.IsVisibleStandalone(user))
 1273                .SelectMany(i => _libraryManager.GetCollectionFolders(i))
 1274                .DistinctBy(x => x.Id)
 1275                .OrderBy(i => i.SortName)
 1276                .ToList();
 1277
 1278            var channels = await _channelManager.GetChannelsInternalAsync(new MediaBrowser.Model.Channels.ChannelQuery
 1279            {
 1280                UserId = user.Id,
 1281                IsRecordingsFolder = true,
 1282                RefreshLatestChannelItems = refreshChannels
 1283            }).ConfigureAwait(false);
 1284
 1285            folders.AddRange(channels.Items);
 1286
 1287            return folders.Cast<BaseItem>().ToArray();
 1288        }
 1289    }
 1290}

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)
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.Data.Entities.User,System.Boolean)
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.Data.Entities.User)
AddInfoToRecordingDto(MediaBrowser.Controller.Entities.BaseItem,MediaBrowser.Model.Dto.BaseItemDto,MediaBrowser.Controller.LiveTv.ActiveRecordingInfo,Jellyfin.Data.Entities.User)
AddChannelInfo(System.Collections.Generic.IReadOnlyCollection`1<System.ValueTuple`2<MediaBrowser.Model.Dto.BaseItemDto,MediaBrowser.Controller.LiveTv.LiveTvChannel>>,MediaBrowser.Controller.Dto.DtoOptions,Jellyfin.Data.Entities.User)
GetServiceInfos()
GetServiceInfo(MediaBrowser.Controller.LiveTv.ILiveTvService)
GetLiveTvInfo(System.Threading.CancellationToken)
IsLiveTvEnabled(Jellyfin.Data.Entities.User)
GetEnabledUsers()
ResetTuner(System.String,System.Threading.CancellationToken)
RemoveFields(MediaBrowser.Controller.Dto.DtoOptions)
GetInternalLiveTvFolder(System.Threading.CancellationToken)
GetRecordingFoldersAsync(Jellyfin.Data.Entities.User)