From 0ec986865f5d48998cd807d0e6573b0886167b7b Mon Sep 17 00:00:00 2001 From: jeffvli Date: Mon, 16 Aug 2021 18:38:45 -0700 Subject: [PATCH] update player functionality --- package.json | 1 + src/App.tsx | 33 ++- src/components/layout/styled.tsx | 3 +- src/components/player/Player.tsx | 62 +++--- src/components/player/PlayerBar.tsx | 323 ++++++++++++++++++---------- src/redux/playQueueSlice.ts | 41 +++- yarn.lock | 24 ++- 7 files changed, 317 insertions(+), 170 deletions(-) diff --git a/package.json b/package.json index e7c5596..b37eae3 100644 --- a/package.json +++ b/package.json @@ -252,6 +252,7 @@ "electron-redux": "^1.5.4", "electron-settings": "^4.0.2", "electron-updater": "^4.3.4", + "format-duration": "^1.4.0", "history": "^5.0.0", "lodash": "^4.17.21", "md5": "^2.3.0", diff --git a/src/App.tsx b/src/App.tsx index 92883d6..6d6712a 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -6,7 +6,6 @@ import PlaylistList from './components/playlist/PlaylistList'; import PlaylistView from './components/playlist/PlaylistView'; import Settings from './components/settings/Settings'; import NowPlayingView from './components/player/NowPlayingView'; -import Player from './components/player/Player'; import Login from './components/settings/Login'; import StarredView from './components/starred/StarredView'; import Dashboard from './components/dashboard/Dashboard'; @@ -19,23 +18,21 @@ const App = () => { } return ( - - - }> - - - - - - - - - - - - - - + + }> + + + + + + + + + + + + + ); }; diff --git a/src/components/layout/styled.tsx b/src/components/layout/styled.tsx index 8c98b79..f1bbb9c 100644 --- a/src/components/layout/styled.tsx +++ b/src/components/layout/styled.tsx @@ -5,7 +5,6 @@ import { Container, Content, Footer, Header, Sidebar } from 'rsuite'; // Layout.tsx export const RootContainer = styled(Container)` height: 100vh; - padding-bottom: 10px; `; interface ContainerProps { @@ -29,7 +28,7 @@ export const MainContainer = styled(StyledContainer)` `; export const RootFooter = styled(Footer)` - height: 88px; + height: 98px; `; // Titlebar.tsx diff --git a/src/components/player/Player.tsx b/src/components/player/Player.tsx index fb49480..2ffbcf8 100644 --- a/src/components/player/Player.tsx +++ b/src/components/player/Player.tsx @@ -1,4 +1,11 @@ -import React, { useState, createContext, useRef, useEffect } from 'react'; +import React, { + useState, + createContext, + useRef, + useEffect, + useImperativeHandle, + forwardRef, +} from 'react'; import ReactAudioPlayer from 'react-audio-player'; import { useAppDispatch, useAppSelector } from '../../redux/hooks'; import { @@ -6,19 +13,27 @@ import { incrementPlayerIndex, setCurrentPlayer, setPlayerVolume, + setCurrentSeek, } from '../../redux/playQueueSlice'; export const PlayerContext = createContext({}); -const Player = ({ children }: any) => { +const Player = ({ children }: any, ref: any) => { const [incremented, setIncremented] = useState(false); - const player1Ref = useRef(); const player2Ref = useRef(); - const dispatch = useAppDispatch(); const playQueue = useAppSelector((state) => state.playQueue); + useImperativeHandle(ref, () => ({ + get player1() { + return player1Ref.current; + }, + get player2() { + return player2Ref.current; + }, + })); + useEffect(() => { if (playQueue.status === 'PLAYING') { if (playQueue.currentPlayer === 1) { @@ -42,11 +57,11 @@ const Player = ({ children }: any) => { const handleListen = () => { const fadeDuration = 10; - const currentTime = player1Ref.current?.audioEl.current?.currentTime || 0; + const currentSeek = player1Ref.current?.audioEl.current?.currentTime || 0; const duration = player1Ref.current?.audioEl.current?.duration; const fadeAtTime = duration - fadeDuration; - if (currentTime >= fadeAtTime) { + if (currentSeek >= fadeAtTime) { if (player2Ref.current.audioEl.current) { // Once fading starts, start playing player 2 and set current to 2 const player1Volume = @@ -72,17 +87,19 @@ const Player = ({ children }: any) => { dispatch(setCurrentPlayer(2)); } console.log('fading player1...'); + } else { + dispatch(setCurrentSeek(currentSeek)); } - console.log(`player1: ${currentTime} / ${fadeAtTime}`); + console.log(`player1: ${currentSeek} / ${fadeAtTime}`); }; const handleListen2 = () => { const fadeDuration = 10; - const currentTime = player2Ref.current?.audioEl.current?.currentTime || 0; + const currentSeek = player2Ref.current?.audioEl.current?.currentTime || 0; const duration = player2Ref.current?.audioEl.current?.duration; const fadeAtTime = duration - fadeDuration; - if (currentTime >= fadeAtTime) { + if (currentSeek >= fadeAtTime) { if (player1Ref.current.audioEl.current) { // Once fading starts, start playing player 1 and set current to 1 const player1Volume = @@ -108,8 +125,11 @@ const Player = ({ children }: any) => { dispatch(setCurrentPlayer(1)); } console.log('fading player2...'); + } else { + dispatch(setCurrentSeek(currentSeek)); } - console.log(`player2: ${currentTime} / ${fadeAtTime}`); + + console.log(`player2: ${currentSeek} / ${fadeAtTime}`); }; const handleOnEnded1 = () => { @@ -126,22 +146,6 @@ const Player = ({ children }: any) => { setIncremented(false); }; - /* const handleOnLoadStart = () => { - dispatch(setIsLoading()); - }; - - const handleOnLoadedData = () => { - dispatch(setIsLoaded()); - }; - - const handleOnClickNext = () => { - dispatch(incrementCurrentIndex()); - }; - - const handleOnClickPrevious = () => { - dispatch(decrementCurrentIndex()); - }; */ - return ( { }} > { onListen={handleListen} onEnded={handleOnEnded1} volume={playQueue.player1.volume} - controls autoPlay={playQueue.player1.index === playQueue.currentIndex} /> { onListen={handleListen2} onEnded={handleOnEnded2} volume={playQueue.player2.volume} - controls autoPlay={playQueue.player2.index === playQueue.currentIndex} /> {children} @@ -178,4 +178,4 @@ const Player = ({ children }: any) => { ); }; -export default Player; +export default forwardRef(Player); diff --git a/src/components/player/PlayerBar.tsx b/src/components/player/PlayerBar.tsx index b97ee14..1a56fc7 100644 --- a/src/components/player/PlayerBar.tsx +++ b/src/components/player/PlayerBar.tsx @@ -1,132 +1,229 @@ -import React, { useContext, useEffect, useRef } from 'react'; -import ReactAudioPlayer from 'react-audio-player'; -import { Button } from 'rsuite'; -import { useAppDispatch, useAppSelector } from '../../redux/hooks'; +import React, { useEffect, useState, useRef } from 'react'; +import { FlexboxGrid, Icon, Slider, Button } from 'rsuite'; +import format from 'format-duration'; +import styled from 'styled-components'; import { incrementCurrentIndex, - incrementPlayerIndex, + decrementCurrentIndex, + setVolume, + setPlayerVolume, + setStatus, } from '../../redux/playQueueSlice'; -import { PlayerContext } from './Player'; +import { useAppDispatch, useAppSelector } from '../../redux/hooks'; +import Player from './Player'; +import 'react-rangeslider/lib/index.css'; -const PlayerBar = () => { - const player1Ref = useRef(); - const player2Ref = useRef(); - const { - player1Volume, - player2Volume, - setPlayer1Volume, - setPlayer2Volume, - incremented, - setIncremented, - globalVolume, - currentPlayer, - setCurrentPlayer, - } = useContext(PlayerContext); +const PlayerContainer = styled.div` + background: #000000; + height: 100%; + border-top: 1px solid #48545c; +`; - const dispatch = useAppDispatch(); +const PlayerColumn = styled.div<{ + left?: boolean; + center?: boolean; + right?: boolean; + height: string; +}>` + height: ${(props) => props.height}; + display: flex; + align-items: center; + justify-content: ${(props) => + props.left + ? 'flex-start' + : props.center + ? 'center' + : props.right + ? 'flex-end' + : 'center'}; +`; + +const PlayerControlIcon = styled(Icon)` + color: #b3b3b3; + padding: 0 15px 0 15px; + &:hover { + color: #fff; + } +`; + +const PlayerBar = () => { const playQueue = useAppSelector((state) => state.playQueue); + const dispatch = useAppDispatch(); + const [seek, setSeek] = useState(0); + const [isDragging, setIsDragging] = useState(false); + const [manualSeek, setManualSeek] = useState(0); + const [isLoading, setIsLoading] = useState(false); + const playersRef = useRef(); + + useEffect(() => { + setSeek(playQueue.currentSeek); + }, [playQueue.currentSeek]); - const handleListen = () => { - const fadeDuration = 10; - const currentTime = player1Ref.current?.audioEl.current?.currentTime || 0; - const duration = player1Ref.current?.audioEl.current?.duration; - const fadeAtTime = duration - fadeDuration; - - if (currentTime >= fadeAtTime) { - if (player2Ref.current.audioEl.current) { - // Once fading starts, start playing player 2 and set current to 2 - setPlayer1Volume( - player1Volume - globalVolume / (fadeDuration * 2) <= 0 - ? 0 - : player1Volume - globalVolume / (fadeDuration * 2) - ); - setPlayer2Volume( - player2Volume + globalVolume / (fadeDuration * 1.5) >= globalVolume - ? globalVolume - : player2Volume + globalVolume / (fadeDuration * 1.5) - ); - player2Ref.current.audioEl.current.play(); - if (!incremented) { - dispatch(incrementCurrentIndex('none')); - setIncremented(true); - } - setCurrentPlayer(2); + useEffect(() => { + if (isDragging) { + if (playQueue.currentPlayer === 1) { + playersRef.current.player1.audioEl.current.currentTime = manualSeek; + } else { + playersRef.current.player2.audioEl.current.currentTime = manualSeek; } - console.log('fading player1...'); + + // Wait for the seek to catch up, otherwise the bar will bounce back and forth + setTimeout(() => { + setIsDragging(false); + }, 1500); } - console.log(`player1: ${currentTime} / ${fadeAtTime}`); + }, [isDragging, manualSeek, playQueue.currentPlayer]); + + /* const handleOnLoadStart = () => { + dispatch(setIsLoading()); }; - const handleListen2 = () => { - const fadeDuration = 10; - const currentTime = player2Ref.current?.audioEl.current?.currentTime || 0; - const duration = player2Ref.current?.audioEl.current?.duration; - const fadeAtTime = duration - fadeDuration; - - if (currentTime >= fadeAtTime) { - if (player1Ref.current.audioEl.current) { - // Once fading starts, start playing player 1 and set current to 1 - setPlayer1Volume( - player1Volume + globalVolume / (fadeDuration * 1.5) >= globalVolume - ? globalVolume - : player1Volume + globalVolume / (fadeDuration * 1.5) - ); - setPlayer2Volume( - player2Volume - globalVolume / (fadeDuration * 2) <= 0 - ? 0 - : player2Volume - globalVolume / (fadeDuration * 2) - ); - player1Ref.current.audioEl.current.play(); - if (!incremented) { - dispatch(incrementCurrentIndex('none')); - setIncremented(true); - } - setCurrentPlayer(1); - } - console.log('fading player2...'); - } - console.log(`player2: ${currentTime} / ${fadeAtTime}`); + const handleOnLoadedData = () => { + dispatch(setIsLoaded()); + }; */ + + const handleClickNext = () => { + dispatch(incrementCurrentIndex('usingHotkey')); + }; + + const handleClickPrevious = () => { + dispatch(decrementCurrentIndex('usingHotkey')); }; - const handleOnEnded1 = () => { - dispatch(incrementPlayerIndex(1)); - setPlayer1Volume(0); - setPlayer2Volume(globalVolume); - setIncremented(false); + const handleClickPlayPause = () => { + dispatch(setStatus(playQueue.status === 'PLAYING' ? 'PAUSED' : 'PLAYING')); }; - const handleOnEnded2 = () => { - dispatch(incrementPlayerIndex(2)); - setPlayer1Volume(globalVolume); - setPlayer2Volume(0); - setIncremented(false); + const handleVolumeSlider = (e: number) => { + const vol = Number((e / 100).toFixed(2)); + dispatch(setVolume(vol)); + dispatch(setPlayerVolume({ player: playQueue.currentPlayer, volume: vol })); + }; + + const handleSeekSlider = (e: number) => { + setIsDragging(true); + setManualSeek(e); + console.log(e); + }; + + const handleOnWaiting = () => { + /* console.log( + (playersRef.current?.player1.audioEl.current.onwaiting = () => { + console.log('Waiting'); + }) + ); */ }; return ( - <> - - - + + +
{`Current index: ${playQueue.currentIndex} | `} {`Player1 index: ${playQueue.player1Index} - ${ @@ -136,8 +233,8 @@ const PlayerBar = () => { playQueue.entry[playQueue.player2Index]?.title } | `} {`CurrentPlayer: ${playQueue.currentPlayer}`} -
- + */} +
); }; diff --git a/src/redux/playQueueSlice.ts b/src/redux/playQueueSlice.ts index e6b233b..ea31d0e 100644 --- a/src/redux/playQueueSlice.ts +++ b/src/redux/playQueueSlice.ts @@ -34,6 +34,7 @@ export interface PlayQueue { currentIndex: number; currentSongId: string; currentPlayer: number; + currentSeek: number; player1: { index: number; volume: number; @@ -53,6 +54,7 @@ const initialState: PlayQueue = { currentIndex: 0, currentSongId: '', currentPlayer: 1, + currentSeek: 0, player1: { index: 0, volume: 0.5, @@ -72,7 +74,17 @@ const playQueueSlice = createSlice({ initialState, reducers: { setStatus: (state, action: PayloadAction) => { - state.status = action.payload; + if (state.entry.length >= 1) { + state.status = action.payload; + } + }, + + setVolume: (state, action: PayloadAction) => { + state.volume = action.payload; + }, + + setCurrentSeek: (state, action: PayloadAction) => { + state.currentSeek = action.payload; }, setCurrentPlayer: (state, action: PayloadAction) => { @@ -85,6 +97,7 @@ const playQueueSlice = createSlice({ incrementCurrentIndex: (state, action: PayloadAction) => { if (state.entry.length >= 1) { + state.currentSeek = 0; if (state.currentIndex < state.entry.length - 1) { state.currentIndex += 1; if (action.payload === 'usingHotkey') { @@ -123,6 +136,7 @@ const playQueueSlice = createSlice({ (track) => track.id === action.payload.id ); + state.currentSeek = 0; state.player1.index = findIndex; state.player1.volume = state.volume; state.player2.index = findIndex + 1; @@ -145,6 +159,7 @@ const playQueueSlice = createSlice({ decrementCurrentIndex: (state, action: PayloadAction) => { if (state.entry.length >= 1) { + state.currentSeek = 0; if (state.currentIndex > 0) { state.currentIndex -= 1; if (action.payload === 'usingHotkey') { @@ -168,10 +183,19 @@ const playQueueSlice = createSlice({ }, setPlayQueue: (state, action: PayloadAction) => { + // Reset player defaults + state.entry = []; + state.status = 'PAUSED'; + state.currentIndex = 0; + state.currentSongId = ''; + state.currentPlayer = 1; + state.currentSeek = 0; + state.player1.index = 0; + state.player2.index = 1; + if (state.status !== 'PLAYING') { state.status = 'PLAYING'; } - state.currentIndex = 0; state.currentSongId = action.payload[0].id; action.payload.map((entry: any) => state.entry.push(entry)); }, @@ -180,7 +204,16 @@ const playQueueSlice = createSlice({ action.payload.map((entry: any) => state.entry.push(entry)); }, - clearPlayQueue: () => initialState, + clearPlayQueue: (state) => { + state.entry = []; + state.status = 'PAUSED'; + state.currentIndex = 0; + state.currentSongId = ''; + state.currentPlayer = 1; + state.currentSeek = 0; + state.player1.index = 0; + state.player2.index = 1; + }, setIsLoading: (state) => { state.isLoading = true; @@ -269,5 +302,7 @@ export const { setCurrentPlayer, setStatus, setPlayerVolume, + setVolume, + setCurrentSeek, } = playQueueSlice.actions; export default playQueueSlice.reducer; diff --git a/yarn.lock b/yarn.lock index a64f5df..2068670 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1064,9 +1064,9 @@ regenerator-runtime "^0.13.4" "@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.10.2", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.1", "@babel/runtime@^7.12.5", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.2", "@babel/runtime@^7.7.2", "@babel/runtime@^7.7.6", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7", "@babel/runtime@^7.9.2": - version "7.14.8" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.14.8.tgz#7119a56f421018852694290b9f9148097391b446" - integrity sha512-twj3L8Og5SaCRCErB4x4ajbvBIVV77CGeFglHpeg5WC5FF8TZzBWXtTJ4MqaD9QszLYTtr+IsaAL2rEUevb+eg== + version "7.15.3" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.15.3.tgz#2e1c2880ca118e5b2f9988322bd8a7656a32502b" + integrity sha512-OvwMLqNXkCXSz1kSm58sEsNuhqOx/fKpnUnKnFB5v8uDda5bLNEHNgKPvhDN6IU0LDcnHQ90LlJ0Q6jnyBSIBA== dependencies: regenerator-runtime "^0.13.4" @@ -2247,6 +2247,11 @@ acorn@^8.0.4: resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.0.4.tgz#7a3ae4191466a6984eee0fe3407a4f3aa9db8354" integrity sha512-XNP0PqF1XD19ZlLKvB7cMmnZswW4C/03pRHgirB30uSJTaS3A3V1/P4sS3HPvFmjoriPCJQs+JDSbm4bL1TxGQ== +add-zero@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/add-zero/-/add-zero-1.0.0.tgz#88e221696717f66db467672f3f9aa004de9f1a2c" + integrity sha1-iOIhaWcX9m20Z2cvP5qgBN6fGiw= + address@^1.0.1: version "1.1.2" resolved "https://registry.yarnpkg.com/address/-/address-1.1.2.tgz#bf1116c9c758c51b7a933d296b72c221ed9428b6" @@ -5920,6 +5925,14 @@ form-data@~2.3.2: combined-stream "^1.0.6" mime-types "^2.1.12" +format-duration@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/format-duration/-/format-duration-1.4.0.tgz#6ad86c88f504150873cd28ca82d26a26898d9971" + integrity sha512-Mcg3hOAiKxo6JRBgfrQ+sbVvr3D9/4wE7eDQXx3WV2d/yKmrcHXHJS4OhrqVeg+iiFE2Op+pHhdOkQUl8yIclw== + dependencies: + add-zero "^1.0.0" + parse-ms "^1.0.1" + forwarded@~0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.1.2.tgz#98c23dab1175657b8c0573e8ceccd91b0ff18c84" @@ -9341,6 +9354,11 @@ parse-json@^5.0.0: json-parse-even-better-errors "^2.3.0" lines-and-columns "^1.1.6" +parse-ms@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/parse-ms/-/parse-ms-1.0.1.tgz#56346d4749d78f23430ca0c713850aef91aa361d" + integrity sha1-VjRtR0nXjyNDDKDHE4UK75GqNh0= + parse5@5.1.1: version "5.1.1" resolved "https://registry.yarnpkg.com/parse5/-/parse5-5.1.1.tgz#f68e4e5ba1852ac2cadc00f4555fff6c2abb6178"