Play works on web websocket not reimplemented

This commit is contained in:
Clément Le Bihan
2024-01-13 17:31:56 +01:00
parent 00ee5cd531
commit f632ed42a3
4 changed files with 97 additions and 52 deletions
+31 -19
View File
@@ -9,6 +9,7 @@ import { SvgContainer } from './SvgContainer';
import LoadingComponent from '../Loading'; import LoadingComponent from '../Loading';
import { SplendidGrandPiano } from 'smplr'; import { SplendidGrandPiano } from 'smplr';
import { atom, useAtom } from 'jotai'; import { atom, useAtom } from 'jotai';
import { useStopwatch } from 'react-use-precision-timer';
export const timestampAtom = atom(0); export const timestampAtom = atom(0);
export const shouldPlayAtom = atom(false); export const shouldPlayAtom = atom(false);
@@ -37,20 +38,21 @@ const getCursorToPlay = (
} }
}; };
const transitionDuration = 50; const transitionDuration = 200;
const PartitionMagic = ({ songID }: ParitionMagicProps) => { const PartitionMagic = ({ songID }: ParitionMagicProps) => {
const { data, isLoading, isError } = useQuery(API.getSongCursorInfos(songID)); const { data, isLoading, isError } = useQuery(API.getSongCursorInfos(songID));
const currentCurIdx = React.useRef(-1); const currentCurIdx = React.useRef(-1);
const stopwatch = useStopwatch();
const [endPartitionReached, setEndPartitionReached] = React.useState(false); const [endPartitionReached, setEndPartitionReached] = React.useState(false);
const [isPartitionSvgLoaded, setIsPartitionSvgLoaded] = React.useState(false); const [isPartitionSvgLoaded, setIsPartitionSvgLoaded] = React.useState(false);
const partitionOffset = useSharedValue(0); const partitionOffset = useSharedValue(0);
const melodySound = React.useRef<Audio.Sound | null>(null); const melodySound = React.useRef<Audio.Sound | null>(null);
const piano = React.useRef<SplendidGrandPiano | null>(null); const piano = React.useRef<SplendidGrandPiano | null>(null);
const [isPianoLoaded, setIsPianoLoaded] = React.useState(false); const [isPianoLoaded, setIsPianoLoaded] = React.useState(false);
const timestamp = useAtom(timestampAtom)[0]; const [timestamp, setTimestamp] = useAtom(timestampAtom);
const shouldPlay = useAtom(shouldPlayAtom)[0]; const shouldPlay = useAtom(shouldPlayAtom)[0];
const [, setPartitionState] = useAtom(partitionStateAtom); const [partitionState, setPartitionState] = useAtom(partitionStateAtom);
const cursorPaddingVertical = 10; const cursorPaddingVertical = 10;
const cursorPaddingHorizontal = 3; const cursorPaddingHorizontal = 3;
@@ -62,13 +64,28 @@ const PartitionMagic = ({ songID }: ParitionMagicProps) => {
const cursorTop = (data?.cursors[cursorDisplayIdx]?.y ?? 0) - cursorPaddingVertical; const cursorTop = (data?.cursors[cursorDisplayIdx]?.y ?? 0) - cursorPaddingVertical;
const cursorLeft = (data?.cursors[0]?.x ?? 0) - cursorPaddingHorizontal; const cursorLeft = (data?.cursors[0]?.x ?? 0) - cursorPaddingHorizontal;
console.log('state', partitionState);
if (!endPartitionReached && currentCurIdx.current + 1 === data?.cursors.length) { if (!endPartitionReached && currentCurIdx.current + 1 === data?.cursors.length) {
// weird contraption but the mobile don't want classic functions to be called // weird contraption but the mobile don't want classic functions to be called
// with the withTiming function :( // with the withTiming function :(
melodySound.current?.pauseAsync(); melodySound.current?.pauseAsync();
piano.current?.stop();
setPartitionState('ended'); setPartitionState('ended');
} }
React.useEffect(() => {
if (isError) {
setPartitionState('error');
}
}, [isError]);
React.useEffect(() => {
if (isPartitionSvgLoaded && !isLoading && (melodySound.current?._loaded || isPianoLoaded)) {
setPartitionState('ready');
}
}, [isPartitionSvgLoaded, isLoading, melodySound.current?._loaded, isPianoLoaded]);
React.useEffect(() => { React.useEffect(() => {
if (Platform.OS === 'web' && !piano.current) { if (Platform.OS === 'web' && !piano.current) {
const audio = new AudioContext(); const audio = new AudioContext();
@@ -106,28 +123,23 @@ const PartitionMagic = ({ songID }: ParitionMagicProps) => {
}, [data]); }, [data]);
React.useEffect(() => { React.useEffect(() => {
if (isError) { const interval = setInterval(() => {
setPartitionState('error'); setTimestamp(stopwatch.getElapsedRunningTime());
} }, 200);
}, [isError]); return () => {
clearInterval(interval);
React.useEffect(() => { };
if (isPartitionSvgLoaded && !isLoading && (melodySound.current?._loaded || isPianoLoaded)) { }, []);
setPartitionState('ready');
}
}, [isPartitionSvgLoaded, isLoading, melodySound.current?._loaded, isPianoLoaded]);
React.useEffect(() => { React.useEffect(() => {
if (Platform.OS === 'web') { if (Platform.OS === 'web') {
if (!piano.current || !isPianoLoaded) { if (!piano.current || !isPianoLoaded) return;
return; shouldPlay ? stopwatch.start() : stopwatch.pause();
}
setPartitionState(shouldPlay ? 'playing' : 'paused'); setPartitionState(shouldPlay ? 'playing' : 'paused');
return; return;
} }
if (!melodySound.current || !melodySound.current._loaded) { if (!melodySound.current || !melodySound.current._loaded) return;
return; shouldPlay ? stopwatch.start() : stopwatch.pause();
}
if (shouldPlay) { if (shouldPlay) {
melodySound.current melodySound.current
.playAsync() .playAsync()
+31
View File
@@ -0,0 +1,31 @@
import PopupCC from '../UI/PopupCC';
import { shouldEndAtom } from './PlayViewControlBar';
import { partitionStateAtom } from './PartitionMagic';
import ScoreModal from '../ScoreModal';
import { useAtom } from 'jotai';
export const PlayEndModal = () => {
const [shouldEnd] = useAtom(shouldEndAtom);
const [partitionState] = useAtom(partitionStateAtom);
const isEnd = shouldEnd || partitionState === 'ended';
return (
<PopupCC isVisible={isEnd}>
<ScoreModal
songId={0}
overallScore={0}
precision={0}
score={{
max_score: 0,
missed: 0,
wrong: 0,
good: 0,
great: 0,
perfect: 0,
current_streak: 0,
max_streak: 0,
}}
/>
</PopupCC>
);
};
+4 -4
View File
@@ -25,7 +25,7 @@ const PlayViewControlBar = ({ song }: PlayViewControlBarProps) => {
const bpm = React.useRef<number>(60); const bpm = React.useRef<number>(60);
const { colors } = useTheme(); const { colors } = useTheme();
const textColor = colors.text; const textColor = colors.text;
const paused = partitionState === 'paused'; const isPlaying = partitionState === 'playing';
const disabled = partitionState === 'loading' || partitionState === 'error'; const disabled = partitionState === 'loading' || partitionState === 'error';
return ( return (
<Row <Row
@@ -108,9 +108,9 @@ const PlayViewControlBar = ({ song }: PlayViewControlBarProps) => {
_icon={{ _icon={{
as: Ionicons, as: Ionicons,
color: colors.coolGray[900], color: colors.coolGray[900],
name: paused ? 'play' : 'pause', name: isPlaying ? 'pause' : 'play',
}} }}
onPress={() => setShouldPlay(paused)} onPress={() => setShouldPlay(!isPlaying)}
/> />
<IconButton <IconButton
size="sm" size="sm"
@@ -144,7 +144,7 @@ const PlayViewControlBar = ({ song }: PlayViewControlBarProps) => {
minWidth: 120, minWidth: 120,
}} }}
> >
<MetronomeControls paused={paused} bpm={bpm.current} /> <MetronomeControls paused={isPlaying} bpm={bpm.current} />
</View> </View>
</Row> </Row>
); );
+31 -29
View File
@@ -25,6 +25,7 @@ import { Clock, Cup } from 'iconsax-react-native';
import PlayViewControlBar from '../components/Play/PlayViewControlBar'; import PlayViewControlBar from '../components/Play/PlayViewControlBar';
import ScoreModal from '../components/ScoreModal'; import ScoreModal from '../components/ScoreModal';
import { PlayScore, ScoreMessage } from '../components/Play/PlayScore'; import { PlayScore, ScoreMessage } from '../components/Play/PlayScore';
import { PlayEndModal } from '../components/Play/PlayEndModal';
type PlayViewProps = { type PlayViewProps = {
songId: number; songId: number;
@@ -63,22 +64,22 @@ const PlayView = ({ songId }: PlayViewProps) => {
const isPhone = screenSize === 'small'; const isPhone = screenSize === 'small';
const song = useQuery(API.getSong(songId, ['artist']), { staleTime: Infinity }); const song = useQuery(API.getSong(songId, ['artist']), { staleTime: Infinity });
const toast = useToast(); const toast = useToast();
const [lastScoreMessage, setLastScoreMessage] = useState<ScoreMessage>(); // const [lastScoreMessage, setLastScoreMessage] = useState<ScoreMessage>();
const webSocket = useRef<WebSocket>(); const webSocket = useRef<WebSocket>();
const [paused, setPause] = useState<boolean>(true); // const [paused, setPause] = useState<boolean>(true);
const stopwatch = useStopwatch(); // const stopwatch = useStopwatch();
const [time, setTime] = useState(0); // const [time, setTime] = useState(0);
const [endResult, setEndResult] = useState<unknown>(); const [endResult, setEndResult] = useState<unknown>();
const [shouldPlay, setShouldPlay] = useState(false); // const [shouldPlay, setShouldPlay] = useState(false);
const songHistory = useQuery(API.getSongHistory(songId)); const songHistory = useQuery(API.getSongHistory(songId));
const [score, setScore] = useState(0); // Between 0 and 100 // const [score, setScore] = useState(0); // Between 0 and 100
const getElapsedTime = () => stopwatch.getElapsedRunningTime(); // const getElapsedTime = () => stopwatch.getElapsedRunningTime();
const [readyToPlay, setReadyToPlay] = useState(false); // const [readyToPlay, setReadyToPlay] = useState(false);
// eslint-disable-next-line @typescript-eslint/no-unused-vars // eslint-disable-next-line @typescript-eslint/no-unused-vars
const [midiKeyboardFound, setMidiKeyboardFound] = useState<boolean>(); const [midiKeyboardFound, setMidiKeyboardFound] = useState<boolean>();
// first number is the note, the other is the time when pressed on release the key is removed // first number is the note, the other is the time when pressed on release the key is removed
// eslint-disable-next-line @typescript-eslint/no-unused-vars // eslint-disable-next-line @typescript-eslint/no-unused-vars
const [streak, setStreak] = useState(0); // const [streak, setStreak] = useState(0);
const colorScheme = useColorScheme(); const colorScheme = useColorScheme();
const { colors } = useTheme(); const { colors } = useTheme();
const statColor = colors.lightText; const statColor = colors.lightText;
@@ -131,7 +132,7 @@ const PlayView = ({ songId }: PlayViewProps) => {
console.log('MIDI inputs', inputs); console.log('MIDI inputs', inputs);
let endMsgReceived = false; // Used to know if to go to error screen when websocket closes let endMsgReceived = false; // Used to know if to go to error screen when websocket closes
if (inputs.size <= 0) { if (inputs.size <= 10) {
toast.show({ description: 'No MIDI Keyboard found' }); toast.show({ description: 'No MIDI Keyboard found' });
return; return;
} }
@@ -238,14 +239,14 @@ const PlayView = ({ songId }: PlayViewProps) => {
useEffect(() => { useEffect(() => {
ScreenOrientation.lockAsync(ScreenOrientation.OrientationLock.LANDSCAPE).catch(() => {}); ScreenOrientation.lockAsync(ScreenOrientation.OrientationLock.LANDSCAPE).catch(() => {});
const interval = setInterval(() => { // const interval = setInterval(() => {
setTime(() => getElapsedTime()); // Countdown // setTime(() => getElapsedTime()); // Countdown
}, 200); // }, 200);
return () => { return () => {
ScreenOrientation.unlockAsync().catch(() => {}); ScreenOrientation.unlockAsync().catch(() => {});
stopwatch.stop(); // stopwatch.stop();
clearInterval(interval); // clearInterval(interval);
}; };
}, []); }, []);
@@ -290,12 +291,13 @@ const PlayView = ({ songId }: PlayViewProps) => {
zIndex: 100, zIndex: 100,
}} }}
> >
<PopupCC isVisible={endResult != undefined}> <PlayEndModal />
{/* <PopupCC isVisible={endResult != undefined}>
{ {
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
(() => (endResult ? <ScoreModal {...(endResult as any)} /> : <></>))() endResult ? <ScoreModal {...(endResult as any)} /> : <></>
} }
</PopupCC> </PopupCC> */}
<PopupCC <PopupCC
title={translate('selectPlayMode')} title={translate('selectPlayMode')}
description={translate('selectPlayModeExplaination')} description={translate('selectPlayModeExplaination')}
@@ -404,18 +406,18 @@ const PlayView = ({ songId }: PlayViewProps) => {
/> />
</View> </View>
<PlayViewControlBar <PlayViewControlBar
score={score} // score={score}
time={time} // time={time}
paused={paused} // paused={paused}
disabled={playType == null || !readyToPlay} // disabled={playType == null || !readyToPlay}
song={song.data} song={song.data}
onEnd={onEnd} // onEnd={onEnd}
onPause={() => { // onPause={() => {
setShouldPlay(false); // setShouldPlay(false);
}} // }}
onResume={() => { // onResume={() => {
setShouldPlay(true); // setShouldPlay(true);
}} // }}
/> />
</SafeAreaView> </SafeAreaView>
{colorScheme === 'dark' && ( {colorScheme === 'dark' && (