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
This commit is contained in:
jeffvli
2021-08-16 23:04:35 -07:00
parent 36c96525ae
commit fc0349b3a9
3 changed files with 186 additions and 163 deletions

View File

@@ -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<any>({});
const Player = ({ children }: any, ref: any) => {
const [incremented, setIncremented] = useState(false);
const player1Ref = useRef<any>();
const player2Ref = useRef<any>();
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 (
<PlayerContext.Provider
value={{
incremented,
setIncremented,
}}
>
<PlayerContext.Provider value={{}}>
<ReactAudioPlayer
ref={player1Ref}
src={playQueue.entry[playQueue.player1.index]?.streamUrl}

View File

@@ -1,5 +1,5 @@
import React, { useEffect, useState, useRef } from 'react';
import { FlexboxGrid, Icon, Slider, Button } from 'rsuite';
import { FlexboxGrid, Icon, Slider } from 'rsuite';
import format from 'format-duration';
import styled from 'styled-components';
import {
@@ -11,7 +11,6 @@ import {
} from '../../redux/playQueueSlice';
import { useAppDispatch, useAppSelector } from '../../redux/hooks';
import Player from './Player';
import 'react-rangeslider/lib/index.css';
const PlayerContainer = styled.div`
background: #000000;
@@ -102,8 +101,24 @@ const PlayerBar = () => {
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 (
<PlayerContainer>
<Player ref={playersRef} isDragging={isDragging} />
<Button onClick={handleOnWaiting} />
<FlexboxGrid align="middle" style={{ height: '100%' }}>
<FlexboxGrid.Item
colspan={6}
style={{ textAlign: 'left', paddingLeft: '25px' }}
>
<PlayerColumn left height="50px">
<div>Is seeking: {isDragging ? 'true' : 'false'}</div>
</PlayerColumn>
</FlexboxGrid.Item>
<FlexboxGrid.Item
colspan={12}
style={{ textAlign: 'center', verticalAlign: 'middle' }}
>
<PlayerColumn center height="45px">
<PlayerControlIcon icon="random" size="lg" />
<PlayerControlIcon
icon="step-backward"
size="lg"
onClick={handleClickPrevious}
/>
<PlayerControlIcon
icon={
playQueue.status === 'PLAYING' ? 'pause-circle' : 'play-circle'
}
size="3x"
onClick={handleClickPlayPause}
/>
<PlayerControlIcon
icon="step-forward"
size="lg"
onClick={handleClickNext}
/>
<PlayerControlIcon
icon="repeat"
size="lg"
onClick={() => console.log('h')}
/>
</PlayerColumn>
<PlayerColumn center height="35px">
<FlexboxGrid
justify="center"
style={{
width: '100%',
display: 'flex',
alignItems: 'center',
height: '35px',
}}
>
<FlexboxGrid.Item
colspan={4}
style={{ textAlign: 'right', paddingRight: '10px' }}
<Player ref={playersRef}>
<PlayerContainer>
<FlexboxGrid align="middle" style={{ height: '100%' }}>
<FlexboxGrid.Item
colspan={6}
style={{ textAlign: 'left', paddingLeft: '25px' }}
>
<PlayerColumn left height="50px">
<div>Is seeking: {isDragging ? 'true' : 'false'}</div>
</PlayerColumn>
</FlexboxGrid.Item>
<FlexboxGrid.Item
colspan={12}
style={{ textAlign: 'center', verticalAlign: 'middle' }}
>
<PlayerColumn center height="45px">
<PlayerControlIcon icon="random" size="lg" />
<PlayerControlIcon
icon="step-backward"
size="lg"
onClick={handleClickPrevious}
/>
<PlayerControlIcon
icon={
playQueue.status === 'PLAYING'
? 'pause-circle'
: 'play-circle'
}
size="3x"
onClick={handleClickPlayPause}
/>
<PlayerControlIcon
icon="step-forward"
size="lg"
onClick={handleClickNext}
/>
<PlayerControlIcon
icon="repeat"
size="lg"
onClick={() => console.log('Set Repeat')}
/>
</PlayerColumn>
<PlayerColumn center height="35px">
<FlexboxGrid
justify="center"
style={{
width: '100%',
display: 'flex',
alignItems: 'center',
height: '35px',
}}
>
{format((isDragging ? manualSeek : seek) * 1000)}
</FlexboxGrid.Item>
<FlexboxGrid.Item colspan={16}>
<Slider
progress
defaultValue={0}
value={isDragging ? manualSeek : seek}
tooltip={false}
max={
playQueue.entry[playQueue.currentIndex]?.duration -
10 * 1.3 || 0
}
onChange={handleSeekSlider}
style={{ width: '100%' }}
/>
</FlexboxGrid.Item>
<FlexboxGrid.Item
colspan={4}
style={{ textAlign: 'left', paddingLeft: '10px' }}
>
{format(
playQueue.entry[playQueue.currentIndex]?.duration * 1000 || 0
)}
</FlexboxGrid.Item>
</FlexboxGrid>
</PlayerColumn>
</FlexboxGrid.Item>
<FlexboxGrid.Item
colspan={6}
style={{ textAlign: 'right', paddingRight: '25px' }}
>
<PlayerColumn right height="45px">
<Icon
icon={
playQueue.volume > 0.7
? 'volume-up'
: playQueue.volume < 0.3
? 'volume-off'
: 'volume-down'
}
size="lg"
style={{ marginRight: '15px' }}
/>
<Slider
progress
value={Math.floor(playQueue.volume * 100)}
style={{ width: '80px' }}
onChange={handleVolumeSlider}
/>
</PlayerColumn>
</FlexboxGrid.Item>
</FlexboxGrid>
{/* <Button onClick={() => console.log(playQueue.entry)}>Length</Button>
<div>
{`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}`}
</div> */}
</PlayerContainer>
<FlexboxGrid.Item
colspan={4}
style={{ textAlign: 'right', paddingRight: '10px' }}
>
{format((isDragging ? manualSeek : seek) * 1000)}
</FlexboxGrid.Item>
<FlexboxGrid.Item colspan={16}>
<Slider
progress
// disabled={playQueue.isFading}
defaultValue={0}
value={isDragging ? manualSeek : seek}
tooltip={false}
max={playQueue.entry[playQueue.currentIndex]?.duration || 0}
onChange={handleSeekSlider}
style={{ width: '100%' }}
/>
</FlexboxGrid.Item>
<FlexboxGrid.Item
colspan={4}
style={{ textAlign: 'left', paddingLeft: '10px' }}
>
{format(
playQueue.entry[playQueue.currentIndex]?.duration * 1000 ||
0
)}
</FlexboxGrid.Item>
</FlexboxGrid>
</PlayerColumn>
</FlexboxGrid.Item>
<FlexboxGrid.Item
colspan={6}
style={{ textAlign: 'right', paddingRight: '25px' }}
>
<PlayerColumn right height="45px">
<Icon
icon={
playQueue.volume > 0.7
? 'volume-up'
: playQueue.volume < 0.3
? 'volume-off'
: 'volume-down'
}
size="lg"
style={{ marginRight: '15px' }}
/>
<Slider
progress
value={Math.floor(playQueue.volume * 100)}
style={{ width: '80px' }}
onChange={handleVolumeSlider}
/>
</PlayerColumn>
</FlexboxGrid.Item>
</FlexboxGrid>
</PlayerContainer>
</Player>
);
};

View File

@@ -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<boolean>) => {
state.autoIncremented = action.payload;
},
setVolume: (state, action: PayloadAction<number>) => {
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<boolean>) => {
state.isFading = action.payload;
},
moveUp: (state, action: PayloadAction<number[]>) => {
// 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;