SyncPlayController.cs 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424
  1. using System.Collections.Generic;
  2. using System.ComponentModel.DataAnnotations;
  3. using System.Linq;
  4. using System.Threading;
  5. using System.Threading.Tasks;
  6. using Jellyfin.Api.Constants;
  7. using Jellyfin.Api.Helpers;
  8. using Jellyfin.Api.Models.SyncPlayDtos;
  9. using MediaBrowser.Controller.Library;
  10. using MediaBrowser.Controller.Session;
  11. using MediaBrowser.Controller.SyncPlay;
  12. using MediaBrowser.Controller.SyncPlay.PlaybackRequests;
  13. using MediaBrowser.Controller.SyncPlay.Requests;
  14. using MediaBrowser.Model.SyncPlay;
  15. using Microsoft.AspNetCore.Authorization;
  16. using Microsoft.AspNetCore.Http;
  17. using Microsoft.AspNetCore.Mvc;
  18. namespace Jellyfin.Api.Controllers
  19. {
  20. /// <summary>
  21. /// The sync play controller.
  22. /// </summary>
  23. [Authorize(Policy = Policies.SyncPlayHasAccess)]
  24. public class SyncPlayController : BaseJellyfinApiController
  25. {
  26. private readonly ISessionManager _sessionManager;
  27. private readonly ISyncPlayManager _syncPlayManager;
  28. private readonly IUserManager _userManager;
  29. /// <summary>
  30. /// Initializes a new instance of the <see cref="SyncPlayController"/> class.
  31. /// </summary>
  32. /// <param name="sessionManager">Instance of the <see cref="ISessionManager"/> interface.</param>
  33. /// <param name="syncPlayManager">Instance of the <see cref="ISyncPlayManager"/> interface.</param>
  34. /// <param name="userManager">Instance of the <see cref="IUserManager"/> interface.</param>
  35. public SyncPlayController(
  36. ISessionManager sessionManager,
  37. ISyncPlayManager syncPlayManager,
  38. IUserManager userManager)
  39. {
  40. _sessionManager = sessionManager;
  41. _syncPlayManager = syncPlayManager;
  42. _userManager = userManager;
  43. }
  44. /// <summary>
  45. /// Create a new SyncPlay group.
  46. /// </summary>
  47. /// <param name="requestData">The settings of the new group.</param>
  48. /// <response code="204">New group created.</response>
  49. /// <returns>A <see cref="NoContentResult"/> indicating success.</returns>
  50. [HttpPost("New")]
  51. [ProducesResponseType(StatusCodes.Status204NoContent)]
  52. [Authorize(Policy = Policies.SyncPlayCreateGroup)]
  53. public async Task<ActionResult> SyncPlayCreateGroup(
  54. [FromBody, Required] NewGroupRequestDto requestData)
  55. {
  56. var currentSession = await RequestHelpers.GetSession(_sessionManager, _userManager, HttpContext).ConfigureAwait(false);
  57. var syncPlayRequest = new NewGroupRequest(requestData.GroupName);
  58. _syncPlayManager.NewGroup(currentSession, syncPlayRequest, CancellationToken.None);
  59. return NoContent();
  60. }
  61. /// <summary>
  62. /// Join an existing SyncPlay group.
  63. /// </summary>
  64. /// <param name="requestData">The group to join.</param>
  65. /// <response code="204">Group join successful.</response>
  66. /// <returns>A <see cref="NoContentResult"/> indicating success.</returns>
  67. [HttpPost("Join")]
  68. [ProducesResponseType(StatusCodes.Status204NoContent)]
  69. [Authorize(Policy = Policies.SyncPlayJoinGroup)]
  70. public async Task<ActionResult> SyncPlayJoinGroup(
  71. [FromBody, Required] JoinGroupRequestDto requestData)
  72. {
  73. var currentSession = await RequestHelpers.GetSession(_sessionManager, _userManager, HttpContext).ConfigureAwait(false);
  74. var syncPlayRequest = new JoinGroupRequest(requestData.GroupId);
  75. _syncPlayManager.JoinGroup(currentSession, syncPlayRequest, CancellationToken.None);
  76. return NoContent();
  77. }
  78. /// <summary>
  79. /// Leave the joined SyncPlay group.
  80. /// </summary>
  81. /// <response code="204">Group leave successful.</response>
  82. /// <returns>A <see cref="NoContentResult"/> indicating success.</returns>
  83. [HttpPost("Leave")]
  84. [ProducesResponseType(StatusCodes.Status204NoContent)]
  85. [Authorize(Policy = Policies.SyncPlayIsInGroup)]
  86. public async Task<ActionResult> SyncPlayLeaveGroup()
  87. {
  88. var currentSession = await RequestHelpers.GetSession(_sessionManager, _userManager, HttpContext).ConfigureAwait(false);
  89. var syncPlayRequest = new LeaveGroupRequest();
  90. _syncPlayManager.LeaveGroup(currentSession, syncPlayRequest, CancellationToken.None);
  91. return NoContent();
  92. }
  93. /// <summary>
  94. /// Gets all SyncPlay groups.
  95. /// </summary>
  96. /// <response code="200">Groups returned.</response>
  97. /// <returns>An <see cref="IEnumerable{GroupInfoView}"/> containing the available SyncPlay groups.</returns>
  98. [HttpGet("List")]
  99. [ProducesResponseType(StatusCodes.Status200OK)]
  100. [Authorize(Policy = Policies.SyncPlayJoinGroup)]
  101. public async Task<ActionResult<IEnumerable<GroupInfoDto>>> SyncPlayGetGroups()
  102. {
  103. var currentSession = await RequestHelpers.GetSession(_sessionManager, _userManager, HttpContext).ConfigureAwait(false);
  104. var syncPlayRequest = new ListGroupsRequest();
  105. return Ok(_syncPlayManager.ListGroups(currentSession, syncPlayRequest).AsEnumerable());
  106. }
  107. /// <summary>
  108. /// Request to set new playlist in SyncPlay group.
  109. /// </summary>
  110. /// <param name="requestData">The new playlist to play in the group.</param>
  111. /// <response code="204">Queue update sent to all group members.</response>
  112. /// <returns>A <see cref="NoContentResult"/> indicating success.</returns>
  113. [HttpPost("SetNewQueue")]
  114. [ProducesResponseType(StatusCodes.Status204NoContent)]
  115. [Authorize(Policy = Policies.SyncPlayIsInGroup)]
  116. public async Task<ActionResult> SyncPlaySetNewQueue(
  117. [FromBody, Required] PlayRequestDto requestData)
  118. {
  119. var currentSession = await RequestHelpers.GetSession(_sessionManager, _userManager, HttpContext).ConfigureAwait(false);
  120. var syncPlayRequest = new PlayGroupRequest(
  121. requestData.PlayingQueue,
  122. requestData.PlayingItemPosition,
  123. requestData.StartPositionTicks);
  124. _syncPlayManager.HandleRequest(currentSession, syncPlayRequest, CancellationToken.None);
  125. return NoContent();
  126. }
  127. /// <summary>
  128. /// Request to change playlist item in SyncPlay group.
  129. /// </summary>
  130. /// <param name="requestData">The new item to play.</param>
  131. /// <response code="204">Queue update sent to all group members.</response>
  132. /// <returns>A <see cref="NoContentResult"/> indicating success.</returns>
  133. [HttpPost("SetPlaylistItem")]
  134. [ProducesResponseType(StatusCodes.Status204NoContent)]
  135. [Authorize(Policy = Policies.SyncPlayIsInGroup)]
  136. public async Task<ActionResult> SyncPlaySetPlaylistItem(
  137. [FromBody, Required] SetPlaylistItemRequestDto requestData)
  138. {
  139. var currentSession = await RequestHelpers.GetSession(_sessionManager, _userManager, HttpContext).ConfigureAwait(false);
  140. var syncPlayRequest = new SetPlaylistItemGroupRequest(requestData.PlaylistItemId);
  141. _syncPlayManager.HandleRequest(currentSession, syncPlayRequest, CancellationToken.None);
  142. return NoContent();
  143. }
  144. /// <summary>
  145. /// Request to remove items from the playlist in SyncPlay group.
  146. /// </summary>
  147. /// <param name="requestData">The items to remove.</param>
  148. /// <response code="204">Queue update sent to all group members.</response>
  149. /// <returns>A <see cref="NoContentResult"/> indicating success.</returns>
  150. [HttpPost("RemoveFromPlaylist")]
  151. [ProducesResponseType(StatusCodes.Status204NoContent)]
  152. [Authorize(Policy = Policies.SyncPlayIsInGroup)]
  153. public async Task<ActionResult> SyncPlayRemoveFromPlaylist(
  154. [FromBody, Required] RemoveFromPlaylistRequestDto requestData)
  155. {
  156. var currentSession = await RequestHelpers.GetSession(_sessionManager, _userManager, HttpContext).ConfigureAwait(false);
  157. var syncPlayRequest = new RemoveFromPlaylistGroupRequest(requestData.PlaylistItemIds, requestData.ClearPlaylist, requestData.ClearPlayingItem);
  158. _syncPlayManager.HandleRequest(currentSession, syncPlayRequest, CancellationToken.None);
  159. return NoContent();
  160. }
  161. /// <summary>
  162. /// Request to move an item in the playlist in SyncPlay group.
  163. /// </summary>
  164. /// <param name="requestData">The new position for the item.</param>
  165. /// <response code="204">Queue update sent to all group members.</response>
  166. /// <returns>A <see cref="NoContentResult"/> indicating success.</returns>
  167. [HttpPost("MovePlaylistItem")]
  168. [ProducesResponseType(StatusCodes.Status204NoContent)]
  169. [Authorize(Policy = Policies.SyncPlayIsInGroup)]
  170. public async Task<ActionResult> SyncPlayMovePlaylistItem(
  171. [FromBody, Required] MovePlaylistItemRequestDto requestData)
  172. {
  173. var currentSession = await RequestHelpers.GetSession(_sessionManager, _userManager, HttpContext).ConfigureAwait(false);
  174. var syncPlayRequest = new MovePlaylistItemGroupRequest(requestData.PlaylistItemId, requestData.NewIndex);
  175. _syncPlayManager.HandleRequest(currentSession, syncPlayRequest, CancellationToken.None);
  176. return NoContent();
  177. }
  178. /// <summary>
  179. /// Request to queue items to the playlist of a SyncPlay group.
  180. /// </summary>
  181. /// <param name="requestData">The items to add.</param>
  182. /// <response code="204">Queue update sent to all group members.</response>
  183. /// <returns>A <see cref="NoContentResult"/> indicating success.</returns>
  184. [HttpPost("Queue")]
  185. [ProducesResponseType(StatusCodes.Status204NoContent)]
  186. [Authorize(Policy = Policies.SyncPlayIsInGroup)]
  187. public async Task<ActionResult> SyncPlayQueue(
  188. [FromBody, Required] QueueRequestDto requestData)
  189. {
  190. var currentSession = await RequestHelpers.GetSession(_sessionManager, _userManager, HttpContext).ConfigureAwait(false);
  191. var syncPlayRequest = new QueueGroupRequest(requestData.ItemIds, requestData.Mode);
  192. _syncPlayManager.HandleRequest(currentSession, syncPlayRequest, CancellationToken.None);
  193. return NoContent();
  194. }
  195. /// <summary>
  196. /// Request unpause in SyncPlay group.
  197. /// </summary>
  198. /// <response code="204">Unpause update sent to all group members.</response>
  199. /// <returns>A <see cref="NoContentResult"/> indicating success.</returns>
  200. [HttpPost("Unpause")]
  201. [ProducesResponseType(StatusCodes.Status204NoContent)]
  202. [Authorize(Policy = Policies.SyncPlayIsInGroup)]
  203. public async Task<ActionResult> SyncPlayUnpause()
  204. {
  205. var currentSession = await RequestHelpers.GetSession(_sessionManager, _userManager, HttpContext).ConfigureAwait(false);
  206. var syncPlayRequest = new UnpauseGroupRequest();
  207. _syncPlayManager.HandleRequest(currentSession, syncPlayRequest, CancellationToken.None);
  208. return NoContent();
  209. }
  210. /// <summary>
  211. /// Request pause in SyncPlay group.
  212. /// </summary>
  213. /// <response code="204">Pause update sent to all group members.</response>
  214. /// <returns>A <see cref="NoContentResult"/> indicating success.</returns>
  215. [HttpPost("Pause")]
  216. [ProducesResponseType(StatusCodes.Status204NoContent)]
  217. [Authorize(Policy = Policies.SyncPlayIsInGroup)]
  218. public async Task<ActionResult> SyncPlayPause()
  219. {
  220. var currentSession = await RequestHelpers.GetSession(_sessionManager, _userManager, HttpContext).ConfigureAwait(false);
  221. var syncPlayRequest = new PauseGroupRequest();
  222. _syncPlayManager.HandleRequest(currentSession, syncPlayRequest, CancellationToken.None);
  223. return NoContent();
  224. }
  225. /// <summary>
  226. /// Request stop in SyncPlay group.
  227. /// </summary>
  228. /// <response code="204">Stop update sent to all group members.</response>
  229. /// <returns>A <see cref="NoContentResult"/> indicating success.</returns>
  230. [HttpPost("Stop")]
  231. [ProducesResponseType(StatusCodes.Status204NoContent)]
  232. [Authorize(Policy = Policies.SyncPlayIsInGroup)]
  233. public async Task<ActionResult> SyncPlayStop()
  234. {
  235. var currentSession = await RequestHelpers.GetSession(_sessionManager, _userManager, HttpContext).ConfigureAwait(false);
  236. var syncPlayRequest = new StopGroupRequest();
  237. _syncPlayManager.HandleRequest(currentSession, syncPlayRequest, CancellationToken.None);
  238. return NoContent();
  239. }
  240. /// <summary>
  241. /// Request seek in SyncPlay group.
  242. /// </summary>
  243. /// <param name="requestData">The new playback position.</param>
  244. /// <response code="204">Seek update sent to all group members.</response>
  245. /// <returns>A <see cref="NoContentResult"/> indicating success.</returns>
  246. [HttpPost("Seek")]
  247. [ProducesResponseType(StatusCodes.Status204NoContent)]
  248. [Authorize(Policy = Policies.SyncPlayIsInGroup)]
  249. public async Task<ActionResult> SyncPlaySeek(
  250. [FromBody, Required] SeekRequestDto requestData)
  251. {
  252. var currentSession = await RequestHelpers.GetSession(_sessionManager, _userManager, HttpContext).ConfigureAwait(false);
  253. var syncPlayRequest = new SeekGroupRequest(requestData.PositionTicks);
  254. _syncPlayManager.HandleRequest(currentSession, syncPlayRequest, CancellationToken.None);
  255. return NoContent();
  256. }
  257. /// <summary>
  258. /// Notify SyncPlay group that member is buffering.
  259. /// </summary>
  260. /// <param name="requestData">The player status.</param>
  261. /// <response code="204">Group state update sent to all group members.</response>
  262. /// <returns>A <see cref="NoContentResult"/> indicating success.</returns>
  263. [HttpPost("Buffering")]
  264. [ProducesResponseType(StatusCodes.Status204NoContent)]
  265. [Authorize(Policy = Policies.SyncPlayIsInGroup)]
  266. public async Task<ActionResult> SyncPlayBuffering(
  267. [FromBody, Required] BufferRequestDto requestData)
  268. {
  269. var currentSession = await RequestHelpers.GetSession(_sessionManager, _userManager, HttpContext).ConfigureAwait(false);
  270. var syncPlayRequest = new BufferGroupRequest(
  271. requestData.When,
  272. requestData.PositionTicks,
  273. requestData.IsPlaying,
  274. requestData.PlaylistItemId);
  275. _syncPlayManager.HandleRequest(currentSession, syncPlayRequest, CancellationToken.None);
  276. return NoContent();
  277. }
  278. /// <summary>
  279. /// Notify SyncPlay group that member is ready for playback.
  280. /// </summary>
  281. /// <param name="requestData">The player status.</param>
  282. /// <response code="204">Group state update sent to all group members.</response>
  283. /// <returns>A <see cref="NoContentResult"/> indicating success.</returns>
  284. [HttpPost("Ready")]
  285. [ProducesResponseType(StatusCodes.Status204NoContent)]
  286. [Authorize(Policy = Policies.SyncPlayIsInGroup)]
  287. public async Task<ActionResult> SyncPlayReady(
  288. [FromBody, Required] ReadyRequestDto requestData)
  289. {
  290. var currentSession = await RequestHelpers.GetSession(_sessionManager, _userManager, HttpContext).ConfigureAwait(false);
  291. var syncPlayRequest = new ReadyGroupRequest(
  292. requestData.When,
  293. requestData.PositionTicks,
  294. requestData.IsPlaying,
  295. requestData.PlaylistItemId);
  296. _syncPlayManager.HandleRequest(currentSession, syncPlayRequest, CancellationToken.None);
  297. return NoContent();
  298. }
  299. /// <summary>
  300. /// Request SyncPlay group to ignore member during group-wait.
  301. /// </summary>
  302. /// <param name="requestData">The settings to set.</param>
  303. /// <response code="204">Member state updated.</response>
  304. /// <returns>A <see cref="NoContentResult"/> indicating success.</returns>
  305. [HttpPost("SetIgnoreWait")]
  306. [ProducesResponseType(StatusCodes.Status204NoContent)]
  307. [Authorize(Policy = Policies.SyncPlayIsInGroup)]
  308. public async Task<ActionResult> SyncPlaySetIgnoreWait(
  309. [FromBody, Required] IgnoreWaitRequestDto requestData)
  310. {
  311. var currentSession = await RequestHelpers.GetSession(_sessionManager, _userManager, HttpContext).ConfigureAwait(false);
  312. var syncPlayRequest = new IgnoreWaitGroupRequest(requestData.IgnoreWait);
  313. _syncPlayManager.HandleRequest(currentSession, syncPlayRequest, CancellationToken.None);
  314. return NoContent();
  315. }
  316. /// <summary>
  317. /// Request next item in SyncPlay group.
  318. /// </summary>
  319. /// <param name="requestData">The current item information.</param>
  320. /// <response code="204">Next item update sent to all group members.</response>
  321. /// <returns>A <see cref="NoContentResult"/> indicating success.</returns>
  322. [HttpPost("NextItem")]
  323. [ProducesResponseType(StatusCodes.Status204NoContent)]
  324. [Authorize(Policy = Policies.SyncPlayIsInGroup)]
  325. public async Task<ActionResult> SyncPlayNextItem(
  326. [FromBody, Required] NextItemRequestDto requestData)
  327. {
  328. var currentSession = await RequestHelpers.GetSession(_sessionManager, _userManager, HttpContext).ConfigureAwait(false);
  329. var syncPlayRequest = new NextItemGroupRequest(requestData.PlaylistItemId);
  330. _syncPlayManager.HandleRequest(currentSession, syncPlayRequest, CancellationToken.None);
  331. return NoContent();
  332. }
  333. /// <summary>
  334. /// Request previous item in SyncPlay group.
  335. /// </summary>
  336. /// <param name="requestData">The current item information.</param>
  337. /// <response code="204">Previous item update sent to all group members.</response>
  338. /// <returns>A <see cref="NoContentResult"/> indicating success.</returns>
  339. [HttpPost("PreviousItem")]
  340. [ProducesResponseType(StatusCodes.Status204NoContent)]
  341. [Authorize(Policy = Policies.SyncPlayIsInGroup)]
  342. public async Task<ActionResult> SyncPlayPreviousItem(
  343. [FromBody, Required] PreviousItemRequestDto requestData)
  344. {
  345. var currentSession = await RequestHelpers.GetSession(_sessionManager, _userManager, HttpContext).ConfigureAwait(false);
  346. var syncPlayRequest = new PreviousItemGroupRequest(requestData.PlaylistItemId);
  347. _syncPlayManager.HandleRequest(currentSession, syncPlayRequest, CancellationToken.None);
  348. return NoContent();
  349. }
  350. /// <summary>
  351. /// Request to set repeat mode in SyncPlay group.
  352. /// </summary>
  353. /// <param name="requestData">The new repeat mode.</param>
  354. /// <response code="204">Play queue update sent to all group members.</response>
  355. /// <returns>A <see cref="NoContentResult"/> indicating success.</returns>
  356. [HttpPost("SetRepeatMode")]
  357. [ProducesResponseType(StatusCodes.Status204NoContent)]
  358. [Authorize(Policy = Policies.SyncPlayIsInGroup)]
  359. public async Task<ActionResult> SyncPlaySetRepeatMode(
  360. [FromBody, Required] SetRepeatModeRequestDto requestData)
  361. {
  362. var currentSession = await RequestHelpers.GetSession(_sessionManager, _userManager, HttpContext).ConfigureAwait(false);
  363. var syncPlayRequest = new SetRepeatModeGroupRequest(requestData.Mode);
  364. _syncPlayManager.HandleRequest(currentSession, syncPlayRequest, CancellationToken.None);
  365. return NoContent();
  366. }
  367. /// <summary>
  368. /// Request to set shuffle mode in SyncPlay group.
  369. /// </summary>
  370. /// <param name="requestData">The new shuffle mode.</param>
  371. /// <response code="204">Play queue update sent to all group members.</response>
  372. /// <returns>A <see cref="NoContentResult"/> indicating success.</returns>
  373. [HttpPost("SetShuffleMode")]
  374. [ProducesResponseType(StatusCodes.Status204NoContent)]
  375. [Authorize(Policy = Policies.SyncPlayIsInGroup)]
  376. public async Task<ActionResult> SyncPlaySetShuffleMode(
  377. [FromBody, Required] SetShuffleModeRequestDto requestData)
  378. {
  379. var currentSession = await RequestHelpers.GetSession(_sessionManager, _userManager, HttpContext).ConfigureAwait(false);
  380. var syncPlayRequest = new SetShuffleModeGroupRequest(requestData.Mode);
  381. _syncPlayManager.HandleRequest(currentSession, syncPlayRequest, CancellationToken.None);
  382. return NoContent();
  383. }
  384. /// <summary>
  385. /// Update session ping.
  386. /// </summary>
  387. /// <param name="requestData">The new ping.</param>
  388. /// <response code="204">Ping updated.</response>
  389. /// <returns>A <see cref="NoContentResult"/> indicating success.</returns>
  390. [HttpPost("Ping")]
  391. [ProducesResponseType(StatusCodes.Status204NoContent)]
  392. public async Task<ActionResult> SyncPlayPing(
  393. [FromBody, Required] PingRequestDto requestData)
  394. {
  395. var currentSession = await RequestHelpers.GetSession(_sessionManager, _userManager, HttpContext).ConfigureAwait(false);
  396. var syncPlayRequest = new PingGroupRequest(requestData.Ping);
  397. _syncPlayManager.HandleRequest(currentSession, syncPlayRequest, CancellationToken.None);
  398. return NoContent();
  399. }
  400. }
  401. }