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.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 = 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 = 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] string playingQueue, [FromQuery, Required] int playingItemPosition, [FromQuery, Required] long startPositionTicks) { var currentSession = RequestHelpers.GetSession(_sessionManager, _authorizationContext, Request); var syncPlayRequest = new PlayGroupRequest() { PlayingItemPosition = playingItemPosition, StartPositionTicks = startPositionTicks }; syncPlayRequest.PlayingQueue.AddRange(RequestHelpers.GetGuids(playingQueue)); _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 = 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(); syncPlayRequest.PlaylistItemIds.AddRange(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 = playlistItemId, NewIndex = newIndex }; _syncPlayManager.HandleRequest(currentSession, syncPlayRequest, CancellationToken.None); return NoContent(); } /// /// Request to queue items to the playlist of a SyncPlay group. /// /// The items to add. Item ids, comma delimited. /// The mode in which to queue items. /// Queue update request sent to all group members. /// A indicating success. [HttpPost("Queue")] [ProducesResponseType(StatusCodes.Status204NoContent)] public ActionResult SyncPlayQueue( [FromQuery, Required] string itemIds, [FromQuery, Required] string mode) { var currentSession = RequestHelpers.GetSession(_sessionManager, _authorizationContext, Request); var syncPlayRequest = new QueueGroupRequest() { Mode = mode }; syncPlayRequest.ItemIds.AddRange(RequestHelpers.GetGuids(itemIds)); _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 = 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 = when, PositionTicks = positionTicks, IsPlaying = isPlaying, PlaylistItemId = playlistItemId }; } else { syncPlayRequest = new ReadyGroupRequest() { When = when, PositionTicks = positionTicks, IsPlaying = isPlaying, PlaylistItemId = 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 = 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 = 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 = 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] string mode) { var currentSession = RequestHelpers.GetSession(_sessionManager, _authorizationContext, Request); var syncPlayRequest = new SetRepeatModeGroupRequest() { Mode = 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] string mode) { var currentSession = RequestHelpers.GetSession(_sessionManager, _authorizationContext, Request); var syncPlayRequest = new SetShuffleModeGroupRequest() { Mode = 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() { Ping = Convert.ToInt64(ping) }; _syncPlayManager.HandleRequest(currentSession, syncPlayRequest, CancellationToken.None); return NoContent(); } } }