< Summary - Jellyfin

Information
Class: Jellyfin.Api.Controllers.LiveTvController
Assembly: Jellyfin.Api
File(s): /srv/git/jellyfin/Jellyfin.Api/Controllers/LiveTvController.cs
Line coverage
8%
Covered lines: 25
Uncovered lines: 281
Coverable lines: 306
Total lines: 1164
Line coverage: 8.1%
Branch coverage
0%
Covered branches: 0
Total branches: 60
Branch coverage: 0%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Coverage history

Coverage history 0 25 50 75 100 2/11/2026 - 12:13:19 AM Line coverage: 21.8% (26/119) Branch coverage: 0% (0/20) Total lines: 11994/19/2026 - 12:14:27 AM Line coverage: 8.7% (27/309) Branch coverage: 0% (0/58) Total lines: 11994/30/2026 - 12:14:58 AM Line coverage: 8.7% (27/309) Branch coverage: 0% (0/58) Total lines: 11875/6/2026 - 12:15:23 AM Line coverage: 8.2% (25/303) Branch coverage: 0% (0/58) Total lines: 11565/18/2026 - 12:15:49 AM Line coverage: 8.1% (25/306) Branch coverage: 0% (0/60) Total lines: 1164 2/11/2026 - 12:13:19 AM Line coverage: 21.8% (26/119) Branch coverage: 0% (0/20) Total lines: 11994/19/2026 - 12:14:27 AM Line coverage: 8.7% (27/309) Branch coverage: 0% (0/58) Total lines: 11994/30/2026 - 12:14:58 AM Line coverage: 8.7% (27/309) Branch coverage: 0% (0/58) Total lines: 11875/6/2026 - 12:15:23 AM Line coverage: 8.2% (25/303) Branch coverage: 0% (0/58) Total lines: 11565/18/2026 - 12:15:49 AM Line coverage: 8.1% (25/306) Branch coverage: 0% (0/60) Total lines: 1164

Coverage delta

Coverage delta 14 -14

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.Mime;
 7using System.Security.Cryptography;
 8using System.Text;
 9using System.Threading;
 10using System.Threading.Tasks;
 11using Jellyfin.Api.Attributes;
 12using Jellyfin.Api.Extensions;
 13using Jellyfin.Api.Helpers;
 14using Jellyfin.Api.ModelBinders;
 15using Jellyfin.Api.Models.LiveTvDtos;
 16using Jellyfin.Data.Enums;
 17using Jellyfin.Database.Implementations.Enums;
 18using Jellyfin.Extensions;
 19using MediaBrowser.Common.Api;
 20using MediaBrowser.Controller.Dto;
 21using MediaBrowser.Controller.Entities;
 22using MediaBrowser.Controller.Entities.TV;
 23using MediaBrowser.Controller.Library;
 24using MediaBrowser.Controller.LiveTv;
 25using MediaBrowser.Controller.MediaEncoding;
 26using MediaBrowser.Controller.Streaming;
 27using MediaBrowser.Model.Dto;
 28using MediaBrowser.Model.Entities;
 29using MediaBrowser.Model.LiveTv;
 30using MediaBrowser.Model.Net;
 31using MediaBrowser.Model.Querying;
 32using Microsoft.AspNetCore.Authorization;
 33using Microsoft.AspNetCore.Http;
 34using Microsoft.AspNetCore.Mvc;
 35
 36namespace Jellyfin.Api.Controllers;
 37
 38/// <summary>
 39/// Live tv controller.
 40/// </summary>
 41public class LiveTvController : BaseJellyfinApiController
 42{
 43    private readonly ILiveTvManager _liveTvManager;
 44    private readonly IGuideManager _guideManager;
 45    private readonly ITunerHostManager _tunerHostManager;
 46    private readonly IListingsManager _listingsManager;
 47    private readonly IRecordingsManager _recordingsManager;
 48    private readonly IUserManager _userManager;
 49    private readonly ILibraryManager _libraryManager;
 50    private readonly IDtoService _dtoService;
 51    private readonly IMediaSourceManager _mediaSourceManager;
 52    private readonly ITranscodeManager _transcodeManager;
 53    private readonly ISchedulesDirectService _schedulesDirectService;
 54
 55    /// <summary>
 56    /// Initializes a new instance of the <see cref="LiveTvController"/> class.
 57    /// </summary>
 58    /// <param name="liveTvManager">Instance of the <see cref="ILiveTvManager"/> interface.</param>
 59    /// <param name="guideManager">Instance of the <see cref="IGuideManager"/> interface.</param>
 60    /// <param name="tunerHostManager">Instance of the <see cref="ITunerHostManager"/> interface.</param>
 61    /// <param name="listingsManager">Instance of the <see cref="IListingsManager"/> interface.</param>
 62    /// <param name="recordingsManager">Instance of the <see cref="IRecordingsManager"/> interface.</param>
 63    /// <param name="userManager">Instance of the <see cref="IUserManager"/> interface.</param>
 64    /// <param name="libraryManager">Instance of the <see cref="ILibraryManager"/> interface.</param>
 65    /// <param name="dtoService">Instance of the <see cref="IDtoService"/> interface.</param>
 66    /// <param name="mediaSourceManager">Instance of the <see cref="IMediaSourceManager"/> interface.</param>
 67    /// <param name="transcodeManager">Instance of the <see cref="ITranscodeManager"/> interface.</param>
 68    /// <param name="schedulesDirectService">Instance of the <see cref="ISchedulesDirectService"/> interface.</param>
 369    public LiveTvController(
 370        ILiveTvManager liveTvManager,
 371        IGuideManager guideManager,
 372        ITunerHostManager tunerHostManager,
 373        IListingsManager listingsManager,
 374        IRecordingsManager recordingsManager,
 375        IUserManager userManager,
 376        ILibraryManager libraryManager,
 377        IDtoService dtoService,
 378        IMediaSourceManager mediaSourceManager,
 379        ITranscodeManager transcodeManager,
 380        ISchedulesDirectService schedulesDirectService)
 81    {
 382        _liveTvManager = liveTvManager;
 383        _guideManager = guideManager;
 384        _tunerHostManager = tunerHostManager;
 385        _listingsManager = listingsManager;
 386        _recordingsManager = recordingsManager;
 387        _userManager = userManager;
 388        _libraryManager = libraryManager;
 389        _dtoService = dtoService;
 390        _mediaSourceManager = mediaSourceManager;
 391        _transcodeManager = transcodeManager;
 392        _schedulesDirectService = schedulesDirectService;
 393    }
 94
 95    /// <summary>
 96    /// Gets available live tv services.
 97    /// </summary>
 98    /// <response code="200">Available live tv services returned.</response>
 99    /// <returns>
 100    /// An <see cref="OkResult"/> containing the available live tv services.
 101    /// </returns>
 102    [HttpGet("Info")]
 103    [ProducesResponseType(StatusCodes.Status200OK)]
 104    [Authorize(Policy = Policies.LiveTvAccess)]
 105    public ActionResult<LiveTvInfo> GetLiveTvInfo()
 106    {
 0107        return _liveTvManager.GetLiveTvInfo(CancellationToken.None);
 108    }
 109
 110    /// <summary>
 111    /// Gets available live tv channels.
 112    /// </summary>
 113    /// <param name="type">Optional. Filter by channel type.</param>
 114    /// <param name="userId">Optional. Filter by user and attach user data.</param>
 115    /// <param name="startIndex">Optional. The record index to start at. All items with a lower index will be dropped fr
 116    /// <param name="isMovie">Optional. Filter for movies.</param>
 117    /// <param name="isSeries">Optional. Filter for series.</param>
 118    /// <param name="isNews">Optional. Filter for news.</param>
 119    /// <param name="isKids">Optional. Filter for kids.</param>
 120    /// <param name="isSports">Optional. Filter for sports.</param>
 121    /// <param name="limit">Optional. The maximum number of records to return.</param>
 122    /// <param name="isFavorite">Optional. Filter by channels that are favorites, or not.</param>
 123    /// <param name="isLiked">Optional. Filter by channels that are liked, or not.</param>
 124    /// <param name="isDisliked">Optional. Filter by channels that are disliked, or not.</param>
 125    /// <param name="enableImages">Optional. Include image information in output.</param>
 126    /// <param name="imageTypeLimit">Optional. The max number of images to return, per image type.</param>
 127    /// <param name="enableImageTypes">"Optional. The image types to include in the output.</param>
 128    /// <param name="fields">Optional. Specify additional fields of information to return in the output.</param>
 129    /// <param name="enableUserData">Optional. Include user data.</param>
 130    /// <param name="sortBy">Optional. Key to sort by.</param>
 131    /// <param name="sortOrder">Optional. Sort order.</param>
 132    /// <param name="enableFavoriteSorting">Optional. Incorporate favorite and like status into channel sorting.</param>
 133    /// <param name="addCurrentProgram">Optional. Adds current program info to each channel.</param>
 134    /// <response code="200">Available live tv channels returned.</response>
 135    /// <returns>
 136    /// An <see cref="OkResult"/> containing the resulting available live tv channels.
 137    /// </returns>
 138    [HttpGet("Channels")]
 139    [ProducesResponseType(StatusCodes.Status200OK)]
 140    [Authorize(Policy = Policies.LiveTvAccess)]
 141    public ActionResult<QueryResult<BaseItemDto>> GetLiveTvChannels(
 142        [FromQuery] ChannelType? type,
 143        [FromQuery] Guid? userId,
 144        [FromQuery] int? startIndex,
 145        [FromQuery] bool? isMovie,
 146        [FromQuery] bool? isSeries,
 147        [FromQuery] bool? isNews,
 148        [FromQuery] bool? isKids,
 149        [FromQuery] bool? isSports,
 150        [FromQuery] int? limit,
 151        [FromQuery] bool? isFavorite,
 152        [FromQuery] bool? isLiked,
 153        [FromQuery] bool? isDisliked,
 154        [FromQuery] bool? enableImages,
 155        [FromQuery] int? imageTypeLimit,
 156        [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ImageType[] enableImageTypes,
 157        [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemFields[] fields,
 158        [FromQuery] bool? enableUserData,
 159        [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemSortBy[] sortBy,
 160        [FromQuery] SortOrder? sortOrder,
 161        [FromQuery] bool enableFavoriteSorting = false,
 162        [FromQuery] bool addCurrentProgram = true)
 163    {
 0164        userId = RequestHelpers.GetUserId(User, userId);
 0165        var dtoOptions = new DtoOptions { Fields = fields }
 0166            .AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes);
 167
 0168        var channelResult = _liveTvManager.GetInternalChannels(
 0169            new LiveTvChannelQuery
 0170            {
 0171                ChannelType = type,
 0172                UserId = userId.Value,
 0173                StartIndex = startIndex,
 0174                Limit = limit,
 0175                IsFavorite = isFavorite,
 0176                IsLiked = isLiked,
 0177                IsDisliked = isDisliked,
 0178                EnableFavoriteSorting = enableFavoriteSorting,
 0179                IsMovie = isMovie,
 0180                IsSeries = isSeries,
 0181                IsNews = isNews,
 0182                IsKids = isKids,
 0183                IsSports = isSports,
 0184                SortBy = sortBy,
 0185                SortOrder = sortOrder ?? SortOrder.Ascending,
 0186                AddCurrentProgram = addCurrentProgram
 0187            },
 0188            dtoOptions,
 0189            CancellationToken.None);
 190
 0191        var user = userId.IsNullOrEmpty()
 0192            ? null
 0193            : _userManager.GetUserById(userId.Value);
 194
 0195        var fieldsList = dtoOptions.Fields.ToList();
 0196        fieldsList.Remove(ItemFields.CanDelete);
 0197        fieldsList.Remove(ItemFields.CanDownload);
 0198        fieldsList.Remove(ItemFields.DisplayPreferencesId);
 0199        fieldsList.Remove(ItemFields.Etag);
 0200        dtoOptions.Fields = fieldsList.ToArray();
 0201        dtoOptions.AddCurrentProgram = addCurrentProgram;
 202
 0203        var returnArray = _dtoService.GetBaseItemDtos(channelResult.Items, dtoOptions, user);
 0204        return new QueryResult<BaseItemDto>(
 0205            startIndex,
 0206            channelResult.TotalRecordCount,
 0207            returnArray);
 208    }
 209
 210    /// <summary>
 211    /// Gets a live tv channel.
 212    /// </summary>
 213    /// <param name="channelId">Channel id.</param>
 214    /// <param name="userId">Optional. Attach user data.</param>
 215    /// <response code="200">Live tv channel returned.</response>
 216    /// <response code="404">Item not found.</response>
 217    /// <returns>An <see cref="OkResult"/> containing the live tv channel.</returns>
 218    [HttpGet("Channels/{channelId}")]
 219    [ProducesResponseType(StatusCodes.Status200OK)]
 220    [ProducesResponseType(StatusCodes.Status404NotFound)]
 221    [Authorize(Policy = Policies.LiveTvAccess)]
 222    public ActionResult<BaseItemDto> GetChannel([FromRoute, Required] Guid channelId, [FromQuery] Guid? userId)
 223    {
 0224        userId = RequestHelpers.GetUserId(User, userId);
 0225        var user = userId.IsNullOrEmpty()
 0226            ? null
 0227            : _userManager.GetUserById(userId.Value);
 0228        var item = channelId.IsEmpty()
 0229            ? _libraryManager.GetUserRootFolder()
 0230            : _libraryManager.GetItemById<BaseItem>(channelId, user);
 231
 0232        if (item is null)
 233        {
 0234            return NotFound();
 235        }
 236
 0237        var dtoOptions = new DtoOptions();
 0238        return _dtoService.GetBaseItemDto(item, dtoOptions, user);
 239    }
 240
 241    /// <summary>
 242    /// Gets live tv recordings.
 243    /// </summary>
 244    /// <param name="channelId">Optional. Filter by channel id.</param>
 245    /// <param name="userId">Optional. Filter by user and attach user data.</param>
 246    /// <param name="startIndex">Optional. The record index to start at. All items with a lower index will be dropped fr
 247    /// <param name="limit">Optional. The maximum number of records to return.</param>
 248    /// <param name="status">Optional. Filter by recording status.</param>
 249    /// <param name="isInProgress">Optional. Filter by recordings that are in progress, or not.</param>
 250    /// <param name="seriesTimerId">Optional. Filter by recordings belonging to a series timer.</param>
 251    /// <param name="enableImages">Optional. Include image information in output.</param>
 252    /// <param name="imageTypeLimit">Optional. The max number of images to return, per image type.</param>
 253    /// <param name="enableImageTypes">Optional. The image types to include in the output.</param>
 254    /// <param name="fields">Optional. Specify additional fields of information to return in the output.</param>
 255    /// <param name="enableUserData">Optional. Include user data.</param>
 256    /// <param name="isMovie">Optional. Filter for movies.</param>
 257    /// <param name="isSeries">Optional. Filter for series.</param>
 258    /// <param name="isKids">Optional. Filter for kids.</param>
 259    /// <param name="isSports">Optional. Filter for sports.</param>
 260    /// <param name="isNews">Optional. Filter for news.</param>
 261    /// <param name="isLibraryItem">Optional. Filter for is library item.</param>
 262    /// <param name="enableTotalRecordCount">Optional. Return total record count.</param>
 263    /// <response code="200">Live tv recordings returned.</response>
 264    /// <returns>An <see cref="OkResult"/> containing the live tv recordings.</returns>
 265    [HttpGet("Recordings")]
 266    [ProducesResponseType(StatusCodes.Status200OK)]
 267    [Authorize(Policy = Policies.LiveTvAccess)]
 268    public async Task<ActionResult<QueryResult<BaseItemDto>>> GetRecordings(
 269        [FromQuery] string? channelId,
 270        [FromQuery] Guid? userId,
 271        [FromQuery] int? startIndex,
 272        [FromQuery] int? limit,
 273        [FromQuery] RecordingStatus? status,
 274        [FromQuery] bool? isInProgress,
 275        [FromQuery] string? seriesTimerId,
 276        [FromQuery] bool? enableImages,
 277        [FromQuery] int? imageTypeLimit,
 278        [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ImageType[] enableImageTypes,
 279        [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemFields[] fields,
 280        [FromQuery] bool? enableUserData,
 281        [FromQuery] bool? isMovie,
 282        [FromQuery] bool? isSeries,
 283        [FromQuery] bool? isKids,
 284        [FromQuery] bool? isSports,
 285        [FromQuery] bool? isNews,
 286        [FromQuery] bool? isLibraryItem,
 287        [FromQuery] bool enableTotalRecordCount = true)
 288    {
 0289        userId = RequestHelpers.GetUserId(User, userId);
 0290        var dtoOptions = new DtoOptions { Fields = fields }
 0291            .AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes);
 292
 0293        return await _liveTvManager.GetRecordingsAsync(
 0294            new RecordingQuery
 0295            {
 0296                ChannelId = channelId,
 0297                UserId = userId.Value,
 0298                StartIndex = startIndex,
 0299                Limit = limit,
 0300                Status = status,
 0301                SeriesTimerId = seriesTimerId,
 0302                IsInProgress = isInProgress,
 0303                EnableTotalRecordCount = enableTotalRecordCount,
 0304                IsMovie = isMovie,
 0305                IsNews = isNews,
 0306                IsSeries = isSeries,
 0307                IsKids = isKids,
 0308                IsSports = isSports,
 0309                IsLibraryItem = isLibraryItem,
 0310                Fields = fields,
 0311                ImageTypeLimit = imageTypeLimit,
 0312                EnableImages = enableImages
 0313            },
 0314            dtoOptions).ConfigureAwait(false);
 0315    }
 316
 317    /// <summary>
 318    /// Gets live tv recording series.
 319    /// </summary>
 320    /// <param name="channelId">Optional. Filter by channel id.</param>
 321    /// <param name="userId">Optional. Filter by user and attach user data.</param>
 322    /// <param name="groupId">Optional. Filter by recording group.</param>
 323    /// <param name="startIndex">Optional. The record index to start at. All items with a lower index will be dropped fr
 324    /// <param name="limit">Optional. The maximum number of records to return.</param>
 325    /// <param name="status">Optional. Filter by recording status.</param>
 326    /// <param name="isInProgress">Optional. Filter by recordings that are in progress, or not.</param>
 327    /// <param name="seriesTimerId">Optional. Filter by recordings belonging to a series timer.</param>
 328    /// <param name="enableImages">Optional. Include image information in output.</param>
 329    /// <param name="imageTypeLimit">Optional. The max number of images to return, per image type.</param>
 330    /// <param name="enableImageTypes">Optional. The image types to include in the output.</param>
 331    /// <param name="fields">Optional. Specify additional fields of information to return in the output.</param>
 332    /// <param name="enableUserData">Optional. Include user data.</param>
 333    /// <param name="enableTotalRecordCount">Optional. Return total record count.</param>
 334    /// <response code="200">Live tv recordings returned.</response>
 335    /// <returns>An <see cref="OkResult"/> containing the live tv recordings.</returns>
 336    [HttpGet("Recordings/Series")]
 337    [ProducesResponseType(StatusCodes.Status200OK)]
 338    [Authorize(Policy = Policies.LiveTvAccess)]
 339    [Obsolete("This endpoint is obsolete.")]
 340    [ApiExplorerSettings(IgnoreApi = true)]
 341    public ActionResult<QueryResult<BaseItemDto>> GetRecordingsSeries(
 342        [FromQuery] string? channelId,
 343        [FromQuery] Guid? userId,
 344        [FromQuery] string? groupId,
 345        [FromQuery] int? startIndex,
 346        [FromQuery] int? limit,
 347        [FromQuery] RecordingStatus? status,
 348        [FromQuery] bool? isInProgress,
 349        [FromQuery] string? seriesTimerId,
 350        [FromQuery] bool? enableImages,
 351        [FromQuery] int? imageTypeLimit,
 352        [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ImageType[] enableImageTypes,
 353        [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemFields[] fields,
 354        [FromQuery] bool? enableUserData,
 355        [FromQuery] bool enableTotalRecordCount = true)
 356    {
 357        return new QueryResult<BaseItemDto>();
 358    }
 359
 360    /// <summary>
 361    /// Gets live tv recording groups.
 362    /// </summary>
 363    /// <param name="userId">Optional. Filter by user and attach user data.</param>
 364    /// <response code="200">Recording groups returned.</response>
 365    /// <returns>An <see cref="OkResult"/> containing the recording groups.</returns>
 366    [HttpGet("Recordings/Groups")]
 367    [ProducesResponseType(StatusCodes.Status200OK)]
 368    [Authorize(Policy = Policies.LiveTvAccess)]
 369    [Obsolete("This endpoint is obsolete.")]
 370    [ApiExplorerSettings(IgnoreApi = true)]
 371    public ActionResult<QueryResult<BaseItemDto>> GetRecordingGroups([FromQuery] Guid? userId)
 372    {
 373        return new QueryResult<BaseItemDto>();
 374    }
 375
 376    /// <summary>
 377    /// Gets recording folders.
 378    /// </summary>
 379    /// <param name="userId">Optional. Filter by user and attach user data.</param>
 380    /// <response code="200">Recording folders returned.</response>
 381    /// <returns>An <see cref="OkResult"/> containing the recording folders.</returns>
 382    [HttpGet("Recordings/Folders")]
 383    [ProducesResponseType(StatusCodes.Status200OK)]
 384    [Authorize(Policy = Policies.LiveTvAccess)]
 385    public async Task<ActionResult<QueryResult<BaseItemDto>>> GetRecordingFolders([FromQuery] Guid? userId)
 386    {
 0387        userId = RequestHelpers.GetUserId(User, userId);
 0388        var user = userId.IsNullOrEmpty()
 0389            ? null
 0390            : _userManager.GetUserById(userId.Value);
 0391        var folders = await _liveTvManager.GetRecordingFoldersAsync(user).ConfigureAwait(false);
 392
 0393        var returnArray = _dtoService.GetBaseItemDtos(folders, new DtoOptions(), user);
 394
 0395        return new QueryResult<BaseItemDto>(returnArray);
 0396    }
 397
 398    /// <summary>
 399    /// Gets a live tv recording.
 400    /// </summary>
 401    /// <param name="recordingId">Recording id.</param>
 402    /// <param name="userId">Optional. Attach user data.</param>
 403    /// <response code="200">Recording returned.</response>
 404    /// <response code="404">Item not found.</response>
 405    /// <returns>An <see cref="OkResult"/> containing the live tv recording.</returns>
 406    [HttpGet("Recordings/{recordingId}")]
 407    [ProducesResponseType(StatusCodes.Status200OK)]
 408    [ProducesResponseType(StatusCodes.Status404NotFound)]
 409    [Authorize(Policy = Policies.LiveTvAccess)]
 410    public ActionResult<BaseItemDto> GetRecording([FromRoute, Required] Guid recordingId, [FromQuery] Guid? userId)
 411    {
 0412        userId = RequestHelpers.GetUserId(User, userId);
 0413        var user = userId.IsNullOrEmpty()
 0414            ? null
 0415            : _userManager.GetUserById(userId.Value);
 0416        var item = recordingId.IsEmpty()
 0417            ? _libraryManager.GetUserRootFolder()
 0418            : _libraryManager.GetItemById<BaseItem>(recordingId, user);
 0419        if (item is null)
 420        {
 0421            return NotFound();
 422        }
 423
 0424        var dtoOptions = new DtoOptions();
 425
 0426        return _dtoService.GetBaseItemDto(item, dtoOptions, user);
 427    }
 428
 429    /// <summary>
 430    /// Resets a tv tuner.
 431    /// </summary>
 432    /// <param name="tunerId">Tuner id.</param>
 433    /// <response code="204">Tuner reset.</response>
 434    /// <returns>A <see cref="NoContentResult"/>.</returns>
 435    [HttpPost("Tuners/{tunerId}/Reset")]
 436    [ProducesResponseType(StatusCodes.Status204NoContent)]
 437    [Authorize(Policy = Policies.RequiresElevation)]
 438    public async Task<ActionResult> ResetTuner([FromRoute, Required] string tunerId)
 439    {
 0440        await _liveTvManager.ResetTuner(tunerId, CancellationToken.None).ConfigureAwait(false);
 0441        return NoContent();
 0442    }
 443
 444    /// <summary>
 445    /// Gets a timer.
 446    /// </summary>
 447    /// <param name="timerId">Timer id.</param>
 448    /// <response code="200">Timer returned.</response>
 449    /// <returns>
 450    /// A <see cref="Task"/> containing an <see cref="OkResult"/> which contains the timer.
 451    /// </returns>
 452    [HttpGet("Timers/{timerId}")]
 453    [ProducesResponseType(StatusCodes.Status200OK)]
 454    [Authorize(Policy = Policies.LiveTvAccess)]
 455    public async Task<ActionResult<TimerInfoDto>> GetTimer([FromRoute, Required] string timerId)
 456    {
 0457        return await _liveTvManager.GetTimer(timerId, CancellationToken.None).ConfigureAwait(false);
 0458    }
 459
 460    /// <summary>
 461    /// Gets the default values for a new timer.
 462    /// </summary>
 463    /// <param name="programId">Optional. To attach default values based on a program.</param>
 464    /// <response code="200">Default values returned.</response>
 465    /// <returns>
 466    /// A <see cref="Task"/> containing an <see cref="OkResult"/> which contains the default values for a timer.
 467    /// </returns>
 468    [HttpGet("Timers/Defaults")]
 469    [ProducesResponseType(StatusCodes.Status200OK)]
 470    [Authorize(Policy = Policies.LiveTvAccess)]
 471    public async Task<ActionResult<SeriesTimerInfoDto>> GetDefaultTimer([FromQuery] string? programId)
 472    {
 0473        return string.IsNullOrEmpty(programId)
 0474            ? await _liveTvManager.GetNewTimerDefaults(CancellationToken.None).ConfigureAwait(false)
 0475            : await _liveTvManager.GetNewTimerDefaults(programId, CancellationToken.None).ConfigureAwait(false);
 0476    }
 477
 478    /// <summary>
 479    /// Gets the live tv timers.
 480    /// </summary>
 481    /// <param name="channelId">Optional. Filter by channel id.</param>
 482    /// <param name="seriesTimerId">Optional. Filter by timers belonging to a series timer.</param>
 483    /// <param name="isActive">Optional. Filter by timers that are active.</param>
 484    /// <param name="isScheduled">Optional. Filter by timers that are scheduled.</param>
 485    /// <returns>
 486    /// A <see cref="Task"/> containing an <see cref="OkResult"/> which contains the live tv timers.
 487    /// </returns>
 488    [HttpGet("Timers")]
 489    [ProducesResponseType(StatusCodes.Status200OK)]
 490    [Authorize(Policy = Policies.LiveTvAccess)]
 491    public async Task<ActionResult<QueryResult<TimerInfoDto>>> GetTimers(
 492        [FromQuery] string? channelId,
 493        [FromQuery] string? seriesTimerId,
 494        [FromQuery] bool? isActive,
 495        [FromQuery] bool? isScheduled)
 496    {
 0497        return await _liveTvManager.GetTimers(
 0498            new TimerQuery
 0499            {
 0500                ChannelId = channelId,
 0501                SeriesTimerId = seriesTimerId,
 0502                IsActive = isActive,
 0503                IsScheduled = isScheduled
 0504            },
 0505            CancellationToken.None).ConfigureAwait(false);
 0506    }
 507
 508    /// <summary>
 509    /// Gets available live tv epgs.
 510    /// </summary>
 511    /// <param name="channelIds">The channels to return guide information for.</param>
 512    /// <param name="userId">Optional. Filter by user id.</param>
 513    /// <param name="minStartDate">Optional. The minimum premiere start date.</param>
 514    /// <param name="hasAired">Optional. Filter by programs that have completed airing, or not.</param>
 515    /// <param name="isAiring">Optional. Filter by programs that are currently airing, or not.</param>
 516    /// <param name="maxStartDate">Optional. The maximum premiere start date.</param>
 517    /// <param name="minEndDate">Optional. The minimum premiere end date.</param>
 518    /// <param name="maxEndDate">Optional. The maximum premiere end date.</param>
 519    /// <param name="isMovie">Optional. Filter for movies.</param>
 520    /// <param name="isSeries">Optional. Filter for series.</param>
 521    /// <param name="isNews">Optional. Filter for news.</param>
 522    /// <param name="isKids">Optional. Filter for kids.</param>
 523    /// <param name="isSports">Optional. Filter for sports.</param>
 524    /// <param name="startIndex">Optional. The record index to start at. All items with a lower index will be dropped fr
 525    /// <param name="limit">Optional. The maximum number of records to return.</param>
 526    /// <param name="sortBy">Optional. Specify one or more sort orders, comma delimited. Options: Name, StartDate.</para
 527    /// <param name="sortOrder">Sort Order - Ascending,Descending.</param>
 528    /// <param name="genres">The genres to return guide information for.</param>
 529    /// <param name="genreIds">The genre ids to return guide information for.</param>
 530    /// <param name="enableImages">Optional. Include image information in output.</param>
 531    /// <param name="imageTypeLimit">Optional. The max number of images to return, per image type.</param>
 532    /// <param name="enableImageTypes">Optional. The image types to include in the output.</param>
 533    /// <param name="enableUserData">Optional. Include user data.</param>
 534    /// <param name="seriesTimerId">Optional. Filter by series timer id.</param>
 535    /// <param name="librarySeriesId">Optional. Filter by library series id.</param>
 536    /// <param name="fields">Optional. Specify additional fields of information to return in the output.</param>
 537    /// <param name="enableTotalRecordCount">Retrieve total record count.</param>
 538    /// <response code="200">Live tv epgs returned.</response>
 539    /// <returns>
 540    /// A <see cref="Task"/> containing a <see cref="OkResult"/> which contains the live tv epgs.
 541    /// </returns>
 542    [HttpGet("Programs")]
 543    [ProducesResponseType(StatusCodes.Status200OK)]
 544    [Authorize(Policy = Policies.LiveTvAccess)]
 545    public async Task<ActionResult<QueryResult<BaseItemDto>>> GetLiveTvPrograms(
 546        [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] channelIds,
 547        [FromQuery] Guid? userId,
 548        [FromQuery] DateTime? minStartDate,
 549        [FromQuery] bool? hasAired,
 550        [FromQuery] bool? isAiring,
 551        [FromQuery] DateTime? maxStartDate,
 552        [FromQuery] DateTime? minEndDate,
 553        [FromQuery] DateTime? maxEndDate,
 554        [FromQuery] bool? isMovie,
 555        [FromQuery] bool? isSeries,
 556        [FromQuery] bool? isNews,
 557        [FromQuery] bool? isKids,
 558        [FromQuery] bool? isSports,
 559        [FromQuery] int? startIndex,
 560        [FromQuery] int? limit,
 561        [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemSortBy[] sortBy,
 562        [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] SortOrder[] sortOrder,
 563        [FromQuery, ModelBinder(typeof(PipeDelimitedCollectionModelBinder))] string[] genres,
 564        [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] genreIds,
 565        [FromQuery] bool? enableImages,
 566        [FromQuery] int? imageTypeLimit,
 567        [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ImageType[] enableImageTypes,
 568        [FromQuery] bool? enableUserData,
 569        [FromQuery] string? seriesTimerId,
 570        [FromQuery] Guid? librarySeriesId,
 571        [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemFields[] fields,
 572        [FromQuery] bool enableTotalRecordCount = true)
 573    {
 0574        userId = RequestHelpers.GetUserId(User, userId);
 0575        var user = userId.IsNullOrEmpty()
 0576            ? null
 0577            : _userManager.GetUserById(userId.Value);
 578
 0579        var query = new InternalItemsQuery(user)
 0580        {
 0581            ChannelIds = channelIds,
 0582            HasAired = hasAired,
 0583            IsAiring = isAiring,
 0584            EnableTotalRecordCount = enableTotalRecordCount,
 0585            MinStartDate = minStartDate,
 0586            MinEndDate = minEndDate,
 0587            MaxStartDate = maxStartDate,
 0588            MaxEndDate = maxEndDate,
 0589            StartIndex = startIndex,
 0590            Limit = limit,
 0591            OrderBy = RequestHelpers.GetOrderBy(sortBy, sortOrder),
 0592            IsNews = isNews,
 0593            IsMovie = isMovie,
 0594            IsSeries = isSeries,
 0595            IsKids = isKids,
 0596            IsSports = isSports,
 0597            SeriesTimerId = seriesTimerId,
 0598            Genres = genres,
 0599            GenreIds = genreIds
 0600        };
 601
 0602        if (!librarySeriesId.IsNullOrEmpty())
 603        {
 0604            query.IsSeries = true;
 605
 0606            var series = _libraryManager.GetItemById<Series>(librarySeriesId.Value);
 0607            if (series is not null)
 608            {
 0609                query.Name = series.Name;
 610            }
 611        }
 612
 0613        var dtoOptions = new DtoOptions { Fields = fields }
 0614            .AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes);
 0615        return await _liveTvManager.GetPrograms(query, dtoOptions, CancellationToken.None).ConfigureAwait(false);
 0616    }
 617
 618    /// <summary>
 619    /// Gets available live tv epgs.
 620    /// </summary>
 621    /// <param name="body">Request body.</param>
 622    /// <response code="200">Live tv epgs returned.</response>
 623    /// <returns>
 624    /// A <see cref="Task"/> containing a <see cref="OkResult"/> which contains the live tv epgs.
 625    /// </returns>
 626    [HttpPost("Programs")]
 627    [ProducesResponseType(StatusCodes.Status200OK)]
 628    [Authorize(Policy = Policies.LiveTvAccess)]
 629    public async Task<ActionResult<QueryResult<BaseItemDto>>> GetPrograms([FromBody] GetProgramsDto body)
 630    {
 0631        var user = body.UserId.IsNullOrEmpty() ? null : _userManager.GetUserById(body.UserId.Value);
 632
 0633        var query = new InternalItemsQuery(user)
 0634        {
 0635            ChannelIds = body.ChannelIds ?? [],
 0636            HasAired = body.HasAired,
 0637            IsAiring = body.IsAiring,
 0638            EnableTotalRecordCount = body.EnableTotalRecordCount,
 0639            MinStartDate = body.MinStartDate,
 0640            MinEndDate = body.MinEndDate,
 0641            MaxStartDate = body.MaxStartDate,
 0642            MaxEndDate = body.MaxEndDate,
 0643            StartIndex = body.StartIndex,
 0644            Limit = body.Limit,
 0645            OrderBy = RequestHelpers.GetOrderBy(body.SortBy ?? [], body.SortOrder ?? []),
 0646            IsNews = body.IsNews,
 0647            IsMovie = body.IsMovie,
 0648            IsSeries = body.IsSeries,
 0649            IsKids = body.IsKids,
 0650            IsSports = body.IsSports,
 0651            SeriesTimerId = body.SeriesTimerId,
 0652            Genres = body.Genres ?? [],
 0653            GenreIds = body.GenreIds ?? []
 0654        };
 655
 0656        if (!body.LibrarySeriesId.IsNullOrEmpty())
 657        {
 0658            query.IsSeries = true;
 659
 0660            var series = _libraryManager.GetItemById<Series>(body.LibrarySeriesId.Value);
 0661            if (series is not null)
 662            {
 0663                query.Name = series.Name;
 664            }
 665        }
 666
 0667        var dtoOptions = new DtoOptions { Fields = body.Fields ?? [] }
 0668            .AddAdditionalDtoOptions(body.EnableImages, body.EnableUserData, body.ImageTypeLimit, body.EnableImageTypes 
 0669        return await _liveTvManager.GetPrograms(query, dtoOptions, CancellationToken.None).ConfigureAwait(false);
 0670    }
 671
 672    /// <summary>
 673    /// Gets recommended live tv epgs.
 674    /// </summary>
 675    /// <param name="userId">Optional. filter by user id.</param>
 676    /// <param name="startIndex">Optional. The record index to start at. All items with a lower index will be dropped fr
 677    /// <param name="limit">Optional. The maximum number of records to return.</param>
 678    /// <param name="isAiring">Optional. Filter by programs that are currently airing, or not.</param>
 679    /// <param name="hasAired">Optional. Filter by programs that have completed airing, or not.</param>
 680    /// <param name="isSeries">Optional. Filter for series.</param>
 681    /// <param name="isMovie">Optional. Filter for movies.</param>
 682    /// <param name="isNews">Optional. Filter for news.</param>
 683    /// <param name="isKids">Optional. Filter for kids.</param>
 684    /// <param name="isSports">Optional. Filter for sports.</param>
 685    /// <param name="enableImages">Optional. Include image information in output.</param>
 686    /// <param name="imageTypeLimit">Optional. The max number of images to return, per image type.</param>
 687    /// <param name="enableImageTypes">Optional. The image types to include in the output.</param>
 688    /// <param name="genreIds">The genres to return guide information for.</param>
 689    /// <param name="fields">Optional. Specify additional fields of information to return in the output.</param>
 690    /// <param name="enableUserData">Optional. include user data.</param>
 691    /// <param name="enableTotalRecordCount">Retrieve total record count.</param>
 692    /// <response code="200">Recommended epgs returned.</response>
 693    /// <returns>A <see cref="OkResult"/> containing the queryresult of recommended epgs.</returns>
 694    [HttpGet("Programs/Recommended")]
 695    [Authorize(Policy = Policies.LiveTvAccess)]
 696    [ProducesResponseType(StatusCodes.Status200OK)]
 697    public async Task<ActionResult<QueryResult<BaseItemDto>>> GetRecommendedPrograms(
 698        [FromQuery] Guid? userId,
 699        [FromQuery] int? startIndex,
 700        [FromQuery] int? limit,
 701        [FromQuery] bool? isAiring,
 702        [FromQuery] bool? hasAired,
 703        [FromQuery] bool? isSeries,
 704        [FromQuery] bool? isMovie,
 705        [FromQuery] bool? isNews,
 706        [FromQuery] bool? isKids,
 707        [FromQuery] bool? isSports,
 708        [FromQuery] bool? enableImages,
 709        [FromQuery] int? imageTypeLimit,
 710        [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ImageType[] enableImageTypes,
 711        [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] genreIds,
 712        [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemFields[] fields,
 713        [FromQuery] bool? enableUserData,
 714        [FromQuery] bool enableTotalRecordCount = true)
 715    {
 0716        userId = RequestHelpers.GetUserId(User, userId);
 0717        var user = userId.IsNullOrEmpty()
 0718            ? null
 0719            : _userManager.GetUserById(userId.Value);
 720
 0721        var query = new InternalItemsQuery(user)
 0722        {
 0723            IsAiring = isAiring,
 0724            StartIndex = startIndex,
 0725            Limit = limit,
 0726            HasAired = hasAired,
 0727            IsSeries = isSeries,
 0728            IsMovie = isMovie,
 0729            IsKids = isKids,
 0730            IsNews = isNews,
 0731            IsSports = isSports,
 0732            EnableTotalRecordCount = enableTotalRecordCount,
 0733            GenreIds = genreIds
 0734        };
 735
 0736        var dtoOptions = new DtoOptions { Fields = fields }
 0737            .AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes);
 0738        return await _liveTvManager.GetRecommendedProgramsAsync(query, dtoOptions, CancellationToken.None).ConfigureAwai
 0739    }
 740
 741    /// <summary>
 742    /// Gets a live tv program.
 743    /// </summary>
 744    /// <param name="programId">Program id.</param>
 745    /// <param name="userId">Optional. Attach user data.</param>
 746    /// <response code="200">Program returned.</response>
 747    /// <response code="404">Program not found.</response>
 748    /// <returns>An <see cref="OkResult"/> containing the livetv program.</returns>
 749    [HttpGet("Programs/{programId}")]
 750    [Authorize(Policy = Policies.LiveTvAccess)]
 751    [ProducesResponseType(StatusCodes.Status200OK)]
 752    [ProducesResponseType(StatusCodes.Status404NotFound)]
 753    public async Task<ActionResult<BaseItemDto>> GetProgram(
 754        [FromRoute, Required] string programId,
 755        [FromQuery] Guid? userId)
 756    {
 0757        userId = RequestHelpers.GetUserId(User, userId);
 0758        var user = userId.IsNullOrEmpty()
 0759            ? null
 0760            : _userManager.GetUserById(userId.Value);
 0761        var result = await _liveTvManager.GetProgram(programId, CancellationToken.None, user).ConfigureAwait(false);
 762
 0763        if (result is null)
 764        {
 0765            return NotFound();
 766        }
 767
 0768        return Ok(result);
 0769    }
 770
 771    /// <summary>
 772    /// Deletes a live tv recording.
 773    /// </summary>
 774    /// <param name="recordingId">Recording id.</param>
 775    /// <response code="204">Recording deleted.</response>
 776    /// <response code="404">Item not found.</response>
 777    /// <returns>A <see cref="NoContentResult"/> on success, or a <see cref="NotFoundResult"/> if item not found.</retur
 778    [HttpDelete("Recordings/{recordingId}")]
 779    [Authorize(Policy = Policies.LiveTvManagement)]
 780    [ProducesResponseType(StatusCodes.Status204NoContent)]
 781    [ProducesResponseType(StatusCodes.Status404NotFound)]
 782    public ActionResult DeleteRecording([FromRoute, Required] Guid recordingId)
 783    {
 0784        var item = _libraryManager.GetItemById<BaseItem>(recordingId, User.GetUserId());
 0785        if (item is null)
 786        {
 0787            return NotFound();
 788        }
 789
 0790        _libraryManager.DeleteItem(item, new DeleteOptions
 0791        {
 0792            DeleteFileLocation = false
 0793        });
 794
 0795        return NoContent();
 796    }
 797
 798    /// <summary>
 799    /// Cancels a live tv timer.
 800    /// </summary>
 801    /// <param name="timerId">Timer id.</param>
 802    /// <response code="204">Timer deleted.</response>
 803    /// <returns>A <see cref="NoContentResult"/>.</returns>
 804    [HttpDelete("Timers/{timerId}")]
 805    [Authorize(Policy = Policies.LiveTvManagement)]
 806    [ProducesResponseType(StatusCodes.Status204NoContent)]
 807    public async Task<ActionResult> CancelTimer([FromRoute, Required] string timerId)
 808    {
 0809        await _liveTvManager.CancelTimer(timerId).ConfigureAwait(false);
 0810        return NoContent();
 0811    }
 812
 813    /// <summary>
 814    /// Updates a live tv timer.
 815    /// </summary>
 816    /// <param name="timerId">Timer id.</param>
 817    /// <param name="timerInfo">New timer info.</param>
 818    /// <response code="204">Timer updated.</response>
 819    /// <returns>A <see cref="NoContentResult"/>.</returns>
 820    [HttpPost("Timers/{timerId}")]
 821    [Authorize(Policy = Policies.LiveTvManagement)]
 822    [ProducesResponseType(StatusCodes.Status204NoContent)]
 823    public async Task<ActionResult> UpdateTimer([FromRoute, Required] string timerId, [FromBody] TimerInfoDto timerInfo)
 824    {
 0825        await _liveTvManager.UpdateTimer(timerInfo, CancellationToken.None).ConfigureAwait(false);
 0826        return NoContent();
 0827    }
 828
 829    /// <summary>
 830    /// Creates a live tv timer.
 831    /// </summary>
 832    /// <param name="timerInfo">New timer info.</param>
 833    /// <response code="204">Timer created.</response>
 834    /// <returns>A <see cref="NoContentResult"/>.</returns>
 835    [HttpPost("Timers")]
 836    [Authorize(Policy = Policies.LiveTvManagement)]
 837    [ProducesResponseType(StatusCodes.Status204NoContent)]
 838    public async Task<ActionResult> CreateTimer([FromBody] TimerInfoDto timerInfo)
 839    {
 0840        await _liveTvManager.CreateTimer(timerInfo, CancellationToken.None).ConfigureAwait(false);
 0841        return NoContent();
 0842    }
 843
 844    /// <summary>
 845    /// Gets a live tv series timer.
 846    /// </summary>
 847    /// <param name="timerId">Timer id.</param>
 848    /// <response code="200">Series timer returned.</response>
 849    /// <response code="404">Series timer not found.</response>
 850    /// <returns>A <see cref="OkResult"/> on success, or a <see cref="NotFoundResult"/> if timer not found.</returns>
 851    [HttpGet("SeriesTimers/{timerId}")]
 852    [Authorize(Policy = Policies.LiveTvAccess)]
 853    [ProducesResponseType(StatusCodes.Status200OK)]
 854    [ProducesResponseType(StatusCodes.Status404NotFound)]
 855    public async Task<ActionResult<SeriesTimerInfoDto>> GetSeriesTimer([FromRoute, Required] string timerId)
 856    {
 0857        var timer = await _liveTvManager.GetSeriesTimer(timerId, CancellationToken.None).ConfigureAwait(false);
 0858        if (timer is null)
 859        {
 0860            return NotFound();
 861        }
 862
 0863        return timer;
 0864    }
 865
 866    /// <summary>
 867    /// Gets live tv series timers.
 868    /// </summary>
 869    /// <param name="sortBy">Optional. Sort by SortName or Priority.</param>
 870    /// <param name="sortOrder">Optional. Sort in Ascending or Descending order.</param>
 871    /// <response code="200">Timers returned.</response>
 872    /// <returns>An <see cref="OkResult"/> of live tv series timers.</returns>
 873    [HttpGet("SeriesTimers")]
 874    [Authorize(Policy = Policies.LiveTvAccess)]
 875    [ProducesResponseType(StatusCodes.Status200OK)]
 876    public async Task<ActionResult<QueryResult<SeriesTimerInfoDto>>> GetSeriesTimers([FromQuery] string? sortBy, [FromQu
 877    {
 0878        return await _liveTvManager.GetSeriesTimers(
 0879            new SeriesTimerQuery
 0880            {
 0881                SortOrder = sortOrder ?? SortOrder.Ascending,
 0882                SortBy = sortBy
 0883            },
 0884            CancellationToken.None).ConfigureAwait(false);
 0885    }
 886
 887    /// <summary>
 888    /// Cancels a live tv series timer.
 889    /// </summary>
 890    /// <param name="timerId">Timer id.</param>
 891    /// <response code="204">Timer cancelled.</response>
 892    /// <returns>A <see cref="NoContentResult"/>.</returns>
 893    [HttpDelete("SeriesTimers/{timerId}")]
 894    [Authorize(Policy = Policies.LiveTvManagement)]
 895    [ProducesResponseType(StatusCodes.Status204NoContent)]
 896    public async Task<ActionResult> CancelSeriesTimer([FromRoute, Required] string timerId)
 897    {
 0898        await _liveTvManager.CancelSeriesTimer(timerId).ConfigureAwait(false);
 0899        return NoContent();
 0900    }
 901
 902    /// <summary>
 903    /// Updates a live tv series timer.
 904    /// </summary>
 905    /// <param name="timerId">Timer id.</param>
 906    /// <param name="seriesTimerInfo">New series timer info.</param>
 907    /// <response code="204">Series timer updated.</response>
 908    /// <returns>A <see cref="NoContentResult"/>.</returns>
 909    [HttpPost("SeriesTimers/{timerId}")]
 910    [Authorize(Policy = Policies.LiveTvManagement)]
 911    [ProducesResponseType(StatusCodes.Status204NoContent)]
 912    public async Task<ActionResult> UpdateSeriesTimer([FromRoute, Required] string timerId, [FromBody] SeriesTimerInfoDt
 913    {
 0914        await _liveTvManager.UpdateSeriesTimer(seriesTimerInfo, CancellationToken.None).ConfigureAwait(false);
 0915        return NoContent();
 0916    }
 917
 918    /// <summary>
 919    /// Creates a live tv series timer.
 920    /// </summary>
 921    /// <param name="seriesTimerInfo">New series timer info.</param>
 922    /// <response code="204">Series timer info created.</response>
 923    /// <returns>A <see cref="NoContentResult"/>.</returns>
 924    [HttpPost("SeriesTimers")]
 925    [Authorize(Policy = Policies.LiveTvManagement)]
 926    [ProducesResponseType(StatusCodes.Status204NoContent)]
 927    public async Task<ActionResult> CreateSeriesTimer([FromBody] SeriesTimerInfoDto seriesTimerInfo)
 928    {
 0929        await _liveTvManager.CreateSeriesTimer(seriesTimerInfo, CancellationToken.None).ConfigureAwait(false);
 0930        return NoContent();
 0931    }
 932
 933    /// <summary>
 934    /// Get guide info.
 935    /// </summary>
 936    /// <response code="200">Guide info returned.</response>
 937    /// <returns>An <see cref="OkResult"/> containing the guide info.</returns>
 938    [HttpGet("GuideInfo")]
 939    [Authorize(Policy = Policies.LiveTvAccess)]
 940    [ProducesResponseType(StatusCodes.Status200OK)]
 941    public ActionResult<GuideInfo> GetGuideInfo()
 0942        => _guideManager.GetGuideInfo();
 943
 944    /// <summary>
 945    /// Adds a tuner host.
 946    /// </summary>
 947    /// <param name="tunerHostInfo">New tuner host.</param>
 948    /// <response code="200">Created tuner host returned.</response>
 949    /// <returns>A <see cref="OkResult"/> containing the created tuner host.</returns>
 950    [HttpPost("TunerHosts")]
 951    [Authorize(Policy = Policies.RequiresElevation)]
 952    [ProducesResponseType(StatusCodes.Status200OK)]
 953    public async Task<ActionResult<TunerHostInfo>> AddTunerHost([FromBody] TunerHostInfo tunerHostInfo)
 3954        => await _tunerHostManager.SaveTunerHost(tunerHostInfo).ConfigureAwait(false);
 955
 956    /// <summary>
 957    /// Deletes a tuner host.
 958    /// </summary>
 959    /// <param name="id">Tuner host id.</param>
 960    /// <response code="204">Tuner host deleted.</response>
 961    /// <returns>A <see cref="NoContentResult"/>.</returns>
 962    [HttpDelete("TunerHosts")]
 963    [Authorize(Policy = Policies.RequiresElevation)]
 964    [ProducesResponseType(StatusCodes.Status204NoContent)]
 965    public ActionResult DeleteTunerHost([FromQuery] string? id)
 966    {
 0967        _tunerHostManager.DeleteTunerHost(id);
 0968        return NoContent();
 969    }
 970
 971    /// <summary>
 972    /// Gets default listings provider info.
 973    /// </summary>
 974    /// <response code="200">Default listings provider info returned.</response>
 975    /// <returns>An <see cref="OkResult"/> containing the default listings provider info.</returns>
 976    [HttpGet("ListingProviders/Default")]
 977    [Authorize(Policy = Policies.LiveTvAccess)]
 978    [ProducesResponseType(StatusCodes.Status200OK)]
 979    public ActionResult<ListingsProviderInfo> GetDefaultListingProvider()
 980    {
 0981        return new ListingsProviderInfo();
 982    }
 983
 984    /// <summary>
 985    /// Adds a listings provider.
 986    /// </summary>
 987    /// <param name="pw">Password.</param>
 988    /// <param name="listingsProviderInfo">New listings info.</param>
 989    /// <param name="validateListings">Validate listings.</param>
 990    /// <param name="validateLogin">Validate login.</param>
 991    /// <response code="200">Created listings provider returned.</response>
 992    /// <returns>A <see cref="OkResult"/> containing the created listings provider.</returns>
 993    [HttpPost("ListingProviders")]
 994    [Authorize(Policy = Policies.RequiresElevation)]
 995    [ProducesResponseType(StatusCodes.Status200OK)]
 996    [SuppressMessage("Microsoft.Performance", "CA5350:RemoveSha1", MessageId = "AddListingProvider", Justification = "Im
 997    public async Task<ActionResult<ListingsProviderInfo>> AddListingProvider(
 998        [FromQuery] string? pw,
 999        [FromBody] ListingsProviderInfo listingsProviderInfo,
 1000        [FromQuery] bool validateListings = false,
 1001        [FromQuery] bool validateLogin = false)
 1002    {
 01003        if (!string.IsNullOrEmpty(pw))
 1004        {
 1005            // TODO: remove ToLower when Convert.ToHexString supports lowercase
 1006            // Schedules Direct requires the hex to be lowercase
 01007            listingsProviderInfo.Password = Convert.ToHexString(SHA1.HashData(Encoding.UTF8.GetBytes(pw))).ToLowerInvari
 1008        }
 1009
 01010        return await _listingsManager.SaveListingProvider(listingsProviderInfo, validateLogin, validateListings).Configu
 01011    }
 1012
 1013    /// <summary>
 1014    /// Delete listing provider.
 1015    /// </summary>
 1016    /// <param name="id">Listing provider id.</param>
 1017    /// <response code="204">Listing provider deleted.</response>
 1018    /// <returns>A <see cref="NoContentResult"/>.</returns>
 1019    [HttpDelete("ListingProviders")]
 1020    [Authorize(Policy = Policies.RequiresElevation)]
 1021    [ProducesResponseType(StatusCodes.Status204NoContent)]
 1022    public ActionResult DeleteListingProvider([FromQuery] string? id)
 1023    {
 01024        _listingsManager.DeleteListingsProvider(id);
 01025        return NoContent();
 1026    }
 1027
 1028    /// <summary>
 1029    /// Gets available lineups.
 1030    /// </summary>
 1031    /// <param name="id">Provider id.</param>
 1032    /// <param name="type">Provider type.</param>
 1033    /// <param name="location">Location.</param>
 1034    /// <param name="country">Country.</param>
 1035    /// <response code="200">Available lineups returned.</response>
 1036    /// <returns>A <see cref="OkResult"/> containing the available lineups.</returns>
 1037    [HttpGet("ListingProviders/Lineups")]
 1038    [Authorize(Policy = Policies.LiveTvAccess)]
 1039    [ProducesResponseType(StatusCodes.Status200OK)]
 1040    public async Task<ActionResult<IEnumerable<NameIdPair>>> GetLineups(
 1041        [FromQuery] string? id,
 1042        [FromQuery] string? type,
 1043        [FromQuery] string? location,
 1044        [FromQuery] string? country)
 01045        => await _listingsManager.GetLineups(type, id, country, location).ConfigureAwait(false);
 1046
 1047    /// <summary>
 1048    /// Gets available countries.
 1049    /// </summary>
 1050    /// <response code="200">Available countries returned.</response>
 1051    /// <returns>A <see cref="FileResult"/> containing the available countries.</returns>
 1052    [HttpGet("ListingProviders/SchedulesDirect/Countries")]
 1053    [Authorize(Policy = Policies.RequiresElevation)]
 1054    [ProducesResponseType(StatusCodes.Status200OK)]
 1055    [ProducesFile(MediaTypeNames.Application.Json)]
 1056    public async Task<ActionResult> GetSchedulesDirectCountries()
 1057    {
 01058        var stream = await _schedulesDirectService.GetAvailableCountries(CancellationToken.None).ConfigureAwait(false);
 01059        return File(stream, MediaTypeNames.Application.Json);
 01060    }
 1061
 1062    /// <summary>
 1063    /// Get channel mapping options.
 1064    /// </summary>
 1065    /// <param name="providerId">Provider id.</param>
 1066    /// <response code="200">Channel mapping options returned.</response>
 1067    /// <returns>An <see cref="OkResult"/> containing the channel mapping options.</returns>
 1068    [HttpGet("ChannelMappingOptions")]
 1069    [Authorize(Policy = Policies.RequiresElevation)]
 1070    [ProducesResponseType(StatusCodes.Status200OK)]
 1071    public Task<ChannelMappingOptionsDto> GetChannelMappingOptions([FromQuery] string? providerId)
 01072        => _listingsManager.GetChannelMappingOptions(providerId);
 1073
 1074    /// <summary>
 1075    /// Set channel mappings.
 1076    /// </summary>
 1077    /// <param name="dto">The set channel mapping dto.</param>
 1078    /// <response code="200">Created channel mapping returned.</response>
 1079    /// <returns>An <see cref="OkResult"/> containing the created channel mapping.</returns>
 1080    [HttpPost("ChannelMappings")]
 1081    [Authorize(Policy = Policies.RequiresElevation)]
 1082    [ProducesResponseType(StatusCodes.Status200OK)]
 1083    public Task<TunerChannelMapping> SetChannelMapping([FromBody, Required] SetChannelMappingDto dto)
 01084        => _listingsManager.SetChannelMapping(dto.ProviderId, dto.TunerChannelId, dto.ProviderChannelId);
 1085
 1086    /// <summary>
 1087    /// Get tuner host types.
 1088    /// </summary>
 1089    /// <response code="200">Tuner host types returned.</response>
 1090    /// <returns>An <see cref="OkResult"/> containing the tuner host types.</returns>
 1091    [HttpGet("TunerHosts/Types")]
 1092    [Authorize(Policy = Policies.LiveTvAccess)]
 1093    [ProducesResponseType(StatusCodes.Status200OK)]
 1094    public IEnumerable<NameIdPair> GetTunerHostTypes()
 01095        => _tunerHostManager.GetTunerHostTypes();
 1096
 1097    /// <summary>
 1098    /// Discover tuners.
 1099    /// </summary>
 1100    /// <param name="newDevicesOnly">Only discover new tuners.</param>
 1101    /// <response code="200">Tuners returned.</response>
 1102    /// <returns>An <see cref="OkResult"/> containing the tuners.</returns>
 1103    [HttpGet("Tuners/Discvover", Name = "DiscvoverTuners")]
 1104    [HttpGet("Tuners/Discover")]
 1105    [Authorize(Policy = Policies.RequiresElevation)]
 1106    [ProducesResponseType(StatusCodes.Status200OK)]
 1107    public IAsyncEnumerable<TunerHostInfo> DiscoverTuners([FromQuery] bool newDevicesOnly = false)
 01108        => _tunerHostManager.DiscoverTuners(newDevicesOnly);
 1109
 1110    /// <summary>
 1111    /// Gets a live tv recording stream.
 1112    /// </summary>
 1113    /// <param name="recordingId">Recording id.</param>
 1114    /// <response code="200">Recording stream returned.</response>
 1115    /// <response code="404">Recording not found.</response>
 1116    /// <returns>
 1117    /// An <see cref="OkResult"/> containing the recording stream on success,
 1118    /// or a <see cref="NotFoundResult"/> if recording not found.
 1119    /// </returns>
 1120    [HttpGet("LiveRecordings/{recordingId}/stream")]
 1121    [ProducesResponseType(StatusCodes.Status200OK)]
 1122    [ProducesResponseType(StatusCodes.Status404NotFound)]
 1123    [ProducesVideoFile]
 1124    public ActionResult GetLiveRecordingFile([FromRoute, Required] string recordingId)
 1125    {
 01126        var path = _recordingsManager.GetActiveRecordingPath(recordingId);
 01127        if (string.IsNullOrWhiteSpace(path))
 1128        {
 01129            return NotFound();
 1130        }
 1131
 01132        var stream = new ProgressiveFileStream(path, null, _transcodeManager);
 01133        return new FileStreamResult(stream, MimeTypes.GetMimeType(path));
 1134    }
 1135
 1136    /// <summary>
 1137    /// Gets a live tv channel stream.
 1138    /// </summary>
 1139    /// <param name="streamId">Stream id.</param>
 1140    /// <param name="container">Container type.</param>
 1141    /// <response code="200">Stream returned.</response>
 1142    /// <response code="404">Stream not found.</response>
 1143    /// <returns>
 1144    /// An <see cref="OkResult"/> containing the channel stream on success,
 1145    /// or a <see cref="NotFoundResult"/> if stream not found.
 1146    /// </returns>
 1147    [HttpGet("LiveStreamFiles/{streamId}/stream.{container}")]
 1148    [ProducesResponseType(StatusCodes.Status200OK)]
 1149    [ProducesResponseType(StatusCodes.Status404NotFound)]
 1150    [ProducesVideoFile]
 1151    public ActionResult GetLiveStreamFile(
 1152        [FromRoute, Required] string streamId,
 1153        [FromRoute, Required][RegularExpression(EncodingHelper.ContainerValidationRegexStr)] string container)
 1154    {
 01155        var liveStreamInfo = _mediaSourceManager.GetLiveStreamInfoByUniqueId(streamId);
 01156        if (liveStreamInfo is null)
 1157        {
 01158            return NotFound();
 1159        }
 1160
 01161        var liveStream = new ProgressiveFileStream(liveStreamInfo.GetStream());
 01162        return new FileStreamResult(liveStream, MimeTypes.GetMimeType("file." + container));
 1163    }
 1164}

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,MediaBrowser.Controller.Library.ILibraryManager,MediaBrowser.Controller.Dto.IDtoService,MediaBrowser.Controller.Library.IMediaSourceManager,MediaBrowser.Controller.MediaEncoding.ITranscodeManager,MediaBrowser.Controller.LiveTv.ISchedulesDirectService)
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>)
GetRecordings()
GetRecordingFolders()
GetRecording(System.Guid,System.Nullable`1<System.Guid>)
ResetTuner()
GetTimer()
GetDefaultTimer()
GetTimers()
GetLiveTvPrograms()
GetPrograms()
GetRecommendedPrograms()
GetProgram()
DeleteRecording(System.Guid)
CancelTimer()
UpdateTimer()
CreateTimer()
GetSeriesTimer()
GetSeriesTimers()
CancelSeriesTimer()
UpdateSeriesTimer()
CreateSeriesTimer()
GetGuideInfo()
AddTunerHost()
DeleteTunerHost(System.String)
GetDefaultListingProvider()
AddListingProvider()
DeleteListingProvider(System.String)
GetLineups()
GetSchedulesDirectCountries()
GetChannelMappingOptions(System.String)
SetChannelMapping(Jellyfin.Api.Models.LiveTvDtos.SetChannelMappingDto)
GetTunerHostTypes()
DiscoverTuners(System.Boolean)
GetLiveRecordingFile(System.String)
GetLiveStreamFile(System.String,System.String)