Tonejs
Seto Elkahfi committed
Jan 23, 2022 at 11:58 UTC
78582622ca17f6e2550a16970c174722c4dfe7aa
3 files changed
+185
-108
package.json
+1
@@ -22,6 +22,7 @@
22
"react-router-dom": "^5.3.0",
23
"react-scripts": "^4.0.0",
24
"react-youtube": "^7.14.0",
25
+ "tone": "^14.7.77",
26
"typescript": "^3.9.10"
27
},
28
"scripts": {
src/components/AudioPlayer.tsx
+157
-106
@@ -1,13 +1,18 @@
1
/* eslint-disable no-script-url */
2
/* eslint-disable jsx-a11y/anchor-is-valid */
3
import axios from 'axios';
4
-import React from 'react';
4
+import React, { CSSProperties } from 'react';
5
import { Component } from 'react';
6
-import { Col, Row } from 'react-bootstrap';
6
+import { Col, Row, Spinner } from 'react-bootstrap';
7
import YouTube, { Options } from 'react-youtube';
8
-import { GiMicrophone, GiDrumKit, GiGuitarHead, GiGuitarBassHead, GiPianoKeys } from 'react-icons/gi';
8
+import { GiMicrophone, GiDrumKit, GiGuitarHead, GiGuitarBassHead, GiPianoKeys, GiPlayButton } from 'react-icons/gi';
9
import { IconContext } from 'react-icons';
10
import { db, AudioFiles } from './db';
11
+import { Channel, Player, Time, Transport } from 'tone';
12
+
13
+const buttonStyle: CSSProperties = {
14
+
15
+}
16
17
interface Result {
18
source_file: string,
@@ -25,7 +30,9 @@ type AudioProps = {
30
31
type AudioState = {
32
audioFile: AudioFile | null,
33
+ playerTime: number,
34
isPlayerReady: boolean,
35
+ isYoutubePlayerReady: boolean,
36
isPlaying: boolean
37
vocalAudioOn: boolean
38
guitarAudioOn: boolean
@@ -40,23 +47,25 @@ export enum Mode {
47
48
class AudioPlayer extends Component<AudioProps, AudioState> {
49
43
- vocalAudio = new Audio();
44
- guitarAudio = new Audio();
45
- bassAudio = new Audio();
46
- drumsAudio = new Audio();
47
- pianoAudio = new Audio()
50
+ vocalPlayer: Player | null = null;
51
+ otherPlayer: Player | null = null;
52
+ bassPlayer: Player | null = null;
53
+ drumsPlayer: Player | null = null;
54
+ pianoPlayer: Player | null = null;
55
56
constructor(props: AudioProps) {
57
super(props);
58
this.state = {
59
audioFile: null,
60
+ playerTime: 0,
61
isPlayerReady: false,
62
+ isYoutubePlayerReady: false,
63
isPlaying: false,
55
- vocalAudioOn: false,
56
- guitarAudioOn: false,
57
- bassAudioOn: false,
58
- drumsAudioOn: false,
59
- pianoAudioOn: false,
64
+ vocalAudioOn: true,
65
+ guitarAudioOn: true,
66
+ bassAudioOn: true,
67
+ drumsAudioOn: true,
68
+ pianoAudioOn: true,
69
}
70
}
71
@@ -68,7 +77,11 @@ class AudioPlayer extends Component<AudioProps, AudioState> {
77
this.setState({ audioFile });
78
console.log(this.state);
79
this.downloadAudioFilesIfNeeded();
71
- })
80
+ });
81
+ }
82
+
83
+ componentWillUnmount() {
84
+ this.pauseAudio();
85
}
86
87
downloadAudioFilesIfNeeded() {
@@ -137,146 +150,184 @@ class AudioPlayer extends Component<AudioProps, AudioState> {
150
151
_setAudioSrc(type: Mode, file: Blob) {
152
// Get window.URL object
140
- var URL = window.URL || window.webkitURL;
153
+ const URL = window.URL || window.webkitURL;
154
// Create and revoke ObjectURL
142
- var audioFileURL = URL.createObjectURL(file);
155
+ const audioFileURL = URL.createObjectURL(file);
156
+ const channel = new Channel().toDestination();
157
158
switch (type) {
159
case Mode.vocalist:
146
- this.vocalAudio.src = audioFileURL;
160
+ this.vocalPlayer = new Player({ url: audioFileURL, onload: this._updatePlayerStatus })
161
+ this.vocalPlayer.connect(channel);
162
break;
163
case Mode.bassist:
149
- this.bassAudio.src = audioFileURL;
164
+ this.bassPlayer = new Player({ url: audioFileURL, onload: this._updatePlayerStatus })
165
+ this.bassPlayer.connect(channel);
166
break;
167
case Mode.drummer:
152
- this.drumsAudio.src = audioFileURL;
168
+ this.drumsPlayer = new Player({ url: audioFileURL, onload: this._updatePlayerStatus })
169
+ this.drumsPlayer.connect(channel);
170
break;
171
case Mode.guitarist:
155
- this.guitarAudio.src = audioFileURL;
172
+ this.otherPlayer = new Player({ url: audioFileURL, onload: this._updatePlayerStatus })
173
+ this.otherPlayer.connect(channel);
174
break;
175
case Mode.keyboardist:
158
- this.pianoAudio.src = audioFileURL;
176
+ this.pianoPlayer = new Player({ url: audioFileURL, onload: this._updatePlayerStatus })
177
+ this.pianoPlayer.connect(channel);
178
break;
179
}
180
}
181
163
- playAudio() {
164
- this.vocalAudio.play();
165
- this.guitarAudio.play();
166
- this.bassAudio.play();
167
- this.guitarAudio.play();
168
- this.drumsAudio.play();
182
+ _updatePlayerStatus: (() => void) = () => {
183
+ if (
184
+ this.vocalPlayer?.loaded &&
185
+ this.drumsPlayer?.loaded &&
186
+ this.otherPlayer?.loaded &&
187
+ this.bassPlayer?.loaded &&
188
+ this.pianoPlayer?.loaded
189
+ ) {
190
+ this.setState({ isPlayerReady: true });
191
+ }
192
+ }
193
+
194
+ playAudio(at: number) {
195
+ this.vocalPlayer?.seek(at).start()
196
+ this.bassPlayer?.seek(at).start()
197
+ this.drumsPlayer?.seek(at).start()
198
+ this.pianoPlayer?.seek(at).start()
199
+ this.otherPlayer?.seek(at).start()
200
+
201
+ console.log("Start playing at", at);
202
}
203
204
pauseAudio() {
172
- this.vocalAudio.pause();
173
- this.guitarAudio.pause();
174
- this.bassAudio.pause();
175
- this.guitarAudio.pause();
176
- this.drumsAudio.pause();
205
+ this.vocalPlayer?.stop();
206
+ this.bassPlayer?.stop();
207
+ this.drumsPlayer?.stop();
208
+ this.pianoPlayer?.stop();
209
+ this.otherPlayer?.stop();
210
+
211
+ console.log("Stop playing");
212
}
213
214
setToggleInstrument(mode: Mode) {
215
switch (mode) {
216
case Mode.vocalist:
182
- this.setState({vocalAudioOn: !this.state.vocalAudioOn});
183
- this.vocalAudio.volume = this.state.vocalAudioOn ? 1 : 0;
217
+ if (!this.vocalPlayer) return;
218
+ this.setState({ vocalAudioOn: !this.state.vocalAudioOn });
219
+ this.vocalPlayer.mute = this.state.vocalAudioOn;
220
break;
221
case Mode.bassist:
186
- this.setState({bassAudioOn: !this.state.bassAudioOn});
187
- this.bassAudio.volume = this.state.bassAudioOn ? 1 : 0;;
222
+ if (!this.bassPlayer) return;
223
+ this.setState({ bassAudioOn: !this.state.bassAudioOn });
224
+ this.bassPlayer.mute = this.state.bassAudioOn;
225
break;
226
case Mode.guitarist:
190
- this.setState({guitarAudioOn: !this.state.guitarAudioOn});
191
- this.guitarAudio.volume = this.state.guitarAudioOn ? 1 : 0;;
227
+ if (!this.otherPlayer) return;
228
+ this.setState({ guitarAudioOn: !this.state.guitarAudioOn });
229
+ this.otherPlayer.mute = this.state.guitarAudioOn;
230
break;
231
case Mode.keyboardist:
194
- this.setState({pianoAudioOn: !this.state.pianoAudioOn});
195
- this.pianoAudio.volume = this.state.pianoAudioOn ? 1 : 0;;
232
+ if (!this.pianoPlayer) return;
233
+ this.setState({ pianoAudioOn: !this.state.pianoAudioOn });
234
+ this.pianoPlayer.mute = this.state.pianoAudioOn;;
235
break;
236
case Mode.drummer:
198
- this.setState({drumsAudioOn: !this.state.drumsAudioOn});
199
- this.drumsAudio.volume = this.state.drumsAudioOn ? 1 : 0;;
237
+ if (!this.drumsPlayer) return;
238
+ this.setState({ drumsAudioOn: !this.state.drumsAudioOn });
239
+ this.drumsPlayer.mute = this.state.drumsAudioOn;
240
break;
241
}
242
}
243
244
render() {
205
- const opts: Options = {
206
- width: '100%',
207
- playerVars: {
208
- // https://developers.google.com/youtube/player_parameters
209
- autoplay: 0,
210
- mute: 1,
211
- controls: 0,
212
- rel: 0,
213
- showinfo: 0
214
- },
215
- };
216
-
217
- let youtubeContent: any = 'Loading player...';
218
- if (this.state.audioFile) {
219
- youtubeContent = <YouTube
220
- videoId={this.state.audioFile?.youtube_video_id}
221
- opts={opts}
222
- onReady={this._onReady.bind(this)}
223
- onStateChange={this._onStateChange.bind(this)}
224
- onPlay={this._onPlay.bind(this)}
225
- onPause={this._onPause.bind(this)}
226
- />
227
- }
228
-
229
- return (
230
- <IconContext.Provider value={{ size: "2em", color: "white", className: "global-class-name" }}>
231
- <Row className="mb-3 mt-3">
232
- <Col>
233
- <a href='javascript:void(0)' onClick={() => this.setToggleInstrument(Mode.vocalist)} >
234
- <GiMicrophone color={this.state.vocalAudioOn ? `white` : `red`} />
235
- </a>
236
- </Col>
237
- <Col>
238
- <a href='javascript:void(0)' onClick={() => this.setToggleInstrument(Mode.drummer)} >
239
- <GiDrumKit color={this.state.drumsAudioOn ? `white` : `red`} />
240
- </a>
241
- </Col>
242
- <Col>
243
- <a href='javascript:void(0)' onClick={() => this.setToggleInstrument(Mode.bassist)} >
244
- <GiGuitarBassHead color={this.state.bassAudioOn ? `white` : `red`} />
245
- </a>
246
- </Col>
247
- <Col>
248
- <a href='javascript:void(0)' onClick={() => this.setToggleInstrument(Mode.guitarist)} >
249
- <GiGuitarHead color={this.state.guitarAudioOn ? `white` : `red`} />
250
- </a>
251
- </Col>
252
- <Col>
253
- <a href='javascript:void(0)' onClick={() => this.setToggleInstrument(Mode.keyboardist)} >
254
- <GiPianoKeys color={this.state.pianoAudioOn ? `white` : `red`} />
255
- </a>
256
- </Col>
257
- </Row>
245
+ if (this.state.isPlayerReady) {
246
+ const opts: Options = {
247
+ width: '100%',
248
+ playerVars: {
249
+ // https://developers.google.com/youtube/player_parameters
250
+ autoplay: 0,
251
+ mute: 1,
252
+ controls: 0,
253
+ rel: 0,
254
+ showinfo: 0
255
+ },
256
+ };
257
+ return (
258
+ <IconContext.Provider value={{ size: "2em", color: "white", className: "global-class-name" }}>
259
+ <Row className="mb-3 mt-3">
260
+ <Col>
261
+ <GiMicrophone
262
+ color={this.state.vocalAudioOn ? `white` : `red`}
263
+ onClick={() => this.setToggleInstrument(Mode.vocalist)}
264
+ style={buttonStyle} />
265
+ </Col>
266
+ <Col>
267
+ <GiDrumKit
268
+ color={this.state.drumsAudioOn ? `white` : `red`}
269
+ onClick={() => this.setToggleInstrument(Mode.drummer)}
270
+ style={buttonStyle} />
271
+ </Col>
272
+ <Col>
273
+ <GiGuitarBassHead
274
+ color={this.state.bassAudioOn ? `white` : `red`}
275
+ onClick={() => this.setToggleInstrument(Mode.bassist)}
276
+ style={buttonStyle} />
277
+ </Col>
278
+ <Col>
279
+ <GiGuitarHead
280
+ color={this.state.guitarAudioOn ? `white` : `red`}
281
+ onClick={() => this.setToggleInstrument(Mode.guitarist)}
282
+ style={buttonStyle} />
283
+ </Col>
284
+ <Col>
285
+ <GiPianoKeys
286
+ color={this.state.pianoAudioOn ? `white` : `red`}
287
+ onClick={() => this.setToggleInstrument(Mode.keyboardist)}
288
+ style={buttonStyle} />
289
+ </Col>
290
+ </Row>
291
+ <Row>
292
+ <Col>
293
+ <YouTube
294
+ videoId={this.state.audioFile?.youtube_video_id}
295
+ opts={opts}
296
+ onReady={this._onReady.bind(this)}
297
+ onStateChange={this._onStateChange.bind(this)}
298
+ />
299
+ </Col>
300
+ </Row>
301
+ </IconContext.Provider>
302
+ )
303
+ } else {
304
+ return (
305
<Row>
259
- <Col>
260
- {youtubeContent}
306
+ <Col className="mb-3 mt-3">
307
+ <p>Loading player...</p>
308
+ <Spinner animation='grow'></Spinner>
309
</Col>
310
</Row>
263
- </IconContext.Provider>
264
- )
311
+ )
312
+ }
313
}
314
315
_onReady(event: any) {
268
- console.log('YouTube ready: ', event)
316
+ console.log('YouTube ready: ', event);
317
+ this.setState({ isYoutubePlayerReady: true });
318
}
319
320
_onStateChange(event: any) {
321
console.log('State changed: ', event)
273
- }
322
+ console.log('getCurrentTime: ', event.target.getCurrentTime())
323
+ console.log('getPlayerState: ', event.target.getPlayerState())
324
275
- _onPlay() {
276
- this.playAudio()
277
- }
278
- _onPause() {
279
- this.pauseAudio()
325
+ if (event.target.getPlayerState() === 1) { // Play
326
+ const at = event.target.getCurrentTime()
327
+ this.playAudio(at)
328
+ } else if (event.target.getPlayerState() === 2) {// pause
329
+ this.pauseAudio()
330
+ }
331
}
332
}
333
yarn.lock
+27
-2
@@ -2917,6 +2917,14 @@ atob@^2.1.2:
2917
resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9"
2918
integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==
2919
2920
+automation-events@^4.0.12:
2921
+ version "4.0.12"
2922
+ resolved "https://registry.yarnpkg.com/automation-events/-/automation-events-4.0.12.tgz#53a65907187aa3d4b9dc53042b05887c5e643f65"
2923
+ integrity sha512-7tu7q/rw3vFuAwjBoarCGql3V0HkC5QAbZUYhOblxxkHzdLUTjVotm0iaIRwiCSqxMeoFK64LrgaSCuSZguvGA==
2924
+ dependencies:
2925
+ "@babel/runtime" "^7.16.7"
2926
+ tslib "^2.3.1"
2927
+
2928
autoprefixer@^9.6.1:
2929
version "9.8.8"
2930
resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-9.8.8.tgz#fd4bd4595385fa6f06599de749a4d5f7a474957a"
@@ -9709,7 +9717,7 @@ react-rotating-text@^1.4.1:
9717
lodash.toarray "^4.4.0"
9718
prop-types "^15.5.10"
9719
9712
-react-router-dom@^5.3.1:
9720
+react-router-dom@^5.3.0:
9721
version "5.3.0"
9722
resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-5.3.0.tgz#da1bfb535a0e89a712a93b97dd76f47ad1f32363"
9723
integrity sha512-ObVBLjUZsphUUMVycibxgMdh5jJ1e3o+KpAZBVeHcNQZ4W+uUGGWsokurzlF4YOldQYRQL4y6yFRWM4m3svmuQ==
@@ -10736,6 +10744,15 @@ stackframe@^1.1.1:
10744
resolved "https://registry.yarnpkg.com/stackframe/-/stackframe-1.2.0.tgz#52429492d63c62eb989804c11552e3d22e779303"
10745
integrity sha512-GrdeshiRmS1YLMYgzF16olf2jJ/IzxXY9lhKOskuVziubpTYcYqyOwYeJKzQkwy7uN0fYSsbsC4RQaXf9LCrYA==
10746
10747
+standardized-audio-context@^25.1.8:
10748
+ version "25.3.19"
10749
+ resolved "https://registry.yarnpkg.com/standardized-audio-context/-/standardized-audio-context-25.3.19.tgz#bcb5bea0365571f5b3bef2c40860069371a3c2d1"
10750
+ integrity sha512-GOXncphk6PLnT+79lyvVen2hn68zEX5K++qvRQXdZXobYjPriW0sShgFxqg3AMVysZifc/5S+8IM/9mNf0x+sA==
10751
+ dependencies:
10752
+ "@babel/runtime" "^7.16.7"
10753
+ automation-events "^4.0.12"
10754
+ tslib "^2.3.1"
10755
+
10756
static-extend@^0.1.1:
10757
version "0.1.2"
10758
resolved "https://registry.yarnpkg.com/static-extend/-/static-extend-0.1.2.tgz#60809c39cbff55337226fd5e0b520f341f1fb5c6"
@@ -11222,6 +11239,14 @@ toidentifier@1.0.1:
11239
resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35"
11240
integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==
11241
11242
+tone@^14.7.77:
11243
+ version "14.7.77"
11244
+ resolved "https://registry.yarnpkg.com/tone/-/tone-14.7.77.tgz#12a2a9f033952ccdb552275a6384ca5d36d4b5ed"
11245
+ integrity sha512-tCfK73IkLHyzoKUvGq47gyDyxiKLFvKiVCOobynGgBB9Dl0NkxTM2p+eRJXyCYrjJwy9Y0XCMqD3uOYsYt2Fdg==
11246
+ dependencies:
11247
+ standardized-audio-context "^25.1.8"
11248
+ tslib "^2.0.1"
11249
+
11250
tough-cookie@^4.0.0:
11251
version "4.0.0"
11252
resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-4.0.0.tgz#d822234eeca882f991f0f908824ad2622ddbece4"
@@ -11263,7 +11288,7 @@ tslib@^1.8.1:
11288
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00"
11289
integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==
11290
11266
-tslib@^2.0.3, tslib@^2.1.0:
11291
+tslib@^2.0.1, tslib@^2.0.3, tslib@^2.1.0, tslib@^2.3.1:
11292
version "2.3.1"
11293
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.3.1.tgz#e8a335add5ceae51aa261d32a490158ef042ef01"
11294
integrity sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==