using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Threading; using Jellyfin.Api.Constants; using Jellyfin.Api.Helpers; using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Session; using MediaBrowser.Controller.SyncPlay; using MediaBrowser.Controller.SyncPlay.PlaybackRequests; 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.DefaultAuthorization)] 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 name of the new group. /// New group created. /// A indicating success. [HttpPost("New")] [ProducesResponseType(StatusCodes.Status204NoContent)] public ActionResult SyncPlayCreateGroup( [FromQuery, Required] string groupName) { var currentSession = RequestHelpers.GetSession(_sessionManager, _authorizationContext, Request); var newGroupRequest = new NewGroupRequest(groupName); _syncPlayManager.NewGroup(currentSession, newGroupRequest, CancellationToken.None); return NoContent(); } /// /// Join an existing SyncPlay group. /// /// The sync play group id. /// Group join successful. /// A indicating success. [HttpPost("Join")] [ProducesResponseType(StatusCodes.Status204NoContent)] public ActionResult SyncPlayJoinGroup( [FromQuery, Required] Guid groupId) { var currentSession = RequestHelpers.GetSession(_sessionManager, _authorizationContext, Request); var joinRequest = new JoinGroupRequest(groupId); _syncPlayManager.JoinGroup(currentSession, groupId, joinRequest, CancellationToken.None); return NoContent(); } /// /// Leave the joined SyncPlay group. /// /// Group leave successful. /// A indicating success. [HttpPost("Leave")] [ProducesResponseType(StatusCodes.Status204NoContent)] public ActionResult SyncPlayLeaveGroup() { var currentSession = RequestHelpers.GetSession(_sessionManager, _authorizationContext, Request); _syncPlayManager.LeaveGroup(currentSession, CancellationToken.None); return NoContent(); } /// /// Gets all SyncPlay groups. /// /// Groups returned. /// An containing the available SyncPlay groups. [HttpGet("List")] [ProducesResponseType(StatusCodes.Status200OK)] public ActionResult> SyncPlayGetGroups() { var currentSession = RequestHelpers.GetSession(_sessionManager, _authorizationContext, Request); return Ok(_syncPlayManager.ListGroups(currentSession)); } /// /// Request play in SyncPlay group. /// /// The playing queue. Item ids in the playing queue, comma delimited. /// The playing item position from the queue. /// The start position ticks. /// Play request sent to all group members. /// A indicating success. [HttpPost("Play")] [ProducesResponseType(StatusCodes.Status204NoContent)] public ActionResult SyncPlayPlay( [FromQuery, Required] Guid[] playingQueue, [FromQuery, Required] int playingItemPosition, [FromQuery, Required] long startPositionTicks) { var currentSession = RequestHelpers.GetSession(_sessionManager, _authorizationContext, Request); var syncPlayRequest = new PlayGroupRequest(playingQueue, playingItemPosition, startPositionTicks); _syncPlayManager.HandleRequest(currentSession, syncPlayRequest, CancellationToken.None); return NoContent(); } /// /// Request to change playlist item in SyncPlay group. /// /// The playlist id of the item. /// Queue update request sent to all group members. /// A indicating success. [HttpPost("SetPlaylistItem")] [ProducesResponseType(StatusCodes.Status204NoContent)] public ActionResult SyncPlaySetPlaylistItem( [FromQuery, Required] string playlistItemId) { var currentSession = RequestHelpers.GetSession(_sessionManager, _authorizationContext, Request); var syncPlayRequest = new SetPlaylistItemGroupRequest(playlistItemId); _syncPlayManager.HandleRequest(currentSession, syncPlayRequest, CancellationToken.None); return NoContent(); } /// /// Request to remove items from the playlist in SyncPlay group. /// /// The playlist ids of the items to remove. /// Queue update request sent to all group members. /// A indicating success. [HttpPost("RemoveFromPlaylist")] [ProducesResponseType(StatusCodes.Status204NoContent)] public ActionResult SyncPlayRemoveFromPlaylist( [FromQuery, Required] string[] playlistItemIds) { var currentSession = RequestHelpers.GetSession(_sessionManager, _authorizationContext, Request); var syncPlayRequest = new RemoveFromPlaylistGroupRequest(playlistItemIds); _syncPlayManager.HandleRequest(currentSession, syncPlayRequest, CancellationToken.None); return NoContent(); } /// /// Request to move an item in the playlist in SyncPlay group. /// /// The playlist id of the item to move. /// The new position. /// Queue update request sent to all group members. /// A indicating success. [HttpPost("MovePlaylistItem")] [ProducesResponseType(StatusCodes.Status204NoContent)] public ActionResult SyncPlayMovePlaylistItem( [FromQuery, Required] string playlistItemId, [FromQuery, Required] int newIndex) { var currentSession = RequestHelpers.GetSession(_sessionManager, _authorizationContext, Request); var syncPlayRequest = new MovePlaylistItemGroupRequest(playlistItemId, newIndex); _syncPlayManager.HandleRequest(currentSession, syncPlayRequest, CancellationToken.None); return NoContent(); } /// /// Request to queue items to the playlist of a SyncPlay group. /// /// The items to add. /// The mode in which to enqueue the items. /// Queue update request sent to all group members. /// A indicating success. [HttpPost("Queue")] [ProducesResponseType(StatusCodes.Status204NoContent)] public ActionResult SyncPlayQueue( [FromQuery, Required] Guid[] itemIds, [FromQuery, Required] GroupQueueMode mode) { var currentSession = RequestHelpers.GetSession(_sessionManager, _authorizationContext, Request); var syncPlayRequest = new QueueGroupRequest(itemIds, mode); _syncPlayManager.HandleRequest(currentSession, syncPlayRequest, CancellationToken.None); return NoContent(); } /// /// Request unpause in SyncPlay group. /// /// Unpause request sent to all group members. /// A indicating success. [HttpPost("Unpause")] [ProducesResponseType(StatusCodes.Status204NoContent)] 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 request sent to all group members. /// A indicating success. [HttpPost("Pause")] [ProducesResponseType(StatusCodes.Status204NoContent)] 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 request sent to all group members. /// A indicating success. [HttpPost("Stop")] [ProducesResponseType(StatusCodes.Status204NoContent)] 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 playback position in ticks. /// Seek request sent to all group members. /// A indicating success. [HttpPost("Seek")] [ProducesResponseType(StatusCodes.Status204NoContent)] public ActionResult SyncPlaySeek( [FromQuery, Required] long positionTicks) { var currentSession = RequestHelpers.GetSession(_sessionManager, _authorizationContext, Request); var syncPlayRequest = new SeekGroupRequest(positionTicks); _syncPlayManager.HandleRequest(currentSession, syncPlayRequest, CancellationToken.None); return NoContent(); } /// /// Request group wait in SyncPlay group while buffering. /// /// When the request has been made by the client. /// The playback position in ticks. /// Whether the client's playback is playing or not. /// The playlist item id. /// Whether the buffering is done. /// Buffering request sent to all group members. /// A indicating success. [HttpPost("Buffering")] [ProducesResponseType(StatusCodes.Status204NoContent)] public ActionResult SyncPlayBuffering( [FromQuery, Required] DateTime when, [FromQuery, Required] long positionTicks, [FromQuery, Required] bool isPlaying, [FromQuery, Required] string playlistItemId, [FromQuery, Required] bool bufferingDone) { var currentSession = RequestHelpers.GetSession(_sessionManager, _authorizationContext, Request); IGroupPlaybackRequest syncPlayRequest; if (!bufferingDone) { syncPlayRequest = new BufferGroupRequest(when, positionTicks, isPlaying, playlistItemId); } else { syncPlayRequest = new ReadyGroupRequest(when, positionTicks, isPlaying, playlistItemId); } _syncPlayManager.HandleRequest(currentSession, syncPlayRequest, CancellationToken.None); return NoContent(); } /// /// Request SyncPlay group to ignore member during group-wait. /// /// Whether to ignore the member. /// Member state updated. /// A indicating success. [HttpPost("SetIgnoreWait")] [ProducesResponseType(StatusCodes.Status204NoContent)] public ActionResult SyncPlaySetIgnoreWait( [FromQuery, Required] bool ignoreWait) { var currentSession = RequestHelpers.GetSession(_sessionManager, _authorizationContext, Request); var syncPlayRequest = new IgnoreWaitGroupRequest(ignoreWait); _syncPlayManager.HandleRequest(currentSession, syncPlayRequest, CancellationToken.None); return NoContent(); } /// /// Request next track in SyncPlay group. /// /// The playing item id. /// Next track request sent to all group members. /// A indicating success. [HttpPost("NextTrack")] [ProducesResponseType(StatusCodes.Status204NoContent)] public ActionResult SyncPlayNextTrack( [FromQuery, Required] string playlistItemId) { var currentSession = RequestHelpers.GetSession(_sessionManager, _authorizationContext, Request); var syncPlayRequest = new NextTrackGroupRequest(playlistItemId); _syncPlayManager.HandleRequest(currentSession, syncPlayRequest, CancellationToken.None); return NoContent(); } /// /// Request previous track in SyncPlay group. /// /// The playing item id. /// Previous track request sent to all group members. /// A indicating success. [HttpPost("PreviousTrack")] [ProducesResponseType(StatusCodes.Status204NoContent)] public ActionResult SyncPlayPreviousTrack( [FromQuery, Required] string playlistItemId) { var currentSession = RequestHelpers.GetSession(_sessionManager, _authorizationContext, Request); var syncPlayRequest = new PreviousTrackGroupRequest(playlistItemId); _syncPlayManager.HandleRequest(currentSession, syncPlayRequest, CancellationToken.None); return NoContent(); } /// /// Request to set repeat mode in SyncPlay group. /// /// The repeat mode. /// Play queue update sent to all group members. /// A indicating success. [HttpPost("SetRepeatMode")] [ProducesResponseType(StatusCodes.Status204NoContent)] public ActionResult SyncPlaySetRepeatMode( [FromQuery, Required] GroupRepeatMode mode) { var currentSession = RequestHelpers.GetSession(_sessionManager, _authorizationContext, Request); var syncPlayRequest = new SetRepeatModeGroupRequest(mode); _syncPlayManager.HandleRequest(currentSession, syncPlayRequest, CancellationToken.None); return NoContent(); } /// /// Request to set shuffle mode in SyncPlay group. /// /// The shuffle mode. /// Play queue update sent to all group members. /// A indicating success. [HttpPost("SetShuffleMode")] [ProducesResponseType(StatusCodes.Status204NoContent)] public ActionResult SyncPlaySetShuffleMode( [FromQuery, Required] GroupShuffleMode mode) { var currentSession = RequestHelpers.GetSession(_sessionManager, _authorizationContext, Request); var syncPlayRequest = new SetShuffleModeGroupRequest(mode); _syncPlayManager.HandleRequest(currentSession, syncPlayRequest, CancellationToken.None); return NoContent(); } /// /// Update session ping. /// /// The ping. /// Ping updated. /// A indicating success. [HttpPost("Ping")] [ProducesResponseType(StatusCodes.Status204NoContent)] public ActionResult SyncPlayPing( [FromQuery, Required] double ping) { var currentSession = RequestHelpers.GetSession(_sessionManager, _authorizationContext, Request); var syncPlayRequest = new PingGroupRequest(Convert.ToInt64(ping)); _syncPlayManager.HandleRequest(currentSession, syncPlayRequest, CancellationToken.None); return NoContent(); } } }