effects.ts 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165
  1. import {
  2. ActionQueueOrdered,
  3. ActionQueuePushed,
  4. ActionStateSetLocal,
  5. ActionTypeLocal,
  6. ActionTypeRemote,
  7. LocalAction,
  8. RemoteAction,
  9. } from '../actions';
  10. import { GlobalState } from '../reducer/types';
  11. import { getNextPlayerStateFromAction, isMaster } from '../selectors';
  12. const reverseInArray = <T>(array: T[], index: number): T[] => [
  13. ...array.slice(0, Math.max(0, index)),
  14. ...array.slice(Math.max(0, index), index + 2).reverse(),
  15. ...array.slice(index + 2),
  16. ];
  17. function reorderQueue(queue: number[], action: ActionQueueOrdered): number[] {
  18. const currentIndex = queue.indexOf(action.payload.songId);
  19. if (currentIndex === -1) {
  20. return queue;
  21. }
  22. const reverseIndex = action.payload.delta === 1 ? currentIndex : currentIndex - 1;
  23. return reverseInArray(queue, reverseIndex);
  24. }
  25. function pushToQueue(state: GlobalState, action: ActionQueuePushed): RemoteAction | null {
  26. const nextQueue = Array.from(new Set([...state.player.queue, ...action.payload]));
  27. if (!state.player.master || nextQueue.length === state.player.queue.length) {
  28. return null;
  29. }
  30. return {
  31. type: ActionTypeRemote.StateSet,
  32. payload: {
  33. ...state.player,
  34. queue: nextQueue,
  35. },
  36. };
  37. }
  38. function sendStateUpdateToServer(
  39. state: GlobalState,
  40. action: ActionStateSetLocal,
  41. ): RemoteAction | null {
  42. const nextPlayer = getNextPlayerStateFromAction(state.player, action.payload);
  43. if (!state.player.master && !nextPlayer?.master) {
  44. return null;
  45. }
  46. return {
  47. type: ActionTypeRemote.StateSet,
  48. payload: nextPlayer,
  49. };
  50. }
  51. export function globalEffects(state: GlobalState, action: LocalAction): RemoteAction | null {
  52. switch (action.type) {
  53. case ActionTypeLocal.StateSet:
  54. return sendStateUpdateToServer(state, action);
  55. case ActionTypeLocal.Seeked:
  56. if (!state.player.master) {
  57. return null;
  58. }
  59. return {
  60. type: ActionTypeRemote.StateSet,
  61. payload: { ...state.player, seekTime: action.payload },
  62. };
  63. case ActionTypeLocal.MasterSet:
  64. if (action.payload) {
  65. return {
  66. type: ActionTypeRemote.StateSet,
  67. payload: {
  68. ...state.player,
  69. seekTime: state.player.currentTime,
  70. master: action.payload,
  71. },
  72. };
  73. }
  74. return {
  75. type: ActionTypeRemote.StateSet,
  76. payload: {
  77. ...state.player,
  78. playing: false,
  79. seekTime: -1,
  80. master: state.myClientName,
  81. },
  82. };
  83. case ActionTypeLocal.ActiveClientToggled:
  84. return {
  85. type: ActionTypeRemote.StateSet,
  86. payload: {
  87. ...state.player,
  88. activeClients: state.player.activeClients.includes(action.payload)
  89. ? state.player.activeClients.filter((client) => client !== action.payload)
  90. : [...state.player.activeClients, action.payload],
  91. },
  92. };
  93. case ActionTypeLocal.PlayPaused:
  94. return {
  95. type: ActionTypeRemote.StateSet,
  96. payload: {
  97. ...state.player,
  98. playing: !state.player.playing,
  99. },
  100. };
  101. case ActionTypeLocal.SongInfoFetched:
  102. if (isMaster(state) || !action.payload.replace || !state.player.master) {
  103. return null;
  104. }
  105. return {
  106. type: ActionTypeRemote.StateSet,
  107. payload: {
  108. ...state.player,
  109. songId: action.payload.song?.id ?? null,
  110. playing: !!action.payload.song,
  111. currentTime: 0,
  112. seekTime: 0,
  113. },
  114. };
  115. case ActionTypeLocal.QueuePushed:
  116. return pushToQueue(state, action);
  117. case ActionTypeLocal.QueueShifted:
  118. if (!state.player.master) {
  119. return null;
  120. }
  121. return {
  122. type: ActionTypeRemote.StateSet,
  123. payload: {
  124. ...state.player,
  125. queue: state.player.queue.slice(1),
  126. playing: !!state.player.queue[0],
  127. songId: state.player.queue[0],
  128. currentTime: 0,
  129. seekTime: 0,
  130. },
  131. };
  132. case ActionTypeLocal.QueueRemoved:
  133. if (!state.player.master) {
  134. return null;
  135. }
  136. return {
  137. type: ActionTypeRemote.StateSet,
  138. payload: {
  139. ...state.player,
  140. queue: state.player.queue.filter((id) => id !== action.payload),
  141. },
  142. };
  143. case ActionTypeLocal.QueueOrdered:
  144. return {
  145. type: ActionTypeRemote.StateSet,
  146. payload: { ...state.player, queue: reorderQueue(state.player.queue, action) },
  147. };
  148. default:
  149. return null;
  150. }
  151. }