fix player

Seto Elkahfi committed Aug 1, 2024 at 21:08 UTC ebe348aa6ed04f36784f450693fc98f1a7e56aaa
3 files changed +264 -6
frontend/splitfire-desktop/app/_src/components/player/Player.tsx
+2 -2
@@ -22,14 +22,14 @@ enum State {
22 ERROR,
23 }
24
25 -enum PlayerState {
25 +export enum PlayerState {
26 STOPPED,
27 PLAYING,
28 PAUSED,
29 RECORDING
30 }
31
32 -interface PlayerVolume {
32 +export interface PlayerVolume {
33 mode: ModeDemucs,
34 volume: string
35 }
frontend/splitfire-desktop/app/play/_components/player.tsx
+249 -2
@@ -1,9 +1,256 @@
1 +"use client";
2 +
3 +import { ControlButtonsView } from "@/app/_src/components/player/ControlButtons";
4 +import { CountDownView } from "@/app/_src/components/player/CountDownView";
5 +import { HideShowToggleView } from "@/app/_src/components/player/HideShowToggle";
6 +import { ModeDemucs } from "@/app/_src/components/player/models/Mode";
7 +import { PlayerState, PlayerVolume } from "@/app/_src/components/player/Player";
8 +import { RecordingView } from "@/app/_src/components/player/RecordingView";
9 +import { VolumeSliderView } from "@/app/_src/components/player/VolumeSliderView";
10 +import { useLogger } from "@/app/_src/lib/logger";
11 +import {
12 + TAURI_PLAYER_PAUSED,
13 + TAURI_PLAYER_PLAY,
14 + TAURI_PLAYER_RECORD,
15 + TAURI_PLAYER_RECORD_STOP,
16 + TAURI_PLAYER_RECORDING_LENGTH,
17 + TAURI_PLAYER_RESUMED,
18 + TAURI_PLAYER_SET_VOLUME,
19 + TAURI_PLAYER_STOP,
20 +} from "@/app/_src/lib/tauriHandler";
21 +import { invoke } from "@tauri-apps/api";
22 +import { useState } from "react";
23 +import { Row, Col, Container } from "react-bootstrap";
24 +
25 +export default function Player({
26 + audioId,
27 + userId,
28 +}: {
29 + audioId: string;
30 + userId: number;
31 +}) {
32 + // Logger
33 + const log = useLogger("Player");
34 +
35 + // State and constants
36 + const COUNTING_DOWN_NUMBER = 3;
37 + const [isCountingCountdown, setIsCountingCountdown] = useState(false);
38 + const [recordingLength, setRecordingLength] = useState<number>(0);
39 + const [hideVolumeSliders, setHideVolumeSliders] = useState(false);
40 + const [playerState, setPlayerState] = useState<PlayerState>(
41 + PlayerState.STOPPED
42 + );
43 +
44 + // Player states
45 + const [vocalsVolume, setVocalsVolume] = useState<string>("100");
46 + const [drumsVolume, setDrumsVolume] = useState<string>("100");
47 + const [bassVolume, setBassVolume] = useState<string>("100");
48 + const [otherVolume, setOtherVolume] = useState<string>("100");
49 +
50 + // Player functions
51 + const _onVolumeChange = (mode: ModeDemucs, value: string) => {
52 + log.debug("_onVolumeChange", mode, value);
53 + switch (mode) {
54 + case ModeDemucs.Vocals:
55 + setVocalsVolume(value);
56 + break;
57 + case ModeDemucs.Drums:
58 + setDrumsVolume(value);
59 + break;
60 + case ModeDemucs.Bass:
61 + setBassVolume(value);
62 + break;
63 + case ModeDemucs.Other:
64 + setOtherVolume(value);
65 + break;
66 + }
67 + invoke(TAURI_PLAYER_SET_VOLUME, { mode: mode, volume: value });
68 + };
69 +
70 + const onFinishedRecording = () => {
71 + log.debug("Recording finished");
72 + //_toggleRecording()
73 + };
74 +
75 + const _toggleRecording = async () => {
76 + try {
77 + // If we are playing playbacks, stop it before recording.
78 + if (playerState === PlayerState.PLAYING) {
79 + setPlayerState(PlayerState.STOPPED);
80 + const result = await invoke(TAURI_PLAYER_STOP, {
81 + audioId,
82 + userId,
83 + });
84 + log.debug(TAURI_PLAYER_STOP, result);
85 + }
86 +
87 + // If we're already recording, we want to stop it and return.
88 + if (playerState === PlayerState.RECORDING) {
89 + _stopRecording();
90 + return;
91 + }
92 +
93 + // Otherwise, we're good to go.
94 + // Get the length of the audio file.
95 +
96 + let length: number = await invoke(TAURI_PLAYER_RECORDING_LENGTH, {
97 + audioId,
98 + userId,
99 + });
100 + log.debug(TAURI_PLAYER_RECORDING_LENGTH, length);
101 + setRecordingLength(length);
102 + setIsCountingCountdown(true);
103 +
104 + setTimeout(async () => {
105 + log.debug(TAURI_PLAYER_RECORD, "Start recording");
106 + setPlayerState(PlayerState.RECORDING);
107 + setIsCountingCountdown(false);
108 + const result = await invoke(TAURI_PLAYER_RECORD, {
109 + audioId,
110 + userId,
111 + playerVolumes: _playerVolumes(),
112 + });
113 + log.debug(TAURI_PLAYER_RECORD, result);
114 + }, COUNTING_DOWN_NUMBER * 1000);
115 + } catch (error) {
116 + log.error(error);
117 + }
118 + };
119 +
120 + const _stopRecording = async () => {
121 + setRecordingLength(0);
122 + setPlayerState(PlayerState.STOPPED);
123 + const stop_recording_result = await invoke(TAURI_PLAYER_RECORD_STOP, {
124 + audioId,
125 + userId,
126 + });
127 + const result = await invoke(TAURI_PLAYER_STOP, {
128 + audioId,
129 + userId,
130 + });
131 + log.debug(TAURI_PLAYER_STOP, result);
132 + log.debug(TAURI_PLAYER_RECORD_STOP, stop_recording_result);
133 + };
134 +
135 + const _stopPlayer = async () => {
136 + try {
137 + setPlayerState(PlayerState.STOPPED);
138 + setRecordingLength(0);
139 + const result = await invoke(TAURI_PLAYER_STOP, {
140 + audioId,
141 + userId,
142 + });
143 + log.debug(TAURI_PLAYER_STOP, result);
144 + } catch (error) {
145 + log.error(error);
146 + }
147 + };
148 + const _playerVolumes = (): PlayerVolume[] => {
149 + return [
150 + { mode: ModeDemucs.Vocals, volume: vocalsVolume },
151 + { mode: ModeDemucs.Drums, volume: drumsVolume },
152 + { mode: ModeDemucs.Bass, volume: bassVolume },
153 + { mode: ModeDemucs.Other, volume: otherVolume },
154 + ];
155 + };
156 +
157 + const _togglePlayAudio = async () => {
158 + // Should disabled when recording
159 + if (playerState === PlayerState.RECORDING) {
160 + return;
161 + }
162 + try {
163 + switch (playerState) {
164 + case PlayerState.STOPPED:
165 + log.debug(TAURI_PLAYER_PLAY, "Start playing");
166 + setIsCountingCountdown(true);
167 + setTimeout(async () => {
168 + setIsCountingCountdown(false);
169 + setPlayerState(PlayerState.PLAYING);
170 + await invoke(TAURI_PLAYER_PLAY, {
171 + playerVolumes: _playerVolumes(),
172 + });
173 + }, COUNTING_DOWN_NUMBER * 1000);
174 + break;
175 + case PlayerState.PLAYING:
176 + log.debug(TAURI_PLAYER_PAUSED, "Paused");
177 + setPlayerState(PlayerState.PAUSED);
178 + await invoke(TAURI_PLAYER_PAUSED);
179 + break;
180 + case PlayerState.PAUSED:
181 + log.debug(TAURI_PLAYER_RESUMED, "Resumed");
182 + setPlayerState(PlayerState.PLAYING);
183 + await invoke(TAURI_PLAYER_PLAY, { playerVolumes: _playerVolumes() });
184 + }
185 + } catch (error) {
186 + log.error(error);
187 + }
188 + };
189 +
190 + let buttonOrCounting = <></>;
191 + if (isCountingCountdown) {
192 + buttonOrCounting = <CountDownView seconds={COUNTING_DOWN_NUMBER} type="" />;
193 + } else {
194 + buttonOrCounting = (
195 + <ControlButtonsView
196 + isPlaying={playerState === PlayerState.PLAYING}
197 + isRecording={playerState === PlayerState.RECORDING}
198 + onClick={_togglePlayAudio}
199 + onStop={_stopPlayer}
200 + onRecord={_toggleRecording}
201 + />
202 + );
203 + }
204 +
205 + let audioWaveform = <></>;
206 + if (
207 + playerState === PlayerState.PLAYING ||
208 + playerState === PlayerState.PAUSED
209 + ) {
210 + audioWaveform = (
211 + <>
212 + <CountDownView seconds={recordingLength} type="Practicing!!!" />
213 + </>
214 + );
215 + } else if (playerState === PlayerState.RECORDING) {
216 + audioWaveform = (
217 + <RecordingView
218 + length={recordingLength}
219 + onRecordingEnd={onFinishedRecording}
220 + />
221 + );
222 + }
223
2 -export default async function Page() {
224 return (
225 <div className="prose prose-sm prose-invert max-w-none">
226 <div className="grid grid-cols-1 gap-6 lg:grid-cols-3">
6 - Splitting file
227 + <Container>
228 + {buttonOrCounting}
229 + <Row className="h-100 d-inline-block">
230 + <Col xs={12} className="mb-3 mt-3">
231 + <Container style={{ height: 300 }}>
232 + <div className="d-flex align-items-center justify-content-center h-100">
233 + <div className="d-flex flex-column">{audioWaveform}</div>
234 + </div>
235 + </Container>
236 + </Col>
237 + </Row>
238 + <Row className="border border-light">
239 + <Col xs={2} className="mb-3 mt-3">
240 + <p color="red">Volume</p>
241 + </Col>
242 + <Col xs={{ span: 2, offset: 8 }} className="mb-3 mt-3">
243 + <HideShowToggleView
244 + hideVolumeSliders={hideVolumeSliders}
245 + setHideVolumeSliders={setHideVolumeSliders}
246 + />
247 + </Col>
248 + <VolumeSliderView
249 + _onVolumeChange={_onVolumeChange}
250 + isHidden={hideVolumeSliders}
251 + />
252 + </Row>
253 + </Container>
254 </div>
255 </div>
256 );
frontend/splitfire-desktop/app/play/page.tsx
+13 -2
@@ -1,13 +1,15 @@
1 "use client";
2
3 import { invoke } from "@tauri-apps/api";
4 -import { useEffect, useState } from "react";
4 +import { useContext, useEffect, useState } from "react";
5 import { TAURI_PLAYER_PREPARE } from "../_src/lib/tauriHandler";
6 import { useSearchParams } from "next/navigation";
7 import { useLogger } from "../_src/lib/logger";
8 import { IconSpinner } from "../_ui/components/icons";
9 import { PlayerPrepareResponse } from "@/models/content";
10 import { TauriResponse } from "@/models/shared";
11 +import Player from "./_components/player";
12 +import { UserContext } from "../_src/lib/CurrentUserContext";
13
14 enum State {
15 LOADING,
@@ -21,6 +23,8 @@ export default function Page() {
23 const audioFileId = searchParams.get("audioFileId");
24 const [title, setTitle] = useState("Loading song...");
25 const [state, setState] = useState(State.LOADING);
26 + const user = useContext(UserContext);
27 + const userId = user.user?.user.id;
28
29 useEffect(() => {
30 log.debug("Play page loaded.");
@@ -49,6 +53,13 @@ export default function Page() {
53 fetchData();
54 // eslint-disable-next-line react-hooks/exhaustive-deps
55 }, []);
56 +
57 + // Sanity check
58 + if (!audioFileId || !userId) {
59 + log.error("Missing audio file ID or user ID");
60 + return <div>Missing audio file ID</div>;
61 + }
62 +
63 return (
64 <>
65 <div className="flex justify-between">
@@ -65,7 +76,7 @@ export default function Page() {
76 <div className="text-red-500">Failed to load song</div>
77 )}
78 {state === State.LOADED && (
68 - <div className="text-green-500">Song loaded</div>
79 + <Player audioId={audioFileId} userId={userId} />
80 )}
81 </div>
82 </div>