react-native-nitro-player 1.5.0-alpha.3 → 1.5.1-alpha.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.
- package/android/src/main/java/com/margelo/nitro/nitroplayer/HybridPlayerQueue.kt +18 -7
- package/android/src/main/java/com/margelo/nitro/nitroplayer/HybridTrackPlayer.kt +71 -56
- package/android/src/main/java/com/margelo/nitro/nitroplayer/core/TrackPlayerCore.kt +82 -9
- package/android/src/main/java/com/margelo/nitro/nitroplayer/core/TrackPlayerListener.kt +10 -0
- package/android/src/main/java/com/margelo/nitro/nitroplayer/core/TrackPlayerPlayback.kt +84 -70
- package/android/src/main/java/com/margelo/nitro/nitroplayer/core/TrackPlayerQueue.kt +4 -2
- package/android/src/main/java/com/margelo/nitro/nitroplayer/core/TrackPlayerQueueBuild.kt +43 -11
- package/android/src/main/java/com/margelo/nitro/nitroplayer/core/TrackPlayerSetup.kt +6 -2
- package/android/src/main/java/com/margelo/nitro/nitroplayer/core/TrackPlayerTempQueue.kt +67 -53
- package/android/src/main/java/com/margelo/nitro/nitroplayer/core/TrackPlayerUrlLoader.kt +42 -11
- package/android/src/main/java/com/margelo/nitro/nitroplayer/download/DownloadDatabase.kt +26 -4
- package/ios/HybridTrackPlayer.swift +52 -29
- package/ios/core/TrackPlayerCore.swift +9 -1
- package/ios/core/TrackPlayerListener.swift +20 -5
- package/ios/core/TrackPlayerNotify.swift +9 -7
- package/ios/core/TrackPlayerPlayback.swift +75 -39
- package/ios/core/TrackPlayerQueue.swift +28 -4
- package/ios/core/TrackPlayerQueueBuild.swift +100 -40
- package/ios/core/TrackPlayerRecovery.swift +1 -1
- package/ios/core/TrackPlayerTempQueue.swift +129 -122
- package/ios/core/TrackPlayerUrlLoader.swift +34 -10
- package/ios/download/DownloadDatabase.swift +41 -13
- package/ios/media/MediaSessionManager.swift +19 -12
- package/ios/queue/HybridPlayerQueue.swift +10 -5
- package/lib/hooks/callbackManager.d.ts +17 -0
- package/lib/hooks/callbackManager.js +67 -1
- package/lib/hooks/useActualQueue.js +18 -12
- package/lib/hooks/useNowPlaying.js +10 -2
- package/package.json +1 -1
- package/src/components/CastButton.tsx +14 -2
- package/src/hooks/callbackManager.ts +87 -1
- package/src/hooks/useActualQueue.ts +18 -13
- package/src/hooks/useNowPlaying.ts +12 -2
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { TrackPlayer } from '../index';
|
|
1
|
+
import { PlayerQueue, TrackPlayer } from '../index';
|
|
2
2
|
/**
|
|
3
3
|
* Internal subscription manager that allows multiple hooks to subscribe
|
|
4
4
|
* to a single native callback. This solves the problem where registering
|
|
@@ -9,10 +9,14 @@ class CallbackSubscriptionManager {
|
|
|
9
9
|
trackChangeSubscribers = new Set();
|
|
10
10
|
playbackProgressSubscribers = new Set();
|
|
11
11
|
seekSubscribers = new Set();
|
|
12
|
+
temporaryQueueSubscribers = new Set();
|
|
13
|
+
playlistsChangedSubscribers = new Set();
|
|
14
|
+
isPlaylistsChangedRegistered = false;
|
|
12
15
|
isPlaybackStateRegistered = false;
|
|
13
16
|
isTrackChangeRegistered = false;
|
|
14
17
|
isPlaybackProgressRegistered = false;
|
|
15
18
|
isSeekRegistered = false;
|
|
19
|
+
isTemporaryQueueRegistered = false;
|
|
16
20
|
/**
|
|
17
21
|
* Subscribe to playback state changes
|
|
18
22
|
* @returns Unsubscribe function
|
|
@@ -117,6 +121,68 @@ class CallbackSubscriptionManager {
|
|
|
117
121
|
console.error('[CallbackManager] Failed to register playback progress callback:', error);
|
|
118
122
|
}
|
|
119
123
|
}
|
|
124
|
+
/**
|
|
125
|
+
* Subscribe to playNext / upNext changes
|
|
126
|
+
* @returns Unsubscribe function
|
|
127
|
+
*/
|
|
128
|
+
subscribeToTemporaryQueueChange(callback) {
|
|
129
|
+
this.temporaryQueueSubscribers.add(callback);
|
|
130
|
+
this.ensureTemporaryQueueRegistered();
|
|
131
|
+
return () => {
|
|
132
|
+
this.temporaryQueueSubscribers.delete(callback);
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Subscribe to playlist mutations (tracks added/removed/reordered).
|
|
137
|
+
* @returns Unsubscribe function
|
|
138
|
+
*/
|
|
139
|
+
subscribeToPlaylistsChanged(callback) {
|
|
140
|
+
this.playlistsChangedSubscribers.add(callback);
|
|
141
|
+
this.ensurePlaylistsChangedRegistered();
|
|
142
|
+
return () => {
|
|
143
|
+
this.playlistsChangedSubscribers.delete(callback);
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
ensurePlaylistsChangedRegistered() {
|
|
147
|
+
if (this.isPlaylistsChangedRegistered)
|
|
148
|
+
return;
|
|
149
|
+
try {
|
|
150
|
+
PlayerQueue.onPlaylistsChanged(() => {
|
|
151
|
+
this.playlistsChangedSubscribers.forEach((subscriber) => {
|
|
152
|
+
try {
|
|
153
|
+
subscriber();
|
|
154
|
+
}
|
|
155
|
+
catch (error) {
|
|
156
|
+
console.error('[CallbackManager] Error in playlists changed subscriber:', error);
|
|
157
|
+
}
|
|
158
|
+
});
|
|
159
|
+
});
|
|
160
|
+
this.isPlaylistsChangedRegistered = true;
|
|
161
|
+
}
|
|
162
|
+
catch (error) {
|
|
163
|
+
console.error('[CallbackManager] Failed to register playlists changed callback:', error);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
ensureTemporaryQueueRegistered() {
|
|
167
|
+
if (this.isTemporaryQueueRegistered)
|
|
168
|
+
return;
|
|
169
|
+
try {
|
|
170
|
+
TrackPlayer.onTemporaryQueueChange((playNextQueue, upNextQueue) => {
|
|
171
|
+
this.temporaryQueueSubscribers.forEach((subscriber) => {
|
|
172
|
+
try {
|
|
173
|
+
subscriber(playNextQueue, upNextQueue);
|
|
174
|
+
}
|
|
175
|
+
catch (error) {
|
|
176
|
+
console.error('[CallbackManager] Error in temporary queue subscriber:', error);
|
|
177
|
+
}
|
|
178
|
+
});
|
|
179
|
+
});
|
|
180
|
+
this.isTemporaryQueueRegistered = true;
|
|
181
|
+
}
|
|
182
|
+
catch (error) {
|
|
183
|
+
console.error('[CallbackManager] Failed to register temporary queue callback:', error);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
120
186
|
ensureSeekRegistered() {
|
|
121
187
|
if (this.isSeekRegistered)
|
|
122
188
|
return;
|
|
@@ -75,24 +75,30 @@ export function useActualQueue() {
|
|
|
75
75
|
isMounted.current = false;
|
|
76
76
|
};
|
|
77
77
|
}, [updateQueue]);
|
|
78
|
-
// Update queue on track changes
|
|
78
|
+
// Update queue on track changes.
|
|
79
|
+
//
|
|
80
|
+
// Deliberately NOT subscribed to playback-state changes: those fire several times
|
|
81
|
+
// per second while buffering or seeking, and each one used to pull the entire queue
|
|
82
|
+
// across the bridge. A playback state change never alters queue membership — only
|
|
83
|
+
// track changes and temporary-queue edits do.
|
|
79
84
|
useEffect(() => {
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
setTimeout(updateQueue, 50);
|
|
85
|
+
return callbackManager.subscribeToTrackChange(() => {
|
|
86
|
+
updateQueue();
|
|
83
87
|
});
|
|
84
|
-
return () => {
|
|
85
|
-
unsubscribe();
|
|
86
|
-
};
|
|
87
88
|
}, [updateQueue]);
|
|
88
|
-
// Update queue
|
|
89
|
+
// Update queue when playNext / upNext change (native pushes the new lists).
|
|
89
90
|
useEffect(() => {
|
|
90
|
-
|
|
91
|
+
return callbackManager.subscribeToTemporaryQueueChange(() => {
|
|
92
|
+
updateQueue();
|
|
93
|
+
});
|
|
94
|
+
}, [updateQueue]);
|
|
95
|
+
// Update queue when the playlist itself is edited (track added / removed / reordered).
|
|
96
|
+
// Without this, a playlist mutation while paused would leave the queue stale until the
|
|
97
|
+
// next track change — playback-state changes used to paper over it.
|
|
98
|
+
useEffect(() => {
|
|
99
|
+
return callbackManager.subscribeToPlaylistsChanged(() => {
|
|
91
100
|
updateQueue();
|
|
92
101
|
});
|
|
93
|
-
return () => {
|
|
94
|
-
unsubscribe();
|
|
95
|
-
};
|
|
96
102
|
}, [updateQueue]);
|
|
97
103
|
return { queue, refreshQueue, isLoading };
|
|
98
104
|
}
|
|
@@ -80,12 +80,20 @@ export function useNowPlaying() {
|
|
|
80
80
|
fetchFullState();
|
|
81
81
|
});
|
|
82
82
|
}, [fetchFullState]);
|
|
83
|
-
// Subscribe to progress changes — lightweight position/duration update
|
|
83
|
+
// Subscribe to progress changes — lightweight position/duration update.
|
|
84
|
+
// Bail out when neither value changed at whole-second resolution: the native tick
|
|
85
|
+
// is sub-second, and a new state object on every tick re-renders every consumer.
|
|
84
86
|
useEffect(() => {
|
|
85
87
|
return callbackManager.subscribeToPlaybackProgressChange((currentPosition, totalDuration) => {
|
|
86
88
|
if (!isMounted.current)
|
|
87
89
|
return;
|
|
88
|
-
setState((prev) =>
|
|
90
|
+
setState((prev) => {
|
|
91
|
+
if (Math.floor(prev.currentPosition) === Math.floor(currentPosition) &&
|
|
92
|
+
prev.totalDuration === totalDuration) {
|
|
93
|
+
return prev;
|
|
94
|
+
}
|
|
95
|
+
return { ...prev, currentPosition, totalDuration };
|
|
96
|
+
});
|
|
89
97
|
});
|
|
90
98
|
}, []);
|
|
91
99
|
// Subscribe to seek events — lightweight position/duration update
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-native-nitro-player",
|
|
3
|
-
"version": "1.5.
|
|
3
|
+
"version": "1.5.1-alpha.0",
|
|
4
4
|
"description": "A powerful audio player library for React Native with playlist management, playback controls, and support for Android Auto and CarPlay",
|
|
5
5
|
"main": "lib/index",
|
|
6
6
|
"module": "lib/index",
|
|
@@ -176,8 +176,20 @@ function CastGlyph({
|
|
|
176
176
|
}}
|
|
177
177
|
/>
|
|
178
178
|
{/* Two concentric broadcast arcs radiating from the corner dot */}
|
|
179
|
-
<RippleArc
|
|
180
|
-
|
|
179
|
+
<RippleArc
|
|
180
|
+
cx={cx}
|
|
181
|
+
cy={cy}
|
|
182
|
+
radius={size * 0.2}
|
|
183
|
+
stroke={stroke}
|
|
184
|
+
color={color}
|
|
185
|
+
/>
|
|
186
|
+
<RippleArc
|
|
187
|
+
cx={cx}
|
|
188
|
+
cy={cy}
|
|
189
|
+
radius={size * 0.34}
|
|
190
|
+
stroke={stroke}
|
|
191
|
+
color={color}
|
|
192
|
+
/>
|
|
181
193
|
{/* Broadcast dot */}
|
|
182
194
|
<View
|
|
183
195
|
style={{
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { TrackPlayer } from '../index'
|
|
1
|
+
import { PlayerQueue, TrackPlayer } from '../index'
|
|
2
2
|
import type { TrackItem, TrackPlayerState, Reason } from '../types/PlayerQueue'
|
|
3
3
|
|
|
4
4
|
type PlaybackStateCallback = (state: TrackPlayerState, reason?: Reason) => void
|
|
@@ -9,6 +9,10 @@ type PlaybackProgressCallback = (
|
|
|
9
9
|
isManuallySeeked?: boolean
|
|
10
10
|
) => void
|
|
11
11
|
type SeekCallback = (position: number, totalDuration: number) => void
|
|
12
|
+
type TemporaryQueueCallback = (
|
|
13
|
+
playNextQueue: TrackItem[],
|
|
14
|
+
upNextQueue: TrackItem[]
|
|
15
|
+
) => void
|
|
12
16
|
|
|
13
17
|
/**
|
|
14
18
|
* Internal subscription manager that allows multiple hooks to subscribe
|
|
@@ -20,10 +24,14 @@ class CallbackSubscriptionManager {
|
|
|
20
24
|
private trackChangeSubscribers = new Set<TrackChangeCallback>()
|
|
21
25
|
private playbackProgressSubscribers = new Set<PlaybackProgressCallback>()
|
|
22
26
|
private seekSubscribers = new Set<SeekCallback>()
|
|
27
|
+
private temporaryQueueSubscribers = new Set<TemporaryQueueCallback>()
|
|
28
|
+
private playlistsChangedSubscribers = new Set<() => void>()
|
|
29
|
+
private isPlaylistsChangedRegistered = false
|
|
23
30
|
private isPlaybackStateRegistered = false
|
|
24
31
|
private isTrackChangeRegistered = false
|
|
25
32
|
private isPlaybackProgressRegistered = false
|
|
26
33
|
private isSeekRegistered = false
|
|
34
|
+
private isTemporaryQueueRegistered = false
|
|
27
35
|
|
|
28
36
|
/**
|
|
29
37
|
* Subscribe to playback state changes
|
|
@@ -156,6 +164,84 @@ class CallbackSubscriptionManager {
|
|
|
156
164
|
}
|
|
157
165
|
}
|
|
158
166
|
|
|
167
|
+
/**
|
|
168
|
+
* Subscribe to playNext / upNext changes
|
|
169
|
+
* @returns Unsubscribe function
|
|
170
|
+
*/
|
|
171
|
+
subscribeToTemporaryQueueChange(
|
|
172
|
+
callback: TemporaryQueueCallback
|
|
173
|
+
): () => void {
|
|
174
|
+
this.temporaryQueueSubscribers.add(callback)
|
|
175
|
+
this.ensureTemporaryQueueRegistered()
|
|
176
|
+
|
|
177
|
+
return () => {
|
|
178
|
+
this.temporaryQueueSubscribers.delete(callback)
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Subscribe to playlist mutations (tracks added/removed/reordered).
|
|
184
|
+
* @returns Unsubscribe function
|
|
185
|
+
*/
|
|
186
|
+
subscribeToPlaylistsChanged(callback: () => void): () => void {
|
|
187
|
+
this.playlistsChangedSubscribers.add(callback)
|
|
188
|
+
this.ensurePlaylistsChangedRegistered()
|
|
189
|
+
|
|
190
|
+
return () => {
|
|
191
|
+
this.playlistsChangedSubscribers.delete(callback)
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
private ensurePlaylistsChangedRegistered(): void {
|
|
196
|
+
if (this.isPlaylistsChangedRegistered) return
|
|
197
|
+
|
|
198
|
+
try {
|
|
199
|
+
PlayerQueue.onPlaylistsChanged(() => {
|
|
200
|
+
this.playlistsChangedSubscribers.forEach((subscriber) => {
|
|
201
|
+
try {
|
|
202
|
+
subscriber()
|
|
203
|
+
} catch (error) {
|
|
204
|
+
console.error(
|
|
205
|
+
'[CallbackManager] Error in playlists changed subscriber:',
|
|
206
|
+
error
|
|
207
|
+
)
|
|
208
|
+
}
|
|
209
|
+
})
|
|
210
|
+
})
|
|
211
|
+
this.isPlaylistsChangedRegistered = true
|
|
212
|
+
} catch (error) {
|
|
213
|
+
console.error(
|
|
214
|
+
'[CallbackManager] Failed to register playlists changed callback:',
|
|
215
|
+
error
|
|
216
|
+
)
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
private ensureTemporaryQueueRegistered(): void {
|
|
221
|
+
if (this.isTemporaryQueueRegistered) return
|
|
222
|
+
|
|
223
|
+
try {
|
|
224
|
+
TrackPlayer.onTemporaryQueueChange((playNextQueue, upNextQueue) => {
|
|
225
|
+
this.temporaryQueueSubscribers.forEach((subscriber) => {
|
|
226
|
+
try {
|
|
227
|
+
subscriber(playNextQueue, upNextQueue)
|
|
228
|
+
} catch (error) {
|
|
229
|
+
console.error(
|
|
230
|
+
'[CallbackManager] Error in temporary queue subscriber:',
|
|
231
|
+
error
|
|
232
|
+
)
|
|
233
|
+
}
|
|
234
|
+
})
|
|
235
|
+
})
|
|
236
|
+
this.isTemporaryQueueRegistered = true
|
|
237
|
+
} catch (error) {
|
|
238
|
+
console.error(
|
|
239
|
+
'[CallbackManager] Failed to register temporary queue callback:',
|
|
240
|
+
error
|
|
241
|
+
)
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
159
245
|
private ensureSeekRegistered(): void {
|
|
160
246
|
if (this.isSeekRegistered) return
|
|
161
247
|
|
|
@@ -89,27 +89,32 @@ export function useActualQueue(): UseActualQueueResult {
|
|
|
89
89
|
}
|
|
90
90
|
}, [updateQueue])
|
|
91
91
|
|
|
92
|
-
// Update queue on track changes
|
|
92
|
+
// Update queue on track changes.
|
|
93
|
+
//
|
|
94
|
+
// Deliberately NOT subscribed to playback-state changes: those fire several times
|
|
95
|
+
// per second while buffering or seeking, and each one used to pull the entire queue
|
|
96
|
+
// across the bridge. A playback state change never alters queue membership — only
|
|
97
|
+
// track changes and temporary-queue edits do.
|
|
93
98
|
useEffect(() => {
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
setTimeout(updateQueue, 50)
|
|
99
|
+
return callbackManager.subscribeToTrackChange(() => {
|
|
100
|
+
updateQueue()
|
|
97
101
|
})
|
|
98
|
-
|
|
99
|
-
return () => {
|
|
100
|
-
unsubscribe()
|
|
101
|
-
}
|
|
102
102
|
}, [updateQueue])
|
|
103
103
|
|
|
104
|
-
// Update queue
|
|
104
|
+
// Update queue when playNext / upNext change (native pushes the new lists).
|
|
105
105
|
useEffect(() => {
|
|
106
|
-
|
|
106
|
+
return callbackManager.subscribeToTemporaryQueueChange(() => {
|
|
107
107
|
updateQueue()
|
|
108
108
|
})
|
|
109
|
+
}, [updateQueue])
|
|
109
110
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
111
|
+
// Update queue when the playlist itself is edited (track added / removed / reordered).
|
|
112
|
+
// Without this, a playlist mutation while paused would leave the queue stale until the
|
|
113
|
+
// next track change — playback-state changes used to paper over it.
|
|
114
|
+
useEffect(() => {
|
|
115
|
+
return callbackManager.subscribeToPlaylistsChanged(() => {
|
|
116
|
+
updateQueue()
|
|
117
|
+
})
|
|
113
118
|
}, [updateQueue])
|
|
114
119
|
|
|
115
120
|
return { queue, refreshQueue, isLoading }
|
|
@@ -88,12 +88,22 @@ export function useNowPlaying(): PlayerState {
|
|
|
88
88
|
})
|
|
89
89
|
}, [fetchFullState])
|
|
90
90
|
|
|
91
|
-
// Subscribe to progress changes — lightweight position/duration update
|
|
91
|
+
// Subscribe to progress changes — lightweight position/duration update.
|
|
92
|
+
// Bail out when neither value changed at whole-second resolution: the native tick
|
|
93
|
+
// is sub-second, and a new state object on every tick re-renders every consumer.
|
|
92
94
|
useEffect(() => {
|
|
93
95
|
return callbackManager.subscribeToPlaybackProgressChange(
|
|
94
96
|
(currentPosition, totalDuration) => {
|
|
95
97
|
if (!isMounted.current) return
|
|
96
|
-
setState((prev) =>
|
|
98
|
+
setState((prev) => {
|
|
99
|
+
if (
|
|
100
|
+
Math.floor(prev.currentPosition) === Math.floor(currentPosition) &&
|
|
101
|
+
prev.totalDuration === totalDuration
|
|
102
|
+
) {
|
|
103
|
+
return prev
|
|
104
|
+
}
|
|
105
|
+
return { ...prev, currentPosition, totalDuration }
|
|
106
|
+
})
|
|
97
107
|
}
|
|
98
108
|
)
|
|
99
109
|
}, [])
|