WIP
Seto Elkahfi committed
Jan 22, 2022 at 02:29 UTC
5ca492375be74cc12777b76fb812ad9d00b62e69
7 files changed
+351
-26
package.json
+3
@@ -11,14 +11,17 @@
11
"@types/react-router-dom": "^5.3.2",
12
"axios": "^0.25.0",
13
"bootstrap": "5.1.3",
14
+ "dexie": "^3.2.0",
15
"firebase": "^8.6.2",
16
"react": "^16.14.0",
17
"react-bootstrap": "^2.1.1",
18
"react-dom": "^16.14.0",
19
+ "react-icons": "^4.3.1",
20
"react-intl": "^2.9.0",
21
"react-rotating-text": "^1.4.1",
22
"react-router-dom": "^4.3.1",
23
"react-scripts": "^4.0.0",
24
+ "react-youtube": "^7.14.0",
25
"typescript": "^3.9.10"
26
},
27
"scripts": {
src/components/AudioPlayer.tsx
new
+231
@@ -0,0 +1,231 @@
1
+import axios from 'axios';
2
+import React from 'react';
3
+import { Component } from 'react';
4
+import { Container } from 'react-bootstrap';
5
+import YouTube, { Options } from 'react-youtube';
6
+import { GiMicrophone, GiDrumKit, GiGuitarHead, GiGuitarBassHead, GiPianoKeys } from 'react-icons/gi';
7
+import { IconContext } from 'react-icons';
8
+import { db, AudioFiles } from './db';
9
+
10
+interface Result {
11
+ source_file: string,
12
+ filename: string
13
+}
14
+
15
+interface AudioFile {
16
+ youtube_video_id: string,
17
+ results: Result[]
18
+}
19
+
20
+type AudioProps = {
21
+ audioFileId: string
22
+}
23
+
24
+type AudioState = {
25
+ audioFile: AudioFile | null,
26
+ isPlayerReady: boolean,
27
+ isPlaying: boolean
28
+}
29
+
30
+export enum Mode {
31
+ vocalist, bassist, guitarist, drummer, keyboardist
32
+}
33
+
34
+class AudioPlayer extends Component<AudioProps, AudioState> {
35
+
36
+ vocalAudio = new Audio();
37
+ guitarAudio = new Audio();
38
+ bassAudio = new Audio();
39
+ drumsAudio = new Audio();
40
+ pianoAudio = new Audio()
41
+
42
+ constructor(props: AudioProps) {
43
+ super(props);
44
+ this.state = {
45
+ audioFile: null,
46
+ isPlayerReady: false,
47
+ isPlaying: false
48
+ }
49
+ }
50
+
51
+ componentDidMount() {
52
+ axios.get(`/splitfire/${this.props.audioFileId}`)
53
+ .then(res => {
54
+ const audioFile = res.data.audio_file;
55
+ this.setState({ audioFile });
56
+ console.log(this.state);
57
+ this.downloadAudioFilesIfNeeded();
58
+ })
59
+ }
60
+
61
+ downloadAudioFilesIfNeeded() {
62
+ this._prepareAudio(Mode.vocalist);
63
+ this._prepareAudio(Mode.bassist);
64
+ this._prepareAudio(Mode.drummer);
65
+ this._prepareAudio(Mode.guitarist);
66
+ this._prepareAudio(Mode.keyboardist);
67
+ }
68
+
69
+ _downloadFile(type: Mode) {
70
+ console.log('_downloadFile', type);
71
+ let audioFile: Result | undefined;
72
+ switch (type) {
73
+ case Mode.vocalist:
74
+ audioFile = this.state.audioFile?.results.find(obj => { return obj.filename.startsWith('vocals') });
75
+ break;
76
+ case Mode.bassist:
77
+ audioFile = this.state.audioFile?.results.find(obj => { return obj.filename.startsWith('bass') });
78
+ break;
79
+ case Mode.drummer:
80
+ audioFile = this.state.audioFile?.results.find(obj => { return obj.filename.startsWith('drums') });
81
+ break;
82
+ case Mode.guitarist:
83
+ audioFile = this.state.audioFile?.results.find(obj => { return obj.filename.startsWith('other') });
84
+ break;
85
+ case Mode.keyboardist:
86
+ break;
87
+ }
88
+
89
+ const audioFileId = this.props.audioFileId;
90
+ axios({
91
+ url: audioFile?.source_file,
92
+ method: 'GET',
93
+ responseType: 'blob',
94
+ }).then((response) => {
95
+ const file = new Blob([response.data], { type: 'audio/mp3' });
96
+ const item: AudioFiles = {
97
+ audioFileId, type: type, file
98
+ }
99
+ db.audioFiles.add(item);
100
+ console.log('File Downloaded', file);
101
+ this._setAudioSrc(type, file);
102
+ });
103
+ }
104
+
105
+ _prepareAudio(type: Mode) {
106
+ const audioFileId = this.props.audioFileId;
107
+ db.audioFiles
108
+ .get({ audioFileId: audioFileId, type: type })
109
+ .then(record => {
110
+ if (record?.file === undefined) {
111
+ this._downloadFile(type);
112
+ return
113
+ }
114
+ this._setAudioSrc(type, record.file)
115
+ })
116
+ }
117
+
118
+ _setAudioSrc(type: Mode, file: Blob) {
119
+ // Get window.URL object
120
+ var URL = window.URL || window.webkitURL;
121
+ // Create and revoke ObjectURL
122
+ var audioFileURL = URL.createObjectURL(file);
123
+
124
+ switch (type) {
125
+ case Mode.vocalist:
126
+ this.vocalAudio.src = audioFileURL;
127
+ break;
128
+ case Mode.bassist:
129
+ this.bassAudio.src = audioFileURL;
130
+ break;
131
+ case Mode.drummer:
132
+ this.drumsAudio.src = audioFileURL;
133
+ break;
134
+ case Mode.guitarist:
135
+ this.guitarAudio.src = audioFileURL;
136
+ break;
137
+ case Mode.keyboardist:
138
+ this.pianoAudio.src = audioFileURL;
139
+ break;
140
+ }
141
+ }
142
+
143
+ playAudio() {
144
+ this.vocalAudio.play();
145
+ this.guitarAudio.play();
146
+ this.bassAudio.play();
147
+ this.guitarAudio.play();
148
+ this.drumsAudio.play();
149
+ }
150
+
151
+ pauseAudio() {
152
+
153
+ }
154
+
155
+ setMode(mode: Mode) {
156
+ switch (mode) {
157
+ case Mode.vocalist:
158
+ this.vocalAudio.volume = 0;
159
+ break;
160
+ case Mode.bassist:
161
+ this.bassAudio.volume = 0;
162
+ break;
163
+ case Mode.guitarist:
164
+ this.guitarAudio.volume = 0;
165
+ break;
166
+ case Mode.keyboardist:
167
+ this.pianoAudio.volume = 0;
168
+ break;
169
+ case Mode.drummer:
170
+ this.drumsAudio.volume = 0;
171
+ break;
172
+ }
173
+ }
174
+
175
+ render() {
176
+ const opts: Options = {
177
+ playerVars: {
178
+ // https://developers.google.com/youtube/player_parameters
179
+ autoplay: 0,
180
+ mute: 1,
181
+ controls: 0,
182
+ rel: 0,
183
+ showinfo: 0
184
+ },
185
+ };
186
+
187
+ let youtubeContent: any = 'Loading player...';
188
+ if (this.state.audioFile) {
189
+ youtubeContent = <YouTube
190
+ videoId={this.state.audioFile?.youtube_video_id}
191
+ opts={opts}
192
+ onReady={this._onReady.bind(this)}
193
+ onStateChange={this._onStateChange.bind(this)}
194
+ onPlay={this._onPlay.bind(this)}
195
+ onPause={this._onPause.bind(this)}
196
+ />
197
+ }
198
+
199
+ return (
200
+ <IconContext.Provider value={{ size: "2em", color: "white", className: "global-class-name" }}>
201
+ <Container>
202
+ <GiMicrophone onClick={() => this.setMode(Mode.vocalist)} />
203
+ <GiDrumKit onClick={() => this.setMode(Mode.drummer)} />
204
+ <GiGuitarBassHead onClick={() => this.setMode(Mode.bassist)} />
205
+ <GiGuitarHead onClick={() => this.setMode(Mode.guitarist)} />
206
+ <GiPianoKeys onClick={() => this.setMode(Mode.keyboardist)} />
207
+ </Container>
208
+ <Container>
209
+ {youtubeContent}
210
+ </Container>
211
+ </IconContext.Provider>
212
+ )
213
+ }
214
+
215
+ _onReady(event: any) {
216
+ console.log('YouTube ready: ', event)
217
+ }
218
+
219
+ _onStateChange(event: any) {
220
+ console.log('State changed: ', event)
221
+ }
222
+
223
+ _onPlay() {
224
+ this.playAudio()
225
+ }
226
+ _onPause() {
227
+ this.pauseAudio()
228
+ }
229
+}
230
+
231
+export default AudioPlayer;
\ No newline at end of file
src/components/Home.tsx
+33
-21
@@ -4,6 +4,8 @@ import { FormattedMessage } from 'react-intl';
4
import Firebase from './Firebase';
5
import app from 'firebase/app';
6
import axios from 'axios';
7
+import { Button, Carousel } from 'react-bootstrap';
8
+import { Link } from 'react-router-dom';
9
10
const pStyle: CSSProperties = {
11
lineHeight: '32pt',
@@ -66,11 +68,11 @@ class Home extends Component<HomeProps, HomeState> {
68
});
69
});
70
69
- axios.get(`https://musik88.com/api/v1/splitfire`)
71
+ axios.get(`https://localhost:3001/api/v1/splitfire`)
72
.then(res => {
71
- const files = res.data.audio_files;
72
- this.setState({ files });
73
- })
73
+ const files = res.data.audio_files;
74
+ this.setState({ files });
75
+ })
76
}
77
78
componentWillUnmount() {
@@ -97,29 +99,39 @@ class Home extends Component<HomeProps, HomeState> {
99
}
100
101
render() {
100
- const {files} = this.state;
101
-
102
+ const { files } = this.state;
103
+
104
return (
105
<div>
106
<h1>
105
- <FormattedMessage id="home.title"
106
- defaultMessage="Hello, my name is {name}"
107
- description="Welcome message"
108
- values={{ name: 'Seto Elkahfi' }}/>
107
+ <FormattedMessage id="home.title"
108
+ defaultMessage="Hello, my name is {name}"
109
+ description="Welcome message"
110
+ values={{ name: 'Seto Elkahfi' }} />
111
</h1>
112
<p style={this.state.pStyle}>
111
- <FormattedMessage id="home.iam"
112
- defaultMessage="I'm "
113
- description="My self description"/>
114
- <ReactRotatingText style={this.state.wordStyle} items={this.state.alterEgos}/></p>
115
- <ul>
113
+ <FormattedMessage id="home.iam"
114
+ defaultMessage="I'm "
115
+ description="My self description" />
116
+ <ReactRotatingText style={this.state.wordStyle} items={this.state.alterEgos} /></p>
117
+ <h2>SplitFire AI</h2>
118
+ <Carousel>
119
{files.map(item => (
117
- <li key={item.id}>
118
- {item.created_at} {item.price}
119
- </li>
120
- ))}
121
- </ul>
122
- </div>
120
+ <Carousel.Item key={item.id} style={{padding: '20px'}}>
121
+ <img
122
+ className="d-block w-100"
123
+ src={`https://img.youtube.com/vi/${item.youtube_video_id}/default.jpg`}
124
+ alt="First slide"
125
+ />
126
+ <Carousel.Caption>
127
+ <Link to={{pathname: `/splitfire/${item.id}`}}>
128
+ <Button variant="dark">{item.filename}</Button>
129
+ </Link>
130
+ </Carousel.Caption>
131
+ </Carousel.Item>
132
+ ))}
133
+ </Carousel>
134
+ </div>
135
);
136
}
137
}
src/components/Main.tsx
+8
-2
@@ -5,9 +5,9 @@ import About from './About';
5
import Cv from './Cv';
6
import Contact from './Contact';
7
import { FirebaseContext } from './Firebase';
8
+import AudioPlayer from './AudioPlayer';
9
10
const mainStyle = {
10
- minHeight: 500,
11
marginTop: 20,
12
marginBottom: 20,
13
marginLeft: 5,
@@ -16,11 +16,16 @@ const mainStyle = {
16
font: '11pt "Helvetica Neue", "Helvetica", Arial, sans-serif',
17
};
18
19
+const contentStyle = {
20
+ borderRadius: '100px!important',
21
+ backgroundColor: 'rgba(52, 52, 52, 0.5)'
22
+}
23
+
24
const Main = () => (
25
<main style={mainStyle}>
26
<section className="py-5 text-center container">
27
<div className="row py-lg-5">
23
- <div className="col-lg-6 col-md-8 mx-auto">
28
+ <div className="col-lg-6 col-md-8 mx-auto" style={contentStyle}>
29
<Switch>
30
<Route exact path='/'>
31
<FirebaseContext.Consumer>
@@ -43,6 +48,7 @@ const Main = () => (
48
{firebase => <Contact firebase={firebase} />}
49
</FirebaseContext.Consumer>
50
</Route>
51
+ <Route exact path='/splitfire/:audio_id' render={(props) => <AudioPlayer audioFileId={props.match.params.audio_id} /> }/>
52
</Switch>
53
</div>
54
</div>
src/components/db.ts
new
+24
@@ -0,0 +1,24 @@
1
+import Dexie, { Table } from 'dexie';
2
+import { Mode } from './AudioPlayer';
3
+
4
+export interface AudioFiles {
5
+ id?: number;
6
+ audioFileId: string,
7
+ type: Mode,
8
+ file: Blob
9
+}
10
+
11
+export class MySubClassedDexie extends Dexie {
12
+ // 'friends' is added by dexie when declaring the stores()
13
+ // We just tell the typing system this is the case
14
+ audioFiles!: Table<AudioFiles>;
15
+
16
+ constructor() {
17
+ super('myDatabase');
18
+ this.version(2).stores({
19
+ audioFiles: '++id, [audioFileId+type]' // Primary key and indexed props
20
+ });
21
+ }
22
+}
23
+
24
+export const db = new MySubClassedDexie();
\ No newline at end of file
src/index.tsx
+2
@@ -3,7 +3,9 @@ import { render } from 'react-dom';
3
import App from './components/App';
4
import * as serviceWorker from './serviceWorker';
5
import 'bootstrap/dist/css/bootstrap.css';
6
+import axios from 'axios';
7
8
+axios.defaults.baseURL = 'https://localhost:3001/api/v1/';
9
10
render(<App />, document.getElementById('root'));
11
yarn.lock
+50
-3
@@ -4308,7 +4308,7 @@ data-urls@^2.0.0:
4308
whatwg-mimetype "^2.3.0"
4309
whatwg-url "^8.0.0"
4310
4311
-debug@2.6.9, debug@^2.2.0, debug@^2.3.3, debug@^2.6.0, debug@^2.6.9:
4311
+debug@2.6.9, debug@^2.2.0, debug@^2.3.3, debug@^2.6.0, debug@^2.6.6, debug@^2.6.9:
4312
version "2.6.9"
4313
resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
4314
integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==
@@ -4467,6 +4467,11 @@ detect-port-alt@1.1.6:
4467
address "^1.0.1"
4468
debug "^2.6.0"
4469
4470
+dexie@^3.2.0:
4471
+ version "3.2.0"
4472
+ resolved "https://registry.yarnpkg.com/dexie/-/dexie-3.2.0.tgz#a1b0267b111f9422c4126da90d6b121b1deabeab"
4473
+ integrity sha512-OpS8ss1CLHYAhxRu6hT+/Gt1uLhKCf0O18xHBdRGlemOWXXRiiOZ0ty1/bACIJzGt1DGmvarzrPwYYt9EkRZfw==
4474
+
4475
diff-sequences@^24.9.0:
4476
version "24.9.0"
4477
resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-24.9.0.tgz#5715d6244e2aa65f48bba0bc972db0b0b11e95b5"
@@ -5283,7 +5288,7 @@ extglob@^2.0.4:
5288
snapdragon "^0.8.1"
5289
to-regex "^3.0.1"
5290
5286
-fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3:
5291
+fast-deep-equal@3.1.3, fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3:
5292
version "3.1.3"
5293
resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525"
5294
integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==
@@ -7398,6 +7403,11 @@ lines-and-columns@^1.1.6:
7403
resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632"
7404
integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==
7405
7406
+load-script@^1.0.0:
7407
+ version "1.0.0"
7408
+ resolved "https://registry.yarnpkg.com/load-script/-/load-script-1.0.0.tgz#0491939e0bee5643ee494a7e3da3d2bac70c6ca4"
7409
+ integrity sha1-BJGTngvuVkPuSUp+PaPSuscMbKQ=
7410
+
7411
loader-runner@^2.4.0:
7412
version "2.4.0"
7413
resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-2.4.0.tgz#ed47066bfe534d7e84c4c7b9998c2a75607d9357"
@@ -9369,6 +9379,15 @@ prop-types-extra@^1.1.0:
9379
react-is "^16.3.2"
9380
warning "^4.0.0"
9381
9382
+prop-types@15.7.2:
9383
+ version "15.7.2"
9384
+ resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5"
9385
+ integrity sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==
9386
+ dependencies:
9387
+ loose-envify "^1.4.0"
9388
+ object-assign "^4.1.1"
9389
+ react-is "^16.8.1"
9390
+
9391
prop-types@^15.5.10, prop-types@^15.6.1, prop-types@^15.6.2, prop-types@^15.7.2:
9392
version "15.8.1"
9393
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5"
@@ -9626,6 +9645,11 @@ react-error-overlay@^6.0.9:
9645
resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-6.0.10.tgz#0fe26db4fa85d9dbb8624729580e90e7159a59a6"
9646
integrity sha512-mKR90fX7Pm5seCOfz8q9F+66VCc1PGsWSBxKbITjfKVQHMNF2zudxHnMdJiB1fRCb+XsbQV9sO9DCkgsMQgBIA==
9647
9648
+react-icons@^4.3.1:
9649
+ version "4.3.1"
9650
+ resolved "https://registry.yarnpkg.com/react-icons/-/react-icons-4.3.1.tgz#2fa92aebbbc71f43d2db2ed1aed07361124e91ca"
9651
+ integrity sha512-cB10MXLTs3gVuXimblAdI71jrJx8njrJZmNMEMC+sQu5B/BIOmlsAjskdqpn81y8UBVEGuHODd7/ci5DvoSzTQ==
9652
+
9653
react-intl@*:
9654
version "5.24.3"
9655
resolved "https://registry.yarnpkg.com/react-intl/-/react-intl-5.24.3.tgz#e5c929f71603f1aa04404f66205a8f55092ce964"
@@ -9653,7 +9677,7 @@ react-intl@^2.9.0:
9677
intl-relativeformat "^2.1.0"
9678
invariant "^2.1.1"
9679
9656
-react-is@^16.13.1, react-is@^16.3.2, react-is@^16.7.0, react-is@^16.8.4:
9680
+react-is@^16.13.1, react-is@^16.3.2, react-is@^16.7.0, react-is@^16.8.1, react-is@^16.8.4:
9681
version "16.13.1"
9682
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
9683
integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
@@ -9783,6 +9807,15 @@ react-transition-group@^4.4.1:
9807
loose-envify "^1.4.0"
9808
prop-types "^15.6.2"
9809
9810
+react-youtube@^7.14.0:
9811
+ version "7.14.0"
9812
+ resolved "https://registry.yarnpkg.com/react-youtube/-/react-youtube-7.14.0.tgz#0505d86491521ca94ef0afb74af3f7936dc7bc86"
9813
+ integrity sha512-SUHZ4F4pd1EHmQu0CV0KSQvAs5KHOT5cfYaq4WLCcDbU8fBo1ouTXaAOIASWbrz8fHwg+G1evfoSIYpV2AwSAg==
9814
+ dependencies:
9815
+ fast-deep-equal "3.1.3"
9816
+ prop-types "15.7.2"
9817
+ youtube-player "5.5.2"
9818
+
9819
react@^16.14.0:
9820
version "16.14.0"
9821
resolved "https://registry.yarnpkg.com/react/-/react-16.14.0.tgz#94d776ddd0aaa37da3eda8fc5b6b18a4c9a3114d"
@@ -10468,6 +10501,11 @@ simple-swizzle@^0.2.2:
10501
dependencies:
10502
is-arrayish "^0.3.1"
10503
10504
+sister@^3.0.0:
10505
+ version "3.0.2"
10506
+ resolved "https://registry.yarnpkg.com/sister/-/sister-3.0.2.tgz#bb3e39f07b1f75bbe1945f29a27ff1e5a2f26be4"
10507
+ integrity sha512-p19rtTs+NksBRKW9qn0UhZ8/TUI9BPw9lmtHny+Y3TinWlOa9jWh9xB0AtPSdmOy49NJJJSSe0Ey4C7h0TrcYA==
10508
+
10509
sisteransi@^1.0.5:
10510
version "1.0.5"
10511
resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.5.tgz#134d681297756437cc05ca01370d3a7a571075ed"
@@ -12157,3 +12195,12 @@ yocto-queue@^0.1.0:
12195
version "0.1.0"
12196
resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b"
12197
integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==
12198
+
12199
+youtube-player@5.5.2:
12200
+ version "5.5.2"
12201
+ resolved "https://registry.yarnpkg.com/youtube-player/-/youtube-player-5.5.2.tgz#052b86b1eabe21ff331095ffffeae285fa7f7cb5"
12202
+ integrity sha512-ZGtsemSpXnDky2AUYWgxjaopgB+shFHgXVpiJFeNB5nWEugpW1KWYDaHKuLqh2b67r24GtP6HoSW5swvf0fFIQ==
12203
+ dependencies:
12204
+ debug "^2.6.6"
12205
+ load-script "^1.0.0"
12206
+ sister "^3.0.0"