@@ -6,28 +6,80 @@ import {Buttons, GeneralEvents} from '../Events';
66import { MediaManager } from '../../service/MediaManager' ;
77
88export class Speaker {
9- oninit ( ) {
9+ oninit ( vnode ) {
1010 if ( ! ( Service . activeService ( ) instanceof Service ) || ! Service . activeService ( ) . isSpeaker ) {
1111 m . route . set ( '/Splash' ) ;
1212 }
13+
14+ vnode . state . visualization = { } ;
15+ }
16+
17+ // oncreate(vnode) {
18+ // const canvas = document.getElementById('speaker-visualization-canvas');
19+ // if (canvas) {
20+ // // const analyser = Service.activeService().audioAnalyser;
21+ //
22+ // canvas.width = canvas.offsetWidth * window.devicePixelRatio;
23+ // canvas.height = canvas.offsetHeight * window.devicePixelRatio;
24+ //
25+ // vnode.state.visualization.canvas = canvas;
26+ // vnode.state.visualization.context = canvas.getContext('2d');
27+ // // vnode.state.visualization.analyser = analyser;
28+ // // vnode.state.visualization.bufferLength = analyser.frequencyBinCount;
29+ // // vnode.state.visualization.buffer = new Uint8Array(vnode.state.visualization.bufferLength);
30+ //
31+ // this._renderLoop(vnode);
32+ // }
33+ // }
34+
35+ onupdate ( vnode ) {
36+ const canvas = document . getElementById ( 'speaker-visualization-canvas' ) ;
37+ if ( canvas && vnode . state . visualization . canvas !== canvas ) {
38+ if ( vnode . state . visualization . animation ) {
39+ cancelAnimationFrame ( vnode . state . visualization . animation ) ;
40+ }
41+
42+ const analyser = Service . activeService ( ) . audioAnalyser ;
43+
44+ canvas . width = canvas . offsetWidth * window . devicePixelRatio ;
45+ canvas . height = canvas . offsetHeight * window . devicePixelRatio ;
46+
47+ vnode . state . visualization . canvas = canvas ;
48+ vnode . state . visualization . context = canvas . getContext ( '2d' ) ;
49+ vnode . state . visualization . analyser = analyser ;
50+ vnode . state . visualization . bufferLength = analyser . frequencyBinCount ;
51+ vnode . state . visualization . buffer = new Uint8Array ( vnode . state . visualization . bufferLength ) ;
52+ vnode . state . visualization . sampleRate = Service . activeService ( ) . audioContext . sampleRate ;
53+
54+ this . _renderLoop ( vnode ) ;
55+ }
56+ }
57+
58+ onremove ( vnode ) {
59+ if ( vnode . state . visualization . animation ) {
60+ cancelAnimationFrame ( vnode . state . visualization . animation ) ;
61+ }
62+ vnode . state . visualization = null ;
1363 }
1464
1565 view ( ) {
1666 if ( ! Service . activeService ( ) || ! Service . activeService ( ) . isSpeakerPlaying ) {
17- return m ( '.speaker-message-container' , [
18- m ( '.speaker-message-title' , 'Speaker' ) ,
19- m ( '.speaker-message-content' , 'This device is ready to be used as a speaker, please make sure to adjust the delay and that your device is not muted.' ) ,
20- m ( '.speaker-start-button' , m ( Button , {
21- raised : true ,
22- label : 'START' ,
23- events : {
24- onclick : ( ) => {
25- EventCenter . emit ( GeneralEvents . BUTTON_PRESS , Buttons . SPEAKER_START_STREAMING , this ) ;
26- m . redraw ( ) ;
67+ return [
68+ m ( '.speaker-message-container' , [
69+ m ( '.speaker-message-title' , 'Speaker' ) ,
70+ m ( '.speaker-message-content' , 'This device is ready to be used as a speaker, please make sure to adjust the delay and that your device is not muted.' ) ,
71+ m ( '.speaker-start-button' , m ( Button , {
72+ raised : true ,
73+ label : 'START' ,
74+ events : {
75+ onclick : ( ) => {
76+ EventCenter . emit ( GeneralEvents . BUTTON_PRESS , Buttons . SPEAKER_START_STREAMING , this ) ;
77+ m . redraw ( ) ;
78+ } ,
2779 } ,
28- } ,
29- } ) ) ,
30- ] ) ;
80+ } ) ) ,
81+ ] ) ,
82+ ] ;
3183 }
3284
3385 const currentSong = MediaManager . currentSong ;
@@ -43,9 +95,246 @@ export class Speaker {
4395 currentSong . formattedDuration ? m ( '.song-list-view-duration' , currentSong . formattedDuration ) : null ,
4496 ] ) : m ( '.now-playing-song-info-container' , m ( '.now-playing-list-title' , 'Not Playing' ) ) ;
4597
46- return m ( '.now-playing-song-container' , [
47- // artwork,
48- info ,
49- ] ) ;
98+ return [
99+ m ( '.now-playing-song-container' , [
100+ // artwork,
101+ info ,
102+ ] ) ,
103+ m ( 'canvas' , { class : 'speaker-visualization-canvas' , id : 'speaker-visualization-canvas' } ) ,
104+ ] ;
105+ }
106+
107+ _renderLoop ( vnode ) {
108+ if ( vnode . state . visualization ) {
109+ vnode . state . visualization . animation = requestAnimationFrame ( ( ) => this . _renderLoop ( vnode ) ) ;
110+ }
111+
112+ this . _drawSpeaker ( vnode , vnode . state . visualization . canvas . width ) ;
113+ }
114+
115+ _drawSpeaker ( vnode , size ) {
116+ const visualization = vnode . state . visualization ;
117+ const canvas = visualization . canvas ;
118+ const context = visualization . context ;
119+ const buffer = visualization . buffer ;
120+ const bufferLength = visualization . bufferLength ;
121+ const hzPerBin = ( visualization . sampleRate * 0.5 ) / bufferLength ;
122+ vnode . state . visualization . analyser . getByteFrequencyData ( buffer ) ;
123+
124+ const bassStart = 0 ;
125+ const bassEnd = Math . ceil ( 120 / hzPerBin ) ;
126+
127+ const midStart = bassEnd + 1 ;
128+ const midEnd = Math . ceil ( 600 / hzPerBin ) ;
129+
130+ const highStart = midEnd + 1 ;
131+ const highEnd = bufferLength ; // Math.ceil(200/ hzPerBin);
132+
133+ context . clearRect ( 0 , 0 , canvas . width , canvas . height ) ;
134+
135+ context . beginPath ( ) ;
136+ context . rect ( 0.2 * size , 0.025 * size , 0.6 * size , 0.95 * size ) ;
137+ context . closePath ( ) ;
138+
139+ // cabinet
140+ this . _drawCircle ( context ,
141+ 0.25 * size ,
142+ 0.075 * size ,
143+ 0.01 * size ,
144+ true ) ;
145+ context . closePath ( ) ;
146+ this . _drawCircle ( context ,
147+ 0.75 * size ,
148+ 0.075 * size ,
149+ 0.01 * size ,
150+ true ) ;
151+ context . closePath ( ) ;
152+ this . _drawCircle ( context ,
153+ 0.25 * size ,
154+ 0.925 * size ,
155+ 0.01 * size ,
156+ true ) ;
157+ context . closePath ( ) ;
158+ this . _drawCircle ( context ,
159+ 0.75 * size ,
160+ 0.925 * size ,
161+ 0.01 * size ,
162+ true ) ;
163+ context . closePath ( ) ;
164+
165+ this . _drawCircle ( context ,
166+ 0.675 * size ,
167+ 0.875 * size ,
168+ 0.01 * size ,
169+ true ) ;
170+ context . closePath ( ) ;
171+ this . _drawCircle ( context ,
172+ 0.325 * size ,
173+ 0.525 * size ,
174+ 0.01 * size ,
175+ true ) ;
176+ context . closePath ( ) ;
177+ this . _drawCircle ( context ,
178+ 0.675 * size ,
179+ 0.525 * size ,
180+ 0.01 * size ,
181+ true ) ;
182+ context . closePath ( ) ;
183+ this . _drawCircle ( context ,
184+ 0.325 * size ,
185+ 0.875 * size ,
186+ 0.01 * size ,
187+ true ) ;
188+ context . closePath ( ) ;
189+
190+ this . _drawCircle ( context ,
191+ 0.4 * size ,
192+ 0.1 * size ,
193+ 0.0075 * size ,
194+ true ) ;
195+ context . closePath ( ) ;
196+ this . _drawCircle ( context ,
197+ 0.4 * size ,
198+ 0.3 * size ,
199+ 0.0075 * size ,
200+ true ) ;
201+ context . closePath ( ) ;
202+ this . _drawCircle ( context ,
203+ 0.6 * size ,
204+ 0.1 * size ,
205+ 0.0075 * size ,
206+ true ) ;
207+ context . closePath ( ) ;
208+ this . _drawCircle ( context ,
209+ 0.6 * size ,
210+ 0.3 * size ,
211+ 0.0075 * size ,
212+ true ) ;
213+ context . closePath ( ) ;
214+
215+ this . _drawCircle ( context ,
216+ 0.35 * size ,
217+ 0.4375 * size ,
218+ 0.005 * size ,
219+ true ) ;
220+ context . closePath ( ) ;
221+ this . _drawCircle ( context ,
222+ 0.25 * size ,
223+ 0.3375 * size ,
224+ 0.005 * size ,
225+ true ) ;
226+ context . closePath ( ) ;
227+
228+ this . _drawCircle ( context ,
229+ 0.65 * size ,
230+ 0.4375 * size ,
231+ 0.005 * size ,
232+ true ) ;
233+ context . closePath ( ) ;
234+ this . _drawCircle ( context ,
235+ 0.75 * size ,
236+ 0.3375 * size ,
237+ 0.005 * size ,
238+ true ) ;
239+ context . closePath ( ) ;
240+
241+ // speakers
242+ this . _drawCircle ( context ,
243+ 0.5 * size ,
244+ 0.7 * size ,
245+ 0.2 * size ,
246+ true ) ;
247+ context . closePath ( ) ;
248+ this . _drawCircle ( context ,
249+ 0.5 * size ,
250+ 0.7 * size ,
251+ this . _radiusFromSpectrum ( buffer , bassStart , bassEnd , 0.1625 * size , 0.19 * size ) ,
252+ false ) ;
253+ context . closePath ( ) ;
254+ this . _drawCircle ( context ,
255+ 0.5 * size ,
256+ 0.7 * size ,
257+ this . _radiusFromSpectrum ( buffer , bassStart , bassEnd , 0.05 * size , 0.075 * size ) ,
258+ true ) ;
259+ context . closePath ( ) ;
260+
261+ this . _drawCircle ( context ,
262+ 0.5 * size ,
263+ 0.2 * size ,
264+ 0.1125 * size ,
265+ true ) ;
266+ context . closePath ( ) ;
267+ this . _drawCircle ( context ,
268+ 0.5 * size ,
269+ 0.2 * size ,
270+ this . _radiusFromSpectrum ( buffer , midStart , midEnd , 0.0875 * size , 0.1025 * size ) ,
271+ false ) ;
272+ context . closePath ( ) ;
273+ this . _drawCircle ( context ,
274+ 0.5 * size ,
275+ 0.2 * size ,
276+ this . _radiusFromSpectrum ( buffer , midStart , midEnd , 0.025 * size , 0.05 * size ) ,
277+ true ) ;
278+ context . closePath ( ) ;
279+
280+ this . _drawCircle ( context ,
281+ 0.3 * size ,
282+ 0.3875 * size ,
283+ 0.05 * size ,
284+ true ) ;
285+ context . closePath ( ) ;
286+ this . _drawCircle ( context ,
287+ 0.3 * size ,
288+ 0.3875 * size ,
289+ this . _radiusFromSpectrum ( buffer , highStart , highEnd , 0.03 * size , 0.0475 * size , this . _easeInQuad ) ,
290+ false ) ;
291+ context . closePath ( ) ;
292+
293+ this . _drawCircle ( context ,
294+ 0.7 * size ,
295+ 0.3875 * size ,
296+ 0.05 * size ,
297+ true ) ;
298+ context . closePath ( ) ;
299+ this . _drawCircle ( context ,
300+ 0.7 * size ,
301+ 0.3875 * size ,
302+ this . _radiusFromSpectrum ( buffer , highStart , highEnd , 0.03 * size , 0.0475 * size , this . _easeInQuad ) ,
303+ true ) ;
304+ context . closePath ( ) ;
305+
306+ context . fillStyle = 'rgba(0, 0, 0, 0.7)' ;
307+ context . fill ( ) ;
308+ }
309+
310+ _drawCircle ( context , x , y , radius , anticlockwise = false ) {
311+ context . arc ( x , y , radius , 0 , 2 * Math . PI , anticlockwise ) ;
312+ }
313+
314+ _radiusFromSpectrum ( spectrum , start , end , min , max , easeFunc = this . _easeIn ) {
315+ let value = 0 ;
316+ let count = 0 ;
317+
318+ for ( let i = start ; i < end ; ++ i ) {
319+ if ( spectrum [ i ] !== - Infinity && spectrum [ i ] > 32 ) {
320+ count += 1 ;
321+ value += spectrum [ i ] ;
322+ }
323+ }
324+
325+ if ( count ) {
326+ value /= count ;
327+ value = easeFunc ( value / 255 ) ;
328+ }
329+
330+ return min + ( ( max - min ) * value ) ;
331+ }
332+
333+ _easeIn ( value ) {
334+ return Math . pow ( value , 5 ) ;
335+ }
336+
337+ _easeInQuad ( value ) {
338+ return value * value ;
50339 }
51340}
0 commit comments