< 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: 1292
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;
 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 = new[] { BaseItemKind.LiveTvChannel },
 0129                TopParentIds = new[] { 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        {
 179            var program = _libraryManager.GetItemById(id);
 180
 181            var dto = _dtoService.GetBaseItemDto(program, new DtoOptions(), user);
 182
 183            var list = new List<(BaseItemDto ItemDto, string ExternalId, string ExternalSeriesId)>
 184            {
 185                (dto, program.ExternalId, program.ExternalSeriesId)
 186            };
 187
 188            await AddRecordingInfo(list, cancellationToken).ConfigureAwait(false);
 189
 190            return dto;
 191        }
 192
 193        public async Task<QueryResult<BaseItemDto>> GetPrograms(InternalItemsQuery query, DtoOptions options, Cancellati
 194        {
 195            var user = query.User;
 196
 197            var topFolder = GetInternalLiveTvFolder(cancellationToken);
 198
 199            if (query.OrderBy.Count == 0)
 200            {
 201                // Unless something else was specified, order by start date to take advantage of a specialized index
 202                query.OrderBy = new[]
 203                {
 204                    (ItemSortBy.StartDate, SortOrder.Ascending)
 205                };
 206            }
 207
 208            RemoveFields(options);
 209
 210            var internalQuery = new InternalItemsQuery(user)
 211            {
 212                IncludeItemTypes = new[] { BaseItemKind.LiveTvProgram },
 213                MinEndDate = query.MinEndDate,
 214                MinStartDate = query.MinStartDate,
 215                MaxEndDate = query.MaxEndDate,
 216                MaxStartDate = query.MaxStartDate,
 217                ChannelIds = query.ChannelIds,
 218                IsMovie = query.IsMovie,
 219                IsSeries = query.IsSeries,
 220                IsSports = query.IsSports,
 221                IsKids = query.IsKids,
 222                IsNews = query.IsNews,
 223                Genres = query.Genres,
 224                GenreIds = query.GenreIds,
 225                StartIndex = query.StartIndex,
 226                Limit = query.Limit,
 227                OrderBy = query.OrderBy,
 228                EnableTotalRecordCount = query.EnableTotalRecordCount,
 229                TopParentIds = new[] { topFolder.Id },
 230                Name = query.Name,
 231                DtoOptions = options,
 232                HasAired = query.HasAired,
 233                IsAiring = query.IsAiring
 234            };
 235
 236            if (!string.IsNullOrWhiteSpace(query.SeriesTimerId))
 237            {
 238                var seriesTimers = await GetSeriesTimersInternal(new SeriesTimerQuery(), cancellationToken).ConfigureAwa
 239                var seriesTimer = seriesTimers.Items.FirstOrDefault(i => string.Equals(_tvDtoService.GetInternalSeriesTi
 240                if (seriesTimer is not null)
 241                {
 242                    internalQuery.ExternalSeriesId = seriesTimer.SeriesId;
 243
 244                    if (string.IsNullOrWhiteSpace(seriesTimer.SeriesId))
 245                    {
 246                        // Better to return nothing than every program in the database
 247                        return new QueryResult<BaseItemDto>();
 248                    }
 249                }
 250                else
 251                {
 252                    // Better to return nothing than every program in the database
 253                    return new QueryResult<BaseItemDto>();
 254                }
 255            }
 256
 257            var queryResult = _libraryManager.QueryItems(internalQuery);
 258
 259            var returnArray = _dtoService.GetBaseItemDtos(queryResult.Items, options, user);
 260
 261            return new QueryResult<BaseItemDto>(
 262                query.StartIndex,
 263                queryResult.TotalRecordCount,
 264                returnArray);
 265        }
 266
 267        public QueryResult<BaseItem> GetRecommendedProgramsInternal(InternalItemsQuery query, DtoOptions options, Cancel
 268        {
 0269            var user = query.User;
 270
 0271            var topFolder = GetInternalLiveTvFolder(cancellationToken);
 272
 0273            var internalQuery = new InternalItemsQuery(user)
 0274            {
 0275                IncludeItemTypes = new[] { BaseItemKind.LiveTvProgram },
 0276                IsAiring = query.IsAiring,
 0277                HasAired = query.HasAired,
 0278                IsNews = query.IsNews,
 0279                IsMovie = query.IsMovie,
 0280                IsSeries = query.IsSeries,
 0281                IsSports = query.IsSports,
 0282                IsKids = query.IsKids,
 0283                EnableTotalRecordCount = query.EnableTotalRecordCount,
 0284                OrderBy = new[] { (ItemSortBy.StartDate, SortOrder.Ascending) },
 0285                TopParentIds = new[] { topFolder.Id },
 0286                DtoOptions = options,
 0287                GenreIds = query.GenreIds
 0288            };
 289
 0290            if (query.Limit.HasValue)
 291            {
 0292                internalQuery.Limit = Math.Max(query.Limit.Value * 4, 200);
 293            }
 294
 0295            var programList = _libraryManager.QueryItems(internalQuery).Items;
 0296            var totalCount = programList.Count;
 297
 0298            var orderedPrograms = programList.Cast<LiveTvProgram>().OrderBy(i => i.StartDate.Date);
 299
 0300            if (query.IsAiring ?? false)
 301            {
 0302                orderedPrograms = orderedPrograms
 0303                    .ThenByDescending(i => GetRecommendationScore(i, user, true));
 304            }
 305
 0306            IEnumerable<BaseItem> programs = orderedPrograms;
 307
 0308            if (query.Limit.HasValue)
 309            {
 0310                programs = programs.Take(query.Limit.Value);
 311            }
 312
 0313            return new QueryResult<BaseItem>(
 0314                query.StartIndex,
 0315                totalCount,
 0316                programs.ToArray());
 317        }
 318
 319        public Task<QueryResult<BaseItemDto>> GetRecommendedProgramsAsync(InternalItemsQuery query, DtoOptions options, 
 320        {
 0321            if (!(query.IsAiring ?? false))
 322            {
 0323                return GetPrograms(query, options, cancellationToken);
 324            }
 325
 0326            RemoveFields(options);
 327
 0328            var internalResult = GetRecommendedProgramsInternal(query, options, cancellationToken);
 329
 0330            return Task.FromResult(new QueryResult<BaseItemDto>(
 0331                query.StartIndex,
 0332                internalResult.TotalRecordCount,
 0333                _dtoService.GetBaseItemDtos(internalResult.Items, options, query.User)));
 334        }
 335
 336        private int GetRecommendationScore(LiveTvProgram program, User user, bool factorChannelWatchCount)
 337        {
 0338            var score = 0;
 339
 0340            if (program.IsLive)
 341            {
 0342                score++;
 343            }
 344
 0345            if (program.IsSeries && !program.IsRepeat)
 346            {
 0347                score++;
 348            }
 349
 0350            var channel = _libraryManager.GetItemById(program.ChannelId);
 351
 0352            if (channel is null)
 353            {
 0354                return score;
 355            }
 356
 0357            var channelUserdata = _userDataManager.GetUserData(user, channel);
 358
 0359            if (channelUserdata.Likes.HasValue)
 360            {
 0361                score += channelUserdata.Likes.Value ? 2 : -2;
 362            }
 363
 0364            if (channelUserdata.IsFavorite)
 365            {
 0366                score += 3;
 367            }
 368
 0369            if (factorChannelWatchCount)
 370            {
 0371                score += channelUserdata.PlayCount;
 372            }
 373
 0374            return score;
 375        }
 376
 377        private async Task AddRecordingInfo(IEnumerable<(BaseItemDto ItemDto, string ExternalId, string ExternalSeriesId
 378        {
 379            IReadOnlyList<TimerInfo> timerList = null;
 380            IReadOnlyList<SeriesTimerInfo> seriesTimerList = null;
 381
 382            foreach (var programTuple in programs)
 383            {
 384                var program = programTuple.ItemDto;
 385                var externalProgramId = programTuple.ExternalId;
 386                string externalSeriesId = programTuple.ExternalSeriesId;
 387
 388                timerList ??= (await GetTimersInternal(new TimerQuery(), cancellationToken).ConfigureAwait(false)).Items
 389
 390                var timer = timerList.FirstOrDefault(i => string.Equals(i.ProgramId, externalProgramId, StringComparison
 391                var foundSeriesTimer = false;
 392
 393                if (timer is not null)
 394                {
 395                    if (timer.Status != RecordingStatus.Cancelled && timer.Status != RecordingStatus.Error)
 396                    {
 397                        program.TimerId = _tvDtoService.GetInternalTimerId(timer.Id);
 398
 399                        program.Status = timer.Status.ToString();
 400                    }
 401
 402                    if (!string.IsNullOrEmpty(timer.SeriesTimerId))
 403                    {
 404                        program.SeriesTimerId = _tvDtoService.GetInternalSeriesTimerId(timer.SeriesTimerId)
 405                            .ToString("N", CultureInfo.InvariantCulture);
 406
 407                        foundSeriesTimer = true;
 408                    }
 409                }
 410
 411                if (foundSeriesTimer || string.IsNullOrWhiteSpace(externalSeriesId))
 412                {
 413                    continue;
 414                }
 415
 416                seriesTimerList ??= (await GetSeriesTimersInternal(new SeriesTimerQuery(), cancellationToken).ConfigureA
 417
 418                var seriesTimer = seriesTimerList.FirstOrDefault(i => string.Equals(i.SeriesId, externalSeriesId, String
 419
 420                if (seriesTimer is not null)
 421                {
 422                    program.SeriesTimerId = _tvDtoService.GetInternalSeriesTimerId(seriesTimer.Id)
 423                        .ToString("N", CultureInfo.InvariantCulture);
 424                }
 425            }
 426        }
 427
 428        private async Task<QueryResult<BaseItem>> GetEmbyRecordingsAsync(RecordingQuery query, DtoOptions dtoOptions, Us
 429        {
 430            if (user is null)
 431            {
 432                return new QueryResult<BaseItem>();
 433            }
 434
 435            var folders = await GetRecordingFoldersAsync(user, true).ConfigureAwait(false);
 436            var folderIds = Array.ConvertAll(folders, x => x.Id);
 437
 438            var excludeItemTypes = new List<BaseItemKind>();
 439
 440            if (folderIds.Length == 0)
 441            {
 442                return new QueryResult<BaseItem>();
 443            }
 444
 445            var includeItemTypes = new List<BaseItemKind>();
 446            var genres = new List<string>();
 447
 448            if (query.IsMovie.HasValue)
 449            {
 450                if (query.IsMovie.Value)
 451                {
 452                    includeItemTypes.Add(BaseItemKind.Movie);
 453                }
 454                else
 455                {
 456                    excludeItemTypes.Add(BaseItemKind.Movie);
 457                }
 458            }
 459
 460            if (query.IsSeries.HasValue)
 461            {
 462                if (query.IsSeries.Value)
 463                {
 464                    includeItemTypes.Add(BaseItemKind.Episode);
 465                }
 466                else
 467                {
 468                    excludeItemTypes.Add(BaseItemKind.Episode);
 469                }
 470            }
 471
 472            if (query.IsSports ?? false)
 473            {
 474                genres.Add("Sports");
 475            }
 476
 477            if (query.IsKids ?? false)
 478            {
 479                genres.Add("Kids");
 480                genres.Add("Children");
 481                genres.Add("Family");
 482            }
 483
 484            var limit = query.Limit;
 485
 486            if (query.IsInProgress ?? false)
 487            {
 488                // limit = (query.Limit ?? 10) * 2;
 489                limit = null;
 490
 491                // var allActivePaths = EmbyTV.EmbyTV.Current.GetAllActiveRecordings().Select(i => i.Path).ToArray();
 492                // var items = allActivePaths.Select(i => _libraryManager.FindByPath(i, false)).Where(i => i is not null
 493
 494                // return new QueryResult<BaseItem>
 495                // {
 496                //    Items = items,
 497                //    TotalRecordCount = items.Length
 498                // };
 499
 500                dtoOptions.Fields = dtoOptions.Fields.Concat(new[] { ItemFields.Tags }).Distinct().ToArray();
 501            }
 502
 503            var result = _libraryManager.GetItemsResult(new InternalItemsQuery(user)
 504            {
 505                MediaTypes = new[] { MediaType.Video },
 506                Recursive = true,
 507                AncestorIds = folderIds,
 508                IsFolder = false,
 509                IsVirtualItem = false,
 510                Limit = limit,
 511                StartIndex = query.StartIndex,
 512                OrderBy = new[] { (ItemSortBy.DateCreated, SortOrder.Descending) },
 513                EnableTotalRecordCount = query.EnableTotalRecordCount,
 514                IncludeItemTypes = includeItemTypes.ToArray(),
 515                ExcludeItemTypes = excludeItemTypes.ToArray(),
 516                Genres = genres.ToArray(),
 517                DtoOptions = dtoOptions
 518            });
 519
 520            if (query.IsInProgress ?? false)
 521            {
 522                // TODO: Fix The co-variant conversion between Video[] and BaseItem[], this can generate runtime issues.
 523                result.Items = result
 524                    .Items
 525                    .OfType<Video>()
 526                    .Where(i => !i.IsCompleteMedia)
 527                    .ToArray();
 528
 529                result.TotalRecordCount = result.Items.Count;
 530            }
 531
 532            return result;
 533        }
 534
 535        public Task AddInfoToProgramDto(IReadOnlyCollection<(BaseItem Item, BaseItemDto ItemDto)> programs, IReadOnlyLis
 536        {
 0537            var programTuples = new List<(BaseItemDto Dto, string ExternalId, string ExternalSeriesId)>();
 0538            var hasChannelImage = fields.Contains(ItemFields.ChannelImage);
 0539            var hasChannelInfo = fields.Contains(ItemFields.ChannelInfo);
 540
 0541            foreach (var (item, dto) in programs)
 542            {
 0543                var program = (LiveTvProgram)item;
 544
 0545                dto.StartDate = program.StartDate;
 0546                dto.EpisodeTitle = program.EpisodeTitle;
 0547                dto.IsRepeat |= program.IsRepeat;
 0548                dto.IsMovie |= program.IsMovie;
 0549                dto.IsSeries |= program.IsSeries;
 0550                dto.IsSports |= program.IsSports;
 0551                dto.IsLive |= program.IsLive;
 0552                dto.IsNews |= program.IsNews;
 0553                dto.IsKids |= program.IsKids;
 0554                dto.IsPremiere |= program.IsPremiere;
 555
 0556                if (hasChannelInfo || hasChannelImage)
 557                {
 0558                    var channel = _libraryManager.GetItemById(program.ChannelId);
 559
 0560                    if (channel is LiveTvChannel liveChannel)
 561                    {
 0562                        dto.ChannelName = liveChannel.Name;
 0563                        dto.MediaType = liveChannel.MediaType;
 0564                        dto.ChannelNumber = liveChannel.Number;
 565
 0566                        if (hasChannelImage && liveChannel.HasImage(ImageType.Primary))
 567                        {
 0568                            dto.ChannelPrimaryImageTag = _tvDtoService.GetImageTag(liveChannel);
 569                        }
 570                    }
 571                }
 572
 0573                programTuples.Add((dto, program.ExternalId, program.ExternalSeriesId));
 574            }
 575
 0576            return AddRecordingInfo(programTuples, CancellationToken.None);
 577        }
 578
 579        public void AddInfoToRecordingDto(BaseItem item, BaseItemDto dto, ActiveRecordingInfo activeRecordingInfo, User 
 580        {
 0581            var info = activeRecordingInfo.Timer;
 582
 0583            var channel = string.IsNullOrWhiteSpace(info.ChannelId)
 0584                ? null
 0585                : _libraryManager.GetItemById(_tvDtoService.GetInternalChannelId(DefaultLiveTvService.ServiceName, info.
 586
 0587            dto.SeriesTimerId = string.IsNullOrEmpty(info.SeriesTimerId)
 0588                ? null
 0589                : _tvDtoService.GetInternalSeriesTimerId(info.SeriesTimerId).ToString("N", CultureInfo.InvariantCulture)
 590
 0591            dto.TimerId = string.IsNullOrEmpty(info.Id)
 0592                ? null
 0593                : _tvDtoService.GetInternalTimerId(info.Id);
 594
 0595            var startDate = info.StartDate;
 0596            var endDate = info.EndDate;
 597
 0598            dto.StartDate = startDate;
 0599            dto.EndDate = endDate;
 0600            dto.Status = info.Status.ToString();
 0601            dto.IsRepeat = info.IsRepeat;
 0602            dto.EpisodeTitle = info.EpisodeTitle;
 0603            dto.IsMovie = info.IsMovie;
 0604            dto.IsSeries = info.IsSeries;
 0605            dto.IsSports = info.IsSports;
 0606            dto.IsLive = info.IsLive;
 0607            dto.IsNews = info.IsNews;
 0608            dto.IsKids = info.IsKids;
 0609            dto.IsPremiere = info.IsPremiere;
 610
 0611            if (info.Status == RecordingStatus.InProgress)
 612            {
 0613                startDate = info.StartDate.AddSeconds(0 - info.PrePaddingSeconds);
 0614                endDate = info.EndDate.AddSeconds(info.PostPaddingSeconds);
 615
 0616                var now = DateTime.UtcNow.Ticks;
 0617                var start = startDate.Ticks;
 0618                var end = endDate.Ticks;
 619
 0620                var pct = now - start;
 621
 0622                pct /= end;
 0623                pct *= 100;
 0624                dto.CompletionPercentage = pct;
 625            }
 626
 0627            if (channel is not null)
 628            {
 0629                dto.ChannelName = channel.Name;
 630
 0631                if (channel.HasImage(ImageType.Primary))
 632                {
 0633                    dto.ChannelPrimaryImageTag = _tvDtoService.GetImageTag(channel);
 634                }
 635            }
 0636        }
 637
 638        public async Task<QueryResult<BaseItemDto>> GetRecordingsAsync(RecordingQuery query, DtoOptions options)
 639        {
 640            var user = query.UserId.Equals(default)
 641                ? null
 642                : _userManager.GetUserById(query.UserId);
 643
 644            RemoveFields(options);
 645
 646            var internalResult = await GetEmbyRecordingsAsync(query, options, user).ConfigureAwait(false);
 647
 648            var returnArray = _dtoService.GetBaseItemDtos(internalResult.Items, options, user);
 649
 650            return new QueryResult<BaseItemDto>(
 651                query.StartIndex,
 652                internalResult.TotalRecordCount,
 653                returnArray);
 654        }
 655
 656        private async Task<QueryResult<TimerInfo>> GetTimersInternal(TimerQuery query, CancellationToken cancellationTok
 657        {
 658            var tasks = _services.Select(async i =>
 659            {
 660                try
 661                {
 662                    var recs = await i.GetTimersAsync(cancellationToken).ConfigureAwait(false);
 663                    return recs.Select(r => new Tuple<TimerInfo, ILiveTvService>(r, i));
 664                }
 665                catch (Exception ex)
 666                {
 667                    _logger.LogError(ex, "Error getting recordings");
 668                    return new List<Tuple<TimerInfo, ILiveTvService>>();
 669                }
 670            });
 671            var results = await Task.WhenAll(tasks).ConfigureAwait(false);
 672            var timers = results.SelectMany(i => i.ToList());
 673
 674            if (query.IsActive.HasValue)
 675            {
 676                if (query.IsActive.Value)
 677                {
 678                    timers = timers.Where(i => i.Item1.Status == RecordingStatus.InProgress);
 679                }
 680                else
 681                {
 682                    timers = timers.Where(i => i.Item1.Status != RecordingStatus.InProgress);
 683                }
 684            }
 685
 686            if (query.IsScheduled.HasValue)
 687            {
 688                if (query.IsScheduled.Value)
 689                {
 690                    timers = timers.Where(i => i.Item1.Status == RecordingStatus.New);
 691                }
 692                else
 693                {
 694                    timers = timers.Where(i => i.Item1.Status != RecordingStatus.New);
 695                }
 696            }
 697
 698            if (!string.IsNullOrEmpty(query.ChannelId))
 699            {
 700                var guid = new Guid(query.ChannelId);
 701                timers = timers.Where(i => _tvDtoService.GetInternalChannelId(i.Item2.Name, i.Item1.ChannelId).Equals(gu
 702            }
 703
 704            if (!string.IsNullOrEmpty(query.SeriesTimerId))
 705            {
 706                var guid = new Guid(query.SeriesTimerId);
 707
 708                timers = timers
 709                    .Where(i => _tvDtoService.GetInternalSeriesTimerId(i.Item1.SeriesTimerId).Equals(guid));
 710            }
 711
 712            if (!string.IsNullOrEmpty(query.Id))
 713            {
 714                timers = timers
 715                    .Where(i => string.Equals(_tvDtoService.GetInternalTimerId(i.Item1.Id), query.Id, StringComparison.O
 716            }
 717
 718            var returnArray = timers
 719                .Select(i => i.Item1)
 720                .OrderBy(i => i.StartDate)
 721                .ToArray();
 722
 723            return new QueryResult<TimerInfo>(returnArray);
 724        }
 725
 726        public async Task<QueryResult<TimerInfoDto>> GetTimers(TimerQuery query, CancellationToken cancellationToken)
 727        {
 728            var tasks = _services.Select(async i =>
 729            {
 730                try
 731                {
 732                    var recs = await i.GetTimersAsync(cancellationToken).ConfigureAwait(false);
 733                    return recs.Select(r => new Tuple<TimerInfo, ILiveTvService>(r, i));
 734                }
 735                catch (Exception ex)
 736                {
 737                    _logger.LogError(ex, "Error getting recordings");
 738                    return new List<Tuple<TimerInfo, ILiveTvService>>();
 739                }
 740            });
 741            var results = await Task.WhenAll(tasks).ConfigureAwait(false);
 742            var timers = results.SelectMany(i => i.ToList());
 743
 744            if (query.IsActive.HasValue)
 745            {
 746                if (query.IsActive.Value)
 747                {
 748                    timers = timers.Where(i => i.Item1.Status == RecordingStatus.InProgress);
 749                }
 750                else
 751                {
 752                    timers = timers.Where(i => i.Item1.Status != RecordingStatus.InProgress);
 753                }
 754            }
 755
 756            if (query.IsScheduled.HasValue)
 757            {
 758                if (query.IsScheduled.Value)
 759                {
 760                    timers = timers.Where(i => i.Item1.Status == RecordingStatus.New);
 761                }
 762                else
 763                {
 764                    timers = timers.Where(i => i.Item1.Status != RecordingStatus.New);
 765                }
 766            }
 767
 768            if (!string.IsNullOrEmpty(query.ChannelId))
 769            {
 770                var guid = new Guid(query.ChannelId);
 771                timers = timers.Where(i => _tvDtoService.GetInternalChannelId(i.Item2.Name, i.Item1.ChannelId).Equals(gu
 772            }
 773
 774            if (!string.IsNullOrEmpty(query.SeriesTimerId))
 775            {
 776                var guid = new Guid(query.SeriesTimerId);
 777
 778                timers = timers
 779                    .Where(i => _tvDtoService.GetInternalSeriesTimerId(i.Item1.SeriesTimerId).Equals(guid));
 780            }
 781
 782            if (!string.IsNullOrEmpty(query.Id))
 783            {
 784                timers = timers
 785                    .Where(i => string.Equals(_tvDtoService.GetInternalTimerId(i.Item1.Id), query.Id, StringComparison.O
 786            }
 787
 788            var returnList = new List<TimerInfoDto>();
 789
 790            foreach (var i in timers)
 791            {
 792                var program = string.IsNullOrEmpty(i.Item1.ProgramId) ?
 793                    null :
 794                    _libraryManager.GetItemById(_tvDtoService.GetInternalProgramId(i.Item1.ProgramId)) as LiveTvProgram;
 795
 796                var channel = string.IsNullOrEmpty(i.Item1.ChannelId) ? null : _libraryManager.GetItemById(_tvDtoService
 797
 798                returnList.Add(_tvDtoService.GetTimerInfoDto(i.Item1, i.Item2, program, channel));
 799            }
 800
 801            var returnArray = returnList
 802                .OrderBy(i => i.StartDate)
 803                .ToArray();
 804
 805            return new QueryResult<TimerInfoDto>(returnArray);
 806        }
 807
 808        public async Task CancelTimer(string id)
 809        {
 810            var timer = await GetTimer(id, CancellationToken.None).ConfigureAwait(false);
 811
 812            if (timer is null)
 813            {
 814                throw new ResourceNotFoundException(string.Format(CultureInfo.InvariantCulture, "Timer with Id {0} not f
 815            }
 816
 817            var service = GetService(timer.ServiceName);
 818
 819            await service.CancelTimerAsync(timer.ExternalId, CancellationToken.None).ConfigureAwait(false);
 820
 821            if (service is not DefaultLiveTvService)
 822            {
 823                TimerCancelled?.Invoke(this, new GenericEventArgs<TimerEventInfo>(new TimerEventInfo(id)));
 824            }
 825        }
 826
 827        public async Task CancelSeriesTimer(string id)
 828        {
 829            var timer = await GetSeriesTimer(id, CancellationToken.None).ConfigureAwait(false);
 830
 831            if (timer is null)
 832            {
 833                throw new ResourceNotFoundException(string.Format(CultureInfo.InvariantCulture, "SeriesTimer with Id {0}
 834            }
 835
 836            var service = GetService(timer.ServiceName);
 837
 838            await service.CancelSeriesTimerAsync(timer.ExternalId, CancellationToken.None).ConfigureAwait(false);
 839
 840            SeriesTimerCancelled?.Invoke(this, new GenericEventArgs<TimerEventInfo>(new TimerEventInfo(id)));
 841        }
 842
 843        public async Task<TimerInfoDto> GetTimer(string id, CancellationToken cancellationToken)
 844        {
 845            var results = await GetTimers(
 846                new TimerQuery
 847                {
 848                    Id = id
 849                },
 850                cancellationToken).ConfigureAwait(false);
 851
 852            return results.Items.FirstOrDefault(i => string.Equals(i.Id, id, StringComparison.OrdinalIgnoreCase));
 853        }
 854
 855        public async Task<SeriesTimerInfoDto> GetSeriesTimer(string id, CancellationToken cancellationToken)
 856        {
 857            var results = await GetSeriesTimers(new SeriesTimerQuery(), cancellationToken).ConfigureAwait(false);
 858
 859            return results.Items.FirstOrDefault(i => string.Equals(i.Id, id, StringComparison.OrdinalIgnoreCase));
 860        }
 861
 862        private async Task<QueryResult<SeriesTimerInfo>> GetSeriesTimersInternal(SeriesTimerQuery query, CancellationTok
 863        {
 864            var tasks = _services.Select(async i =>
 865            {
 866                try
 867                {
 868                    var recs = await i.GetSeriesTimersAsync(cancellationToken).ConfigureAwait(false);
 869                    return recs.Select(r =>
 870                    {
 871                        r.ServiceName = i.Name;
 872                        return new Tuple<SeriesTimerInfo, ILiveTvService>(r, i);
 873                    });
 874                }
 875                catch (Exception ex)
 876                {
 877                    _logger.LogError(ex, "Error getting recordings");
 878                    return new List<Tuple<SeriesTimerInfo, ILiveTvService>>();
 879                }
 880            });
 881            var results = await Task.WhenAll(tasks).ConfigureAwait(false);
 882            var timers = results.SelectMany(i => i.ToList());
 883
 884            if (string.Equals(query.SortBy, "Priority", StringComparison.OrdinalIgnoreCase))
 885            {
 886                timers = query.SortOrder == SortOrder.Descending ?
 887                    timers.OrderBy(i => i.Item1.Priority).ThenByStringDescending(i => i.Item1.Name) :
 888                    timers.OrderByDescending(i => i.Item1.Priority).ThenByString(i => i.Item1.Name);
 889            }
 890            else
 891            {
 892                timers = query.SortOrder == SortOrder.Descending ?
 893                    timers.OrderByStringDescending(i => i.Item1.Name) :
 894                    timers.OrderByString(i => i.Item1.Name);
 895            }
 896
 897            var returnArray = timers
 898                .Select(i => i.Item1)
 899                .ToArray();
 900
 901            return new QueryResult<SeriesTimerInfo>(returnArray);
 902        }
 903
 904        public async Task<QueryResult<SeriesTimerInfoDto>> GetSeriesTimers(SeriesTimerQuery query, CancellationToken can
 905        {
 906            var tasks = _services.Select(async i =>
 907            {
 908                try
 909                {
 910                    var recs = await i.GetSeriesTimersAsync(cancellationToken).ConfigureAwait(false);
 911                    return recs.Select(r => new Tuple<SeriesTimerInfo, ILiveTvService>(r, i));
 912                }
 913                catch (Exception ex)
 914                {
 915                    _logger.LogError(ex, "Error getting recordings");
 916                    return new List<Tuple<SeriesTimerInfo, ILiveTvService>>();
 917                }
 918            });
 919            var results = await Task.WhenAll(tasks).ConfigureAwait(false);
 920            var timers = results.SelectMany(i => i.ToList());
 921
 922            if (string.Equals(query.SortBy, "Priority", StringComparison.OrdinalIgnoreCase))
 923            {
 924                timers = query.SortOrder == SortOrder.Descending ?
 925                    timers.OrderBy(i => i.Item1.Priority).ThenByStringDescending(i => i.Item1.Name) :
 926                    timers.OrderByDescending(i => i.Item1.Priority).ThenByString(i => i.Item1.Name);
 927            }
 928            else
 929            {
 930                timers = query.SortOrder == SortOrder.Descending ?
 931                    timers.OrderByStringDescending(i => i.Item1.Name) :
 932                    timers.OrderByString(i => i.Item1.Name);
 933            }
 934
 935            var returnArray = timers
 936                .Select(i =>
 937                {
 938                    string channelName = null;
 939
 940                    if (!string.IsNullOrEmpty(i.Item1.ChannelId))
 941                    {
 942                        var internalChannelId = _tvDtoService.GetInternalChannelId(i.Item2.Name, i.Item1.ChannelId);
 943                        var channel = _libraryManager.GetItemById(internalChannelId);
 944                        channelName = channel?.Name;
 945                    }
 946
 947                    return _tvDtoService.GetSeriesTimerInfoDto(i.Item1, i.Item2, channelName);
 948                })
 949                .ToArray();
 950
 951            return new QueryResult<SeriesTimerInfoDto>(returnArray);
 952        }
 953
 954        public void AddChannelInfo(IReadOnlyCollection<(BaseItemDto ItemDto, LiveTvChannel Channel)> items, DtoOptions o
 955        {
 0956            var now = DateTime.UtcNow;
 957
 0958            var channelIds = items.Select(i => i.Channel.Id).Distinct().ToArray();
 959
 0960            var programs = options.AddCurrentProgram ? _libraryManager.GetItemList(new InternalItemsQuery(user)
 0961            {
 0962                IncludeItemTypes = new[] { BaseItemKind.LiveTvProgram },
 0963                ChannelIds = channelIds,
 0964                MaxStartDate = now,
 0965                MinEndDate = now,
 0966                Limit = channelIds.Length,
 0967                OrderBy = new[] { (ItemSortBy.StartDate, SortOrder.Ascending) },
 0968                TopParentIds = new[] { GetInternalLiveTvFolder(CancellationToken.None).Id },
 0969                DtoOptions = options
 0970            }) : new List<BaseItem>();
 971
 0972            RemoveFields(options);
 973
 0974            var currentProgramsList = new List<BaseItem>();
 0975            var currentChannelsDict = new Dictionary<Guid, BaseItemDto>();
 976
 0977            var addCurrentProgram = options.AddCurrentProgram;
 978
 0979            foreach (var (dto, channel) in items)
 980            {
 0981                dto.Number = channel.Number;
 0982                dto.ChannelNumber = channel.Number;
 0983                dto.ChannelType = channel.ChannelType;
 984
 0985                currentChannelsDict[dto.Id] = dto;
 986
 0987                if (addCurrentProgram)
 988                {
 0989                    var currentProgram = programs.FirstOrDefault(i => channel.Id.Equals(i.ChannelId));
 990
 0991                    if (currentProgram is not null)
 992                    {
 0993                        currentProgramsList.Add(currentProgram);
 994                    }
 995                }
 996            }
 997
 0998            if (addCurrentProgram)
 999            {
 01000                var currentProgramDtos = _dtoService.GetBaseItemDtos(currentProgramsList, options, user);
 1001
 01002                foreach (var programDto in currentProgramDtos)
 1003                {
 01004                    if (programDto.ChannelId.HasValue && currentChannelsDict.TryGetValue(programDto.ChannelId.Value, out
 1005                    {
 01006                        channelDto.CurrentProgram = programDto;
 1007                    }
 1008                }
 1009            }
 01010        }
 1011
 1012        private async Task<Tuple<SeriesTimerInfo, ILiveTvService>> GetNewTimerDefaultsInternal(CancellationToken cancell
 1013        {
 1014            ILiveTvService service = null;
 1015            ProgramInfo programInfo = null;
 1016
 1017            if (program is not null)
 1018            {
 1019                service = GetService(program);
 1020
 1021                var channel = _libraryManager.GetItemById(program.ChannelId);
 1022
 1023                programInfo = new ProgramInfo
 1024                {
 1025                    Audio = program.Audio,
 1026                    ChannelId = channel.ExternalId,
 1027                    CommunityRating = program.CommunityRating,
 1028                    EndDate = program.EndDate ?? DateTime.MinValue,
 1029                    EpisodeTitle = program.EpisodeTitle,
 1030                    Genres = program.Genres.ToList(),
 1031                    Id = program.ExternalId,
 1032                    IsHD = program.IsHD,
 1033                    IsKids = program.IsKids,
 1034                    IsLive = program.IsLive,
 1035                    IsMovie = program.IsMovie,
 1036                    IsNews = program.IsNews,
 1037                    IsPremiere = program.IsPremiere,
 1038                    IsRepeat = program.IsRepeat,
 1039                    IsSeries = program.IsSeries,
 1040                    IsSports = program.IsSports,
 1041                    OriginalAirDate = program.PremiereDate,
 1042                    Overview = program.Overview,
 1043                    StartDate = program.StartDate,
 1044                    // ImagePath = program.ExternalImagePath,
 1045                    Name = program.Name,
 1046                    OfficialRating = program.OfficialRating
 1047                };
 1048            }
 1049
 1050            service ??= _services[0];
 1051
 1052            var info = await service.GetNewTimerDefaultsAsync(cancellationToken, programInfo).ConfigureAwait(false);
 1053
 1054            info.RecordAnyTime = true;
 1055            info.Days = new List<DayOfWeek>
 1056            {
 1057                DayOfWeek.Sunday,
 1058                DayOfWeek.Monday,
 1059                DayOfWeek.Tuesday,
 1060                DayOfWeek.Wednesday,
 1061                DayOfWeek.Thursday,
 1062                DayOfWeek.Friday,
 1063                DayOfWeek.Saturday
 1064            };
 1065
 1066            info.Id = null;
 1067
 1068            return new Tuple<SeriesTimerInfo, ILiveTvService>(info, service);
 1069        }
 1070
 1071        public async Task<SeriesTimerInfoDto> GetNewTimerDefaults(CancellationToken cancellationToken)
 1072        {
 1073            var info = await GetNewTimerDefaultsInternal(cancellationToken).ConfigureAwait(false);
 1074
 1075            return _tvDtoService.GetSeriesTimerInfoDto(info.Item1, info.Item2, null);
 1076        }
 1077
 1078        public async Task<SeriesTimerInfoDto> GetNewTimerDefaults(string programId, CancellationToken cancellationToken)
 1079        {
 1080            var program = (LiveTvProgram)_libraryManager.GetItemById(programId);
 1081            var programDto = await GetProgram(programId, cancellationToken).ConfigureAwait(false);
 1082
 1083            var defaults = await GetNewTimerDefaultsInternal(cancellationToken, program).ConfigureAwait(false);
 1084            var info = _tvDtoService.GetSeriesTimerInfoDto(defaults.Item1, defaults.Item2, null);
 1085
 1086            info.Days = defaults.Item1.Days.ToArray();
 1087
 1088            info.DayPattern = _tvDtoService.GetDayPattern(info.Days);
 1089
 1090            info.Name = program.Name;
 1091            info.ChannelId = programDto.ChannelId ?? Guid.Empty;
 1092            info.ChannelName = programDto.ChannelName;
 1093            info.StartDate = program.StartDate;
 1094            info.Name = program.Name;
 1095            info.Overview = program.Overview;
 1096            info.ProgramId = programDto.Id.ToString("N", CultureInfo.InvariantCulture);
 1097            info.ExternalProgramId = program.ExternalId;
 1098
 1099            if (program.EndDate.HasValue)
 1100            {
 1101                info.EndDate = program.EndDate.Value;
 1102            }
 1103
 1104            return info;
 1105        }
 1106
 1107        public async Task CreateTimer(TimerInfoDto timer, CancellationToken cancellationToken)
 1108        {
 1109            var service = GetService(timer.ServiceName);
 1110
 1111            var info = await _tvDtoService.GetTimerInfo(timer, true, this, cancellationToken).ConfigureAwait(false);
 1112
 1113            // Set priority from default values
 1114            var defaultValues = await service.GetNewTimerDefaultsAsync(cancellationToken).ConfigureAwait(false);
 1115            info.Priority = defaultValues.Priority;
 1116
 1117            string newTimerId = null;
 1118            if (service is ISupportsNewTimerIds supportsNewTimerIds)
 1119            {
 1120                newTimerId = await supportsNewTimerIds.CreateTimer(info, cancellationToken).ConfigureAwait(false);
 1121                newTimerId = _tvDtoService.GetInternalTimerId(newTimerId);
 1122            }
 1123            else
 1124            {
 1125                await service.CreateTimerAsync(info, cancellationToken).ConfigureAwait(false);
 1126            }
 1127
 1128            _logger.LogInformation("New recording scheduled");
 1129
 1130            if (service is not DefaultLiveTvService)
 1131            {
 1132                TimerCreated?.Invoke(this, new GenericEventArgs<TimerEventInfo>(
 1133                    new TimerEventInfo(newTimerId)
 1134                    {
 1135                        ProgramId = _tvDtoService.GetInternalProgramId(info.ProgramId)
 1136                    }));
 1137            }
 1138        }
 1139
 1140        public async Task CreateSeriesTimer(SeriesTimerInfoDto timer, CancellationToken cancellationToken)
 1141        {
 1142            var service = GetService(timer.ServiceName);
 1143
 1144            var info = await _tvDtoService.GetSeriesTimerInfo(timer, true, this, cancellationToken).ConfigureAwait(false
 1145
 1146            // Set priority from default values
 1147            var defaultValues = await service.GetNewTimerDefaultsAsync(cancellationToken).ConfigureAwait(false);
 1148            info.Priority = defaultValues.Priority;
 1149
 1150            string newTimerId = null;
 1151            if (service is ISupportsNewTimerIds supportsNewTimerIds)
 1152            {
 1153                newTimerId = await supportsNewTimerIds.CreateSeriesTimer(info, cancellationToken).ConfigureAwait(false);
 1154                newTimerId = _tvDtoService.GetInternalSeriesTimerId(newTimerId).ToString("N", CultureInfo.InvariantCultu
 1155            }
 1156            else
 1157            {
 1158                await service.CreateSeriesTimerAsync(info, cancellationToken).ConfigureAwait(false);
 1159            }
 1160
 1161            SeriesTimerCreated?.Invoke(this, new GenericEventArgs<TimerEventInfo>(
 1162                new TimerEventInfo(newTimerId)
 1163                {
 1164                    ProgramId = _tvDtoService.GetInternalProgramId(info.ProgramId)
 1165                }));
 1166        }
 1167
 1168        public async Task UpdateTimer(TimerInfoDto timer, CancellationToken cancellationToken)
 1169        {
 1170            var info = await _tvDtoService.GetTimerInfo(timer, false, this, cancellationToken).ConfigureAwait(false);
 1171
 1172            var service = GetService(timer.ServiceName);
 1173
 1174            await service.UpdateTimerAsync(info, cancellationToken).ConfigureAwait(false);
 1175        }
 1176
 1177        public async Task UpdateSeriesTimer(SeriesTimerInfoDto timer, CancellationToken cancellationToken)
 1178        {
 1179            var info = await _tvDtoService.GetSeriesTimerInfo(timer, false, this, cancellationToken).ConfigureAwait(fals
 1180
 1181            var service = GetService(timer.ServiceName);
 1182
 1183            await service.UpdateSeriesTimerAsync(info, cancellationToken).ConfigureAwait(false);
 1184        }
 1185
 1186        private LiveTvServiceInfo[] GetServiceInfos()
 1187        {
 01188            return Services.Select(GetServiceInfo).ToArray();
 1189        }
 1190
 1191        private static LiveTvServiceInfo GetServiceInfo(ILiveTvService service)
 1192        {
 01193            return new LiveTvServiceInfo
 01194            {
 01195                Name = service.Name
 01196            };
 1197        }
 1198
 1199        public LiveTvInfo GetLiveTvInfo(CancellationToken cancellationToken)
 1200        {
 01201            var services = GetServiceInfos();
 1202
 01203            var info = new LiveTvInfo
 01204            {
 01205                Services = services,
 01206                IsEnabled = services.Length > 0,
 01207                EnabledUsers = _userManager.Users
 01208                    .Where(IsLiveTvEnabled)
 01209                    .Select(i => i.Id.ToString("N", CultureInfo.InvariantCulture))
 01210                    .ToArray()
 01211            };
 1212
 01213            return info;
 1214        }
 1215
 1216        private bool IsLiveTvEnabled(User user)
 1217        {
 11218            return user.HasPermission(PermissionKind.EnableLiveTvAccess) && (Services.Count > 1 || _config.GetLiveTvConf
 1219        }
 1220
 1221        public IEnumerable<User> GetEnabledUsers()
 1222        {
 11223            return _userManager.Users
 11224                .Where(IsLiveTvEnabled);
 1225        }
 1226
 1227        /// <summary>
 1228        /// Resets the tuner.
 1229        /// </summary>
 1230        /// <param name="id">The identifier.</param>
 1231        /// <param name="cancellationToken">The cancellation token.</param>
 1232        /// <returns>Task.</returns>
 1233        public Task ResetTuner(string id, CancellationToken cancellationToken)
 1234        {
 01235            var parts = id.Split('_', 2);
 1236
 01237            var service = _services.FirstOrDefault(i => string.Equals(i.GetType().FullName.GetMD5().ToString("N", Cultur
 1238
 01239            if (service is null)
 1240            {
 01241                throw new ArgumentException("Service not found.");
 1242            }
 1243
 01244            return service.ResetTuner(parts[1], cancellationToken);
 1245        }
 1246
 1247        private static void RemoveFields(DtoOptions options)
 1248        {
 01249            var fields = options.Fields.ToList();
 1250
 01251            fields.Remove(ItemFields.CanDelete);
 01252            fields.Remove(ItemFields.CanDownload);
 01253            fields.Remove(ItemFields.DisplayPreferencesId);
 01254            fields.Remove(ItemFields.Etag);
 01255            options.Fields = fields.ToArray();
 01256        }
 1257
 1258        public Folder GetInternalLiveTvFolder(CancellationToken cancellationToken)
 1259        {
 01260            var name = _localization.GetLocalizedString("HeaderLiveTV");
 01261            return _libraryManager.GetNamedView(name, CollectionType.livetv, name);
 1262        }
 1263
 1264        /// <inheritdoc />
 1265        public Task<BaseItem[]> GetRecordingFoldersAsync(User user)
 01266            => GetRecordingFoldersAsync(user, false);
 1267
 1268        private async Task<BaseItem[]> GetRecordingFoldersAsync(User user, bool refreshChannels)
 1269        {
 1270            var folders = _recordingsManager.GetRecordingFolders()
 1271                .SelectMany(i => i.Locations)
 1272                .Distinct(StringComparer.OrdinalIgnoreCase)
 1273                .Select(i => _libraryManager.FindByPath(i, true))
 1274                .Where(i => i is not null && i.IsVisibleStandalone(user))
 1275                .SelectMany(i => _libraryManager.GetCollectionFolders(i))
 1276                .DistinctBy(x => x.Id)
 1277                .OrderBy(i => i.SortName)
 1278                .ToList();
 1279
 1280            var channels = await _channelManager.GetChannelsInternalAsync(new MediaBrowser.Model.Channels.ChannelQuery
 1281            {
 1282                UserId = user.Id,
 1283                IsRecordingsFolder = true,
 1284                RefreshLatestChannelItems = refreshChannels
 1285            }).ConfigureAwait(false);
 1286
 1287            folders.AddRange(channels.Items);
 1288
 1289            return folders.Cast<BaseItem>().ToArray();
 1290        }
 1291    }
 1292}

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.Database.Implementations.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.Database.Implementations.Entities.User)
AddInfoToRecordingDto(MediaBrowser.Controller.Entities.BaseItem,MediaBrowser.Model.Dto.BaseItemDto,MediaBrowser.Controller.LiveTv.ActiveRecordingInfo,Jellyfin.Database.Implementations.Entities.User)
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)
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)