react-native-nitro-player 0.0.1 → 0.3.0-alpha.10

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 (47) hide show
  1. package/README.md +282 -2
  2. package/android/src/main/java/com/margelo/nitro/nitroplayer/HybridAudioDevices.kt +37 -29
  3. package/android/src/main/java/com/margelo/nitro/nitroplayer/HybridTrackPlayer.kt +24 -0
  4. package/android/src/main/java/com/margelo/nitro/nitroplayer/core/TrackPlayerCore.kt +408 -16
  5. package/ios/HybridAudioRoutePicker.swift +47 -46
  6. package/ios/HybridTrackPlayer.swift +22 -0
  7. package/ios/core/TrackPlayerCore.swift +538 -48
  8. package/lib/hooks/callbackManager.d.ts +28 -0
  9. package/lib/hooks/callbackManager.js +76 -0
  10. package/lib/hooks/index.d.ts +7 -0
  11. package/lib/hooks/index.js +3 -0
  12. package/lib/hooks/useActualQueue.d.ts +48 -0
  13. package/lib/hooks/useActualQueue.js +98 -0
  14. package/lib/hooks/useNowPlaying.d.ts +36 -0
  15. package/lib/hooks/useNowPlaying.js +87 -0
  16. package/lib/hooks/useOnChangeTrack.d.ts +33 -6
  17. package/lib/hooks/useOnChangeTrack.js +65 -9
  18. package/lib/hooks/useOnPlaybackStateChange.d.ts +32 -6
  19. package/lib/hooks/useOnPlaybackStateChange.js +65 -9
  20. package/lib/hooks/usePlaylist.d.ts +48 -0
  21. package/lib/hooks/usePlaylist.js +136 -0
  22. package/lib/index.d.ts +1 -0
  23. package/lib/specs/TrackPlayer.nitro.d.ts +6 -0
  24. package/nitrogen/generated/android/c++/JHybridTrackPlayerSpec.cpp +46 -9
  25. package/nitrogen/generated/android/c++/JHybridTrackPlayerSpec.hpp +5 -0
  26. package/nitrogen/generated/android/c++/JRepeatMode.hpp +62 -0
  27. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/HybridTrackPlayerSpec.kt +20 -0
  28. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/RepeatMode.kt +22 -0
  29. package/nitrogen/generated/ios/NitroPlayer-Swift-Cxx-Bridge.hpp +9 -0
  30. package/nitrogen/generated/ios/NitroPlayer-Swift-Cxx-Umbrella.hpp +3 -0
  31. package/nitrogen/generated/ios/c++/HybridTrackPlayerSpecSwift.hpp +44 -4
  32. package/nitrogen/generated/ios/swift/HybridTrackPlayerSpec.swift +5 -0
  33. package/nitrogen/generated/ios/swift/HybridTrackPlayerSpec_cxx.swift +64 -0
  34. package/nitrogen/generated/ios/swift/RepeatMode.swift +44 -0
  35. package/nitrogen/generated/shared/c++/HybridTrackPlayerSpec.cpp +5 -0
  36. package/nitrogen/generated/shared/c++/HybridTrackPlayerSpec.hpp +12 -3
  37. package/nitrogen/generated/shared/c++/RepeatMode.hpp +80 -0
  38. package/package.json +13 -12
  39. package/src/hooks/callbackManager.ts +96 -0
  40. package/src/hooks/index.ts +7 -0
  41. package/src/hooks/useActualQueue.ts +116 -0
  42. package/src/hooks/useNowPlaying.ts +97 -0
  43. package/src/hooks/useOnChangeTrack.ts +77 -13
  44. package/src/hooks/useOnPlaybackStateChange.ts +83 -13
  45. package/src/hooks/usePlaylist.ts +161 -0
  46. package/src/index.ts +1 -1
  47. package/src/specs/TrackPlayer.nitro.ts +7 -0
@@ -1,24 +1,88 @@
1
- import { useEffect, useState } from 'react'
1
+ import { useEffect, useState, useRef } from 'react'
2
2
  import { TrackPlayer } from '../index'
3
+ import { callbackManager } from './callbackManager'
3
4
  import type { TrackItem, Reason } from '../types/PlayerQueue'
4
5
 
6
+ export interface TrackChangeResult {
7
+ track: TrackItem | null
8
+ reason: Reason | undefined
9
+ isReady: boolean
10
+ }
11
+
5
12
  /**
6
- * Hook to get the current track and track change reason
7
- * @returns Object with current track and reason, or undefined if no track is playing
13
+ * Hook to subscribe to track changes.
14
+ *
15
+ * This hook provides real-time track change updates using native callbacks.
16
+ * Multiple components can use this hook simultaneously without interfering
17
+ * with each other.
18
+ *
19
+ * @returns Object with:
20
+ * - track: Current track or null if no track is playing
21
+ * - reason: Reason for the last track change
22
+ * - isReady: Whether the initial state has been loaded
23
+ *
24
+ * @example
25
+ * ```tsx
26
+ * function NowPlaying() {
27
+ * const { track, reason, isReady } = useOnChangeTrack()
28
+ *
29
+ * if (!isReady) return <Text>Loading...</Text>
30
+ * if (!track) return <Text>No track playing</Text>
31
+ *
32
+ * return (
33
+ * <View>
34
+ * <Text>{track.title}</Text>
35
+ * <Text>{track.artist}</Text>
36
+ * </View>
37
+ * )
38
+ * }
39
+ * ```
8
40
  */
9
- export function useOnChangeTrack(): {
10
- track: TrackItem | undefined
11
- reason: Reason | undefined
12
- } {
13
- const [track, setTrack] = useState<TrackItem | undefined>(undefined)
41
+ export function useOnChangeTrack(): TrackChangeResult {
42
+ const [track, setTrack] = useState<TrackItem | null>(null)
14
43
  const [reason, setReason] = useState<Reason | undefined>(undefined)
44
+ const [isReady, setIsReady] = useState(false)
45
+ const isMounted = useRef(true)
15
46
 
47
+ // Initialize with current track from the player
16
48
  useEffect(() => {
17
- TrackPlayer.onChangeTrack((newTrack, newReason) => {
18
- setTrack(newTrack)
19
- setReason(newReason)
20
- })
49
+ isMounted.current = true
50
+
51
+ try {
52
+ const playerState = TrackPlayer.getState()
53
+ if (isMounted.current) {
54
+ setTrack(playerState.currentTrack)
55
+ setIsReady(true)
56
+ }
57
+ } catch (error) {
58
+ console.error('[useOnChangeTrack] Failed to get initial state:', error)
59
+ if (isMounted.current) {
60
+ setTrack(null)
61
+ setIsReady(true)
62
+ }
63
+ }
64
+
65
+ return () => {
66
+ isMounted.current = false
67
+ }
68
+ }, [])
69
+
70
+ // Subscribe to track changes
71
+ useEffect(() => {
72
+ const handleTrackChange = (newTrack: TrackItem, newReason?: Reason) => {
73
+ if (isMounted.current) {
74
+ setTrack(newTrack)
75
+ setReason(newReason)
76
+ }
77
+ }
78
+
79
+ const unsubscribe =
80
+ callbackManager.subscribeToTrackChange(handleTrackChange)
81
+
82
+ return () => {
83
+ unsubscribe()
84
+ }
21
85
  }, [])
22
86
 
23
- return { track, reason }
87
+ return { track, reason, isReady }
24
88
  }
@@ -1,24 +1,94 @@
1
- import { useEffect, useState } from 'react'
1
+ import { useEffect, useState, useRef } from 'react'
2
2
  import { TrackPlayer } from '../index'
3
+ import { callbackManager } from './callbackManager'
3
4
  import type { TrackPlayerState, Reason } from '../types/PlayerQueue'
4
5
 
6
+ export interface PlaybackStateResult {
7
+ state: TrackPlayerState
8
+ reason: Reason | undefined
9
+ isReady: boolean
10
+ }
11
+
5
12
  /**
6
- * Hook to get the current playback state and reason
7
- * @returns Object with current playback state and reason
13
+ * Hook to subscribe to playback state changes.
14
+ *
15
+ * This hook provides real-time playback state updates using native callbacks.
16
+ * Multiple components can use this hook simultaneously without interfering
17
+ * with each other.
18
+ *
19
+ * @returns Object with:
20
+ * - state: Current playback state ('playing' | 'paused' | 'stopped')
21
+ * - reason: Reason for the last state change
22
+ * - isReady: Whether the initial state has been loaded
23
+ *
24
+ * @example
25
+ * ```tsx
26
+ * function PlaybackIndicator() {
27
+ * const { state, reason, isReady } = useOnPlaybackStateChange()
28
+ *
29
+ * if (!isReady) return <Text>Loading...</Text>
30
+ *
31
+ * return (
32
+ * <View>
33
+ * <Text>State: {state}</Text>
34
+ * {reason && <Text>Reason: {reason}</Text>}
35
+ * </View>
36
+ * )
37
+ * }
38
+ * ```
8
39
  */
9
- export function useOnPlaybackStateChange(): {
10
- state: TrackPlayerState | undefined
11
- reason: Reason | undefined
12
- } {
13
- const [state, setState] = useState<TrackPlayerState | undefined>(undefined)
40
+ export function useOnPlaybackStateChange(): PlaybackStateResult {
41
+ const [state, setState] = useState<TrackPlayerState>('stopped')
14
42
  const [reason, setReason] = useState<Reason | undefined>(undefined)
43
+ const [isReady, setIsReady] = useState(false)
44
+ const isMounted = useRef(true)
15
45
 
46
+ // Initialize with current state from the player
16
47
  useEffect(() => {
17
- TrackPlayer.onPlaybackStateChange((newState, newReason) => {
18
- setState(newState)
19
- setReason(newReason)
20
- })
48
+ isMounted.current = true
49
+
50
+ // Get initial state synchronously
51
+ try {
52
+ const playerState = TrackPlayer.getState()
53
+ if (isMounted.current) {
54
+ setState(playerState.currentState)
55
+ setIsReady(true)
56
+ }
57
+ } catch (error) {
58
+ console.error(
59
+ '[useOnPlaybackStateChange] Failed to get initial state:',
60
+ error
61
+ )
62
+ if (isMounted.current) {
63
+ setState('stopped')
64
+ setIsReady(true)
65
+ }
66
+ }
67
+
68
+ return () => {
69
+ isMounted.current = false
70
+ }
71
+ }, [])
72
+
73
+ // Subscribe to playback state changes
74
+ useEffect(() => {
75
+ const handleStateChange = (
76
+ newState: TrackPlayerState,
77
+ newReason?: Reason
78
+ ) => {
79
+ if (isMounted.current) {
80
+ setState(newState)
81
+ setReason(newReason)
82
+ }
83
+ }
84
+
85
+ const unsubscribe =
86
+ callbackManager.subscribeToPlaybackState(handleStateChange)
87
+
88
+ return () => {
89
+ unsubscribe()
90
+ }
21
91
  }, [])
22
92
 
23
- return { state, reason }
93
+ return { state, reason, isReady }
24
94
  }
@@ -0,0 +1,161 @@
1
+ import { useEffect, useState, useRef, useCallback } from 'react'
2
+ import { PlayerQueue } from '../index'
3
+ import { callbackManager } from './callbackManager'
4
+ import type { Playlist, TrackItem } from '../types/PlayerQueue'
5
+
6
+ export interface UsePlaylistResult {
7
+ /** The currently loaded playlist */
8
+ currentPlaylist: Playlist | null
9
+ /** ID of the currently loaded playlist */
10
+ currentPlaylistId: string | null
11
+ /** All available playlists */
12
+ allPlaylists: Playlist[]
13
+ /** All tracks from all playlists (flattened) */
14
+ allTracks: TrackItem[]
15
+ /** Whether the playlists are currently loading */
16
+ isLoading: boolean
17
+ /** Manually refresh playlist data */
18
+ refreshPlaylists: () => void
19
+ }
20
+
21
+ /**
22
+ * Hook to manage playlist state
23
+ *
24
+ * Provides current playlist, all playlists, and all tracks across playlists.
25
+ * Automatically refreshes when:
26
+ * - Component mounts
27
+ * - Track changes (to update currentPlaylistId)
28
+ * - Playlists are modified via PlayerQueue methods
29
+ *
30
+ * Call `refreshPlaylists()` after creating/deleting playlists to update the state.
31
+ *
32
+ * @returns Object containing playlist state and refresh function
33
+ *
34
+ * @example
35
+ * ```tsx
36
+ * function MyComponent() {
37
+ * const { currentPlaylist, allTracks, refreshPlaylists } = usePlaylist();
38
+ *
39
+ * const handleCreatePlaylist = () => {
40
+ * PlayerQueue.createPlaylist('New Playlist');
41
+ * refreshPlaylists(); // Refresh to see the new playlist
42
+ * };
43
+ *
44
+ * return (
45
+ * <View>
46
+ * <Text>{currentPlaylist?.name}</Text>
47
+ * <Text>Total tracks: {allTracks.length}</Text>
48
+ * </View>
49
+ * );
50
+ * }
51
+ * ```
52
+ */
53
+ export function usePlaylist(): UsePlaylistResult {
54
+ const [currentPlaylist, setCurrentPlaylist] = useState<Playlist | null>(null)
55
+ const [currentPlaylistId, setCurrentPlaylistId] = useState<string | null>(
56
+ null
57
+ )
58
+ const [allPlaylists, setAllPlaylists] = useState<Playlist[]>([])
59
+ const [allTracks, setAllTracks] = useState<TrackItem[]>([])
60
+ const [isLoading, setIsLoading] = useState(true)
61
+ const isMounted = useRef(true)
62
+ const hasSubscribed = useRef(false)
63
+
64
+ const refreshPlaylists = useCallback(() => {
65
+ if (!isMounted.current) return
66
+
67
+ try {
68
+ // Get current playlist ID
69
+ const playlistId = PlayerQueue.getCurrentPlaylistId()
70
+ if (!isMounted.current) return
71
+ setCurrentPlaylistId(playlistId)
72
+
73
+ // Get current playlist details
74
+ if (playlistId) {
75
+ const playlist = PlayerQueue.getPlaylist(playlistId)
76
+ if (isMounted.current) {
77
+ setCurrentPlaylist(playlist)
78
+ }
79
+ } else {
80
+ if (isMounted.current) {
81
+ setCurrentPlaylist(null)
82
+ }
83
+ }
84
+
85
+ // Get all playlists
86
+ const playlists = PlayerQueue.getAllPlaylists()
87
+ if (!isMounted.current) return
88
+ setAllPlaylists(playlists)
89
+
90
+ // Get all tracks from all playlists (deduplicated by id)
91
+ const trackMap = new Map<string, TrackItem>()
92
+ playlists.forEach((playlist) => {
93
+ playlist.tracks.forEach((track) => {
94
+ if (!trackMap.has(track.id)) {
95
+ trackMap.set(track.id, track)
96
+ }
97
+ })
98
+ })
99
+ if (isMounted.current) {
100
+ setAllTracks(Array.from(trackMap.values()))
101
+ setIsLoading(false)
102
+ }
103
+ } catch (error) {
104
+ console.error('[usePlaylist] Error refreshing playlists:', error)
105
+ if (isMounted.current) {
106
+ setIsLoading(false)
107
+ }
108
+ }
109
+ }, [])
110
+
111
+ // Initialize and setup mounted ref
112
+ useEffect(() => {
113
+ isMounted.current = true
114
+
115
+ // Initial load
116
+ refreshPlaylists()
117
+
118
+ return () => {
119
+ isMounted.current = false
120
+ }
121
+ }, [refreshPlaylists])
122
+
123
+ // Subscribe to native playlist changes (only once)
124
+ useEffect(() => {
125
+ if (hasSubscribed.current) return
126
+ hasSubscribed.current = true
127
+
128
+ try {
129
+ PlayerQueue.onPlaylistsChanged(() => {
130
+ if (isMounted.current) {
131
+ refreshPlaylists()
132
+ }
133
+ })
134
+ } catch (error) {
135
+ console.error('[usePlaylist] Error setting up playlist listener:', error)
136
+ }
137
+ }, [refreshPlaylists])
138
+
139
+ // Also refresh when track changes (as it might indicate playlist loaded)
140
+ useEffect(() => {
141
+ const unsubscribe = callbackManager.subscribeToTrackChange(() => {
142
+ // Refresh to update currentPlaylistId when track changes
143
+ if (isMounted.current) {
144
+ refreshPlaylists()
145
+ }
146
+ })
147
+
148
+ return () => {
149
+ unsubscribe()
150
+ }
151
+ }, [refreshPlaylists])
152
+
153
+ return {
154
+ currentPlaylist,
155
+ currentPlaylistId,
156
+ allPlaylists,
157
+ allTracks,
158
+ isLoading,
159
+ refreshPlaylists,
160
+ }
161
+ }
package/src/index.ts CHANGED
@@ -42,6 +42,6 @@ export * from './hooks'
42
42
  export * from './types/PlayerQueue'
43
43
  export * from './types/AndroidAutoMediaLibrary'
44
44
  export type { TAudioDevice } from './specs/AudioDevices.nitro'
45
-
45
+ export type { RepeatMode } from './specs/TrackPlayer.nitro'
46
46
  // Export utilities
47
47
  export { AndroidAutoMediaLibraryHelper } from './utils/androidAutoMediaLibrary'
@@ -54,6 +54,8 @@ export interface PlayerQueue
54
54
  ): void
55
55
  }
56
56
 
57
+ export type RepeatMode = 'off' | 'Playlist' | 'track'
58
+
57
59
  export interface TrackPlayer
58
60
  extends HybridObject<{ android: 'kotlin'; ios: 'swift' }> {
59
61
  play(): void
@@ -62,7 +64,11 @@ export interface TrackPlayer
62
64
  skipToNext(): void
63
65
  skipToPrevious(): void
64
66
  seek(position: number): void
67
+ addToUpNext(trackId: string): void
68
+ playNext(trackId: string): void
69
+ getActualQueue(): TrackItem[]
65
70
  getState(): PlayerState
71
+ setRepeatMode(mode: RepeatMode): boolean
66
72
  configure(config: PlayerConfig): void
67
73
  onChangeTrack(callback: (track: TrackItem, reason?: Reason) => void): void
68
74
  onPlaybackStateChange(
@@ -78,4 +84,5 @@ export interface TrackPlayer
78
84
  ): void
79
85
  onAndroidAutoConnectionChange(callback: (connected: boolean) => void): void
80
86
  isAndroidAutoConnected(): boolean
87
+ setVolume(volume: number): boolean
81
88
  }