1
0

Player.js 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109
  1. import {useReducer, useEffect} from 'react';
  2. import Sound from 'react-sound';
  3. import Main from './Main';
  4. import Queue from './Queue';
  5. import Controls from './Controls';
  6. import MediaSession from './MediaSession.js';
  7. const NO_SOUND = {url: '', playStatus: Sound.status.STOPPED};
  8. const NO_METADATA = {artist: '', album: '', title: '', cover: ''};
  9. const NO_QUEUE = {items: [], pos: -1};
  10. const NO_ERROR = null;
  11. function storeQueue(queue) {
  12. localStorage.setItem('queue', JSON.stringify(queue));
  13. }
  14. function loadQueue() {
  15. const queue = localStorage.getItem('queue');
  16. return queue && JSON.parse(queue);
  17. }
  18. function playTrack(track, status) {
  19. return {
  20. sound: {url: track.url,
  21. position: 0,
  22. playStatus: (status || Sound.status.PLAYING)},
  23. metadata: {
  24. title: track.title,
  25. artist: track.artist,
  26. album: track.album,
  27. cover: track.cover
  28. },
  29. error: NO_ERROR
  30. }
  31. }
  32. function playQueue(state, pos) {
  33. if (pos < 0 || pos >= state.queue.items.length)
  34. return {...state, sound: NO_SOUND, metadata: NO_METADATA};
  35. const queue = {...state.queue, pos}
  36. storeQueue(queue);
  37. return {...state, queue, ...playTrack(state.queue.items[pos])}
  38. }
  39. function playerReducer(state, action) {
  40. switch (action.type) {
  41. case 'ALBUM_PLAY': {
  42. if (!action.payload || !action.payload.tracks) return state;
  43. const queue = {items: action.payload.tracks}
  44. return playQueue({...state, queue}, (action.payload.pos || 0))}
  45. case 'ALBUM_ENQUEUE': {
  46. if (!action.payload) return state;
  47. const queue = {...state.queue, items: state.queue.items.concat(action.payload.tracks)};
  48. return {...state, queue}}
  49. case 'CONTROL_PLAY':
  50. return {...state, sound: {...state.sound, playStatus: Sound.status.PLAYING}}
  51. case 'CONTROL_PAUSE':
  52. return {...state, sound: {...state.sound, playStatus: Sound.status.PAUSED}}
  53. case 'CONTROL_PREV':
  54. return playQueue(state, state.queue.pos - 1);
  55. case 'CONTROL_NEXT':
  56. return playQueue(state, state.queue.pos + 1);
  57. case 'CONTROL_STOP':
  58. return {...state, queue: {...state.queue, pos:-1}, sound: NO_SOUND, metadata: NO_METADATA}
  59. case 'QUEUE_PLAY':
  60. return playQueue(state, action.payload);
  61. case 'QUEUE_LOAD': {
  62. const queue = action.payload;
  63. return {...state, queue, ...playTrack(queue.items[queue.pos], Sound.status.PAUSED)}}
  64. case 'SOUND_ERROR':
  65. return {...state, sound: NO_SOUND, metadata: NO_METADATA, error: action.payload}
  66. case 'SOUND_PLAYING':
  67. return {...state, sound: {...state.sound, ...action.payload}}
  68. case 'SOUND_FINISHED':
  69. return playQueue(state, state.queue.pos + 1);
  70. default: throw new Error(`Unknown action type: ${action.type}`);
  71. }
  72. }
  73. export default function Player() {
  74. const INIT = {sound: NO_SOUND,
  75. error: NO_ERROR,
  76. queue: NO_QUEUE,
  77. metadata: NO_METADATA}
  78. const [state, dispatch] = useReducer(playerReducer, INIT);
  79. useEffect(()=>{
  80. const queue = loadQueue();
  81. if (queue) dispatch({type: 'QUEUE_LOAD', payload: queue});
  82. }, []);
  83. return (
  84. <>
  85. <Main dispatch={dispatch} />
  86. <Queue queue={state.queue} dispatch={dispatch} />
  87. <Controls {...state} dispatch={dispatch} />
  88. <Sound {...state.sound}
  89. onError={(code,desc) => dispatch({type: 'SOUND_ERROR', payload: {code, desc}})}
  90. onPlaying={(payload) => dispatch({type: 'SOUND_PLAYING', payload})}
  91. onFinishedPlaying={() => dispatch({type: 'SOUND_FINISHED'})} />
  92. <MediaSession
  93. {...state.metadata}
  94. onPlay={()=>dispatch({type:'CONTROL_PLAY'})}
  95. onPause={()=>dispatch({type:'CONTROL_PAUSE'})}
  96. onPreviousTrack={()=>dispatch({type:'CONTROL_PREV'})}
  97. onNextTrack={()=>dispatch({type:'CONTROL_NEXT'})}
  98. />
  99. </>
  100. );
  101. }