From fc0349b3a9b44dcac641b4a0da5632ddbbc329f4 Mon Sep 17 00:00:00 2001 From: jeffvli Date: Mon, 16 Aug 2021 23:04:35 -0700 Subject: [PATCH] update gapless - current track now changes after track ends, instead of at fade start - calculate the fade by time left instead of total fade duration --- src/components/player/Player.tsx | 80 +++++---- src/components/player/PlayerBar.tsx | 248 ++++++++++++++-------------- src/redux/playQueueSlice.ts | 21 +++ 3 files changed, 186 insertions(+), 163 deletions(-) diff --git a/src/components/player/Player.tsx b/src/components/player/Player.tsx index 2ffbcf8..356d7e1 100644 --- a/src/components/player/Player.tsx +++ b/src/components/player/Player.tsx @@ -1,5 +1,4 @@ import React, { - useState, createContext, useRef, useEffect, @@ -14,12 +13,13 @@ import { setCurrentPlayer, setPlayerVolume, setCurrentSeek, + setIsFading, + setAutoIncremented, } from '../../redux/playQueueSlice'; export const PlayerContext = createContext({}); const Player = ({ children }: any, ref: any) => { - const [incremented, setIncremented] = useState(false); const player1Ref = useRef(); const player2Ref = useRef(); const dispatch = useAppDispatch(); @@ -62,35 +62,31 @@ const Player = ({ children }: any, ref: any) => { const fadeAtTime = duration - fadeDuration; if (currentSeek >= fadeAtTime) { + // Once fading starts, start playing player 2 and set current to 2 + const timeLeft = duration - currentSeek; + if (player2Ref.current.audioEl.current) { - // Once fading starts, start playing player 2 and set current to 2 const player1Volume = - playQueue.player1.volume - playQueue.volume / (fadeDuration * 2) <= 0 + playQueue.player1.volume - playQueue.volume / timeLeft <= 0 ? 0 - : playQueue.player1.volume - playQueue.volume / (fadeDuration * 2); + : playQueue.player1.volume - playQueue.volume / timeLeft; const player2Volume = - playQueue.player2.volume + playQueue.volume / (fadeDuration * 1.5) >= + playQueue.player2.volume + playQueue.volume / timeLeft >= playQueue.volume ? playQueue.volume - : playQueue.player2.volume + - playQueue.volume / (fadeDuration * 1.5); + : playQueue.player2.volume + playQueue.volume / timeLeft; dispatch(setPlayerVolume({ player: 1, volume: player1Volume })); dispatch(setPlayerVolume({ player: 2, volume: player2Volume })); - player2Ref.current.audioEl.current.play(); - if (!incremented) { - dispatch(incrementCurrentIndex('none')); - setIncremented(true); - } - dispatch(setCurrentPlayer(2)); + dispatch(setIsFading(true)); } - console.log('fading player1...'); - } else { + } + + if (playQueue.currentPlayer === 1) { dispatch(setCurrentSeek(currentSeek)); } - console.log(`player1: ${currentSeek} / ${fadeAtTime}`); }; const handleListen2 = () => { @@ -100,59 +96,61 @@ const Player = ({ children }: any, ref: any) => { const fadeAtTime = duration - fadeDuration; if (currentSeek >= fadeAtTime) { + const timeLeft = duration - currentSeek; + + // Once fading starts, start playing player 1 and set current to 1 if (player1Ref.current.audioEl.current) { - // Once fading starts, start playing player 1 and set current to 1 const player1Volume = - playQueue.player1.volume + playQueue.volume / (fadeDuration * 1.5) >= + playQueue.player1.volume + playQueue.volume / timeLeft >= playQueue.volume ? playQueue.volume - : playQueue.player1.volume + - playQueue.volume / (fadeDuration * 1.5); + : playQueue.player1.volume + playQueue.volume / timeLeft; const player2Volume = - playQueue.player2.volume - playQueue.volume / (fadeDuration * 2) <= 0 + playQueue.player2.volume - playQueue.volume / timeLeft <= 0 ? 0 - : playQueue.player2.volume - playQueue.volume / (fadeDuration * 2); + : playQueue.player2.volume - playQueue.volume / timeLeft; dispatch(setPlayerVolume({ player: 1, volume: player1Volume })); dispatch(setPlayerVolume({ player: 2, volume: player2Volume })); - player1Ref.current.audioEl.current.play(); - if (!incremented) { - dispatch(incrementCurrentIndex('none')); - setIncremented(true); - } - dispatch(setCurrentPlayer(1)); + dispatch(setIsFading(true)); } - console.log('fading player2...'); - } else { - dispatch(setCurrentSeek(currentSeek)); } - console.log(`player2: ${currentSeek} / ${fadeAtTime}`); + if (playQueue.currentPlayer === 2) { + dispatch(setCurrentSeek(currentSeek)); + } }; const handleOnEnded1 = () => { + if (!playQueue.autoIncremented) { + dispatch(incrementCurrentIndex('none')); + dispatch(setAutoIncremented(true)); + } + dispatch(setCurrentPlayer(2)); dispatch(incrementPlayerIndex(1)); dispatch(setPlayerVolume({ player: 1, volume: 0 })); dispatch(setPlayerVolume({ player: 2, volume: playQueue.volume })); - setIncremented(false); + dispatch(setIsFading(false)); + dispatch(setAutoIncremented(false)); }; const handleOnEnded2 = () => { + if (!playQueue.autoIncremented) { + dispatch(incrementCurrentIndex('none')); + dispatch(setAutoIncremented(true)); + } + dispatch(setCurrentPlayer(1)); dispatch(incrementPlayerIndex(2)); dispatch(setPlayerVolume({ player: 1, volume: playQueue.volume })); dispatch(setPlayerVolume({ player: 2, volume: 0 })); - setIncremented(false); + dispatch(setIsFading(false)); + dispatch(setAutoIncremented(false)); }; return ( - + { const handleSeekSlider = (e: number) => { setIsDragging(true); + + // If trying to seek back while fading to the next track, we need to + // pause and reset the next track so that they don't begin overlapping + if (playQueue.isFading) { + if (playQueue.currentPlayer === 1) { + playersRef.current.player2.audioEl.current.pause(); + playersRef.current.player2.audioEl.current.currentTime = 0; + dispatch(setPlayerVolume({ player: 1, volume: playQueue.volume })); + dispatch(setPlayerVolume({ player: 2, volume: 0 })); + } else { + playersRef.current.player1.audioEl.current.pause(); + playersRef.current.player1.audioEl.current.currentTime = 0; + dispatch(setPlayerVolume({ player: 1, volume: 0 })); + dispatch(setPlayerVolume({ player: 2, volume: playQueue.volume })); + } + } + setManualSeek(e); - console.log(e); }; const handleOnWaiting = () => { @@ -115,126 +130,115 @@ const PlayerBar = () => { }; return ( - - - -
- {`Current index: ${playQueue.currentIndex} | `} - {`Player1 index: ${playQueue.player1Index} - ${ - playQueue.entry[playQueue.player1Index]?.title - } | `} - {`Player2 index: ${playQueue.player2Index} - ${ - playQueue.entry[playQueue.player2Index]?.title - } | `} - {`CurrentPlayer: ${playQueue.currentPlayer}`} -
*/} -
+ + {format((isDragging ? manualSeek : seek) * 1000)} + + + + + + {format( + playQueue.entry[playQueue.currentIndex]?.duration * 1000 || + 0 + )} + + + + + + + 0.7 + ? 'volume-up' + : playQueue.volume < 0.3 + ? 'volume-off' + : 'volume-down' + } + size="lg" + style={{ marginRight: '15px' }} + /> + + + + + + ); }; diff --git a/src/redux/playQueueSlice.ts b/src/redux/playQueueSlice.ts index ea31d0e..6865089 100644 --- a/src/redux/playQueueSlice.ts +++ b/src/redux/playQueueSlice.ts @@ -35,6 +35,8 @@ export interface PlayQueue { currentSongId: string; currentPlayer: number; currentSeek: number; + isFading: boolean; + autoIncremented: boolean; player1: { index: number; volume: number; @@ -55,6 +57,8 @@ const initialState: PlayQueue = { currentSongId: '', currentPlayer: 1, currentSeek: 0, + isFading: false, + autoIncremented: false, player1: { index: 0, volume: 0.5, @@ -69,6 +73,8 @@ const initialState: PlayQueue = { entry: [], }; +// TODO: Needs refactoring due to rapid experimental changes to add gapless playback + const playQueueSlice = createSlice({ name: 'nowPlaying', initialState, @@ -79,6 +85,10 @@ const playQueueSlice = createSlice({ } }, + setAutoIncremented: (state, action: PayloadAction) => { + state.autoIncremented = action.payload; + }, + setVolume: (state, action: PayloadAction) => { state.volume = action.payload; }, @@ -102,6 +112,7 @@ const playQueueSlice = createSlice({ state.currentIndex += 1; if (action.payload === 'usingHotkey') { state.currentPlayer = 1; + state.isFading = false; state.player1.volume = state.volume; state.player1.index = state.currentIndex; state.player2.index = state.currentIndex + 1; @@ -111,6 +122,7 @@ const playQueueSlice = createSlice({ if (state.repeatAll) { state.currentIndex = 0; if (action.payload === 'usingHotkey') { + state.isFading = false; state.currentPlayer = 1; state.player1.index = 0; state.player2.index = 1; @@ -137,6 +149,7 @@ const playQueueSlice = createSlice({ ); state.currentSeek = 0; + state.isFading = false; state.player1.index = findIndex; state.player1.volume = state.volume; state.player2.index = findIndex + 1; @@ -186,6 +199,7 @@ const playQueueSlice = createSlice({ // Reset player defaults state.entry = []; state.status = 'PAUSED'; + state.isFading = false; state.currentIndex = 0; state.currentSongId = ''; state.currentPlayer = 1; @@ -207,6 +221,7 @@ const playQueueSlice = createSlice({ clearPlayQueue: (state) => { state.entry = []; state.status = 'PAUSED'; + state.isFading = false; state.currentIndex = 0; state.currentSongId = ''; state.currentPlayer = 1; @@ -223,6 +238,10 @@ const playQueueSlice = createSlice({ state.isLoading = false; }, + setIsFading: (state, action: PayloadAction) => { + state.isFading = action.payload; + }, + moveUp: (state, action: PayloadAction) => { // Create a copy of the queue so we can mutate it in place with arrayMove.mutate const tempQueue = state.entry.slice(); @@ -304,5 +323,7 @@ export const { setPlayerVolume, setVolume, setCurrentSeek, + setIsFading, + setAutoIncremented, } = playQueueSlice.actions; export default playQueueSlice.reducer;