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

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.Database.Implementations.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)