react-native-mp3 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/android/build.gradle +111 -0
  2. package/android/src/main/AndroidManifest.xml +44 -0
  3. package/android/src/main/java/com/reactnativemp3/Mp3Package.kt +23 -0
  4. package/android/src/main/java/com/reactnativemp3/Mp3TurboModule.kt +43 -0
  5. package/android/src/main/java/com/reactnativemp3/database/MusicDatabase.kt +48 -0
  6. package/android/src/main/java/com/reactnativemp3/database/dao/SongDao.kt +72 -0
  7. package/android/src/main/java/com/reactnativemp3/database/entities/PlaylistEntity.kt +58 -0
  8. package/android/src/main/java/com/reactnativemp3/database/entities/SongEntity.kt +104 -0
  9. package/android/src/main/java/com/reactnativemp3/database/entities/ThumbnailCacheEntity.kt +43 -0
  10. package/android/src/main/java/com/reactnativemp3/managers/CacheManager.kt +0 -0
  11. package/android/src/main/java/com/reactnativemp3/managers/EqualizerManager.kt +0 -0
  12. package/android/src/main/java/com/reactnativemp3/modules/MetadataModule.kt +330 -0
  13. package/android/src/main/java/com/reactnativemp3/modules/NotificationModule.kt +236 -0
  14. package/android/src/main/java/com/reactnativemp3/modules/PlayerModule.kt +710 -0
  15. package/android/src/main/java/com/reactnativemp3/modules/ScannerModule.kt +640 -0
  16. package/android/src/main/java/com/reactnativemp3/services/AudioFocusService.kt +0 -0
  17. package/android/src/main/java/com/reactnativemp3/services/FileObserverService.kt +0 -0
  18. package/android/src/main/java/com/reactnativemp3/services/MusicService.kt +309 -0
  19. package/android/src/main/java/com/reactnativemp3/utils/MediaStoreUtils.kt +0 -0
  20. package/android/src/main/java/com/reactnativemp3/utils/PermissionUtils.kt +0 -0
  21. package/android/src/main/jni/Mp3TurboModule.cpp +29 -0
  22. package/android/src/main/res/drawable/ic_music_note.xml +11 -0
  23. package/android/src/main/res/drawable/ic_pause.xml +11 -0
  24. package/android/src/main/res/drawable/ic_play.xml +11 -0
  25. package/android/src/main/res/drawable/ic_skip_next.xml +11 -0
  26. package/android/src/main/res/drawable/ic_skip_previous.xml +11 -0
  27. package/android/src/main/res/drawable/ic_stop.xml +11 -0
  28. package/lib/components/MusicList.d.ts +0 -0
  29. package/lib/components/MusicList.js +1 -0
  30. package/lib/components/MusicPlayerUI.d.ts +0 -0
  31. package/lib/components/MusicPlayerUI.js +1 -0
  32. package/lib/hooks/useMusicPlayer.d.ts +38 -0
  33. package/lib/hooks/useMusicPlayer.js +242 -0
  34. package/lib/hooks/useMusicScanner.d.ts +27 -0
  35. package/lib/hooks/useMusicScanner.js +217 -0
  36. package/lib/hooks/usePermissions.d.ts +9 -0
  37. package/lib/hooks/usePermissions.js +55 -0
  38. package/lib/index.d.ts +144 -0
  39. package/lib/index.js +148 -0
  40. package/lib/types/common.types.d.ts +79 -0
  41. package/lib/types/common.types.js +2 -0
  42. package/lib/types/index.d.ts +3 -0
  43. package/lib/types/index.js +2 -0
  44. package/lib/types/player.types.d.ts +35 -0
  45. package/lib/types/player.types.js +2 -0
  46. package/lib/types/scanner.types.d.ts +29 -0
  47. package/lib/types/scanner.types.js +2 -0
  48. package/lib/utils/constants.d.ts +31 -0
  49. package/lib/utils/constants.js +55 -0
  50. package/lib/utils/events.d.ts +0 -0
  51. package/lib/utils/events.js +1 -0
  52. package/package.json +62 -0
  53. package/src/components/MusicList.tsx +0 -0
  54. package/src/components/MusicPlayerUI.tsx +0 -0
  55. package/src/hooks/useMusicPlayer.ts +358 -0
  56. package/src/hooks/useMusicScanner.ts +286 -0
  57. package/src/hooks/usePermissions.ts +64 -0
  58. package/src/index.ts +214 -0
  59. package/src/types/common.types.ts +86 -0
  60. package/src/types/index.ts +4 -0
  61. package/src/types/player.types.ts +37 -0
  62. package/src/types/scanner.types.ts +31 -0
  63. package/src/utils/constants.ts +56 -0
  64. package/src/utils/events.ts +0 -0
@@ -0,0 +1,358 @@
1
+ import { useState, useEffect, useCallback } from 'react';
2
+ import { Player, PlayerEvents } from '../index';
3
+ import type { Song, PlaybackState, PlayerOptions } from '../types/common.types';
4
+
5
+ interface UseMusicPlayerProps {
6
+ autoSetup?: boolean;
7
+ playerOptions?: PlayerOptions;
8
+ }
9
+
10
+ interface UseMusicPlayerReturn {
11
+ // State
12
+ playbackState: PlaybackState;
13
+ currentSong: Song | null;
14
+ queue: Song[];
15
+ isPlaying: boolean;
16
+ isLoading: boolean;
17
+ error: string | null;
18
+
19
+ // Playback Controls
20
+ play: (uri: string) => Promise<void>;
21
+ playSong: (song: Song) => Promise<void>;
22
+ playFromQueue: (index: number) => void;
23
+ pause: () => void;
24
+ stop: () => void;
25
+ seekTo: (position: number) => void;
26
+ skipToNext: () => void;
27
+ skipToPrevious: () => void;
28
+ setVolume: (volume: number) => void;
29
+ setPlaybackRate: (rate: number) => void;
30
+
31
+ // Queue Management
32
+ setQueue: (songs: Song[]) => void;
33
+ addToQueue: (songs: Song[]) => void;
34
+ clearQueue: () => void;
35
+ getQueue: () => Promise<Song[]>;
36
+
37
+ // Playback Modes
38
+ setRepeatMode: (mode: 'none' | 'one' | 'all') => void;
39
+ setShuffleMode: (enabled: boolean) => void;
40
+
41
+ // Equalizer
42
+ setEqualizerPreset: (preset: string) => void;
43
+ setEqualizerBands: (bands: number[]) => void;
44
+ enableEqualizer: (enabled: boolean) => void;
45
+
46
+ // Background Playback
47
+ setupBackgroundPlayback: (config?: any) => void;
48
+ stopBackgroundPlayback: () => void;
49
+
50
+ // Sleep Timer
51
+ setSleepTimer: (minutes: number) => void;
52
+ cancelSleepTimer: () => void;
53
+ }
54
+
55
+ const defaultPlaybackState: PlaybackState = {
56
+ isPlaying: false,
57
+ currentTime: 0,
58
+ duration: 0,
59
+ buffered: 0,
60
+ playbackRate: 1.0,
61
+ volume: 1.0,
62
+ repeatMode: 'none',
63
+ shuffleMode: false,
64
+ currentSong: undefined,
65
+ queue: [],
66
+ queuePosition: -1,
67
+ error: undefined,
68
+ };
69
+
70
+ export const useMusicPlayer = ({
71
+ autoSetup = true,
72
+ playerOptions,
73
+ }: UseMusicPlayerProps = {}): UseMusicPlayerReturn => {
74
+ const [playbackState, setPlaybackState] = useState<PlaybackState>(defaultPlaybackState);
75
+ const [currentSong, setCurrentSong] = useState<Song | null>(null);
76
+ const [queue, setQueue] = useState<Song[]>([]);
77
+ const [isPlaying, setIsPlaying] = useState<boolean>(false);
78
+ const [isLoading, setIsLoading] = useState<boolean>(false);
79
+ const [error, setError] = useState<string | null>(null);
80
+
81
+ // Initialize player
82
+ const initializePlayer = useCallback(async () => {
83
+ try {
84
+ setIsLoading(true);
85
+ setError(null);
86
+
87
+ // Setup background playback if options specify
88
+ if (playerOptions?.audioFocus !== false) {
89
+ Player.setupBackgroundPlayback({
90
+ keepAwake: playerOptions?.keepAwake ?? true,
91
+ ducking: playerOptions?.ducking ?? true,
92
+ });
93
+ }
94
+
95
+ // Get initial state
96
+ const state = await Player.getPlaybackState();
97
+ updateStateFromNative(state);
98
+
99
+ } catch (err: any) {
100
+ setError(err.message || 'Failed to initialize player');
101
+ console.error('Error initializing player:', err);
102
+ } finally {
103
+ setIsLoading(false);
104
+ }
105
+ }, [playerOptions]);
106
+
107
+ // Update state from native response
108
+ const updateStateFromNative = useCallback((state: any) => {
109
+ setPlaybackState({
110
+ isPlaying: state.isPlaying,
111
+ currentTime: state.currentTime,
112
+ duration: state.duration,
113
+ playbackRate: state.playbackRate,
114
+ volume: state.volume,
115
+ repeatMode: state.repeatMode,
116
+ shuffleMode: state.shuffleMode,
117
+ currentSong: state.currentSong,
118
+ queue: state.queue || [],
119
+ queuePosition: state.queuePosition,
120
+ });
121
+
122
+ setIsPlaying(state.isPlaying);
123
+ setCurrentSong(state.currentSong || null);
124
+ setQueue(state.queue || []);
125
+ }, []);
126
+
127
+ // Play a song by URI
128
+ const play = useCallback(async (uri: string) => {
129
+ try {
130
+ setIsLoading(true);
131
+ setError(null);
132
+ await Player.play(uri);
133
+ } catch (err: any) {
134
+ setError(err.message || 'Failed to play song');
135
+ console.error('Error playing song:', err);
136
+ throw err;
137
+ } finally {
138
+ setIsLoading(false);
139
+ }
140
+ }, []);
141
+
142
+ // Play a song object
143
+ const playSong = useCallback(async (song: Song) => {
144
+ await play(song.uri);
145
+ }, [play]);
146
+
147
+ // Play from queue
148
+ const playFromQueue = useCallback((index: number) => {
149
+ Player.playFromQueue(index);
150
+ }, []);
151
+
152
+ // Pause playback
153
+ const pause = useCallback(() => {
154
+ Player.pause();
155
+ }, []);
156
+
157
+ // Stop playback
158
+ const stop = useCallback(() => {
159
+ Player.stop();
160
+ }, []);
161
+
162
+ // Seek to position
163
+ const seekTo = useCallback((position: number) => {
164
+ Player.seekTo(position);
165
+ }, []);
166
+
167
+ // Skip to next
168
+ const skipToNext = useCallback(() => {
169
+ Player.skipToNext();
170
+ }, []);
171
+
172
+ // Skip to previous
173
+ const skipToPrevious = useCallback(() => {
174
+ Player.skipToPrevious();
175
+ }, []);
176
+
177
+ // Set volume
178
+ const setVolume = useCallback((volume: number) => {
179
+ Player.setVolume(volume);
180
+ }, []);
181
+
182
+ // Set playback rate
183
+ const setPlaybackRate = useCallback((rate: number) => {
184
+ Player.setPlaybackRate(rate);
185
+ }, []);
186
+
187
+ // Set queue
188
+ const setQueueCallback = useCallback((songs: Song[]) => {
189
+ Player.setQueue(songs);
190
+ }, []);
191
+
192
+ // Add to queue
193
+ const addToQueue = useCallback((songs: Song[]) => {
194
+ Player.addToQueue(songs);
195
+ }, []);
196
+
197
+ // Clear queue
198
+ const clearQueue = useCallback(() => {
199
+ Player.clearQueue();
200
+ }, []);
201
+
202
+ // Get queue
203
+ const getQueue = useCallback(async (): Promise<Song[]> => {
204
+ return await Player.getQueue();
205
+ }, []);
206
+
207
+ // Set repeat mode
208
+ const setRepeatMode = useCallback((mode: 'none' | 'one' | 'all') => {
209
+ Player.setRepeatMode(mode);
210
+ }, []);
211
+
212
+ // Set shuffle mode
213
+ const setShuffleMode = useCallback((enabled: boolean) => {
214
+ Player.setShuffleMode(enabled);
215
+ }, []);
216
+
217
+ // Equalizer controls
218
+ const setEqualizerPreset = useCallback((preset: string) => {
219
+ Player.setEqualizerPreset(preset);
220
+ }, []);
221
+
222
+ const setEqualizerBands = useCallback((bands: number[]) => {
223
+ Player.setEqualizerBands(bands);
224
+ }, []);
225
+
226
+ const enableEqualizer = useCallback((enabled: boolean) => {
227
+ Player.enableEqualizer(enabled);
228
+ }, []);
229
+
230
+ // Background playback
231
+ const setupBackgroundPlayback = useCallback((config?: any) => {
232
+ Player.setupBackgroundPlayback(config);
233
+ }, []);
234
+
235
+ const stopBackgroundPlayback = useCallback(() => {
236
+ Player.stopBackgroundPlayback();
237
+ }, []);
238
+
239
+ // Sleep timer
240
+ const setSleepTimer = useCallback((minutes: number) => {
241
+ Player.setSleepTimer(minutes);
242
+ }, []);
243
+
244
+ const cancelSleepTimer = useCallback(() => {
245
+ Player.cancelSleepTimer();
246
+ }, []);
247
+
248
+ // Set up event listeners
249
+ useEffect(() => {
250
+ if (!autoSetup) return;
251
+
252
+ const playbackStateSub = Player.addEventListener(
253
+ PlayerEvents.PLAYBACK_STATE_CHANGED,
254
+ (data: any) => {
255
+ setPlaybackState(prev => ({
256
+ ...prev,
257
+ isPlaying: data.isPlaying,
258
+ currentTime: data.currentTime,
259
+ duration: data.duration,
260
+ volume: data.volume,
261
+ playbackRate: data.playbackRate,
262
+ }));
263
+ setIsPlaying(data.isPlaying);
264
+ }
265
+ );
266
+
267
+ const queueChangedSub = Player.addEventListener(
268
+ PlayerEvents.PLAYBACK_QUEUE_CHANGED,
269
+ (data: any) => {
270
+ setQueue(data.queue || []);
271
+ setPlaybackState(prev => ({
272
+ ...prev,
273
+ queue: data.queue || [],
274
+ queuePosition: data.position,
275
+ }));
276
+ }
277
+ );
278
+
279
+ const playbackErrorSub = Player.addEventListener(
280
+ PlayerEvents.PLAYBACK_ERROR,
281
+ (data: any) => {
282
+ setError(data.error || 'Playback error');
283
+ setPlaybackState(prev => ({
284
+ ...prev,
285
+ error: data.error,
286
+ }));
287
+ }
288
+ );
289
+
290
+ const playbackCompleteSub = Player.addEventListener(
291
+ PlayerEvents.PLAYBACK_COMPLETE,
292
+ () => {
293
+ // Auto-play next is handled by native module
294
+ }
295
+ );
296
+
297
+ // Initialize player
298
+ initializePlayer();
299
+
300
+ return () => {
301
+ // Remove specific listeners
302
+ playbackStateSub.remove();
303
+ queueChangedSub.remove();
304
+ playbackErrorSub.remove();
305
+ playbackCompleteSub.remove();
306
+
307
+ // Clean up background playback if it was set up
308
+ if (autoSetup && playerOptions?.audioFocus !== false) {
309
+ Player.stopBackgroundPlayback();
310
+ }
311
+ };
312
+ }, [autoSetup, initializePlayer, playerOptions]);
313
+
314
+ return {
315
+ // State
316
+ playbackState,
317
+ currentSong,
318
+ queue,
319
+ isPlaying,
320
+ isLoading,
321
+ error,
322
+
323
+ // Playback Controls
324
+ play,
325
+ playSong,
326
+ playFromQueue,
327
+ pause,
328
+ stop,
329
+ seekTo,
330
+ skipToNext,
331
+ skipToPrevious,
332
+ setVolume,
333
+ setPlaybackRate,
334
+
335
+ // Queue Management
336
+ setQueue: setQueueCallback,
337
+ addToQueue,
338
+ clearQueue,
339
+ getQueue,
340
+
341
+ // Playback Modes
342
+ setRepeatMode,
343
+ setShuffleMode,
344
+
345
+ // Equalizer
346
+ setEqualizerPreset,
347
+ setEqualizerBands,
348
+ enableEqualizer,
349
+
350
+ // Background Playback
351
+ setupBackgroundPlayback,
352
+ stopBackgroundPlayback,
353
+
354
+ // Sleep Timer
355
+ setSleepTimer,
356
+ cancelSleepTimer,
357
+ };
358
+ };
@@ -0,0 +1,286 @@
1
+ import { useState, useEffect, useCallback } from 'react';
2
+ import { Scanner, ScannerEvents } from '../index';
3
+ import type { Song, Album, Artist, ScannerOptions } from '../types/common.types';
4
+
5
+ interface UseMusicScannerProps {
6
+ autoScan?: boolean;
7
+ scanOptions?: ScannerOptions;
8
+ }
9
+
10
+ interface UseMusicScannerReturn {
11
+ // State
12
+ songs: Song[];
13
+ albums: Album[];
14
+ artists: Artist[];
15
+ isLoading: boolean;
16
+ isScanning: boolean;
17
+ scanProgress: number;
18
+ permissionGranted: boolean;
19
+ error: string | null;
20
+
21
+ // Methods
22
+ scanMusic: (options?: ScannerOptions) => Promise<void>;
23
+ refresh: () => Promise<void>;
24
+ search: (query: string) => Promise<Song[]>;
25
+ deleteSong: (songId: string) => Promise<boolean>;
26
+ addToFavorites: (songId: string) => Promise<void>;
27
+ removeFromFavorites: (songId: string) => Promise<void>;
28
+ requestPermission: () => Promise<boolean>;
29
+ checkPermission: () => Promise<boolean>;
30
+
31
+ // Event handlers
32
+ onScanProgress?: (progress: number) => void;
33
+ onScanComplete?: (totalSongs: number) => void;
34
+ }
35
+
36
+ export const useMusicScanner = ({
37
+ autoScan = true,
38
+ scanOptions,
39
+ }: UseMusicScannerProps = {}): UseMusicScannerReturn => {
40
+ const [songs, setSongs] = useState<Song[]>([]);
41
+ const [albums, setAlbums] = useState<Album[]>([]);
42
+ const [artists, setArtists] = useState<Artist[]>([]);
43
+ const [isLoading, setIsLoading] = useState<boolean>(false);
44
+ const [isScanning, setIsScanning] = useState<boolean>(false);
45
+ const [scanProgress, setScanProgress] = useState<number>(0);
46
+ const [permissionGranted, setPermissionGranted] = useState<boolean>(false);
47
+ const [error, setError] = useState<string | null>(null);
48
+
49
+ // Load initial data
50
+ const loadInitialData = useCallback(async () => {
51
+ try {
52
+ setIsLoading(true);
53
+ setError(null);
54
+
55
+ const hasPermission = await Scanner.checkPermissionStatus();
56
+ setPermissionGranted(hasPermission);
57
+
58
+ if (hasPermission) {
59
+ const cachedSongs = await Scanner.getCachedSongs();
60
+ setSongs(cachedSongs);
61
+
62
+ const [albumsList, artistsList] = await Promise.all([
63
+ Scanner.getAlbums(),
64
+ Scanner.getArtists(),
65
+ ]);
66
+
67
+ setAlbums(albumsList);
68
+ setArtists(artistsList);
69
+ }
70
+ } catch (err: any) {
71
+ setError(err.message || 'Failed to load music library');
72
+ console.error('Error loading music library:', err);
73
+ } finally {
74
+ setIsLoading(false);
75
+ }
76
+ }, []);
77
+
78
+ // Scan music files
79
+ const scanMusic = useCallback(async (options?: ScannerOptions) => {
80
+ try {
81
+ setIsScanning(true);
82
+ setScanProgress(0);
83
+ setError(null);
84
+
85
+ const hasPermission = await Scanner.checkPermissionStatus();
86
+ if (!hasPermission) {
87
+ const granted = await Scanner.requestStoragePermission();
88
+ if (!granted) {
89
+ setError('Storage permission is required to scan music files');
90
+ setIsScanning(false);
91
+ return;
92
+ }
93
+ setPermissionGranted(true);
94
+ }
95
+
96
+ const scanOpts = options || scanOptions || {};
97
+ const result = await Scanner.scanMusicFiles(scanOpts);
98
+
99
+ // Reload data after scan
100
+ await loadInitialData();
101
+
102
+ return result;
103
+ } catch (err: any) {
104
+ setError(err.message || 'Failed to scan music files');
105
+ console.error('Error scanning music files:', err);
106
+ throw err;
107
+ } finally {
108
+ setIsScanning(false);
109
+ setScanProgress(0);
110
+ }
111
+ }, [scanOptions, loadInitialData]);
112
+
113
+ // Refresh data
114
+ const refresh = useCallback(async () => {
115
+ await loadInitialData();
116
+ }, [loadInitialData]);
117
+
118
+ // Search songs
119
+ const search = useCallback(async (query: string): Promise<Song[]> => {
120
+ if (!query.trim()) {
121
+ return songs;
122
+ }
123
+
124
+ try {
125
+ const results = await Scanner.searchSongs(query);
126
+ return results;
127
+ } catch (err: any) {
128
+ setError(err.message || 'Search failed');
129
+ console.error('Error searching songs:', err);
130
+ return [];
131
+ }
132
+ }, [songs]);
133
+
134
+ // Delete song
135
+ const deleteSong = useCallback(async (songId: string): Promise<boolean> => {
136
+ try {
137
+ const success = await Scanner.deleteSong(songId);
138
+ if (success) {
139
+ // Remove from local state
140
+ setSongs(prev => prev.filter(song => song.id !== songId));
141
+ // Update albums and artists
142
+ await refresh();
143
+ }
144
+ return success;
145
+ } catch (err: any) {
146
+ setError(err.message || 'Failed to delete song');
147
+ console.error('Error deleting song:', err);
148
+ return false;
149
+ }
150
+ }, [refresh]);
151
+
152
+ // Add to favorites
153
+ const addToFavorites = useCallback(async (songId: string) => {
154
+ try {
155
+ await Scanner.addToFavorites(songId);
156
+ // Update local state
157
+ setSongs(prev => prev.map(song =>
158
+ song.id === songId ? { ...song, isFavorite: true } : song
159
+ ));
160
+ } catch (err: any) {
161
+ setError(err.message || 'Failed to add to favorites');
162
+ console.error('Error adding to favorites:', err);
163
+ }
164
+ }, []);
165
+
166
+ // Remove from favorites
167
+ const removeFromFavorites = useCallback(async (songId: string) => {
168
+ try {
169
+ await Scanner.removeFromFavorites(songId);
170
+ // Update local state
171
+ setSongs(prev => prev.map(song =>
172
+ song.id === songId ? { ...song, isFavorite: false } : song
173
+ ));
174
+ } catch (err: any) {
175
+ setError(err.message || 'Failed to remove from favorites');
176
+ console.error('Error removing from favorites:', err);
177
+ }
178
+ }, []);
179
+
180
+ // Request permission
181
+ const requestPermission = useCallback(async (): Promise<boolean> => {
182
+ try {
183
+ const granted = await Scanner.requestStoragePermission();
184
+ setPermissionGranted(granted);
185
+ return granted;
186
+ } catch (err: any) {
187
+ setError(err.message || 'Failed to request permission');
188
+ console.error('Error requesting permission:', err);
189
+ return false;
190
+ }
191
+ }, []);
192
+
193
+ // Check permission
194
+ const checkPermission = useCallback(async (): Promise<boolean> => {
195
+ try {
196
+ const granted = await Scanner.checkPermissionStatus();
197
+ setPermissionGranted(granted);
198
+ return granted;
199
+ } catch (err: any) {
200
+ setError(err.message || 'Failed to check permission');
201
+ console.error('Error checking permission:', err);
202
+ return false;
203
+ }
204
+ }, []);
205
+
206
+ // Set up event listeners
207
+ useEffect(() => {
208
+ const scanProgressSub = Scanner.addEventListener(
209
+ ScannerEvents.SCAN_PROGRESS,
210
+ (data: any) => {
211
+ setScanProgress(data.percentage);
212
+ }
213
+ );
214
+
215
+ const scanCompleteSub = Scanner.addEventListener(
216
+ ScannerEvents.SCAN_COMPLETE,
217
+ () => {
218
+ loadInitialData();
219
+ }
220
+ );
221
+
222
+ const fileAddedSub = Scanner.addEventListener(
223
+ ScannerEvents.FILE_ADDED,
224
+ () => {
225
+ loadInitialData();
226
+ }
227
+ );
228
+
229
+ const fileDeletedSub = Scanner.addEventListener(
230
+ ScannerEvents.FILE_DELETED,
231
+ () => {
232
+ loadInitialData();
233
+ }
234
+ );
235
+
236
+ const permissionSub = Scanner.addEventListener(
237
+ ScannerEvents.PERMISSION_STATUS,
238
+ (data: any) => {
239
+ setPermissionGranted(data.granted);
240
+ }
241
+ );
242
+
243
+ // Auto scan on mount if enabled
244
+ if (autoScan && permissionGranted) {
245
+ loadInitialData();
246
+ }
247
+
248
+ return () => {
249
+ // Remove specific listeners instead of all
250
+ scanProgressSub.remove();
251
+ scanCompleteSub.remove();
252
+ fileAddedSub.remove();
253
+ fileDeletedSub.remove();
254
+ permissionSub.remove();
255
+
256
+ // If you want to remove all listeners:
257
+ // Scanner.removeAllListeners(ScannerEvents.SCAN_PROGRESS);
258
+ // Scanner.removeAllListeners(ScannerEvents.SCAN_COMPLETE);
259
+ // Scanner.removeAllListeners(ScannerEvents.FILE_ADDED);
260
+ // Scanner.removeAllListeners(ScannerEvents.FILE_DELETED);
261
+ // Scanner.removeAllListeners(ScannerEvents.PERMISSION_STATUS);
262
+ };
263
+ }, [autoScan, loadInitialData, permissionGranted]);
264
+
265
+ return {
266
+ // State
267
+ songs,
268
+ albums,
269
+ artists,
270
+ isLoading,
271
+ isScanning,
272
+ scanProgress,
273
+ permissionGranted,
274
+ error,
275
+
276
+ // Methods
277
+ scanMusic,
278
+ refresh,
279
+ search,
280
+ deleteSong,
281
+ addToFavorites,
282
+ removeFromFavorites,
283
+ requestPermission,
284
+ checkPermission,
285
+ };
286
+ };
@@ -0,0 +1,64 @@
1
+ import { useState, useEffect } from 'react';
2
+ import { Scanner } from '../index';
3
+
4
+ interface UsePermissionsReturn {
5
+ permissionGranted: boolean;
6
+ isChecking: boolean;
7
+ error: string | null;
8
+ requestPermission: () => Promise<boolean>;
9
+ checkPermission: () => Promise<boolean>;
10
+ }
11
+
12
+ export const usePermissions = (): UsePermissionsReturn => {
13
+ const [permissionGranted, setPermissionGranted] = useState<boolean>(false);
14
+ const [isChecking, setIsChecking] = useState<boolean>(false);
15
+ const [error, setError] = useState<string | null>(null);
16
+
17
+ const checkPermission = async (): Promise<boolean> => {
18
+ try {
19
+ setIsChecking(true);
20
+ setError(null);
21
+
22
+ const granted = await Scanner.checkPermissionStatus();
23
+ setPermissionGranted(granted);
24
+
25
+ return granted;
26
+ } catch (err: any) {
27
+ setError(err.message || 'Failed to check permission');
28
+ console.error('Error checking permission:', err);
29
+ return false;
30
+ } finally {
31
+ setIsChecking(false);
32
+ }
33
+ };
34
+
35
+ const requestPermission = async (): Promise<boolean> => {
36
+ try {
37
+ setIsChecking(true);
38
+ setError(null);
39
+
40
+ const granted = await Scanner.requestStoragePermission();
41
+ setPermissionGranted(granted);
42
+
43
+ return granted;
44
+ } catch (err: any) {
45
+ setError(err.message || 'Failed to request permission');
46
+ console.error('Error requesting permission:', err);
47
+ return false;
48
+ } finally {
49
+ setIsChecking(false);
50
+ }
51
+ };
52
+
53
+ useEffect(() => {
54
+ checkPermission();
55
+ }, []);
56
+
57
+ return {
58
+ permissionGranted,
59
+ isChecking,
60
+ error,
61
+ requestPermission,
62
+ checkPermission,
63
+ };
64
+ };