1
+ import React , { PureComponent , Suspense , lazy } from 'react' ;
2
+ import { createBrowserHistory } from 'history'
3
+ import Home from '../pages/Home' ;
4
+ import percent from '../helpers/percent' ;
5
+ import Menu from '../components/Menu' ;
6
+ import Page from '../components/Page' ;
7
+ import Loader from '../components/Loader' ;
8
+ import Audio from '../helpers/audio' ;
9
+ import { initialState } from '../data' ;
10
+ import './style.scss' ;
11
+
12
+ const List = lazy ( ( ) => import ( '../pages/List' ) ) ;
13
+ const About = lazy ( ( ) => import ( '../pages/About' ) ) ;
14
+ const Detail = lazy ( ( ) => import ( '../pages/Detail' ) ) ;
15
+
16
+ class App extends PureComponent {
17
+ constructor ( props ) {
18
+ super ( props ) ;
19
+
20
+ this . playlistUrl = process . env . REACT_APP_API_URL ;
21
+ this . state = {
22
+ ...initialState
23
+ } ;
24
+
25
+ this . history = createBrowserHistory ( ) ;
26
+ }
27
+
28
+ componentDidMount ( ) {
29
+ this . history . listen ( ( history ) => {
30
+ this . setState ( ( ) => {
31
+ return { currentView : history . location . state . view || '/' } ;
32
+ } ) ;
33
+
34
+ if ( history . location . state . view === 'list' && ! this . state . tracks [ 0 ] . id ) {
35
+ this . fetchPlayList ( ) ;
36
+ } ;
37
+ } ) ;
38
+
39
+ this . setupAudio ( ) ;
40
+
41
+ this . history . push ( '/' , { view : 'home' } ) ;
42
+ }
43
+
44
+ onStartClick = ( ) => {
45
+ this . changeView ( 'list' ) ;
46
+ }
47
+
48
+ fetchPlayList = async ( ) => {
49
+ const result = await fetch ( this . playlistUrl ) ;
50
+ const tracks = await result . json ( ) ;
51
+
52
+ this . updateSate ( tracks ) ;
53
+ }
54
+
55
+ updateSate ( tracks ) {
56
+ const updatedState = {
57
+ tracks : [ ...tracks . map ( ( track , index ) => {
58
+ return Object . assign ( { } , {
59
+ ...this . state . track ,
60
+ id : track . id ,
61
+ stream_url : `${ this . playlistUrl } /stream/${ track . id } ` ,
62
+ uri : track . uri ,
63
+ duration : track . duration ,
64
+ favoritings_count : track . favoritings_count ,
65
+ artist : track . user . username ,
66
+ artwork_url : track . artwork_url ? track . artwork_url . replace ( 'large' , 't50x50' ) : '' ,
67
+ title : track . title . toLowerCase ( ) ,
68
+ permalink_url : track . permalink_url ,
69
+ index,
70
+ } ) ;
71
+ } ) ] ,
72
+ playlistLoaded : true ,
73
+ } ;
74
+
75
+ this . setState ( ( ) => updatedState ) ;
76
+ }
77
+
78
+ changeView ( view ) {
79
+ this . history . push ( `/${ view } ` , { view } ) ;
80
+ }
81
+
82
+ setTrack ( track ) {
83
+ this . setState ( ( ) => {
84
+ return {
85
+ track,
86
+ currentTime : 0 ,
87
+ paused : true ,
88
+ played : false ,
89
+ playing : false ,
90
+ changingTrack : true
91
+ } ;
92
+ } ) ;
93
+ }
94
+
95
+ canChangeTrack ( ) {
96
+ return this . state . changingTrack === false ;
97
+ }
98
+
99
+ getNextTrack ( ) {
100
+ const nextTrack = this . state . tracks [ this . state . track . index + 1 ] ;
101
+
102
+ return nextTrack ? { ...nextTrack } : null ;
103
+ }
104
+
105
+ getPreviousTrack ( ) {
106
+ const prevTrack = this . state . tracks [ this . state . track . index - 1 ] ;
107
+
108
+ return prevTrack ? { ...prevTrack } : null ;
109
+ }
110
+
111
+ changeTrack ( track ) {
112
+ this . audio . setAudioSource ( '' ) ;
113
+
114
+ if ( this . canChangeTrack ( ) && track ) {
115
+ this . setTrack ( track ) ;
116
+ this . onPlayClick ( track ) ;
117
+ }
118
+ }
119
+
120
+ selectTrack = ( id ) => {
121
+ return this . state . tracks . filter ( ( track ) => Number ( id ) === track . id ) [ 0 ] ;
122
+ }
123
+
124
+ setupAudio ( ) {
125
+ this . timeupdate = this . timeupdate . bind ( this ) ;
126
+ this . audioStop = this . audioStop . bind ( this ) ;
127
+
128
+ this . audio = new Audio ( document . querySelector ( '#audio' ) , this . props . audioContext ) ;
129
+ this . audio . setup ( ) ;
130
+ this . audio . setTimerHandler ( this . timeupdate ) ;
131
+ this . audio . setStopHandler ( this . audioStop ) ;
132
+ this . audio . canplay ( ( ) => {
133
+ this . setState ( ( ) => {
134
+ return {
135
+ changingTrack : false
136
+ } ;
137
+ } ) ;
138
+ } )
139
+ }
140
+
141
+ audioStop ( ) {
142
+ this . setState ( {
143
+ track : {
144
+ ...this . state . track , currentTime : 0 , percentage : 0 , playing : false ,
145
+ played : false ,
146
+ paused : true ,
147
+ }
148
+ } ) ;
149
+ }
150
+
151
+ timeupdate = ( evt ) => {
152
+ this . setState ( {
153
+ track : {
154
+ ...this . state . track , currentTime : evt . target . currentTime ,
155
+ percentage : percent ( evt . target . currentTime , evt . target . duration ) / 100
156
+ }
157
+ } ) ;
158
+ }
159
+
160
+ onListClick = ( id ) => {
161
+ if ( id !== this . state . track . id ) {
162
+ this . audio . setAudioSource ( '' ) ;
163
+ }
164
+
165
+ const track = {
166
+ ...this . selectTrack ( id ) ,
167
+ currentTime : 0 ,
168
+ percentage : 0 ,
169
+ playing : this . state . track . id === id ? this . state . track . playing : false ,
170
+ played : this . state . track . id === id ? this . state . track . played : false ,
171
+ paused : this . state . track . id === id ? this . state . track . paused : true ,
172
+ } ;
173
+
174
+ this . setState ( ( ) => {
175
+ return { track } ;
176
+ } ) ;
177
+
178
+ this . changeView ( 'detail' ) ;
179
+
180
+ this . onPlayClick ( track ) ;
181
+ }
182
+
183
+ onPlayClick = async ( track ) => {
184
+ if ( ! track . played ) {
185
+ const result = await fetch ( track . stream_url ) ;
186
+ const urlstream = await result . json ( ) ;
187
+
188
+ this . audio . setAudioSource ( urlstream . http_mp3_128_url ) ;
189
+ }
190
+
191
+ this . setState ( ( ) => {
192
+ return {
193
+ track : {
194
+ ...track ,
195
+ paused : false ,
196
+ playing : true ,
197
+ played : true
198
+ }
199
+ } ;
200
+ } ) ;
201
+
202
+ this . audio . resume ( ) ;
203
+ this . audio . play ( ) ;
204
+ }
205
+
206
+ onPauseClick = ( track ) => {
207
+ this . audio . pause ( ) ;
208
+
209
+ this . setState ( ( ) => {
210
+ return {
211
+ track : {
212
+ ...track ,
213
+ paused : true ,
214
+ playing : false
215
+ }
216
+ }
217
+ } ) ;
218
+ }
219
+
220
+ onBackClick = ( ) => {
221
+ this . history . go ( - 1 ) ;
222
+
223
+ this . setState ( ( ) => {
224
+ return { currentView : this . history . location . state . view || '/' } ;
225
+ } ) ;
226
+ }
227
+
228
+ onAboutClick = ( ) => {
229
+ this . changeView ( 'about' ) ;
230
+ }
231
+
232
+ onPlayNext = ( ) => {
233
+ this . changeTrack ( this . getNextTrack ( ) ) ;
234
+ }
235
+
236
+ onPlayPrev = ( ) => {
237
+ this . changeTrack ( this . getPreviousTrack ( ) ) ;
238
+ }
239
+
240
+ onRepeatClick = ( ) => {
241
+ const repeat = ! this . state . repeat ;
242
+
243
+ this . setState ( ( ) => {
244
+ return { repeat } ;
245
+ } ) ;
246
+
247
+ this . audio . repeat ( repeat ) ;
248
+ }
249
+
250
+ render ( ) {
251
+ return (
252
+ < main className = "app" >
253
+ < audio id = "audio" crossOrigin = "anonymous" > </ audio >
254
+ < div className = "shell" >
255
+ < Menu history = { this . history }
256
+ activeView = { this . state . currentView }
257
+ onBackClick = { this . onBackClick }
258
+ onAboutClick = { this . onAboutClick }
259
+ onCloseClick = { this . onBackClick } />
260
+ < div className = "page-wrapper" >
261
+ < Page className = "home" active = { this . state . currentView === 'home' } >
262
+ < Home onStartClick = { this . onStartClick } />
263
+ </ Page >
264
+ < Suspense fallback = { < Loader /> } >
265
+ < Page className = "list" active = { this . state . currentView === 'list' } >
266
+ < List track = { this . state . track } tracks = { this . state . tracks } onClick = { this . onListClick } />
267
+ </ Page >
268
+ < Page className = "detail" active = { this . state . currentView === 'detail' } >
269
+ < Detail track = { this . state . track }
270
+ repeat = { this . state . repeat }
271
+ onRepeatClick = { this . onRepeatClick }
272
+ onPlayClick = { this . onPlayClick }
273
+ onPlayNext = { this . onPlayNext }
274
+ onPlayPrev = { this . onPlayPrev }
275
+ onPauseClick = { this . onPauseClick } />
276
+ </ Page >
277
+ < Page className = "about" active = { this . state . currentView === 'about' } >
278
+ < About />
279
+ </ Page >
280
+ </ Suspense >
281
+ </ div >
282
+ </ div >
283
+ </ main >
284
+ ) ;
285
+ }
286
+ }
287
+
288
+ export default App ;
0 commit comments