Selaa lähdekoodia

bunch of actions for track controls written. now tracks can be played and switched previous-next/begin-end inside playlist

miskson 2 vuotta sitten
vanhempi
commit
be0604170d
5 muutettua tiedostoa jossa 163 lisäystä ja 132 poistoa
  1. 5 19
      src/App.js
  2. 68 39
      src/actions/index.js
  3. 24 27
      src/components/Page/index.js
  4. 55 41
      src/components/Playerbar.js
  5. 11 6
      src/reducers/index.js

+ 5 - 19
src/App.js

@@ -65,24 +65,10 @@ if (store.getState().auth?.token) {
   history.push('/login')
 }
 
-const PlayerBar = () => {
-  return (
-    <footer>
-      <div>
-        <button>{`<<`}</button>
-        <button>{`> / ||`}</button>
-        <button>{`>>`}</button>
-        <input type="range" />
-      </div>
-      <small>ARTIST - TRACK NAME</small>
-    </footer>
-  )
-}
-
 const Player = () =>
   <div>
-    <header><Link to="/player">Player</Link></header>
-    <div style={{ border:'1px solid blue',display: 'flex', height: '89vh', minHeight: '60vh', overflow:'none'}}>
+    {/* <header><Link to="/player">Player</Link></header> */}
+    <div style={{ border:'1px solid blue', display: 'flex', maxHeight: '89vh', minHeight:'30vh', overflow:'none'}}>
       <aside style={{width: '30%', overflow:'auto'}}>
         <Sidebar.LogoutBtnConnect />
         <Sidebar.ProfileWindowDropzoneConnect />
@@ -90,7 +76,7 @@ const Player = () =>
         <Sidebar.PlaylistAddConnect />
         <Sidebar.PlaylistsConnect />
       </aside>
-      <main style={{ border: '1px solid red', width: '80%', height:'100%', overflow:'auto'}}>
+      <main style={{ border: '1px solid red', width: '80%', height:'inherit', overflow:'auto'}}>
         <Switch>
           <Route path="/player/playlist/:_id" component={Page.PlaylistPageConnect} exact />
           <Route path="/player/tracks/:_id" component={Page.UserTracksPageConnect} exact />
@@ -108,8 +94,8 @@ const Player = () =>
         </Switch>
       </main>
     </div>
-    {/* <PlayerbarConnect /> */}
-    <PlayerBar />
+    <PlayerbarConnect />
+    {/* <PlayerBar /> */}
   </div>
 
 function App() {

+ 68 - 39
src/actions/index.js

@@ -1,29 +1,30 @@
-import { type } from '@testing-library/user-event/dist/type'
 import { jwtDecode, gql, backendURL } from '../App'
 
 const audio = new Audio()
 
-const actionTrackSet = (track, audio, playlist) => 
-    ({
-        type: 'SET_TRACK', 
-        track: track, 
-        playlist: playlist, 
-        duration: audio.duration, 
-        currentTime: audio.currentTime, 
-        volume: audio.volume
-    })
+const actionTrackSet = (track, audio, playlist) =>
+({
+    type: 'SET_TRACK',
+    track: track,
+    playlist: playlist,
+    duration: audio.duration,
+    currentTime: audio.currentTime,
+    volume: audio.volume
+})
 
 const actionTrackSetDuration = (time) => {
     console.log('STAVIM VERMYA', time)
-    return ({type: 'SET_DURATION', duration: time})
+    return ({ type: 'SET_DURATION', duration: time })
 }
 
 const actionTrackSetCurrTime = (time) => {
     console.log('setim vremya', time)
-    return ({type: 'SET_CURRTIME', currentTime: time})
+    return ({ type: 'SET_CURRTIME', currentTime: time })
 }
-const actionTrackPlay = () => ({type: 'PLAY_TRACK'})
-const actionTrackPause = (currentTime) => ({type: 'PAUSE_TRACK', currentTime:currentTime})
+
+const actionTrackSetVolume = (value) => ({ type: 'SET_VOLUME', volume: value })
+const actionTrackPlay = () => ({ type: 'PLAY_TRACK' })
+const actionTrackPause = (currentTime) => ({ type: 'PAUSE_TRACK', currentTime: currentTime })
 
 export const setTrack = (track, playlist) =>
     dispatch => {
@@ -31,23 +32,51 @@ export const setTrack = (track, playlist) =>
         dispatch(actionTrackSet(track, audio, playlist))
     }
 
+export const nextTrack = (currentTrackIndex, playlist) =>
+    dispatch => {
+        if (currentTrackIndex < playlist.length - 1) {
+            dispatch(setTrack(playlist[currentTrackIndex + 1], playlist))
+            dispatch(playTrack())
+        } else if (currentTrackIndex === playlist.length - 1) {
+            dispatch(setTrack(playlist[0], playlist))
+            dispatch(playTrack())
+        }
+    }
+
+export const switchTrack = (isForward, currentTrackIndex, playlist) =>
+    dispatch => {
+        if (isForward ? currentTrackIndex < playlist.length - 1 : currentTrackIndex > 0) {
+            dispatch(setTrack(playlist[currentTrackIndex + (isForward ? 1 : -1)], playlist))
+            dispatch(playTrack())
+        } else if (currentTrackIndex === (isForward ? playlist.length - 1 : 0)) {
+            dispatch(setTrack(playlist[isForward ? 0 : playlist.length - 1], playlist))
+            dispatch(playTrack())
+        }
+    }
+
 export const playTrack = () =>
     dispatch => {
+        console.log('NOW PLAYIN')
         dispatch(actionTrackPlay())
         audio.play()
     }
 
 export const pauseTrack = () =>
     dispatch => {
+        console.log('NOW PAUSE')
         dispatch(actionTrackPause(audio.currentTime))
         audio.pause()
     }
 
+export const setTrackVolume = (volume) =>
+    dispatch => {
+        audio.volume = volume
+        dispatch(actionTrackSetVolume(volume))
+    }
+
 audio.addEventListener('ended', (e) => console.log('TRACK ended', e.target.src))
-audio.addEventListener('durationchange', (e) => {
-    actionTrackSetDuration(e.target.duration)
-})
-audio.ontimeupdate = (e) => dispatch => dispatch(actionTrackSetCurrTime(e.target.currentTime))
+audio.ondurationchange = (e) => dispatch => dispatch(actionTrackSetDuration(e.target.duration))
+audio.ontimeupdate = (e) => dispatch => { console.log(e.target.currentTime); dispatch(actionTrackSetCurrTime(e.target.currentTime)) }
 
 
 
@@ -125,14 +154,14 @@ export const actionGetUserData = () => {
 
 export const actionGetUserPlaylists = () => {
     let _id = jwtDecode(localStorage.authToken).sub.id
-    return(
+    return (
         actionPromise('userPlaylists', gql(`
             query getPlaylistByOwnerId($ownerId:String!) {
                 PlaylistFind(query: $ownerId) {
                     _id, name
                 }
             }
-        `, { ownerId: JSON.stringify([{ ___owner: _id }]) } ))
+        `, { ownerId: JSON.stringify([{ ___owner: _id }]) }))
     )
 }
 
@@ -154,7 +183,7 @@ export const actionGetPlaylistById = (_id/*='5fe35e5ce926687ee86b0a4f'*/) =>
 export const actionGetUserTracks = () => {
     let _id = jwtDecode(localStorage.authToken).sub.id
     //let _id = '5fe35e1ce926687ee86b0a3f' //newUserId
-    return(
+    return (
         actionPromise('userTracks', gql(`
             query getUserTracks($ownerId: String!) {
                 TrackFind(query: $ownerId) {
@@ -162,11 +191,11 @@ export const actionGetUserTracks = () => {
                     id3 { title, artist, album }
                 }
             }
-        `, { ownerId: JSON.stringify([{ ___owner: _id }]) } ))
+        `, { ownerId: JSON.stringify([{ ___owner: _id }]) }))
     )
 }
 
-export const actionAddPlaylist = playlistName => 
+export const actionAddPlaylist = playlistName =>
     async dispatch => {
         await dispatch(actionPromise('addPlaylist', gql(`
             mutation addPlaylist ($playlistName: String!){
@@ -174,22 +203,22 @@ export const actionAddPlaylist = playlistName =>
                     _id, name
                 }
             }
-        `, {playlistName: playlistName})))
+        `, { playlistName: playlistName })))
         dispatch(actionGetUserPlaylists())
     }
 
 export const actionLoadFile = (file, type) => {
     let fd = new FormData()
     console.log('TYPE', type)
-    fd.append(type === 'upload'? 'photo' : type, file)
-    
+    fd.append(type === 'upload' ? 'photo' : type, file)
+
     return (
-        actionPromise('loadFile', fetch(backendURL + `/${type}`,{
-        method: "POST",
-        headers: localStorage.authToken ? {Authorization: 'Bearer ' + localStorage.authToken} : {},
-        body: fd
+        actionPromise('loadFile', fetch(backendURL + `/${type}`, {
+            method: "POST",
+            headers: localStorage.authToken ? { Authorization: 'Bearer ' + localStorage.authToken } : {},
+            body: fd
         })
-        .then(res => res.json())
+            .then(res => res.json())
         )
     )
 }
@@ -203,7 +232,7 @@ export const actionUpdatePlaylist = (playlistId, updPlaylist) => {
                 _id, name, tracks { _id, originalFileName, }
                 }
             }
-            `, { playlistId: playlistId ,  newTracks: updPlaylist }))
+            `, { playlistId: playlistId, newTracks: updPlaylist }))
 
     )
 }
@@ -211,18 +240,18 @@ export const actionUploadUserTrack = (file, playlistId) =>
     async (dispatch, getState) => {
         await dispatch(actionLoadFile(file, 'track'))
 
-        if(!playlistId) {
+        if (!playlistId) {
             dispatch(actionGetUserTracks())
         } else {
             console.log('UPLOADING TO PLAYLIS')
             let updPlaylist = []
             let oldPlaylist = getState().promise.playlistTracks.payload[0].tracks
 
-            if(oldPlaylist) {
+            if (oldPlaylist) {
                 //console.log('id pashet', oldPlaylist)
-                oldPlaylist.forEach(track => updPlaylist.push({_id: track._id}))
+                oldPlaylist.forEach(track => updPlaylist.push({ _id: track._id }))
             }
-            updPlaylist.push({_id: getState().promise.loadFile.payload?._id})
+            updPlaylist.push({ _id: getState().promise.loadFile.payload?._id })
             console.log('UPDATED PLST', updPlaylist)
 
             await dispatch(actionUpdatePlaylist(playlistId, updPlaylist.reverse()))
@@ -232,8 +261,8 @@ export const actionUploadUserTrack = (file, playlistId) =>
 
 export const actionUploadAvatar = (file) =>
     async (dispatch, getState) => {
-      await dispatch(actionLoadFile(file, 'upload'))
-      await dispatch(actionPromise('setAvatar', gql(`
+        await dispatch(actionLoadFile(file, 'upload'))
+        await dispatch(actionPromise('setAvatar', gql(`
         mutation {
           UserUpsert(user:{_id: "${jwtDecode(localStorage.authToken).sub.id}", avatar: {_id: "${getState().promise?.loadFile?.payload?._id}"}}){
             _id, login, avatar{
@@ -242,5 +271,5 @@ export const actionUploadAvatar = (file) =>
           }
         }
       `)))
-      dispatch(actionGetUserData())
+        dispatch(actionGetUserData())
     }

+ 24 - 27
src/components/Page/index.js

@@ -7,9 +7,9 @@ import { sortableContainer, sortableElement } from 'react-sortable-hoc';
 import { arrayMoveImmutable } from 'array-move';
 
 
-const Track = ({ track,  player, playlist, setTrack, playTrack, pauseTrack }) => {
+const Track = ({ track, playlist, player, setTrack, playTrack, pauseTrack }) => {
   let [_player, setPlayer] = useState(player)
-  let [play, setPlay] = useState(true)
+  let [isPlay, setPlay] = useState(true)
 
   useEffect(() => {
     setPlayer(player)
@@ -17,37 +17,33 @@ const Track = ({ track,  player, playlist, setTrack, playTrack, pauseTrack }) =>
 
   return (
     <li style={{ border: '1px solid black', display: 'flex', alignItems: 'center' }}>
-      <div style={{ marginRight: '2%' }}>
+      <div style={{ marginRight: '2%', padding:'2%' }}>
     
-        { play || track?._id !== _player?.track?._id?
+        {isPlay && _player?.isPlaying && _player.track?._id === track._id ?
+          <button style={{fontSize:'3vh'}} onClick={()=> {pauseTrack(); setPlay(false)}}>{`\u23F8`}</button> :
           <button 
-            style={{ padding: '10px', margin: '2px' }} 
+            style={{fontSize:'3vh'}}
             onClick={()=> {
               if(track?._id !== _player?.track?._id) setTrack(track, playlist)
-              playTrack(track, playlist)
-              setPlay(false)
+              playTrack()
+              setPlay(true)
             }}
-          >{`[>]`}</button>:
-          <button style={{ padding: '10px', margin: '2px' }} onClick={()=> {pauseTrack(); setPlay(true)}}>{`[| |]`}</button>
+          >{`\u23F5`}</button>
         }
         
-        <button style={{ padding: '10px', margin: '2px' }}>+</button>
       </div>
       <div style={{ textAlign: 'left' }}>
         <h5>{track.id3.artist || 'Artist: unknown'}</h5>
         <h6>{track.id3.album || 'Album: unknown'}</h6>
         <h5>{track.id3.title || track.originalFileName}</h5>
-        <p>{track._id}</p>
-        <p>{track.url}</p>
       </div>
     </li>
   )
 }
-
 const TrackConnect = connect(
   state => ({
     player: state.player || {},
-    playlist: state.playTrack?.payload[0] || {}
+    //playlist: state.playTrack?.payload[0] || {}
   }), 
   {
     setTrack: action.setTrack,
@@ -56,10 +52,7 @@ const TrackConnect = connect(
   })(Track)
 
 const SortableItem = sortableElement(TrackConnect);
-
-const SortableContainer = sortableContainer(({ children }) => {
-  return <ul>{children}</ul>;
-});
+const SortableContainer = sortableContainer(({ children }) => { return <ul>{children}</ul> });
 
 const Playlist = ({ playlist, updPlaylist }) => {
   let [_tracks, setTracks] = useState()
@@ -70,12 +63,11 @@ const Playlist = ({ playlist, updPlaylist }) => {
     setTracks(arrayMoveImmutable(_tracks, oldIndex, newIndex))
     updPlaylist(playlist[0]._id, arrayMoveImmutable(_tracks, oldIndex, newIndex).map(track => ({ _id: track._id })))
   };
-
   return (
     <>
       <h2>{playlist[0]?.name || 'Playlist'}</h2>
       <SortableContainer onSortEnd={onSortEnd}>
-        {(_tracks || []).map((track, index) => <SortableItem index={index} track={track} />)}
+        {(_tracks || []).map((track, index) => <SortableItem index={index} track={track} playlist={playlist[0]?.tracks}/>)}
       </SortableContainer>
     </>
   )
@@ -96,10 +88,12 @@ const PlaylistTrackDropzone = ({ playlist, uploadTrack }) => {
     uploadTrack(acceptedFiles[0], playlistId)
   }, [uploadTrack, playlistId])
   const { getRootProps, getInputProps, isDragActive } = useDropzone({ onDrop })
-
   return (
-    <div {...getRootProps()} style={isDragActive ? { border: '1px solid mediumseagreen' } : { border: '1px solid black' }}>
-      {isDragActive ? <small>drag here...</small> : <small>to upload drag track here</small>}
+    <div 
+      {...getRootProps()} 
+      style={{height:'fit-content', border: `${isDragActive ? '1px solid mediumseagreen' : '1px solid black'}`}}
+    >
+      {isDragActive ? <small>...drag here</small> : <small>to upload drag track here</small>}
       <PlaylistConnect />
     </div>
   )
@@ -117,10 +111,11 @@ export const PlaylistPageConnect = connect(null, { getTracks: action.actionGetPl
 
 
 const UserTracks = ({ user, tracks }) => {
+  let tracksRev = [...tracks].reverse()
   return (
     <>
       <h2>{user.login || 'My'} tracks:</h2>
-      <ul>{(tracks || []).map(track => <Track track={track} />).reverse()}</ul>
+      <ul>{(tracksRev || []).map(track => <TrackConnect track={track} playlist={tracksRev}/>)}</ul>
     </>
   )
 }
@@ -136,10 +131,12 @@ const UserTracksDropzone = ({ onLoad }) => {
     onLoad(acceptedFiles[0])
   }, [onLoad])
   const { getRootProps, getInputProps, isDragActive } = useDropzone({ onDrop })
-
   return (
-    <div {...getRootProps()} style={isDragActive ? { border: '1px solid mediumseagreen' } : { border: '1px solid black' }}>
-      {isDragActive ? <small>drag here...</small> : <small>to upload drag track here</small>}
+    <div 
+      {...getRootProps()} 
+      style={{ height:'fit-content', border: `${isDragActive ? '1px solid mediumseagreen' : '1px solid black'}`}}
+    >
+      {isDragActive ? <small>...drag here</small> : <small>to upload drag track here</small>}
       <UserTracksConnect />
     </div>
   )

+ 55 - 41
src/components/Playerbar.js

@@ -1,41 +1,55 @@
-// import { useEffect, useState } from "react"
-// import { connect } from "react-redux"
-// import { actionTrackPlay, actionTrackPause } from "../actions"
-
-
-// const Playerbar = ({player}) => {
-//     let [_player, setPlayer] = useState()
-
-//     useEffect(()=> {
-//         console.log(Boolean(_player) , _player)
-//         //console.log('PLAYERBAR', player, _player)
-//         setPlayer(player)
-//     },[player, _player])
-
-//     return (
-//         <footer style={{display: `${_player?.track? 'block':'none'}`}}>
-//         <div>
-//             <button>{`<<`}</button>
-//             {_player?.isPaused?
-//                 <button onClick={() => actionTrackPlay(_player.track, _player.trackInfo)}>{`>`}</button> :
-//                 <button onClick={() => actionTrackPause(_player.track, _player.trackInfo)}>{`| |`}</button>
-//             }
-//             <button>{`>>`}</button>
-//             <input type="range" />
-//         </div>
-
-//         {_player? (_player.trackInfo?.id3?.artist && _player.trackInfo?.id3?.title ?
-//             <small>`${_player.trackInfo?.id3?.artist} - ${_player.trackInfo?.id3?.title}`</small> :
-//             <small>{_player.trackInfo?.originalFileName}</small>) : ''}
-        
-//         </footer>
-//     )
-// }
-
-// export const PlayerbarConnect = connect(
-//     state => ({player: state.player || {}}),
-//     {
-//         playTrack: actionTrackPlay,
-//         pauseTrack: actionTrackPause
-//     }
-// )(Playerbar)
+import { useEffect, useState } from "react"
+import { connect } from "react-redux"
+import * as action from "../actions"
+
+
+const Playerbar = ({ player, playTrack, pauseTrack, switchTrack, setTrackVolume }) => {
+    let [_player, setPlayer] = useState()
+
+    useEffect(() => {
+        setPlayer(player)
+    }, [player, _player])
+
+    return (
+        <footer style={{ display: `${_player?.track ? 'block' : 'none'}` }}>
+            <div>
+                <button
+                    style={{ fontSize: '2.5vh' }}
+                    onClick={() => switchTrack(false, _player?.playlistIndex, _player?.playlist)}
+                >
+                    {_player?.playlistIndex === 0 ? `\u23F4` : `\u23EE`}
+                </button>
+
+                {_player?.isPlaying ?
+                    <button style={{ fontSize: '2.5vh' }} onClick={() => pauseTrack()}>{`\u23F8`}</button> :
+                    <button style={{ fontSize: '2.5vh' }} onClick={() => playTrack()}>{`\u23F5`}</button>
+                }
+
+                <button
+                    style={{ fontSize: '2.5vh' }}
+                    onClick={() => switchTrack(true, _player?.playlistIndex, _player?.playlist)}
+                >
+                    {_player?.playlistIndex === _player?.playlist?.length - 1 ? `\u23F5` : `\u23ED`}
+                </button>
+                <input
+                    type="range" min="0" max="1" step="any"
+                    onChange={(e) => setTrackVolume(e.target.value)} />
+            </div>
+
+            {_player ? (_player.track?.id3?.artist && _player.track?.id3?.title ?
+                <small>{_player.track?.id3?.artist} - {_player.track?.id3?.title}</small> :
+                <small>{_player.track?.originalFileName}</small>) : ''}
+
+        </footer>
+    )
+}
+
+export const PlayerbarConnect = connect(
+    state => ({ player: state.player || {} }),
+    {
+        playTrack: action.playTrack,
+        pauseTrack: action.pauseTrack,
+        switchTrack: action.switchTrack,
+        setTrackVolume: action.setTrackVolume
+    }
+)(Playerbar)

+ 11 - 6
src/reducers/index.js

@@ -1,3 +1,4 @@
+import { setTrack } from '../actions'
 import { jwtDecode } from '../App'
 
 export function authReducer(state, { type, token }) {
@@ -35,32 +36,36 @@ export function promiseReducer(state = {}, { type, name, status, payload, error
 }
 
 export function playerReducer(state, { type, track, playlist, duration, currentTime, volume }) {
-    if(!state) {
+    if (!state || type === 'EJECT_TRACK') {
         return {}
     }
     if (type === 'PLAY_TRACK') {
-        return { ...state, 'isPlaying' : true }
+        return { ...state, 'isPlaying': true }
     }
     if (type === 'PAUSE_TRACK') {
-        return { ...state, 'isPlaying' : false, 'currentTime' : currentTime }
+        return { ...state, 'isPlaying': false, 'currentTime': currentTime }
     }
     if (type === 'SET_CURRTIME') {
         console.log('set_currtime')
-        return { ...state, 'currentTime': currentTime}
+        return { ...state, 'currentTime': currentTime }
     }
     if (type === 'SET_DURATION') {
         console.log('SET_DURATION')
         return { ...state, 'duration': duration }
     }
+    if (type === 'SET_VOLUME') {
+        return { ...state, 'volume': volume }
+    }
     if (type === 'SET_TRACK') {
+        console.log('setTrack')
         return {
             isPlaying: false,
             track: track,
             playlist: playlist,
             duration: duration, //общая длительность трека
             currentTime: currentTime,// текущая позиция в треке
-            volume:volume
-            //playlistIndex: 5
+            volume: volume,
+            playlistIndex: playlist.indexOf(track)
         }
     }
     return state