11import { create } from 'zustand' ;
22import { initPlayer } from './player' ;
33import { initSettings } from './settings' ;
4- import { initVisualizer , VisualizerSettings , VisualizerType } from './visualizer' ;
54import { initPlaylists } from './playlists' ;
65import { initTracks } from './tracks' ;
76import { initUI } from './ui' ;
8-
9- interface VisualizerInstance {
10- setVisualizerType : ( type : VisualizerType ) => void ;
11- updateSettings : ( settings : Partial < VisualizerSettings > ) => void ;
12- }
7+ import { VisualizerManager } from './visualizerManager' ;
138
149export interface AppState {
1510 currentTrack : string | null ;
1611 isPlaying : boolean ;
1712 theme : string ;
18- visualizer : VisualizerInstance | null ;
1913 setCurrentTrack : ( track : string | null ) => void ;
2014 setIsPlaying : ( playing : boolean ) => void ;
2115 setTheme : ( theme : string ) => void ;
22- setVisualizer : ( visualizer : VisualizerInstance ) => void ;
2316}
2417
2518const useStore = create < AppState > ( ( set ) => ( {
2619 currentTrack : null ,
2720 isPlaying : false ,
2821 theme : 'default' ,
29- visualizer : null ,
3022 setCurrentTrack : ( track ) => set ( { currentTrack : track } ) ,
3123 setIsPlaying : ( playing ) => set ( { isPlaying : playing } ) ,
3224 setTheme : ( theme ) => set ( { theme } ) ,
33- setVisualizer : ( visualizer ) => set ( { visualizer } ) ,
3425} ) ) ;
3526
36- async function initApp ( ) {
37- // Initialize visualizer first
38- const visualizerInstance = initVisualizer ( useStore ) ;
39- if ( visualizerInstance ) {
40- useStore . getState ( ) . setVisualizer ( {
41- setVisualizerType : visualizerInstance . setVisualizerType ,
42- updateSettings : visualizerInstance . updateSettings
43- } ) ;
27+ // Enhanced visualizer wrapper to work with Howl.js
28+ class HowlVisualizerAdapter {
29+ private visualizerManager : any = null ; // VisualizerManager instance
30+ private howlInstance : any = null ;
31+ private audioContext : AudioContext | null = null ;
32+ private analyser : AnalyserNode | null = null ;
33+ private source : MediaElementAudioSourceNode | null = null ;
34+
35+ constructor ( canvasContainer : HTMLElement ) {
36+ // We'll initialize the visualizer manager when we get a Howl instance
37+ this . setupCanvasContainer ( canvasContainer ) ;
4438 }
45-
46- // Initialize other components
39+
40+ private setupCanvasContainer ( container : HTMLElement ) {
41+ // Ensure container has proper styling for visualizer
42+ if ( ! container . style . position ) {
43+ container . style . position = 'relative' ;
44+ }
45+ }
46+
47+ public setHowlInstance ( howl : any ) {
48+ this . howlInstance = howl ;
49+ this . setupVisualizerConnection ( ) ;
50+ }
51+
52+ public setupVisualizerConnection ( ) {
53+ if ( ! this . howlInstance ) return ;
54+
55+ try {
56+ // Get the underlying HTML audio element from Howl
57+ const audioElement = this . getAudioElementFromHowl ( ) ;
58+
59+ if ( audioElement && ! this . visualizerManager ) {
60+ const canvasContainer = document . getElementById ( 'visualizer-container' ) ;
61+ if ( canvasContainer ) {
62+ // Now we can create the VisualizerManager with the audio element
63+ this . visualizerManager = new VisualizerManager ( audioElement , canvasContainer ) ;
64+ console . log ( 'Visualizer manager would be created here with audio element' ) ;
65+
66+ // For now, let's set up Web Audio API connection manually
67+ // this.setupWebAudioConnection(audioElement);
68+ }
69+ }
70+ } catch ( error ) {
71+ console . error ( 'Error setting up visualizer connection:' , error ) ;
72+ }
73+ }
74+
75+ private getAudioElementFromHowl ( ) : HTMLAudioElement | null {
76+ try {
77+ // Access the internal HTML audio element from Howl
78+ if ( this . howlInstance && this . howlInstance . _sounds && this . howlInstance . _sounds [ 0 ] ) {
79+ return this . howlInstance . _sounds [ 0 ] . _node ;
80+ }
81+ } catch ( error ) {
82+ console . error ( 'Error accessing audio element from Howl:' , error ) ;
83+ }
84+ return null ;
85+ }
86+
87+ private setupWebAudioConnection ( audioElement : HTMLAudioElement ) {
88+ try {
89+ // Use Howler's existing audio context if available
90+ this . audioContext = ( window as any ) . Howler ?. ctx || new ( window . AudioContext || ( window as any ) . webkitAudioContext ) ( ) ;
91+
92+ if ( ! this . analyser ) {
93+ if ( ! this . audioContext ) {
94+ console . error ( 'AudioContext is not available' ) ;
95+ return ;
96+ }
97+ this . analyser = this . audioContext . createAnalyser ( ) ;
98+ this . analyser . fftSize = 2048 ;
99+
100+ // Create source from audio element
101+ this . source = this . audioContext . createMediaElementSource ( audioElement ) ;
102+ this . source . connect ( this . analyser ) ;
103+ this . analyser . connect ( this . audioContext . destination ) ;
104+
105+ console . log ( 'Web Audio API connection established for visualizer' ) ;
106+ }
107+ } catch ( error ) {
108+ console . error ( 'Error setting up Web Audio API:' , error ) ;
109+ }
110+ }
111+
112+ public setVisualizerType ( type : string ) {
113+ if ( this . visualizerManager ) {
114+ this . visualizerManager . addVisualizer ( type ) ;
115+ } else {
116+ console . warn ( 'Visualizer manager not initialized yet' ) ;
117+ }
118+ }
119+
120+ public updateSettings ( settings : any ) {
121+ // Handle visualizer settings updates
122+ console . log ( 'Updating visualizer settings:' , settings ) ;
123+ }
124+
125+ public getAnalyserNode ( ) : AnalyserNode | null {
126+ return this . analyser ;
127+ }
128+
129+ public getAudioContext ( ) : AudioContext | null {
130+ return this . audioContext ;
131+ }
132+
133+ public destroy ( ) {
134+ if ( this . visualizerManager ) {
135+ // Clean up visualizer manager
136+ this . visualizerManager = null ;
137+ }
138+
139+ if ( this . source ) {
140+ this . source . disconnect ( ) ;
141+ this . source = null ;
142+ }
143+
144+ if ( this . analyser ) {
145+ this . analyser . disconnect ( ) ;
146+ this . analyser = null ;
147+ }
148+ }
149+ }
150+
151+ async function initApp ( ) {
152+ // Initialize other components first
47153 initUI ( useStore ) ;
48154 initSettings ( useStore ) ;
49155 initPlaylists ( useStore ) ;
50156 initTracks ( useStore ) ;
51157
52- // Initialize player after visualizer
158+ // Initialize player
53159 const playerInstance = initPlayer ( useStore ) ;
54160
55- // Connect player and visualizer if both exist
56- if ( playerInstance && visualizerInstance && typeof playerInstance . setVisualizerInstance === 'function' ) {
57- playerInstance . setVisualizerInstance ( visualizerInstance ) ;
161+ // Initialize visualizer adapter
162+ const visualizerContainer = document . getElementById ( 'visualizer-container' ) ;
163+ if ( visualizerContainer && playerInstance ) {
164+ const visualizerAdapter = new HowlVisualizerAdapter ( visualizerContainer ) ;
165+
166+ // Connect the visualizer adapter to the player
167+ if ( typeof playerInstance . setVisualizerInstance === 'function' ) {
168+ playerInstance . setVisualizerInstance ( visualizerAdapter ) ;
169+ console . log ( 'Visualizer adapter connected to audio player' ) ;
170+ }
171+
172+ // Set up visualizer controls
173+ setupVisualizerControls ( visualizerAdapter ) ;
58174 }
59175
60176 setupAddMusicButton ( ) ;
61177
178+ // Keyboard controls
62179 document . addEventListener ( 'keydown' , ( e ) => {
63- if ( e . code === 'Space' ) {
180+ if ( e . code === 'Space' && e . target === document . body ) {
64181 e . preventDefault ( ) ;
65- const store = useStore . getState ( ) ;
66- store . setIsPlaying ( ! store . isPlaying ) ;
182+ const playPauseBtn = document . getElementById ( 'playPauseBtn' ) ;
183+ if ( playPauseBtn ) {
184+ playPauseBtn . click ( ) ;
185+ }
67186 }
68187 } ) ;
188+
189+ // Handle window resize for visualizer
190+ window . addEventListener ( 'resize' , ( ) => {
191+ // If you have a visualizer manager, call its resize method
192+ // visualizerManager?.resizeCanvas();
193+ } ) ;
194+ }
195+
196+ function setupVisualizerControls ( visualizerAdapter : HowlVisualizerAdapter ) {
197+ // Example: Add buttons to switch visualizer types
198+ const visualizerButtons = document . querySelectorAll ( '[data-visualizer-type]' ) ;
199+
200+ visualizerButtons . forEach ( button => {
201+ button . addEventListener ( 'click' , ( e ) => {
202+ const target = e . target as HTMLElement ;
203+ const type = target . dataset . visualizerType ;
204+ if ( type ) {
205+ visualizerAdapter . setVisualizerType ( type ) ;
206+
207+ // Update button states
208+ visualizerButtons . forEach ( btn => btn . classList . remove ( 'active' ) ) ;
209+ target . classList . add ( 'active' ) ;
210+ }
211+ } ) ;
212+ } ) ;
69213}
70214
71215// Directory picker + file input fallback for Add Music
@@ -78,26 +222,20 @@ function setupAddMusicButton() {
78222 uploadBtn . addEventListener ( 'click' , async ( ) => {
79223 if ( 'showDirectoryPicker' in window ) {
80224 try {
81- // @ts -ignore
82225 const dirHandle = await ( window as any ) . showDirectoryPicker ( ) ;
83- // Dispatch a custom event or call a handler to process the directory
84226 const event = new CustomEvent ( 'music-directory-selected' , { detail : dirHandle } ) ;
85227 window . dispatchEvent ( event ) ;
86228 } catch ( e ) {
87- // User cancelled or error, fallback to file input
88229 fileInput . click ( ) ;
89230 }
90231 } else {
91232 console . warn ( 'Directory picker not supported, falling back to file input' ) ;
92- // Fallback for browsers that don't support directory picker
93- // Ensure file input accepts multiple files and audio types
94233 fileInput . setAttribute ( 'multiple' , 'true' ) ;
95234 fileInput . setAttribute ( 'accept' , 'audio/*' ) ;
96- // Trigger file input click
97235 fileInput . click ( ) ;
98236 }
99237 } ) ;
100238}
101239
102- // Call this in initApp
240+ // Initialize the app
103241initApp ( ) ;
0 commit comments