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"