< 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: 93
Coverable lines: 119
Total lines: 1199
Line coverage: 21.8%
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 9/18/2025 - 12:09:59 AM Line coverage: 21.3% (26/122) Branch coverage: 0% (0/20) Total lines: 120612/1/2025 - 12:11:46 AM Line coverage: 21.8% (26/119) Branch coverage: 0% (0/20) Total lines: 1199 9/18/2025 - 12:09:59 AM Line coverage: 21.3% (26/122) Branch coverage: 0% (0/20) Total lines: 120612/1/2025 - 12:11:46 AM Line coverage: 21.8% (26/119) Branch coverage: 0% (0/20) Total lines: 1199

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

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)