< Summary - Jellyfin

Information
Class: Jellyfin.Api.Controllers.LiveTvController
Assembly: Jellyfin.Api
File(s): /srv/git/jellyfin/Jellyfin.Api/Controllers/LiveTvController.cs
Line coverage
21%
Covered lines: 26
Uncovered lines: 96
Coverable lines: 122
Total lines: 1200
Line coverage: 21.3%
Branch coverage
0%
Covered branches: 0
Total branches: 20
Branch coverage: 0%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Coverage history

Coverage history 0 25 50 75 100

Metrics

File(s)

/srv/git/jellyfin/Jellyfin.Api/Controllers/LiveTvController.cs

#LineLine coverage
 1using System;
 2using System.Collections.Generic;
 3using System.ComponentModel.DataAnnotations;
 4using System.Diagnostics.CodeAnalysis;
 5using System.Linq;
 6using System.Net.Http;
 7using System.Net.Mime;
 8using System.Security.Cryptography;
 9using System.Text;
 10using System.Threading;
 11using System.Threading.Tasks;
 12using Jellyfin.Api.Attributes;
 13using Jellyfin.Api.Extensions;
 14using Jellyfin.Api.Helpers;
 15using Jellyfin.Api.ModelBinders;
 16using Jellyfin.Api.Models.LiveTvDtos;
 17using Jellyfin.Data.Enums;
 18using Jellyfin.Extensions;
 19using MediaBrowser.Common.Api;
 20using MediaBrowser.Common.Configuration;
 21using MediaBrowser.Common.Net;
 22using MediaBrowser.Controller.Dto;
 23using MediaBrowser.Controller.Entities;
 24using MediaBrowser.Controller.Entities.TV;
 25using MediaBrowser.Controller.Library;
 26using MediaBrowser.Controller.LiveTv;
 27using MediaBrowser.Controller.MediaEncoding;
 28using MediaBrowser.Controller.Streaming;
 29using MediaBrowser.Model.Dto;
 30using MediaBrowser.Model.Entities;
 31using MediaBrowser.Model.LiveTv;
 32using MediaBrowser.Model.Net;
 33using MediaBrowser.Model.Querying;
 34using Microsoft.AspNetCore.Authorization;
 35using Microsoft.AspNetCore.Http;
 36using Microsoft.AspNetCore.Mvc;
 37
 38namespace Jellyfin.Api.Controllers;
 39
 40/// <summary>
 41/// Live tv controller.
 42/// </summary>
 43public class LiveTvController : BaseJellyfinApiController
 44{
 45    private readonly ILiveTvManager _liveTvManager;
 46    private readonly IGuideManager _guideManager;
 47    private readonly ITunerHostManager _tunerHostManager;
 48    private readonly IListingsManager _listingsManager;
 49    private readonly IRecordingsManager _recordingsManager;
 50    private readonly IUserManager _userManager;
 51    private readonly IHttpClientFactory _httpClientFactory;
 52    private readonly ILibraryManager _libraryManager;
 53    private readonly IDtoService _dtoService;
 54    private readonly IMediaSourceManager _mediaSourceManager;
 55    private readonly IConfigurationManager _configurationManager;
 56    private readonly ITranscodeManager _transcodeManager;
 57
 58    /// <summary>
 59    /// Initializes a new instance of the <see cref="LiveTvController"/> class.
 60    /// </summary>
 61    /// <param name="liveTvManager">Instance of the <see cref="ILiveTvManager"/> interface.</param>
 62    /// <param name="guideManager">Instance of the <see cref="IGuideManager"/> interface.</param>
 63    /// <param name="tunerHostManager">Instance of the <see cref="ITunerHostManager"/> interface.</param>
 64    /// <param name="listingsManager">Instance of the <see cref="IListingsManager"/> interface.</param>
 65    /// <param name="recordingsManager">Instance of the <see cref="IRecordingsManager"/> interface.</param>
 66    /// <param name="userManager">Instance of the <see cref="IUserManager"/> interface.</param>
 67    /// <param name="httpClientFactory">Instance of the <see cref="IHttpClientFactory"/> interface.</param>
 68    /// <param name="libraryManager">Instance of the <see cref="ILibraryManager"/> interface.</param>
 69    /// <param name="dtoService">Instance of the <see cref="IDtoService"/> interface.</param>
 70    /// <param name="mediaSourceManager">Instance of the <see cref="IMediaSourceManager"/> interface.</param>
 71    /// <param name="configurationManager">Instance of the <see cref="IConfigurationManager"/> interface.</param>
 72    /// <param name="transcodeManager">Instance of the <see cref="ITranscodeManager"/> interface.</param>
 373    public LiveTvController(
 374        ILiveTvManager liveTvManager,
 375        IGuideManager guideManager,
 376        ITunerHostManager tunerHostManager,
 377        IListingsManager listingsManager,
 378        IRecordingsManager recordingsManager,
 379        IUserManager userManager,
 380        IHttpClientFactory httpClientFactory,
 381        ILibraryManager libraryManager,
 382        IDtoService dtoService,
 383        IMediaSourceManager mediaSourceManager,
 384        IConfigurationManager configurationManager,
 385        ITranscodeManager transcodeManager)
 86    {
 387        _liveTvManager = liveTvManager;
 388        _guideManager = guideManager;
 389        _tunerHostManager = tunerHostManager;
 390        _listingsManager = listingsManager;
 391        _recordingsManager = recordingsManager;
 392        _userManager = userManager;
 393        _httpClientFactory = httpClientFactory;
 394        _libraryManager = libraryManager;
 395        _dtoService = dtoService;
 396        _mediaSourceManager = mediaSourceManager;
 397        _configurationManager = configurationManager;
 398        _transcodeManager = transcodeManager;
 399    }
 100
 101    /// <summary>
 102    /// Gets available live tv services.
 103    /// </summary>
 104    /// <response code="200">Available live tv services returned.</response>
 105    /// <returns>
 106    /// An <see cref="OkResult"/> containing the available live tv services.
 107    /// </returns>
 108    [HttpGet("Info")]
 109    [ProducesResponseType(StatusCodes.Status200OK)]
 110    [Authorize(Policy = Policies.LiveTvAccess)]
 111    public ActionResult<LiveTvInfo> GetLiveTvInfo()
 112    {
 0113        return _liveTvManager.GetLiveTvInfo(CancellationToken.None);
 114    }
 115
 116    /// <summary>
 117    /// Gets available live tv channels.
 118    /// </summary>
 119    /// <param name="type">Optional. Filter by channel type.</param>
 120    /// <param name="userId">Optional. Filter by user and attach user data.</param>
 121    /// <param name="startIndex">Optional. The record index to start at. All items with a lower index will be dropped fr
 122    /// <param name="isMovie">Optional. Filter for movies.</param>
 123    /// <param name="isSeries">Optional. Filter for series.</param>
 124    /// <param name="isNews">Optional. Filter for news.</param>
 125    /// <param name="isKids">Optional. Filter for kids.</param>
 126    /// <param name="isSports">Optional. Filter for sports.</param>
 127    /// <param name="limit">Optional. The maximum number of records to return.</param>
 128    /// <param name="isFavorite">Optional. Filter by channels that are favorites, or not.</param>
 129    /// <param name="isLiked">Optional. Filter by channels that are liked, or not.</param>
 130    /// <param name="isDisliked">Optional. Filter by channels that are disliked, or not.</param>
 131    /// <param name="enableImages">Optional. Include image information in output.</param>
 132    /// <param name="imageTypeLimit">Optional. The max number of images to return, per image type.</param>
 133    /// <param name="enableImageTypes">"Optional. The image types to include in the output.</param>
 134    /// <param name="fields">Optional. Specify additional fields of information to return in the output.</param>
 135    /// <param name="enableUserData">Optional. Include user data.</param>
 136    /// <param name="sortBy">Optional. Key to sort by.</param>
 137    /// <param name="sortOrder">Optional. Sort order.</param>
 138    /// <param name="enableFavoriteSorting">Optional. Incorporate favorite and like status into channel sorting.</param>
 139    /// <param name="addCurrentProgram">Optional. Adds current program info to each channel.</param>
 140    /// <response code="200">Available live tv channels returned.</response>
 141    /// <returns>
 142    /// An <see cref="OkResult"/> containing the resulting available live tv channels.
 143    /// </returns>
 144    [HttpGet("Channels")]
 145    [ProducesResponseType(StatusCodes.Status200OK)]
 146    [Authorize(Policy = Policies.LiveTvAccess)]
 147    public ActionResult<QueryResult<BaseItemDto>> GetLiveTvChannels(
 148        [FromQuery] ChannelType? type,
 149        [FromQuery] Guid? userId,
 150        [FromQuery] int? startIndex,
 151        [FromQuery] bool? isMovie,
 152        [FromQuery] bool? isSeries,
 153        [FromQuery] bool? isNews,
 154        [FromQuery] bool? isKids,
 155        [FromQuery] bool? isSports,
 156        [FromQuery] int? limit,
 157        [FromQuery] bool? isFavorite,
 158        [FromQuery] bool? isLiked,
 159        [FromQuery] bool? isDisliked,
 160        [FromQuery] bool? enableImages,
 161        [FromQuery] int? imageTypeLimit,
 162        [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes,
 163        [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields,
 164        [FromQuery] bool? enableUserData,
 165        [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemSortBy[] sortBy,
 166        [FromQuery] SortOrder? sortOrder,
 167        [FromQuery] bool enableFavoriteSorting = false,
 168        [FromQuery] bool addCurrentProgram = true)
 169    {
 0170        userId = RequestHelpers.GetUserId(User, userId);
 0171        var dtoOptions = new DtoOptions { Fields = fields }
 0172            .AddClientFields(User)
 0173            .AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes);
 174
 0175        var channelResult = _liveTvManager.GetInternalChannels(
 0176            new LiveTvChannelQuery
 0177            {
 0178                ChannelType = type,
 0179                UserId = userId.Value,
 0180                StartIndex = startIndex,
 0181                Limit = limit,
 0182                IsFavorite = isFavorite,
 0183                IsLiked = isLiked,
 0184                IsDisliked = isDisliked,
 0185                EnableFavoriteSorting = enableFavoriteSorting,
 0186                IsMovie = isMovie,
 0187                IsSeries = isSeries,
 0188                IsNews = isNews,
 0189                IsKids = isKids,
 0190                IsSports = isSports,
 0191                SortBy = sortBy,
 0192                SortOrder = sortOrder ?? SortOrder.Ascending,
 0193                AddCurrentProgram = addCurrentProgram
 0194            },
 0195            dtoOptions,
 0196            CancellationToken.None);
 197
 0198        var user = userId.IsNullOrEmpty()
 0199            ? null
 0200            : _userManager.GetUserById(userId.Value);
 201
 0202        var fieldsList = dtoOptions.Fields.ToList();
 0203        fieldsList.Remove(ItemFields.CanDelete);
 0204        fieldsList.Remove(ItemFields.CanDownload);
 0205        fieldsList.Remove(ItemFields.DisplayPreferencesId);
 0206        fieldsList.Remove(ItemFields.Etag);
 0207        dtoOptions.Fields = fieldsList.ToArray();
 0208        dtoOptions.AddCurrentProgram = addCurrentProgram;
 209
 0210        var returnArray = _dtoService.GetBaseItemDtos(channelResult.Items, dtoOptions, user);
 0211        return new QueryResult<BaseItemDto>(
 0212            startIndex,
 0213            channelResult.TotalRecordCount,
 0214            returnArray);
 215    }
 216
 217    /// <summary>
 218    /// Gets a live tv channel.
 219    /// </summary>
 220    /// <param name="channelId">Channel id.</param>
 221    /// <param name="userId">Optional. Attach user data.</param>
 222    /// <response code="200">Live tv channel returned.</response>
 223    /// <response code="404">Item not found.</response>
 224    /// <returns>An <see cref="OkResult"/> containing the live tv channel.</returns>
 225    [HttpGet("Channels/{channelId}")]
 226    [ProducesResponseType(StatusCodes.Status200OK)]
 227    [ProducesResponseType(StatusCodes.Status404NotFound)]
 228    [Authorize(Policy = Policies.LiveTvAccess)]
 229    public ActionResult<BaseItemDto> GetChannel([FromRoute, Required] Guid channelId, [FromQuery] Guid? userId)
 230    {
 0231        userId = RequestHelpers.GetUserId(User, userId);
 0232        var user = userId.IsNullOrEmpty()
 0233            ? null
 0234            : _userManager.GetUserById(userId.Value);
 0235        var item = channelId.IsEmpty()
 0236            ? _libraryManager.GetUserRootFolder()
 0237            : _libraryManager.GetItemById<BaseItem>(channelId, user);
 238
 0239        if (item is null)
 240        {
 0241            return NotFound();
 242        }
 243
 0244        var dtoOptions = new DtoOptions()
 0245            .AddClientFields(User);
 0246        return _dtoService.GetBaseItemDto(item, dtoOptions, user);
 247    }
 248
 249    /// <summary>
 250    /// Gets live tv recordings.
 251    /// </summary>
 252    /// <param name="channelId">Optional. Filter by channel id.</param>
 253    /// <param name="userId">Optional. Filter by user and attach user data.</param>
 254    /// <param name="startIndex">Optional. The record index to start at. All items with a lower index will be dropped fr
 255    /// <param name="limit">Optional. The maximum number of records to return.</param>
 256    /// <param name="status">Optional. Filter by recording status.</param>
 257    /// <param name="isInProgress">Optional. Filter by recordings that are in progress, or not.</param>
 258    /// <param name="seriesTimerId">Optional. Filter by recordings belonging to a series timer.</param>
 259    /// <param name="enableImages">Optional. Include image information in output.</param>
 260    /// <param name="imageTypeLimit">Optional. The max number of images to return, per image type.</param>
 261    /// <param name="enableImageTypes">Optional. The image types to include in the output.</param>
 262    /// <param name="fields">Optional. Specify additional fields of information to return in the output.</param>
 263    /// <param name="enableUserData">Optional. Include user data.</param>
 264    /// <param name="isMovie">Optional. Filter for movies.</param>
 265    /// <param name="isSeries">Optional. Filter for series.</param>
 266    /// <param name="isKids">Optional. Filter for kids.</param>
 267    /// <param name="isSports">Optional. Filter for sports.</param>
 268    /// <param name="isNews">Optional. Filter for news.</param>
 269    /// <param name="isLibraryItem">Optional. Filter for is library item.</param>
 270    /// <param name="enableTotalRecordCount">Optional. Return total record count.</param>
 271    /// <response code="200">Live tv recordings returned.</response>
 272    /// <returns>An <see cref="OkResult"/> containing the live tv recordings.</returns>
 273    [HttpGet("Recordings")]
 274    [ProducesResponseType(StatusCodes.Status200OK)]
 275    [Authorize(Policy = Policies.LiveTvAccess)]
 276    public async Task<ActionResult<QueryResult<BaseItemDto>>> GetRecordings(
 277        [FromQuery] string? channelId,
 278        [FromQuery] Guid? userId,
 279        [FromQuery] int? startIndex,
 280        [FromQuery] int? limit,
 281        [FromQuery] RecordingStatus? status,
 282        [FromQuery] bool? isInProgress,
 283        [FromQuery] string? seriesTimerId,
 284        [FromQuery] bool? enableImages,
 285        [FromQuery] int? imageTypeLimit,
 286        [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes,
 287        [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields,
 288        [FromQuery] bool? enableUserData,
 289        [FromQuery] bool? isMovie,
 290        [FromQuery] bool? isSeries,
 291        [FromQuery] bool? isKids,
 292        [FromQuery] bool? isSports,
 293        [FromQuery] bool? isNews,
 294        [FromQuery] bool? isLibraryItem,
 295        [FromQuery] bool enableTotalRecordCount = true)
 296    {
 297        userId = RequestHelpers.GetUserId(User, userId);
 298        var dtoOptions = new DtoOptions { Fields = fields }
 299            .AddClientFields(User)
 300            .AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes);
 301
 302        return await _liveTvManager.GetRecordingsAsync(
 303            new RecordingQuery
 304            {
 305                ChannelId = channelId,
 306                UserId = userId.Value,
 307                StartIndex = startIndex,
 308                Limit = limit,
 309                Status = status,
 310                SeriesTimerId = seriesTimerId,
 311                IsInProgress = isInProgress,
 312                EnableTotalRecordCount = enableTotalRecordCount,
 313                IsMovie = isMovie,
 314                IsNews = isNews,
 315                IsSeries = isSeries,
 316                IsKids = isKids,
 317                IsSports = isSports,
 318                IsLibraryItem = isLibraryItem,
 319                Fields = fields,
 320                ImageTypeLimit = imageTypeLimit,
 321                EnableImages = enableImages
 322            },
 323            dtoOptions).ConfigureAwait(false);
 324    }
 325
 326    /// <summary>
 327    /// Gets live tv recording series.
 328    /// </summary>
 329    /// <param name="channelId">Optional. Filter by channel id.</param>
 330    /// <param name="userId">Optional. Filter by user and attach user data.</param>
 331    /// <param name="groupId">Optional. Filter by recording group.</param>
 332    /// <param name="startIndex">Optional. The record index to start at. All items with a lower index will be dropped fr
 333    /// <param name="limit">Optional. The maximum number of records to return.</param>
 334    /// <param name="status">Optional. Filter by recording status.</param>
 335    /// <param name="isInProgress">Optional. Filter by recordings that are in progress, or not.</param>
 336    /// <param name="seriesTimerId">Optional. Filter by recordings belonging to a series timer.</param>
 337    /// <param name="enableImages">Optional. Include image information in output.</param>
 338    /// <param name="imageTypeLimit">Optional. The max number of images to return, per image type.</param>
 339    /// <param name="enableImageTypes">Optional. The image types to include in the output.</param>
 340    /// <param name="fields">Optional. Specify additional fields of information to return in the output.</param>
 341    /// <param name="enableUserData">Optional. Include user data.</param>
 342    /// <param name="enableTotalRecordCount">Optional. Return total record count.</param>
 343    /// <response code="200">Live tv recordings returned.</response>
 344    /// <returns>An <see cref="OkResult"/> containing the live tv recordings.</returns>
 345    [HttpGet("Recordings/Series")]
 346    [ProducesResponseType(StatusCodes.Status200OK)]
 347    [Authorize(Policy = Policies.LiveTvAccess)]
 348    [Obsolete("This endpoint is obsolete.")]
 349    [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "channelId", Justification = 
 350    [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "userId", Justification = "Im
 351    [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "groupId", Justification = "I
 352    [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "startIndex", Justification =
 353    [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "limit", Justification = "Imp
 354    [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "status", Justification = "Im
 355    [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "isInProgress", Justification
 356    [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "seriesTimerId", Justificatio
 357    [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "enableImages", Justification
 358    [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "imageTypeLimit", Justificati
 359    [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "enableImageTypes", Justifica
 360    [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "fields", Justification = "Im
 361    [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "enableUserData", Justificati
 362    [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "enableTotalRecordCount", Jus
 363    public ActionResult<QueryResult<BaseItemDto>> GetRecordingsSeries(
 364        [FromQuery] string? channelId,
 365        [FromQuery] Guid? userId,
 366        [FromQuery] string? groupId,
 367        [FromQuery] int? startIndex,
 368        [FromQuery] int? limit,
 369        [FromQuery] RecordingStatus? status,
 370        [FromQuery] bool? isInProgress,
 371        [FromQuery] string? seriesTimerId,
 372        [FromQuery] bool? enableImages,
 373        [FromQuery] int? imageTypeLimit,
 374        [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes,
 375        [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields,
 376        [FromQuery] bool? enableUserData,
 377        [FromQuery] bool enableTotalRecordCount = true)
 378    {
 379        return new QueryResult<BaseItemDto>();
 380    }
 381
 382    /// <summary>
 383    /// Gets live tv recording groups.
 384    /// </summary>
 385    /// <param name="userId">Optional. Filter by user and attach user data.</param>
 386    /// <response code="200">Recording groups returned.</response>
 387    /// <returns>An <see cref="OkResult"/> containing the recording groups.</returns>
 388    [HttpGet("Recordings/Groups")]
 389    [ProducesResponseType(StatusCodes.Status200OK)]
 390    [Authorize(Policy = Policies.LiveTvAccess)]
 391    [Obsolete("This endpoint is obsolete.")]
 392    [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "userId", Justification = "Im
 393    public ActionResult<QueryResult<BaseItemDto>> GetRecordingGroups([FromQuery] Guid? userId)
 394    {
 395        return new QueryResult<BaseItemDto>();
 396    }
 397
 398    /// <summary>
 399    /// Gets recording folders.
 400    /// </summary>
 401    /// <param name="userId">Optional. Filter by user and attach user data.</param>
 402    /// <response code="200">Recording folders returned.</response>
 403    /// <returns>An <see cref="OkResult"/> containing the recording folders.</returns>
 404    [HttpGet("Recordings/Folders")]
 405    [ProducesResponseType(StatusCodes.Status200OK)]
 406    [Authorize(Policy = Policies.LiveTvAccess)]
 407    public async Task<ActionResult<QueryResult<BaseItemDto>>> GetRecordingFolders([FromQuery] Guid? userId)
 408    {
 409        userId = RequestHelpers.GetUserId(User, userId);
 410        var user = userId.IsNullOrEmpty()
 411            ? null
 412            : _userManager.GetUserById(userId.Value);
 413        var folders = await _liveTvManager.GetRecordingFoldersAsync(user).ConfigureAwait(false);
 414
 415        var returnArray = _dtoService.GetBaseItemDtos(folders, new DtoOptions(), user);
 416
 417        return new QueryResult<BaseItemDto>(returnArray);
 418    }
 419
 420    /// <summary>
 421    /// Gets a live tv recording.
 422    /// </summary>
 423    /// <param name="recordingId">Recording id.</param>
 424    /// <param name="userId">Optional. Attach user data.</param>
 425    /// <response code="200">Recording returned.</response>
 426    /// <response code="404">Item not found.</response>
 427    /// <returns>An <see cref="OkResult"/> containing the live tv recording.</returns>
 428    [HttpGet("Recordings/{recordingId}")]
 429    [ProducesResponseType(StatusCodes.Status200OK)]
 430    [ProducesResponseType(StatusCodes.Status404NotFound)]
 431    [Authorize(Policy = Policies.LiveTvAccess)]
 432    public ActionResult<BaseItemDto> GetRecording([FromRoute, Required] Guid recordingId, [FromQuery] Guid? userId)
 433    {
 0434        userId = RequestHelpers.GetUserId(User, userId);
 0435        var user = userId.IsNullOrEmpty()
 0436            ? null
 0437            : _userManager.GetUserById(userId.Value);
 0438        var item = recordingId.IsEmpty()
 0439            ? _libraryManager.GetUserRootFolder()
 0440            : _libraryManager.GetItemById<BaseItem>(recordingId, user);
 0441        if (item is null)
 442        {
 0443            return NotFound();
 444        }
 445
 0446        var dtoOptions = new DtoOptions()
 0447            .AddClientFields(User);
 448
 0449        return _dtoService.GetBaseItemDto(item, dtoOptions, user);
 450    }
 451
 452    /// <summary>
 453    /// Resets a tv tuner.
 454    /// </summary>
 455    /// <param name="tunerId">Tuner id.</param>
 456    /// <response code="204">Tuner reset.</response>
 457    /// <returns>A <see cref="NoContentResult"/>.</returns>
 458    [HttpPost("Tuners/{tunerId}/Reset")]
 459    [ProducesResponseType(StatusCodes.Status204NoContent)]
 460    [Authorize(Policy = Policies.LiveTvManagement)]
 461    public async Task<ActionResult> ResetTuner([FromRoute, Required] string tunerId)
 462    {
 463        await _liveTvManager.ResetTuner(tunerId, CancellationToken.None).ConfigureAwait(false);
 464        return NoContent();
 465    }
 466
 467    /// <summary>
 468    /// Gets a timer.
 469    /// </summary>
 470    /// <param name="timerId">Timer id.</param>
 471    /// <response code="200">Timer returned.</response>
 472    /// <returns>
 473    /// A <see cref="Task"/> containing an <see cref="OkResult"/> which contains the timer.
 474    /// </returns>
 475    [HttpGet("Timers/{timerId}")]
 476    [ProducesResponseType(StatusCodes.Status200OK)]
 477    [Authorize(Policy = Policies.LiveTvAccess)]
 478    public async Task<ActionResult<TimerInfoDto>> GetTimer([FromRoute, Required] string timerId)
 479    {
 480        return await _liveTvManager.GetTimer(timerId, CancellationToken.None).ConfigureAwait(false);
 481    }
 482
 483    /// <summary>
 484    /// Gets the default values for a new timer.
 485    /// </summary>
 486    /// <param name="programId">Optional. To attach default values based on a program.</param>
 487    /// <response code="200">Default values returned.</response>
 488    /// <returns>
 489    /// A <see cref="Task"/> containing an <see cref="OkResult"/> which contains the default values for a timer.
 490    /// </returns>
 491    [HttpGet("Timers/Defaults")]
 492    [ProducesResponseType(StatusCodes.Status200OK)]
 493    [Authorize(Policy = Policies.LiveTvAccess)]
 494    public async Task<ActionResult<SeriesTimerInfoDto>> GetDefaultTimer([FromQuery] string? programId)
 495    {
 496        return string.IsNullOrEmpty(programId)
 497            ? await _liveTvManager.GetNewTimerDefaults(CancellationToken.None).ConfigureAwait(false)
 498            : await _liveTvManager.GetNewTimerDefaults(programId, CancellationToken.None).ConfigureAwait(false);
 499    }
 500
 501    /// <summary>
 502    /// Gets the live tv timers.
 503    /// </summary>
 504    /// <param name="channelId">Optional. Filter by channel id.</param>
 505    /// <param name="seriesTimerId">Optional. Filter by timers belonging to a series timer.</param>
 506    /// <param name="isActive">Optional. Filter by timers that are active.</param>
 507    /// <param name="isScheduled">Optional. Filter by timers that are scheduled.</param>
 508    /// <returns>
 509    /// A <see cref="Task"/> containing an <see cref="OkResult"/> which contains the live tv timers.
 510    /// </returns>
 511    [HttpGet("Timers")]
 512    [ProducesResponseType(StatusCodes.Status200OK)]
 513    [Authorize(Policy = Policies.LiveTvAccess)]
 514    public async Task<ActionResult<QueryResult<TimerInfoDto>>> GetTimers(
 515        [FromQuery] string? channelId,
 516        [FromQuery] string? seriesTimerId,
 517        [FromQuery] bool? isActive,
 518        [FromQuery] bool? isScheduled)
 519    {
 520        return await _liveTvManager.GetTimers(
 521            new TimerQuery
 522            {
 523                ChannelId = channelId,
 524                SeriesTimerId = seriesTimerId,
 525                IsActive = isActive,
 526                IsScheduled = isScheduled
 527            },
 528            CancellationToken.None).ConfigureAwait(false);
 529    }
 530
 531    /// <summary>
 532    /// Gets available live tv epgs.
 533    /// </summary>
 534    /// <param name="channelIds">The channels to return guide information for.</param>
 535    /// <param name="userId">Optional. Filter by user id.</param>
 536    /// <param name="minStartDate">Optional. The minimum premiere start date.</param>
 537    /// <param name="hasAired">Optional. Filter by programs that have completed airing, or not.</param>
 538    /// <param name="isAiring">Optional. Filter by programs that are currently airing, or not.</param>
 539    /// <param name="maxStartDate">Optional. The maximum premiere start date.</param>
 540    /// <param name="minEndDate">Optional. The minimum premiere end date.</param>
 541    /// <param name="maxEndDate">Optional. The maximum premiere end date.</param>
 542    /// <param name="isMovie">Optional. Filter for movies.</param>
 543    /// <param name="isSeries">Optional. Filter for series.</param>
 544    /// <param name="isNews">Optional. Filter for news.</param>
 545    /// <param name="isKids">Optional. Filter for kids.</param>
 546    /// <param name="isSports">Optional. Filter for sports.</param>
 547    /// <param name="startIndex">Optional. The record index to start at. All items with a lower index will be dropped fr
 548    /// <param name="limit">Optional. The maximum number of records to return.</param>
 549    /// <param name="sortBy">Optional. Specify one or more sort orders, comma delimited. Options: Name, StartDate.</para
 550    /// <param name="sortOrder">Sort Order - Ascending,Descending.</param>
 551    /// <param name="genres">The genres to return guide information for.</param>
 552    /// <param name="genreIds">The genre ids to return guide information for.</param>
 553    /// <param name="enableImages">Optional. Include image information in output.</param>
 554    /// <param name="imageTypeLimit">Optional. The max number of images to return, per image type.</param>
 555    /// <param name="enableImageTypes">Optional. The image types to include in the output.</param>
 556    /// <param name="enableUserData">Optional. Include user data.</param>
 557    /// <param name="seriesTimerId">Optional. Filter by series timer id.</param>
 558    /// <param name="librarySeriesId">Optional. Filter by library series id.</param>
 559    /// <param name="fields">Optional. Specify additional fields of information to return in the output.</param>
 560    /// <param name="enableTotalRecordCount">Retrieve total record count.</param>
 561    /// <response code="200">Live tv epgs returned.</response>
 562    /// <returns>
 563    /// A <see cref="Task"/> containing a <see cref="OkResult"/> which contains the live tv epgs.
 564    /// </returns>
 565    [HttpGet("Programs")]
 566    [ProducesResponseType(StatusCodes.Status200OK)]
 567    [Authorize(Policy = Policies.LiveTvAccess)]
 568    public async Task<ActionResult<QueryResult<BaseItemDto>>> GetLiveTvPrograms(
 569        [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] channelIds,
 570        [FromQuery] Guid? userId,
 571        [FromQuery] DateTime? minStartDate,
 572        [FromQuery] bool? hasAired,
 573        [FromQuery] bool? isAiring,
 574        [FromQuery] DateTime? maxStartDate,
 575        [FromQuery] DateTime? minEndDate,
 576        [FromQuery] DateTime? maxEndDate,
 577        [FromQuery] bool? isMovie,
 578        [FromQuery] bool? isSeries,
 579        [FromQuery] bool? isNews,
 580        [FromQuery] bool? isKids,
 581        [FromQuery] bool? isSports,
 582        [FromQuery] int? startIndex,
 583        [FromQuery] int? limit,
 584        [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemSortBy[] sortBy,
 585        [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] SortOrder[] sortOrder,
 586        [FromQuery, ModelBinder(typeof(PipeDelimitedArrayModelBinder))] string[] genres,
 587        [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] genreIds,
 588        [FromQuery] bool? enableImages,
 589        [FromQuery] int? imageTypeLimit,
 590        [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes,
 591        [FromQuery] bool? enableUserData,
 592        [FromQuery] string? seriesTimerId,
 593        [FromQuery] Guid? librarySeriesId,
 594        [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields,
 595        [FromQuery] bool enableTotalRecordCount = true)
 596    {
 597        userId = RequestHelpers.GetUserId(User, userId);
 598        var user = userId.IsNullOrEmpty()
 599            ? null
 600            : _userManager.GetUserById(userId.Value);
 601
 602        var query = new InternalItemsQuery(user)
 603        {
 604            ChannelIds = channelIds,
 605            HasAired = hasAired,
 606            IsAiring = isAiring,
 607            EnableTotalRecordCount = enableTotalRecordCount,
 608            MinStartDate = minStartDate,
 609            MinEndDate = minEndDate,
 610            MaxStartDate = maxStartDate,
 611            MaxEndDate = maxEndDate,
 612            StartIndex = startIndex,
 613            Limit = limit,
 614            OrderBy = RequestHelpers.GetOrderBy(sortBy, sortOrder),
 615            IsNews = isNews,
 616            IsMovie = isMovie,
 617            IsSeries = isSeries,
 618            IsKids = isKids,
 619            IsSports = isSports,
 620            SeriesTimerId = seriesTimerId,
 621            Genres = genres,
 622            GenreIds = genreIds
 623        };
 624
 625        if (!librarySeriesId.IsNullOrEmpty())
 626        {
 627            query.IsSeries = true;
 628
 629            var series = _libraryManager.GetItemById<Series>(librarySeriesId.Value);
 630            if (series is not null)
 631            {
 632                query.Name = series.Name;
 633            }
 634        }
 635
 636        var dtoOptions = new DtoOptions { Fields = fields }
 637            .AddClientFields(User)
 638            .AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes);
 639        return await _liveTvManager.GetPrograms(query, dtoOptions, CancellationToken.None).ConfigureAwait(false);
 640    }
 641
 642    /// <summary>
 643    /// Gets available live tv epgs.
 644    /// </summary>
 645    /// <param name="body">Request body.</param>
 646    /// <response code="200">Live tv epgs returned.</response>
 647    /// <returns>
 648    /// A <see cref="Task"/> containing a <see cref="OkResult"/> which contains the live tv epgs.
 649    /// </returns>
 650    [HttpPost("Programs")]
 651    [ProducesResponseType(StatusCodes.Status200OK)]
 652    [Authorize(Policy = Policies.LiveTvAccess)]
 653    public async Task<ActionResult<QueryResult<BaseItemDto>>> GetPrograms([FromBody] GetProgramsDto body)
 654    {
 655        var user = body.UserId.IsNullOrEmpty() ? null : _userManager.GetUserById(body.UserId.Value);
 656
 657        var query = new InternalItemsQuery(user)
 658        {
 659            ChannelIds = body.ChannelIds ?? [],
 660            HasAired = body.HasAired,
 661            IsAiring = body.IsAiring,
 662            EnableTotalRecordCount = body.EnableTotalRecordCount,
 663            MinStartDate = body.MinStartDate,
 664            MinEndDate = body.MinEndDate,
 665            MaxStartDate = body.MaxStartDate,
 666            MaxEndDate = body.MaxEndDate,
 667            StartIndex = body.StartIndex,
 668            Limit = body.Limit,
 669            OrderBy = RequestHelpers.GetOrderBy(body.SortBy ?? [], body.SortOrder ?? []),
 670            IsNews = body.IsNews,
 671            IsMovie = body.IsMovie,
 672            IsSeries = body.IsSeries,
 673            IsKids = body.IsKids,
 674            IsSports = body.IsSports,
 675            SeriesTimerId = body.SeriesTimerId,
 676            Genres = body.Genres ?? [],
 677            GenreIds = body.GenreIds ?? []
 678        };
 679
 680        if (!body.LibrarySeriesId.IsNullOrEmpty())
 681        {
 682            query.IsSeries = true;
 683
 684            var series = _libraryManager.GetItemById<Series>(body.LibrarySeriesId.Value);
 685            if (series is not null)
 686            {
 687                query.Name = series.Name;
 688            }
 689        }
 690
 691        var dtoOptions = new DtoOptions { Fields = body.Fields ?? [] }
 692            .AddClientFields(User)
 693            .AddAdditionalDtoOptions(body.EnableImages, body.EnableUserData, body.ImageTypeLimit, body.EnableImageTypes 
 694        return await _liveTvManager.GetPrograms(query, dtoOptions, CancellationToken.None).ConfigureAwait(false);
 695    }
 696
 697    /// <summary>
 698    /// Gets recommended live tv epgs.
 699    /// </summary>
 700    /// <param name="userId">Optional. filter by user id.</param>
 701    /// <param name="limit">Optional. The maximum number of records to return.</param>
 702    /// <param name="isAiring">Optional. Filter by programs that are currently airing, or not.</param>
 703    /// <param name="hasAired">Optional. Filter by programs that have completed airing, or not.</param>
 704    /// <param name="isSeries">Optional. Filter for series.</param>
 705    /// <param name="isMovie">Optional. Filter for movies.</param>
 706    /// <param name="isNews">Optional. Filter for news.</param>
 707    /// <param name="isKids">Optional. Filter for kids.</param>
 708    /// <param name="isSports">Optional. Filter for sports.</param>
 709    /// <param name="enableImages">Optional. Include image information in output.</param>
 710    /// <param name="imageTypeLimit">Optional. The max number of images to return, per image type.</param>
 711    /// <param name="enableImageTypes">Optional. The image types to include in the output.</param>
 712    /// <param name="genreIds">The genres to return guide information for.</param>
 713    /// <param name="fields">Optional. Specify additional fields of information to return in the output.</param>
 714    /// <param name="enableUserData">Optional. include user data.</param>
 715    /// <param name="enableTotalRecordCount">Retrieve total record count.</param>
 716    /// <response code="200">Recommended epgs returned.</response>
 717    /// <returns>A <see cref="OkResult"/> containing the queryresult of recommended epgs.</returns>
 718    [HttpGet("Programs/Recommended")]
 719    [Authorize(Policy = Policies.LiveTvAccess)]
 720    [ProducesResponseType(StatusCodes.Status200OK)]
 721    public async Task<ActionResult<QueryResult<BaseItemDto>>> GetRecommendedPrograms(
 722        [FromQuery] Guid? userId,
 723        [FromQuery] int? limit,
 724        [FromQuery] bool? isAiring,
 725        [FromQuery] bool? hasAired,
 726        [FromQuery] bool? isSeries,
 727        [FromQuery] bool? isMovie,
 728        [FromQuery] bool? isNews,
 729        [FromQuery] bool? isKids,
 730        [FromQuery] bool? isSports,
 731        [FromQuery] bool? enableImages,
 732        [FromQuery] int? imageTypeLimit,
 733        [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes,
 734        [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] genreIds,
 735        [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields,
 736        [FromQuery] bool? enableUserData,
 737        [FromQuery] bool enableTotalRecordCount = true)
 738    {
 739        userId = RequestHelpers.GetUserId(User, userId);
 740        var user = userId.IsNullOrEmpty()
 741            ? null
 742            : _userManager.GetUserById(userId.Value);
 743
 744        var query = new InternalItemsQuery(user)
 745        {
 746            IsAiring = isAiring,
 747            Limit = limit,
 748            HasAired = hasAired,
 749            IsSeries = isSeries,
 750            IsMovie = isMovie,
 751            IsKids = isKids,
 752            IsNews = isNews,
 753            IsSports = isSports,
 754            EnableTotalRecordCount = enableTotalRecordCount,
 755            GenreIds = genreIds
 756        };
 757
 758        var dtoOptions = new DtoOptions { Fields = fields }
 759            .AddClientFields(User)
 760            .AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes);
 761        return await _liveTvManager.GetRecommendedProgramsAsync(query, dtoOptions, CancellationToken.None).ConfigureAwai
 762    }
 763
 764    /// <summary>
 765    /// Gets a live tv program.
 766    /// </summary>
 767    /// <param name="programId">Program id.</param>
 768    /// <param name="userId">Optional. Attach user data.</param>
 769    /// <response code="200">Program returned.</response>
 770    /// <returns>An <see cref="OkResult"/> containing the livetv program.</returns>
 771    [HttpGet("Programs/{programId}")]
 772    [Authorize(Policy = Policies.LiveTvAccess)]
 773    [ProducesResponseType(StatusCodes.Status200OK)]
 774    public async Task<ActionResult<BaseItemDto>> GetProgram(
 775        [FromRoute, Required] string programId,
 776        [FromQuery] Guid? userId)
 777    {
 778        userId = RequestHelpers.GetUserId(User, userId);
 779        var user = userId.IsNullOrEmpty()
 780            ? null
 781            : _userManager.GetUserById(userId.Value);
 782
 783        return await _liveTvManager.GetProgram(programId, CancellationToken.None, user).ConfigureAwait(false);
 784    }
 785
 786    /// <summary>
 787    /// Deletes a live tv recording.
 788    /// </summary>
 789    /// <param name="recordingId">Recording id.</param>
 790    /// <response code="204">Recording deleted.</response>
 791    /// <response code="404">Item not found.</response>
 792    /// <returns>A <see cref="NoContentResult"/> on success, or a <see cref="NotFoundResult"/> if item not found.</retur
 793    [HttpDelete("Recordings/{recordingId}")]
 794    [Authorize(Policy = Policies.LiveTvManagement)]
 795    [ProducesResponseType(StatusCodes.Status204NoContent)]
 796    [ProducesResponseType(StatusCodes.Status404NotFound)]
 797    public ActionResult DeleteRecording([FromRoute, Required] Guid recordingId)
 798    {
 0799        var item = _libraryManager.GetItemById<BaseItem>(recordingId, User.GetUserId());
 0800        if (item is null)
 801        {
 0802            return NotFound();
 803        }
 804
 0805        _libraryManager.DeleteItem(item, new DeleteOptions
 0806        {
 0807            DeleteFileLocation = false
 0808        });
 809
 0810        return NoContent();
 811    }
 812
 813    /// <summary>
 814    /// Cancels a live tv timer.
 815    /// </summary>
 816    /// <param name="timerId">Timer id.</param>
 817    /// <response code="204">Timer deleted.</response>
 818    /// <returns>A <see cref="NoContentResult"/>.</returns>
 819    [HttpDelete("Timers/{timerId}")]
 820    [Authorize(Policy = Policies.LiveTvManagement)]
 821    [ProducesResponseType(StatusCodes.Status204NoContent)]
 822    public async Task<ActionResult> CancelTimer([FromRoute, Required] string timerId)
 823    {
 824        await _liveTvManager.CancelTimer(timerId).ConfigureAwait(false);
 825        return NoContent();
 826    }
 827
 828    /// <summary>
 829    /// Updates a live tv timer.
 830    /// </summary>
 831    /// <param name="timerId">Timer id.</param>
 832    /// <param name="timerInfo">New timer info.</param>
 833    /// <response code="204">Timer updated.</response>
 834    /// <returns>A <see cref="NoContentResult"/>.</returns>
 835    [HttpPost("Timers/{timerId}")]
 836    [Authorize(Policy = Policies.LiveTvManagement)]
 837    [ProducesResponseType(StatusCodes.Status204NoContent)]
 838    [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "timerId", Justification = "I
 839    public async Task<ActionResult> UpdateTimer([FromRoute, Required] string timerId, [FromBody] TimerInfoDto timerInfo)
 840    {
 841        await _liveTvManager.UpdateTimer(timerInfo, CancellationToken.None).ConfigureAwait(false);
 842        return NoContent();
 843    }
 844
 845    /// <summary>
 846    /// Creates a live tv timer.
 847    /// </summary>
 848    /// <param name="timerInfo">New timer info.</param>
 849    /// <response code="204">Timer created.</response>
 850    /// <returns>A <see cref="NoContentResult"/>.</returns>
 851    [HttpPost("Timers")]
 852    [Authorize(Policy = Policies.LiveTvManagement)]
 853    [ProducesResponseType(StatusCodes.Status204NoContent)]
 854    public async Task<ActionResult> CreateTimer([FromBody] TimerInfoDto timerInfo)
 855    {
 856        await _liveTvManager.CreateTimer(timerInfo, CancellationToken.None).ConfigureAwait(false);
 857        return NoContent();
 858    }
 859
 860    /// <summary>
 861    /// Gets a live tv series timer.
 862    /// </summary>
 863    /// <param name="timerId">Timer id.</param>
 864    /// <response code="200">Series timer returned.</response>
 865    /// <response code="404">Series timer not found.</response>
 866    /// <returns>A <see cref="OkResult"/> on success, or a <see cref="NotFoundResult"/> if timer not found.</returns>
 867    [HttpGet("SeriesTimers/{timerId}")]
 868    [Authorize(Policy = Policies.LiveTvAccess)]
 869    [ProducesResponseType(StatusCodes.Status200OK)]
 870    [ProducesResponseType(StatusCodes.Status404NotFound)]
 871    public async Task<ActionResult<SeriesTimerInfoDto>> GetSeriesTimer([FromRoute, Required] string timerId)
 872    {
 873        var timer = await _liveTvManager.GetSeriesTimer(timerId, CancellationToken.None).ConfigureAwait(false);
 874        if (timer is null)
 875        {
 876            return NotFound();
 877        }
 878
 879        return timer;
 880    }
 881
 882    /// <summary>
 883    /// Gets live tv series timers.
 884    /// </summary>
 885    /// <param name="sortBy">Optional. Sort by SortName or Priority.</param>
 886    /// <param name="sortOrder">Optional. Sort in Ascending or Descending order.</param>
 887    /// <response code="200">Timers returned.</response>
 888    /// <returns>An <see cref="OkResult"/> of live tv series timers.</returns>
 889    [HttpGet("SeriesTimers")]
 890    [Authorize(Policy = Policies.LiveTvAccess)]
 891    [ProducesResponseType(StatusCodes.Status200OK)]
 892    public async Task<ActionResult<QueryResult<SeriesTimerInfoDto>>> GetSeriesTimers([FromQuery] string? sortBy, [FromQu
 893    {
 894        return await _liveTvManager.GetSeriesTimers(
 895            new SeriesTimerQuery
 896            {
 897                SortOrder = sortOrder ?? SortOrder.Ascending,
 898                SortBy = sortBy
 899            },
 900            CancellationToken.None).ConfigureAwait(false);
 901    }
 902
 903    /// <summary>
 904    /// Cancels a live tv series timer.
 905    /// </summary>
 906    /// <param name="timerId">Timer id.</param>
 907    /// <response code="204">Timer cancelled.</response>
 908    /// <returns>A <see cref="NoContentResult"/>.</returns>
 909    [HttpDelete("SeriesTimers/{timerId}")]
 910    [Authorize(Policy = Policies.LiveTvManagement)]
 911    [ProducesResponseType(StatusCodes.Status204NoContent)]
 912    public async Task<ActionResult> CancelSeriesTimer([FromRoute, Required] string timerId)
 913    {
 914        await _liveTvManager.CancelSeriesTimer(timerId).ConfigureAwait(false);
 915        return NoContent();
 916    }
 917
 918    /// <summary>
 919    /// Updates a live tv series timer.
 920    /// </summary>
 921    /// <param name="timerId">Timer id.</param>
 922    /// <param name="seriesTimerInfo">New series timer info.</param>
 923    /// <response code="204">Series timer updated.</response>
 924    /// <returns>A <see cref="NoContentResult"/>.</returns>
 925    [HttpPost("SeriesTimers/{timerId}")]
 926    [Authorize(Policy = Policies.LiveTvManagement)]
 927    [ProducesResponseType(StatusCodes.Status204NoContent)]
 928    [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "timerId", Justification = "I
 929    public async Task<ActionResult> UpdateSeriesTimer([FromRoute, Required] string timerId, [FromBody] SeriesTimerInfoDt
 930    {
 931        await _liveTvManager.UpdateSeriesTimer(seriesTimerInfo, CancellationToken.None).ConfigureAwait(false);
 932        return NoContent();
 933    }
 934
 935    /// <summary>
 936    /// Creates a live tv series timer.
 937    /// </summary>
 938    /// <param name="seriesTimerInfo">New series timer info.</param>
 939    /// <response code="204">Series timer info created.</response>
 940    /// <returns>A <see cref="NoContentResult"/>.</returns>
 941    [HttpPost("SeriesTimers")]
 942    [Authorize(Policy = Policies.LiveTvManagement)]
 943    [ProducesResponseType(StatusCodes.Status204NoContent)]
 944    public async Task<ActionResult> CreateSeriesTimer([FromBody] SeriesTimerInfoDto seriesTimerInfo)
 945    {
 946        await _liveTvManager.CreateSeriesTimer(seriesTimerInfo, CancellationToken.None).ConfigureAwait(false);
 947        return NoContent();
 948    }
 949
 950    /// <summary>
 951    /// Get recording group.
 952    /// </summary>
 953    /// <param name="groupId">Group id.</param>
 954    /// <returns>A <see cref="NotFoundResult"/>.</returns>
 955    [HttpGet("Recordings/Groups/{groupId}")]
 956    [Authorize(Policy = Policies.LiveTvAccess)]
 957    [ProducesResponseType(StatusCodes.Status404NotFound)]
 958    [Obsolete("This endpoint is obsolete.")]
 959    public ActionResult<BaseItemDto> GetRecordingGroup([FromRoute, Required] Guid groupId)
 960    {
 961        return NotFound();
 962    }
 963
 964    /// <summary>
 965    /// Get guid info.
 966    /// </summary>
 967    /// <response code="200">Guid info returned.</response>
 968    /// <returns>An <see cref="OkResult"/> containing the guide info.</returns>
 969    [HttpGet("GuideInfo")]
 970    [Authorize(Policy = Policies.LiveTvAccess)]
 971    [ProducesResponseType(StatusCodes.Status200OK)]
 972    public ActionResult<GuideInfo> GetGuideInfo()
 0973        => _guideManager.GetGuideInfo();
 974
 975    /// <summary>
 976    /// Adds a tuner host.
 977    /// </summary>
 978    /// <param name="tunerHostInfo">New tuner host.</param>
 979    /// <response code="200">Created tuner host returned.</response>
 980    /// <returns>A <see cref="OkResult"/> containing the created tuner host.</returns>
 981    [HttpPost("TunerHosts")]
 982    [Authorize(Policy = Policies.LiveTvManagement)]
 983    [ProducesResponseType(StatusCodes.Status200OK)]
 984    public async Task<ActionResult<TunerHostInfo>> AddTunerHost([FromBody] TunerHostInfo tunerHostInfo)
 985        => await _tunerHostManager.SaveTunerHost(tunerHostInfo).ConfigureAwait(false);
 986
 987    /// <summary>
 988    /// Deletes a tuner host.
 989    /// </summary>
 990    /// <param name="id">Tuner host id.</param>
 991    /// <response code="204">Tuner host deleted.</response>
 992    /// <returns>A <see cref="NoContentResult"/>.</returns>
 993    [HttpDelete("TunerHosts")]
 994    [Authorize(Policy = Policies.LiveTvManagement)]
 995    [ProducesResponseType(StatusCodes.Status204NoContent)]
 996    public ActionResult DeleteTunerHost([FromQuery] string? id)
 997    {
 0998        var config = _configurationManager.GetConfiguration<LiveTvOptions>("livetv");
 0999        config.TunerHosts = config.TunerHosts.Where(i => !string.Equals(id, i.Id, StringComparison.OrdinalIgnoreCase)).T
 01000        _configurationManager.SaveConfiguration("livetv", config);
 01001        return NoContent();
 1002    }
 1003
 1004    /// <summary>
 1005    /// Gets default listings provider info.
 1006    /// </summary>
 1007    /// <response code="200">Default listings provider info returned.</response>
 1008    /// <returns>An <see cref="OkResult"/> containing the default listings provider info.</returns>
 1009    [HttpGet("ListingProviders/Default")]
 1010    [Authorize(Policy = Policies.LiveTvAccess)]
 1011    [ProducesResponseType(StatusCodes.Status200OK)]
 1012    public ActionResult<ListingsProviderInfo> GetDefaultListingProvider()
 1013    {
 01014        return new ListingsProviderInfo();
 1015    }
 1016
 1017    /// <summary>
 1018    /// Adds a listings provider.
 1019    /// </summary>
 1020    /// <param name="pw">Password.</param>
 1021    /// <param name="listingsProviderInfo">New listings info.</param>
 1022    /// <param name="validateListings">Validate listings.</param>
 1023    /// <param name="validateLogin">Validate login.</param>
 1024    /// <response code="200">Created listings provider returned.</response>
 1025    /// <returns>A <see cref="OkResult"/> containing the created listings provider.</returns>
 1026    [HttpPost("ListingProviders")]
 1027    [Authorize(Policy = Policies.LiveTvManagement)]
 1028    [ProducesResponseType(StatusCodes.Status200OK)]
 1029    [SuppressMessage("Microsoft.Performance", "CA5350:RemoveSha1", MessageId = "AddListingProvider", Justification = "Im
 1030    public async Task<ActionResult<ListingsProviderInfo>> AddListingProvider(
 1031        [FromQuery] string? pw,
 1032        [FromBody] ListingsProviderInfo listingsProviderInfo,
 1033        [FromQuery] bool validateListings = false,
 1034        [FromQuery] bool validateLogin = false)
 1035    {
 1036        if (!string.IsNullOrEmpty(pw))
 1037        {
 1038            // TODO: remove ToLower when Convert.ToHexString supports lowercase
 1039            // Schedules Direct requires the hex to be lowercase
 1040            listingsProviderInfo.Password = Convert.ToHexString(SHA1.HashData(Encoding.UTF8.GetBytes(pw))).ToLowerInvari
 1041        }
 1042
 1043        return await _listingsManager.SaveListingProvider(listingsProviderInfo, validateLogin, validateListings).Configu
 1044    }
 1045
 1046    /// <summary>
 1047    /// Delete listing provider.
 1048    /// </summary>
 1049    /// <param name="id">Listing provider id.</param>
 1050    /// <response code="204">Listing provider deleted.</response>
 1051    /// <returns>A <see cref="NoContentResult"/>.</returns>
 1052    [HttpDelete("ListingProviders")]
 1053    [Authorize(Policy = Policies.LiveTvManagement)]
 1054    [ProducesResponseType(StatusCodes.Status204NoContent)]
 1055    public ActionResult DeleteListingProvider([FromQuery] string? id)
 1056    {
 01057        _listingsManager.DeleteListingsProvider(id);
 01058        return NoContent();
 1059    }
 1060
 1061    /// <summary>
 1062    /// Gets available lineups.
 1063    /// </summary>
 1064    /// <param name="id">Provider id.</param>
 1065    /// <param name="type">Provider type.</param>
 1066    /// <param name="location">Location.</param>
 1067    /// <param name="country">Country.</param>
 1068    /// <response code="200">Available lineups returned.</response>
 1069    /// <returns>A <see cref="OkResult"/> containing the available lineups.</returns>
 1070    [HttpGet("ListingProviders/Lineups")]
 1071    [Authorize(Policy = Policies.LiveTvAccess)]
 1072    [ProducesResponseType(StatusCodes.Status200OK)]
 1073    public async Task<ActionResult<IEnumerable<NameIdPair>>> GetLineups(
 1074        [FromQuery] string? id,
 1075        [FromQuery] string? type,
 1076        [FromQuery] string? location,
 1077        [FromQuery] string? country)
 1078        => await _listingsManager.GetLineups(type, id, country, location).ConfigureAwait(false);
 1079
 1080    /// <summary>
 1081    /// Gets available countries.
 1082    /// </summary>
 1083    /// <response code="200">Available countries returned.</response>
 1084    /// <returns>A <see cref="FileResult"/> containing the available countries.</returns>
 1085    [HttpGet("ListingProviders/SchedulesDirect/Countries")]
 1086    [Authorize(Policy = Policies.LiveTvAccess)]
 1087    [ProducesResponseType(StatusCodes.Status200OK)]
 1088    [ProducesFile(MediaTypeNames.Application.Json)]
 1089    public async Task<ActionResult> GetSchedulesDirectCountries()
 1090    {
 1091        var client = _httpClientFactory.CreateClient(NamedClient.Default);
 1092        // https://json.schedulesdirect.org/20141201/available/countries
 1093        // Can't dispose the response as it's required up the call chain.
 1094        var response = await client.GetAsync(new Uri("https://json.schedulesdirect.org/20141201/available/countries"))
 1095            .ConfigureAwait(false);
 1096
 1097        return File(await response.Content.ReadAsStreamAsync().ConfigureAwait(false), MediaTypeNames.Application.Json);
 1098    }
 1099
 1100    /// <summary>
 1101    /// Get channel mapping options.
 1102    /// </summary>
 1103    /// <param name="providerId">Provider id.</param>
 1104    /// <response code="200">Channel mapping options returned.</response>
 1105    /// <returns>An <see cref="OkResult"/> containing the channel mapping options.</returns>
 1106    [HttpGet("ChannelMappingOptions")]
 1107    [Authorize(Policy = Policies.LiveTvAccess)]
 1108    [ProducesResponseType(StatusCodes.Status200OK)]
 1109    public Task<ChannelMappingOptionsDto> GetChannelMappingOptions([FromQuery] string? providerId)
 01110        => _listingsManager.GetChannelMappingOptions(providerId);
 1111
 1112    /// <summary>
 1113    /// Set channel mappings.
 1114    /// </summary>
 1115    /// <param name="dto">The set channel mapping dto.</param>
 1116    /// <response code="200">Created channel mapping returned.</response>
 1117    /// <returns>An <see cref="OkResult"/> containing the created channel mapping.</returns>
 1118    [HttpPost("ChannelMappings")]
 1119    [Authorize(Policy = Policies.LiveTvManagement)]
 1120    [ProducesResponseType(StatusCodes.Status200OK)]
 1121    public Task<TunerChannelMapping> SetChannelMapping([FromBody, Required] SetChannelMappingDto dto)
 01122        => _listingsManager.SetChannelMapping(dto.ProviderId, dto.TunerChannelId, dto.ProviderChannelId);
 1123
 1124    /// <summary>
 1125    /// Get tuner host types.
 1126    /// </summary>
 1127    /// <response code="200">Tuner host types returned.</response>
 1128    /// <returns>An <see cref="OkResult"/> containing the tuner host types.</returns>
 1129    [HttpGet("TunerHosts/Types")]
 1130    [Authorize(Policy = Policies.LiveTvAccess)]
 1131    [ProducesResponseType(StatusCodes.Status200OK)]
 1132    public IEnumerable<NameIdPair> GetTunerHostTypes()
 01133        => _tunerHostManager.GetTunerHostTypes();
 1134
 1135    /// <summary>
 1136    /// Discover tuners.
 1137    /// </summary>
 1138    /// <param name="newDevicesOnly">Only discover new tuners.</param>
 1139    /// <response code="200">Tuners returned.</response>
 1140    /// <returns>An <see cref="OkResult"/> containing the tuners.</returns>
 1141    [HttpGet("Tuners/Discvover", Name = "DiscvoverTuners")]
 1142    [HttpGet("Tuners/Discover")]
 1143    [Authorize(Policy = Policies.LiveTvManagement)]
 1144    [ProducesResponseType(StatusCodes.Status200OK)]
 1145    public IAsyncEnumerable<TunerHostInfo> DiscoverTuners([FromQuery] bool newDevicesOnly = false)
 01146        => _tunerHostManager.DiscoverTuners(newDevicesOnly);
 1147
 1148    /// <summary>
 1149    /// Gets a live tv recording stream.
 1150    /// </summary>
 1151    /// <param name="recordingId">Recording id.</param>
 1152    /// <response code="200">Recording stream returned.</response>
 1153    /// <response code="404">Recording not found.</response>
 1154    /// <returns>
 1155    /// An <see cref="OkResult"/> containing the recording stream on success,
 1156    /// or a <see cref="NotFoundResult"/> if recording not found.
 1157    /// </returns>
 1158    [HttpGet("LiveRecordings/{recordingId}/stream")]
 1159    [ProducesResponseType(StatusCodes.Status200OK)]
 1160    [ProducesResponseType(StatusCodes.Status404NotFound)]
 1161    [ProducesVideoFile]
 1162    public ActionResult GetLiveRecordingFile([FromRoute, Required] string recordingId)
 1163    {
 01164        var path = _recordingsManager.GetActiveRecordingPath(recordingId);
 01165        if (string.IsNullOrWhiteSpace(path))
 1166        {
 01167            return NotFound();
 1168        }
 1169
 01170        var stream = new ProgressiveFileStream(path, null, _transcodeManager);
 01171        return new FileStreamResult(stream, MimeTypes.GetMimeType(path));
 1172    }
 1173
 1174    /// <summary>
 1175    /// Gets a live tv channel stream.
 1176    /// </summary>
 1177    /// <param name="streamId">Stream id.</param>
 1178    /// <param name="container">Container type.</param>
 1179    /// <response code="200">Stream returned.</response>
 1180    /// <response code="404">Stream not found.</response>
 1181    /// <returns>
 1182    /// An <see cref="OkResult"/> containing the channel stream on success,
 1183    /// or a <see cref="NotFoundResult"/> if stream not found.
 1184    /// </returns>
 1185    [HttpGet("LiveStreamFiles/{streamId}/stream.{container}")]
 1186    [ProducesResponseType(StatusCodes.Status200OK)]
 1187    [ProducesResponseType(StatusCodes.Status404NotFound)]
 1188    [ProducesVideoFile]
 1189    public ActionResult GetLiveStreamFile([FromRoute, Required] string streamId, [FromRoute, Required] string container)
 1190    {
 01191        var liveStreamInfo = _mediaSourceManager.GetLiveStreamInfoByUniqueId(streamId);
 01192        if (liveStreamInfo is null)
 1193        {
 01194            return NotFound();
 1195        }
 1196
 01197        var liveStream = new ProgressiveFileStream(liveStreamInfo.GetStream());
 01198        return new FileStreamResult(liveStream, MimeTypes.GetMimeType("file." + container));
 1199    }
 1200}

Methods/Properties

.ctor(MediaBrowser.Controller.LiveTv.ILiveTvManager,MediaBrowser.Controller.LiveTv.IGuideManager,MediaBrowser.Controller.LiveTv.ITunerHostManager,MediaBrowser.Controller.LiveTv.IListingsManager,MediaBrowser.Controller.LiveTv.IRecordingsManager,MediaBrowser.Controller.Library.IUserManager,System.Net.Http.IHttpClientFactory,MediaBrowser.Controller.Library.ILibraryManager,MediaBrowser.Controller.Dto.IDtoService,MediaBrowser.Controller.Library.IMediaSourceManager,MediaBrowser.Common.Configuration.IConfigurationManager,MediaBrowser.Controller.MediaEncoding.ITranscodeManager)
GetLiveTvInfo()
GetLiveTvChannels(System.Nullable`1<MediaBrowser.Model.LiveTv.ChannelType>,System.Nullable`1<System.Guid>,System.Nullable`1<System.Int32>,System.Nullable`1<System.Boolean>,System.Nullable`1<System.Boolean>,System.Nullable`1<System.Boolean>,System.Nullable`1<System.Boolean>,System.Nullable`1<System.Boolean>,System.Nullable`1<System.Int32>,System.Nullable`1<System.Boolean>,System.Nullable`1<System.Boolean>,System.Nullable`1<System.Boolean>,System.Nullable`1<System.Boolean>,System.Nullable`1<System.Int32>,MediaBrowser.Model.Entities.ImageType[],MediaBrowser.Model.Querying.ItemFields[],System.Nullable`1<System.Boolean>,Jellyfin.Data.Enums.ItemSortBy[],System.Nullable`1<Jellyfin.Data.Enums.SortOrder>,System.Boolean,System.Boolean)
GetChannel(System.Guid,System.Nullable`1<System.Guid>)
GetRecording(System.Guid,System.Nullable`1<System.Guid>)
DeleteRecording(System.Guid)
GetGuideInfo()
DeleteTunerHost(System.String)
GetDefaultListingProvider()
DeleteListingProvider(System.String)
GetChannelMappingOptions(System.String)
SetChannelMapping(Jellyfin.Api.Models.LiveTvDtos.SetChannelMappingDto)
GetTunerHostTypes()
DiscoverTuners(System.Boolean)
GetLiveRecordingFile(System.String)
GetLiveStreamFile(System.String,System.String)