using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Threading; using Jellyfin.Api.Constants; using Jellyfin.Api.Helpers; using Jellyfin.Api.Models.SyncPlayDtos; using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Session; using MediaBrowser.Controller.SyncPlay; using MediaBrowser.Controller.SyncPlay.PlaybackRequests; using MediaBrowser.Controller.SyncPlay.Requests; using MediaBrowser.Model.SyncPlay; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; namespace Jellyfin.Api.Controllers { /// /// The sync play controller. /// [Authorize(Policy = Policies.SyncPlayHasAccess)] public class SyncPlayController : BaseJellyfinApiController { private readonly ISessionManager _sessionManager; private readonly IAuthorizationContext _authorizationContext; private readonly ISyncPlayManager _syncPlayManager; /// /// Initializes a new instance of the class. /// /// Instance of the interface. /// Instance of the interface. /// Instance of the interface. public SyncPlayController( ISessionManager sessionManager, IAuthorizationContext authorizationContext, ISyncPlayManager syncPlayManager) { _sessionManager = sessionManager; _authorizationContext = authorizationContext; _syncPlayManager = syncPlayManager; } /// /// Create a new SyncPlay group. /// /// The settings of the new group. /// New group created. /// A indicating success. [HttpPost("New")] [ProducesResponseType(StatusCodes.Status204NoContent)] [Authorize(Policy = Policies.SyncPlayCreateGroup)] public ActionResult SyncPlayCreateGroup( [FromBody, Required] NewGroupRequestDto requestData) { var currentSession = RequestHelpers.GetSession(_sessionManager, _authorizationContext, Request); var syncPlayRequest = new NewGroupRequest(requestData.GroupName); _syncPlayManager.NewGroup(currentSession, syncPlayRequest, CancellationToken.None); return NoContent(); } /// /// Join an existing SyncPlay group. /// /// The group to join. /// Group join successful. /// A indicating success. [HttpPost("Join")] [ProducesResponseType(StatusCodes.Status204NoContent)] [Authorize(Policy = Policies.SyncPlayJoinGroup)] public ActionResult SyncPlayJoinGroup( [FromBody, Required] JoinGroupRequestDto requestData) { var currentSession = RequestHelpers.GetSession(_sessionManager, _authorizationContext, Request); var syncPlayRequest = new JoinGroupRequest(requestData.GroupId); _syncPlayManager.JoinGroup(currentSession, syncPlayRequest, CancellationToken.None); return NoContent(); } /// /// Leave the joined SyncPlay group. /// /// Group leave successful. /// A indicating success. [HttpPost("Leave")] [ProducesResponseType(StatusCodes.Status204NoContent)] [Authorize(Policy = Policies.SyncPlayIsInGroup)] public ActionResult SyncPlayLeaveGroup() { var currentSession = RequestHelpers.GetSession(_sessionManager, _authorizationContext, Request); var syncPlayRequest = new LeaveGroupRequest(); _syncPlayManager.LeaveGroup(currentSession, syncPlayRequest, CancellationToken.None); return NoContent(); } /// /// Gets all SyncPlay groups. /// /// Groups returned. /// An containing the available SyncPlay groups. [HttpGet("List")] [ProducesResponseType(StatusCodes.Status200OK)] [Authorize(Policy = Policies.SyncPlayJoinGroup)] public ActionResult> SyncPlayGetGroups() { var currentSession = RequestHelpers.GetSession(_sessionManager, _authorizationContext, Request); var syncPlayRequest = new ListGroupsRequest(); return Ok(_syncPlayManager.ListGroups(currentSession, syncPlayRequest)); } /// /// Request to set new playlist in SyncPlay group. /// /// The new playlist to play in the group. /// Queue update sent to all group members. /// A indicating success. [HttpPost("SetNewQueue")] [ProducesResponseType(StatusCodes.Status204NoContent)] [Authorize(Policy = Policies.SyncPlayIsInGroup)] public ActionResult SyncPlaySetNewQueue( [FromBody, Required] PlayRequestDto requestData) { var currentSession = RequestHelpers.GetSession(_sessionManager, _authorizationContext, Request); var syncPlayRequest = new PlayGroupRequest( requestData.PlayingQueue, requestData.PlayingItemPosition, requestData.StartPositionTicks); _syncPlayManager.HandleRequest(currentSession, syncPlayRequest, CancellationToken.None); return NoContent(); } /// /// Request to change playlist item in SyncPlay group. /// /// The new item to play. /// Queue update sent to all group members. /// A indicating success. [HttpPost("SetPlaylistItem")] [ProducesResponseType(StatusCodes.Status204NoContent)] [Authorize(Policy = Policies.SyncPlayIsInGroup)] public ActionResult SyncPlaySetPlaylistItem( [FromBody, Required] SetPlaylistItemRequestDto requestData) { var currentSession = RequestHelpers.GetSession(_sessionManager, _authorizationContext, Request); var syncPlayRequest = new SetPlaylistItemGroupRequest(requestData.PlaylistItemId); _syncPlayManager.HandleRequest(currentSession, syncPlayRequest, CancellationToken.None); return NoContent(); } /// /// Request to remove items from the playlist in SyncPlay group. /// /// The items to remove. /// Queue update sent to all group members. /// A indicating success. [HttpPost("RemoveFromPlaylist")] [ProducesResponseType(StatusCodes.Status204NoContent)] [Authorize(Policy = Policies.SyncPlayIsInGroup)] public ActionResult SyncPlayRemoveFromPlaylist( [FromBody, Required] RemoveFromPlaylistRequestDto requestData) { var currentSession = RequestHelpers.GetSession(_sessionManager, _authorizationContext, Request); var syncPlayRequest = new RemoveFromPlaylistGroupRequest(requestData.PlaylistItemIds); _syncPlayManager.HandleRequest(currentSession, syncPlayRequest, CancellationToken.None); return NoContent(); } /// /// Request to move an item in the playlist in SyncPlay group. /// /// The new position for the item. /// Queue update sent to all group members. /// A indicating success. [HttpPost("MovePlaylistItem")] [ProducesResponseType(StatusCodes.Status204NoContent)] [Authorize(Policy = Policies.SyncPlayIsInGroup)] public ActionResult SyncPlayMovePlaylistItem( [FromBody, Required] MovePlaylistItemRequestDto requestData) { var currentSession = RequestHelpers.GetSession(_sessionManager, _authorizationContext, Request); var syncPlayRequest = new MovePlaylistItemGroupRequest(requestData.PlaylistItemId, requestData.NewIndex); _syncPlayManager.HandleRequest(currentSession, syncPlayRequest, CancellationToken.None); return NoContent(); } /// /// Request to queue items to the playlist of a SyncPlay group. /// /// The items to add. /// Queue update sent to all group members. /// A indicating success. [HttpPost("Queue")] [ProducesResponseType(StatusCodes.Status204NoContent)] [Authorize(Policy = Policies.SyncPlayIsInGroup)] public ActionResult SyncPlayQueue( [FromBody, Required] QueueRequestDto requestData) { var currentSession = RequestHelpers.GetSession(_sessionManager, _authorizationContext, Request); var syncPlayRequest = new QueueGroupRequest(requestData.ItemIds, requestData.Mode); _syncPlayManager.HandleRequest(currentSession, syncPlayRequest, CancellationToken.None); return NoContent(); } /// /// Request unpause in SyncPlay group. /// /// Unpause update sent to all group members. /// A indicating success. [HttpPost("Unpause")] [ProducesResponseType(StatusCodes.Status204NoContent)] [Authorize(Policy = Policies.SyncPlayIsInGroup)] public ActionResult SyncPlayUnpause() { var currentSession = RequestHelpers.GetSession(_sessionManager, _authorizationContext, Request); var syncPlayRequest = new UnpauseGroupRequest(); _syncPlayManager.HandleRequest(currentSession, syncPlayRequest, CancellationToken.None); return NoContent(); } /// /// Request pause in SyncPlay group. /// /// Pause update sent to all group members. /// A indicating success. [HttpPost("Pause")] [ProducesResponseType(StatusCodes.Status204NoContent)] [Authorize(Policy = Policies.SyncPlayIsInGroup)] public ActionResult SyncPlayPause() { var currentSession = RequestHelpers.GetSession(_sessionManager, _authorizationContext, Request); var syncPlayRequest = new PauseGroupRequest(); _syncPlayManager.HandleRequest(currentSession, syncPlayRequest, CancellationToken.None); return NoContent(); } /// /// Request stop in SyncPlay group. /// /// Stop update sent to all group members. /// A indicating success. [HttpPost("Stop")] [ProducesResponseType(StatusCodes.Status204NoContent)] [Authorize(Policy = Policies.SyncPlayIsInGroup)] public ActionResult SyncPlayStop() { var currentSession = RequestHelpers.GetSession(_sessionManager, _authorizationContext, Request); var syncPlayRequest = new StopGroupRequest(); _syncPlayManager.HandleRequest(currentSession, syncPlayRequest, CancellationToken.None); return NoContent(); } /// /// Request seek in SyncPlay group. /// /// The new playback position. /// Seek update sent to all group members. /// A indicating success. [HttpPost("Seek")] [ProducesResponseType(StatusCodes.Status204NoContent)] [Authorize(Policy = Policies.SyncPlayIsInGroup)] public ActionResult SyncPlaySeek( [FromBody, Required] SeekRequestDto requestData) { var currentSession = RequestHelpers.GetSession(_sessionManager, _authorizationContext, Request); var syncPlayRequest = new SeekGroupRequest(requestData.PositionTicks); _syncPlayManager.HandleRequest(currentSession, syncPlayRequest, CancellationToken.None); return NoContent(); } /// /// Notify SyncPlay group that member is buffering. /// /// The player status. /// Group state update sent to all group members. /// A indicating success. [HttpPost("Buffering")] [ProducesResponseType(StatusCodes.Status204NoContent)] [Authorize(Policy = Policies.SyncPlayIsInGroup)] public ActionResult SyncPlayBuffering( [FromBody, Required] BufferRequestDto requestData) { var currentSession = RequestHelpers.GetSession(_sessionManager, _authorizationContext, Request); var syncPlayRequest = new BufferGroupRequest( requestData.When, requestData.PositionTicks, requestData.IsPlaying, requestData.PlaylistItemId); _syncPlayManager.HandleRequest(currentSession, syncPlayRequest, CancellationToken.None); return NoContent(); } /// /// Notify SyncPlay group that member is ready for playback. /// /// The player status. /// Group state update sent to all group members. /// A indicating success. [HttpPost("Ready")] [ProducesResponseType(StatusCodes.Status204NoContent)] [Authorize(Policy = Policies.SyncPlayIsInGroup)] public ActionResult SyncPlayReady( [FromBody, Required] ReadyRequestDto requestData) { var currentSession = RequestHelpers.GetSession(_sessionManager, _authorizationContext, Request); var syncPlayRequest = new ReadyGroupRequest( requestData.When, requestData.PositionTicks, requestData.IsPlaying, requestData.PlaylistItemId); _syncPlayManager.HandleRequest(currentSession, syncPlayRequest, CancellationToken.None); return NoContent(); } /// /// Request SyncPlay group to ignore member during group-wait. /// /// The settings to set. /// Member state updated. /// A indicating success. [HttpPost("SetIgnoreWait")] [ProducesResponseType(StatusCodes.Status204NoContent)] [Authorize(Policy = Policies.SyncPlayIsInGroup)] public ActionResult SyncPlaySetIgnoreWait( [FromBody, Required] IgnoreWaitRequestDto requestData) { var currentSession = RequestHelpers.GetSession(_sessionManager, _authorizationContext, Request); var syncPlayRequest = new IgnoreWaitGroupRequest(requestData.IgnoreWait); _syncPlayManager.HandleRequest(currentSession, syncPlayRequest, CancellationToken.None); return NoContent(); } /// /// Request next item in SyncPlay group. /// /// The current item information. /// Next item update sent to all group members. /// A indicating success. [HttpPost("NextItem")] [ProducesResponseType(StatusCodes.Status204NoContent)] [Authorize(Policy = Policies.SyncPlayIsInGroup)] public ActionResult SyncPlayNextItem( [FromBody, Required] NextItemRequestDto requestData) { var currentSession = RequestHelpers.GetSession(_sessionManager, _authorizationContext, Request); var syncPlayRequest = new NextItemGroupRequest(requestData.PlaylistItemId); _syncPlayManager.HandleRequest(currentSession, syncPlayRequest, CancellationToken.None); return NoContent(); } /// /// Request previous item in SyncPlay group. /// /// The current item information. /// Previous item update sent to all group members. /// A indicating success. [HttpPost("PreviousItem")] [ProducesResponseType(StatusCodes.Status204NoContent)] [Authorize(Policy = Policies.SyncPlayIsInGroup)] public ActionResult SyncPlayPreviousItem( [FromBody, Required] PreviousItemRequestDto requestData) { var currentSession = RequestHelpers.GetSession(_sessionManager, _authorizationContext, Request); var syncPlayRequest = new PreviousItemGroupRequest(requestData.PlaylistItemId); _syncPlayManager.HandleRequest(currentSession, syncPlayRequest, CancellationToken.None); return NoContent(); } /// /// Request to set repeat mode in SyncPlay group. /// /// The new repeat mode. /// Play queue update sent to all group members. /// A indicating success. [HttpPost("SetRepeatMode")] [ProducesResponseType(StatusCodes.Status204NoContent)] [Authorize(Policy = Policies.SyncPlayIsInGroup)] public ActionResult SyncPlaySetRepeatMode( [FromBody, Required] SetRepeatModeRequestDto requestData) { var currentSession = RequestHelpers.GetSession(_sessionManager, _authorizationContext, Request); var syncPlayRequest = new SetRepeatModeGroupRequest(requestData.Mode); _syncPlayManager.HandleRequest(currentSession, syncPlayRequest, CancellationToken.None); return NoContent(); } /// /// Request to set shuffle mode in SyncPlay group. /// /// The new shuffle mode. /// Play queue update sent to all group members. /// A indicating success. [HttpPost("SetShuffleMode")] [ProducesResponseType(StatusCodes.Status204NoContent)] [Authorize(Policy = Policies.SyncPlayIsInGroup)] public ActionResult SyncPlaySetShuffleMode( [FromBody, Required] SetShuffleModeRequestDto requestData) { var currentSession = RequestHelpers.GetSession(_sessionManager, _authorizationContext, Request); var syncPlayRequest = new SetShuffleModeGroupRequest(requestData.Mode); _syncPlayManager.HandleRequest(currentSession, syncPlayRequest, CancellationToken.None); return NoContent(); } /// /// Update session ping. /// /// The new ping. /// Ping updated. /// A indicating success. [HttpPost("Ping")] [ProducesResponseType(StatusCodes.Status204NoContent)] public ActionResult SyncPlayPing( [FromBody, Required] PingRequestDto requestData) { var currentSession = RequestHelpers.GetSession(_sessionManager, _authorizationContext, Request); var syncPlayRequest = new PingGroupRequest(requestData.Ping); _syncPlayManager.HandleRequest(currentSession, syncPlayRequest, CancellationToken.None); return NoContent(); } } }