using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Linq; using System.Threading.Tasks; using Jellyfin.Api.Constants; using Jellyfin.Api.Extensions; using Jellyfin.Api.Helpers; using Jellyfin.Api.ModelBinders; using Jellyfin.Api.Models.PlaylistDtos; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Playlists; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Playlists; using MediaBrowser.Model.Querying; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.ModelBinding; namespace Jellyfin.Api.Controllers { /// /// Playlists controller. /// [Authorize(Policy = Policies.DefaultAuthorization)] public class PlaylistsController : BaseJellyfinApiController { private readonly IPlaylistManager _playlistManager; private readonly IDtoService _dtoService; private readonly IUserManager _userManager; private readonly ILibraryManager _libraryManager; /// /// Initializes a new instance of the class. /// /// Instance of the interface. /// Instance of the interface. /// Instance of the interface. /// Instance of the interface. public PlaylistsController( IDtoService dtoService, IPlaylistManager playlistManager, IUserManager userManager, ILibraryManager libraryManager) { _dtoService = dtoService; _playlistManager = playlistManager; _userManager = userManager; _libraryManager = libraryManager; } /// /// Creates a new playlist. /// /// /// For backwards compatibility parameters can be sent via Query or Body, with Query having higher precedence. /// /// The playlist name. /// The item ids. /// The user id. /// The media type. /// The create playlist payload. /// /// A that represents the asynchronous operation to create a playlist. /// The task result contains an indicating success. /// [HttpPost] [ProducesResponseType(StatusCodes.Status200OK)] public async Task> CreatePlaylist( [FromQuery] string? name, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] IReadOnlyList ids, [FromQuery] Guid? userId, [FromQuery] string? mediaType, [FromBody(EmptyBodyBehavior = EmptyBodyBehavior.Allow)] CreatePlaylistDto? createPlaylistRequest) { if (ids.Count == 0) { ids = createPlaylistRequest?.Ids ?? Array.Empty(); } var result = await _playlistManager.CreatePlaylist(new PlaylistCreationRequest { Name = name ?? createPlaylistRequest?.Name, ItemIdList = ids, UserId = userId ?? createPlaylistRequest?.UserId ?? default, MediaType = mediaType ?? createPlaylistRequest?.MediaType }).ConfigureAwait(false); return result; } /// /// Adds items to a playlist. /// /// The playlist id. /// Item id, comma delimited. /// The userId. /// Items added to playlist. /// An on success. [HttpPost("{playlistId}/Items")] [ProducesResponseType(StatusCodes.Status204NoContent)] public async Task AddToPlaylist( [FromRoute, Required] Guid playlistId, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] ids, [FromQuery] Guid? userId) { await _playlistManager.AddToPlaylistAsync(playlistId, ids, userId ?? Guid.Empty).ConfigureAwait(false); return NoContent(); } /// /// Moves a playlist item. /// /// The playlist id. /// The item id. /// The new index. /// Item moved to new index. /// An on success. [HttpPost("{playlistId}/Items/{itemId}/Move/{newIndex}")] [ProducesResponseType(StatusCodes.Status204NoContent)] public async Task MoveItem( [FromRoute, Required] string playlistId, [FromRoute, Required] string itemId, [FromRoute, Required] int newIndex) { await _playlistManager.MoveItemAsync(playlistId, itemId, newIndex).ConfigureAwait(false); return NoContent(); } /// /// Removes items from a playlist. /// /// The playlist id. /// The item ids, comma delimited. /// Items removed. /// An on success. [HttpDelete("{playlistId}/Items")] [ProducesResponseType(StatusCodes.Status204NoContent)] public async Task RemoveFromPlaylist( [FromRoute, Required] string playlistId, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] entryIds) { await _playlistManager.RemoveFromPlaylistAsync(playlistId, entryIds).ConfigureAwait(false); return NoContent(); } /// /// Gets the original items of a playlist. /// /// The playlist id. /// User id. /// Optional. The record index to start at. All items with a lower index will be dropped from the results. /// Optional. The maximum number of records to return. /// Optional. Specify additional fields of information to return in the output. /// Optional. Include image information in output. /// Optional. Include user data. /// Optional. The max number of images to return, per image type. /// Optional. The image types to include in the output. /// Original playlist returned. /// Playlist not found. /// The original playlist items. [HttpGet("{playlistId}/Items")] public ActionResult> GetPlaylistItems( [FromRoute, Required] Guid playlistId, [FromQuery, Required] Guid userId, [FromQuery] int? startIndex, [FromQuery] int? limit, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields, [FromQuery] bool? enableImages, [FromQuery] bool? enableUserData, [FromQuery] int? imageTypeLimit, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes) { var playlist = (Playlist)_libraryManager.GetItemById(playlistId); if (playlist == null) { return NotFound(); } var user = !userId.Equals(Guid.Empty) ? _userManager.GetUserById(userId) : null; var items = playlist.GetManageableItems().ToArray(); var count = items.Length; if (startIndex.HasValue) { items = items.Skip(startIndex.Value).ToArray(); } if (limit.HasValue) { items = items.Take(limit.Value).ToArray(); } var dtoOptions = new DtoOptions { Fields = fields } .AddClientFields(Request) .AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes); var dtos = _dtoService.GetBaseItemDtos(items.Select(i => i.Item2).ToList(), dtoOptions, user); for (int index = 0; index < dtos.Count; index++) { dtos[index].PlaylistItemId = items[index].Item1.Id; } var result = new QueryResult { Items = dtos, TotalRecordCount = count }; return result; } } }