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.
- package/README.md +282 -2
- package/android/src/main/java/com/margelo/nitro/nitroplayer/HybridAudioDevices.kt +37 -29
- package/android/src/main/java/com/margelo/nitro/nitroplayer/HybridTrackPlayer.kt +24 -0
- package/android/src/main/java/com/margelo/nitro/nitroplayer/core/TrackPlayerCore.kt +408 -16
- package/ios/HybridAudioRoutePicker.swift +47 -46
- package/ios/HybridTrackPlayer.swift +22 -0
- package/ios/core/TrackPlayerCore.swift +538 -48
- package/lib/hooks/callbackManager.d.ts +28 -0
- package/lib/hooks/callbackManager.js +76 -0
- package/lib/hooks/index.d.ts +7 -0
- package/lib/hooks/index.js +3 -0
- package/lib/hooks/useActualQueue.d.ts +48 -0
- package/lib/hooks/useActualQueue.js +98 -0
- package/lib/hooks/useNowPlaying.d.ts +36 -0
- package/lib/hooks/useNowPlaying.js +87 -0
- package/lib/hooks/useOnChangeTrack.d.ts +33 -6
- package/lib/hooks/useOnChangeTrack.js +65 -9
- package/lib/hooks/useOnPlaybackStateChange.d.ts +32 -6
- package/lib/hooks/useOnPlaybackStateChange.js +65 -9
- package/lib/hooks/usePlaylist.d.ts +48 -0
- package/lib/hooks/usePlaylist.js +136 -0
- package/lib/index.d.ts +1 -0
- package/lib/specs/TrackPlayer.nitro.d.ts +6 -0
- package/nitrogen/generated/android/c++/JHybridTrackPlayerSpec.cpp +46 -9
- package/nitrogen/generated/android/c++/JHybridTrackPlayerSpec.hpp +5 -0
- package/nitrogen/generated/android/c++/JRepeatMode.hpp +62 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/HybridTrackPlayerSpec.kt +20 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/RepeatMode.kt +22 -0
- package/nitrogen/generated/ios/NitroPlayer-Swift-Cxx-Bridge.hpp +9 -0
- package/nitrogen/generated/ios/NitroPlayer-Swift-Cxx-Umbrella.hpp +3 -0
- package/nitrogen/generated/ios/c++/HybridTrackPlayerSpecSwift.hpp +44 -4
- package/nitrogen/generated/ios/swift/HybridTrackPlayerSpec.swift +5 -0
- package/nitrogen/generated/ios/swift/HybridTrackPlayerSpec_cxx.swift +64 -0
- package/nitrogen/generated/ios/swift/RepeatMode.swift +44 -0
- package/nitrogen/generated/shared/c++/HybridTrackPlayerSpec.cpp +5 -0
- package/nitrogen/generated/shared/c++/HybridTrackPlayerSpec.hpp +12 -3
- package/nitrogen/generated/shared/c++/RepeatMode.hpp +80 -0
- package/package.json +13 -12
- package/src/hooks/callbackManager.ts +96 -0
- package/src/hooks/index.ts +7 -0
- package/src/hooks/useActualQueue.ts +116 -0
- package/src/hooks/useNowPlaying.ts +97 -0
- package/src/hooks/useOnChangeTrack.ts +77 -13
- package/src/hooks/useOnPlaybackStateChange.ts +83 -13
- package/src/hooks/usePlaylist.ts +161 -0
- package/src/index.ts +1 -1
- package/src/specs/TrackPlayer.nitro.ts +7 -0
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { TrackItem, TrackPlayerState, Reason } from '../types/PlayerQueue';
|
|
2
|
+
type PlaybackStateCallback = (state: TrackPlayerState, reason?: Reason) => void;
|
|
3
|
+
type TrackChangeCallback = (track: TrackItem, reason?: Reason) => void;
|
|
4
|
+
/**
|
|
5
|
+
* Internal subscription manager that allows multiple hooks to subscribe
|
|
6
|
+
* to a single native callback. This solves the problem where registering
|
|
7
|
+
* a new callback overwrites the previous one.
|
|
8
|
+
*/
|
|
9
|
+
declare class CallbackSubscriptionManager {
|
|
10
|
+
private playbackStateSubscribers;
|
|
11
|
+
private trackChangeSubscribers;
|
|
12
|
+
private isPlaybackStateRegistered;
|
|
13
|
+
private isTrackChangeRegistered;
|
|
14
|
+
/**
|
|
15
|
+
* Subscribe to playback state changes
|
|
16
|
+
* @returns Unsubscribe function
|
|
17
|
+
*/
|
|
18
|
+
subscribeToPlaybackState(callback: PlaybackStateCallback): () => void;
|
|
19
|
+
/**
|
|
20
|
+
* Subscribe to track changes
|
|
21
|
+
* @returns Unsubscribe function
|
|
22
|
+
*/
|
|
23
|
+
subscribeToTrackChange(callback: TrackChangeCallback): () => void;
|
|
24
|
+
private ensurePlaybackStateRegistered;
|
|
25
|
+
private ensureTrackChangeRegistered;
|
|
26
|
+
}
|
|
27
|
+
export declare const callbackManager: CallbackSubscriptionManager;
|
|
28
|
+
export {};
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { TrackPlayer } from '../index';
|
|
2
|
+
/**
|
|
3
|
+
* Internal subscription manager that allows multiple hooks to subscribe
|
|
4
|
+
* to a single native callback. This solves the problem where registering
|
|
5
|
+
* a new callback overwrites the previous one.
|
|
6
|
+
*/
|
|
7
|
+
class CallbackSubscriptionManager {
|
|
8
|
+
playbackStateSubscribers = new Set();
|
|
9
|
+
trackChangeSubscribers = new Set();
|
|
10
|
+
isPlaybackStateRegistered = false;
|
|
11
|
+
isTrackChangeRegistered = false;
|
|
12
|
+
/**
|
|
13
|
+
* Subscribe to playback state changes
|
|
14
|
+
* @returns Unsubscribe function
|
|
15
|
+
*/
|
|
16
|
+
subscribeToPlaybackState(callback) {
|
|
17
|
+
this.playbackStateSubscribers.add(callback);
|
|
18
|
+
this.ensurePlaybackStateRegistered();
|
|
19
|
+
return () => {
|
|
20
|
+
this.playbackStateSubscribers.delete(callback);
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Subscribe to track changes
|
|
25
|
+
* @returns Unsubscribe function
|
|
26
|
+
*/
|
|
27
|
+
subscribeToTrackChange(callback) {
|
|
28
|
+
this.trackChangeSubscribers.add(callback);
|
|
29
|
+
this.ensureTrackChangeRegistered();
|
|
30
|
+
return () => {
|
|
31
|
+
this.trackChangeSubscribers.delete(callback);
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
ensurePlaybackStateRegistered() {
|
|
35
|
+
if (this.isPlaybackStateRegistered)
|
|
36
|
+
return;
|
|
37
|
+
try {
|
|
38
|
+
TrackPlayer.onPlaybackStateChange((state, reason) => {
|
|
39
|
+
this.playbackStateSubscribers.forEach((subscriber) => {
|
|
40
|
+
try {
|
|
41
|
+
subscriber(state, reason);
|
|
42
|
+
}
|
|
43
|
+
catch (error) {
|
|
44
|
+
console.error('[CallbackManager] Error in playback state subscriber:', error);
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
this.isPlaybackStateRegistered = true;
|
|
49
|
+
}
|
|
50
|
+
catch (error) {
|
|
51
|
+
console.error('[CallbackManager] Failed to register playback state callback:', error);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
ensureTrackChangeRegistered() {
|
|
55
|
+
if (this.isTrackChangeRegistered)
|
|
56
|
+
return;
|
|
57
|
+
try {
|
|
58
|
+
TrackPlayer.onChangeTrack((track, reason) => {
|
|
59
|
+
this.trackChangeSubscribers.forEach((subscriber) => {
|
|
60
|
+
try {
|
|
61
|
+
subscriber(track, reason);
|
|
62
|
+
}
|
|
63
|
+
catch (error) {
|
|
64
|
+
console.error('[CallbackManager] Error in track change subscriber:', error);
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
this.isTrackChangeRegistered = true;
|
|
69
|
+
}
|
|
70
|
+
catch (error) {
|
|
71
|
+
console.error('[CallbackManager] Failed to register track change callback:', error);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
// Export singleton instance
|
|
76
|
+
export const callbackManager = new CallbackSubscriptionManager();
|
package/lib/hooks/index.d.ts
CHANGED
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
export { useOnChangeTrack } from './useOnChangeTrack';
|
|
2
|
+
export type { TrackChangeResult } from './useOnChangeTrack';
|
|
2
3
|
export { useOnPlaybackStateChange } from './useOnPlaybackStateChange';
|
|
4
|
+
export type { PlaybackStateResult } from './useOnPlaybackStateChange';
|
|
3
5
|
export { useOnSeek } from './useOnSeek';
|
|
4
6
|
export { useOnPlaybackProgressChange } from './useOnPlaybackProgressChange';
|
|
5
7
|
export { useAndroidAutoConnection } from './useAndroidAutoConnection';
|
|
6
8
|
export { useAudioDevices } from './useAudioDevices';
|
|
9
|
+
export { useNowPlaying } from './useNowPlaying';
|
|
10
|
+
export { usePlaylist } from './usePlaylist';
|
|
11
|
+
export type { UsePlaylistResult } from './usePlaylist';
|
|
12
|
+
export { useActualQueue } from './useActualQueue';
|
|
13
|
+
export type { UseActualQueueResult } from './useActualQueue';
|
package/lib/hooks/index.js
CHANGED
|
@@ -4,3 +4,6 @@ export { useOnSeek } from './useOnSeek';
|
|
|
4
4
|
export { useOnPlaybackProgressChange } from './useOnPlaybackProgressChange';
|
|
5
5
|
export { useAndroidAutoConnection } from './useAndroidAutoConnection';
|
|
6
6
|
export { useAudioDevices } from './useAudioDevices';
|
|
7
|
+
export { useNowPlaying } from './useNowPlaying';
|
|
8
|
+
export { usePlaylist } from './usePlaylist';
|
|
9
|
+
export { useActualQueue } from './useActualQueue';
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { TrackItem } from '../types/PlayerQueue';
|
|
2
|
+
export interface UseActualQueueResult {
|
|
3
|
+
/** The current queue in playback order */
|
|
4
|
+
queue: TrackItem[];
|
|
5
|
+
/** Manually refresh the queue */
|
|
6
|
+
refreshQueue: () => void;
|
|
7
|
+
/** Whether the queue is currently loading */
|
|
8
|
+
isLoading: boolean;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Hook to get the actual playback queue including temporary tracks
|
|
12
|
+
*
|
|
13
|
+
* Returns the complete queue in playback order:
|
|
14
|
+
* [tracks_before_current] + [current] + [playNext_stack] + [upNext_queue] + [remaining_tracks]
|
|
15
|
+
*
|
|
16
|
+
* Auto-updates when:
|
|
17
|
+
* - Track changes
|
|
18
|
+
* - Playback state changes
|
|
19
|
+
*
|
|
20
|
+
* Call `refreshQueue()` after adding tracks via `playNext()` or `addToUpNext()`
|
|
21
|
+
* to immediately see the updated queue.
|
|
22
|
+
*
|
|
23
|
+
* @returns Object containing queue array, refresh function, and loading state
|
|
24
|
+
*
|
|
25
|
+
* @example
|
|
26
|
+
* ```tsx
|
|
27
|
+
* function QueueView() {
|
|
28
|
+
* const { queue, refreshQueue, isLoading } = useActualQueue();
|
|
29
|
+
*
|
|
30
|
+
* const handleAddToUpNext = (trackId: string) => {
|
|
31
|
+
* TrackPlayer.addToUpNext(trackId);
|
|
32
|
+
* // Refresh queue after adding track
|
|
33
|
+
* setTimeout(refreshQueue, 100);
|
|
34
|
+
* };
|
|
35
|
+
*
|
|
36
|
+
* return (
|
|
37
|
+
* <ScrollView>
|
|
38
|
+
* {queue.map((track, index) => (
|
|
39
|
+
* <Text key={track.id}>
|
|
40
|
+
* {index + 1}. {track.title}
|
|
41
|
+
* </Text>
|
|
42
|
+
* ))}
|
|
43
|
+
* </ScrollView>
|
|
44
|
+
* );
|
|
45
|
+
* }
|
|
46
|
+
* ```
|
|
47
|
+
*/
|
|
48
|
+
export declare function useActualQueue(): UseActualQueueResult;
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { useEffect, useState, useRef, useCallback } from 'react';
|
|
2
|
+
import { TrackPlayer } from '../index';
|
|
3
|
+
import { callbackManager } from './callbackManager';
|
|
4
|
+
/**
|
|
5
|
+
* Hook to get the actual playback queue including temporary tracks
|
|
6
|
+
*
|
|
7
|
+
* Returns the complete queue in playback order:
|
|
8
|
+
* [tracks_before_current] + [current] + [playNext_stack] + [upNext_queue] + [remaining_tracks]
|
|
9
|
+
*
|
|
10
|
+
* Auto-updates when:
|
|
11
|
+
* - Track changes
|
|
12
|
+
* - Playback state changes
|
|
13
|
+
*
|
|
14
|
+
* Call `refreshQueue()` after adding tracks via `playNext()` or `addToUpNext()`
|
|
15
|
+
* to immediately see the updated queue.
|
|
16
|
+
*
|
|
17
|
+
* @returns Object containing queue array, refresh function, and loading state
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* ```tsx
|
|
21
|
+
* function QueueView() {
|
|
22
|
+
* const { queue, refreshQueue, isLoading } = useActualQueue();
|
|
23
|
+
*
|
|
24
|
+
* const handleAddToUpNext = (trackId: string) => {
|
|
25
|
+
* TrackPlayer.addToUpNext(trackId);
|
|
26
|
+
* // Refresh queue after adding track
|
|
27
|
+
* setTimeout(refreshQueue, 100);
|
|
28
|
+
* };
|
|
29
|
+
*
|
|
30
|
+
* return (
|
|
31
|
+
* <ScrollView>
|
|
32
|
+
* {queue.map((track, index) => (
|
|
33
|
+
* <Text key={track.id}>
|
|
34
|
+
* {index + 1}. {track.title}
|
|
35
|
+
* </Text>
|
|
36
|
+
* ))}
|
|
37
|
+
* </ScrollView>
|
|
38
|
+
* );
|
|
39
|
+
* }
|
|
40
|
+
* ```
|
|
41
|
+
*/
|
|
42
|
+
export function useActualQueue() {
|
|
43
|
+
const [queue, setQueue] = useState([]);
|
|
44
|
+
const [isLoading, setIsLoading] = useState(true);
|
|
45
|
+
const isMounted = useRef(true);
|
|
46
|
+
const updateQueue = useCallback(() => {
|
|
47
|
+
if (!isMounted.current)
|
|
48
|
+
return;
|
|
49
|
+
try {
|
|
50
|
+
const actualQueue = TrackPlayer.getActualQueue();
|
|
51
|
+
if (isMounted.current) {
|
|
52
|
+
setQueue(actualQueue);
|
|
53
|
+
setIsLoading(false);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
catch (error) {
|
|
57
|
+
console.error('[useActualQueue] Error getting queue:', error);
|
|
58
|
+
if (isMounted.current) {
|
|
59
|
+
setQueue([]);
|
|
60
|
+
setIsLoading(false);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}, []);
|
|
64
|
+
const refreshQueue = useCallback(() => {
|
|
65
|
+
if (!isMounted.current)
|
|
66
|
+
return;
|
|
67
|
+
setIsLoading(true);
|
|
68
|
+
updateQueue();
|
|
69
|
+
}, [updateQueue]);
|
|
70
|
+
// Initialize queue
|
|
71
|
+
useEffect(() => {
|
|
72
|
+
isMounted.current = true;
|
|
73
|
+
updateQueue();
|
|
74
|
+
return () => {
|
|
75
|
+
isMounted.current = false;
|
|
76
|
+
};
|
|
77
|
+
}, [updateQueue]);
|
|
78
|
+
// Update queue on track changes (with slight delay to ensure native side has updated)
|
|
79
|
+
useEffect(() => {
|
|
80
|
+
const unsubscribe = callbackManager.subscribeToTrackChange(() => {
|
|
81
|
+
// Small delay to ensure native queue is updated
|
|
82
|
+
setTimeout(updateQueue, 50);
|
|
83
|
+
});
|
|
84
|
+
return () => {
|
|
85
|
+
unsubscribe();
|
|
86
|
+
};
|
|
87
|
+
}, [updateQueue]);
|
|
88
|
+
// Update queue on playback state changes
|
|
89
|
+
useEffect(() => {
|
|
90
|
+
const unsubscribe = callbackManager.subscribeToPlaybackState(() => {
|
|
91
|
+
updateQueue();
|
|
92
|
+
});
|
|
93
|
+
return () => {
|
|
94
|
+
unsubscribe();
|
|
95
|
+
};
|
|
96
|
+
}, [updateQueue]);
|
|
97
|
+
return { queue, refreshQueue, isLoading };
|
|
98
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { PlayerState } from '../types/PlayerQueue';
|
|
2
|
+
/**
|
|
3
|
+
* Hook to get the current player state (same as TrackPlayer.getState())
|
|
4
|
+
*
|
|
5
|
+
* This hook provides all player state information including:
|
|
6
|
+
* - Current track
|
|
7
|
+
* - Current position and duration
|
|
8
|
+
* - Playback state (playing, paused, stopped)
|
|
9
|
+
* - Current playlist ID
|
|
10
|
+
* - Current track index
|
|
11
|
+
*
|
|
12
|
+
* The hook uses native callbacks for immediate updates when state changes.
|
|
13
|
+
* Multiple components can use this hook simultaneously.
|
|
14
|
+
*
|
|
15
|
+
* @returns PlayerState object with all current player information
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* ```tsx
|
|
19
|
+
* function PlayerComponent() {
|
|
20
|
+
* const state = useNowPlaying()
|
|
21
|
+
*
|
|
22
|
+
* return (
|
|
23
|
+
* <View>
|
|
24
|
+
* {state.currentTrack && (
|
|
25
|
+
* <Text>Now Playing: {state.currentTrack.title}</Text>
|
|
26
|
+
* )}
|
|
27
|
+
* <Text>Position: {state.currentPosition} / {state.totalDuration}</Text>
|
|
28
|
+
* <Text>State: {state.currentState}</Text>
|
|
29
|
+
* <Text>Playlist: {state.currentPlaylistId || 'None'}</Text>
|
|
30
|
+
* <Text>Index: {state.currentIndex}</Text>
|
|
31
|
+
* </View>
|
|
32
|
+
* )
|
|
33
|
+
* }
|
|
34
|
+
* ```
|
|
35
|
+
*/
|
|
36
|
+
export declare function useNowPlaying(): PlayerState;
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { useEffect, useState, useRef, useCallback } from 'react';
|
|
2
|
+
import { TrackPlayer } from '../index';
|
|
3
|
+
import { callbackManager } from './callbackManager';
|
|
4
|
+
const DEFAULT_STATE = {
|
|
5
|
+
currentTrack: null,
|
|
6
|
+
currentPosition: 0,
|
|
7
|
+
totalDuration: 0,
|
|
8
|
+
currentState: 'stopped',
|
|
9
|
+
currentPlaylistId: null,
|
|
10
|
+
currentIndex: -1,
|
|
11
|
+
};
|
|
12
|
+
/**
|
|
13
|
+
* Hook to get the current player state (same as TrackPlayer.getState())
|
|
14
|
+
*
|
|
15
|
+
* This hook provides all player state information including:
|
|
16
|
+
* - Current track
|
|
17
|
+
* - Current position and duration
|
|
18
|
+
* - Playback state (playing, paused, stopped)
|
|
19
|
+
* - Current playlist ID
|
|
20
|
+
* - Current track index
|
|
21
|
+
*
|
|
22
|
+
* The hook uses native callbacks for immediate updates when state changes.
|
|
23
|
+
* Multiple components can use this hook simultaneously.
|
|
24
|
+
*
|
|
25
|
+
* @returns PlayerState object with all current player information
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* ```tsx
|
|
29
|
+
* function PlayerComponent() {
|
|
30
|
+
* const state = useNowPlaying()
|
|
31
|
+
*
|
|
32
|
+
* return (
|
|
33
|
+
* <View>
|
|
34
|
+
* {state.currentTrack && (
|
|
35
|
+
* <Text>Now Playing: {state.currentTrack.title}</Text>
|
|
36
|
+
* )}
|
|
37
|
+
* <Text>Position: {state.currentPosition} / {state.totalDuration}</Text>
|
|
38
|
+
* <Text>State: {state.currentState}</Text>
|
|
39
|
+
* <Text>Playlist: {state.currentPlaylistId || 'None'}</Text>
|
|
40
|
+
* <Text>Index: {state.currentIndex}</Text>
|
|
41
|
+
* </View>
|
|
42
|
+
* )
|
|
43
|
+
* }
|
|
44
|
+
* ```
|
|
45
|
+
*/
|
|
46
|
+
export function useNowPlaying() {
|
|
47
|
+
const [state, setState] = useState(DEFAULT_STATE);
|
|
48
|
+
const isMounted = useRef(true);
|
|
49
|
+
const updateState = useCallback(() => {
|
|
50
|
+
if (!isMounted.current)
|
|
51
|
+
return;
|
|
52
|
+
try {
|
|
53
|
+
const newState = TrackPlayer.getState();
|
|
54
|
+
setState(newState);
|
|
55
|
+
}
|
|
56
|
+
catch (error) {
|
|
57
|
+
console.error('[useNowPlaying] Error updating player state:', error);
|
|
58
|
+
}
|
|
59
|
+
}, []);
|
|
60
|
+
// Initialize with current state
|
|
61
|
+
useEffect(() => {
|
|
62
|
+
isMounted.current = true;
|
|
63
|
+
updateState();
|
|
64
|
+
return () => {
|
|
65
|
+
isMounted.current = false;
|
|
66
|
+
};
|
|
67
|
+
}, [updateState]);
|
|
68
|
+
// Subscribe to track changes
|
|
69
|
+
useEffect(() => {
|
|
70
|
+
const unsubscribe = callbackManager.subscribeToTrackChange(() => {
|
|
71
|
+
updateState();
|
|
72
|
+
});
|
|
73
|
+
return () => {
|
|
74
|
+
unsubscribe();
|
|
75
|
+
};
|
|
76
|
+
}, [updateState]);
|
|
77
|
+
// Subscribe to playback state changes
|
|
78
|
+
useEffect(() => {
|
|
79
|
+
const unsubscribe = callbackManager.subscribeToPlaybackState(() => {
|
|
80
|
+
updateState();
|
|
81
|
+
});
|
|
82
|
+
return () => {
|
|
83
|
+
unsubscribe();
|
|
84
|
+
};
|
|
85
|
+
}, [updateState]);
|
|
86
|
+
return state;
|
|
87
|
+
}
|
|
@@ -1,9 +1,36 @@
|
|
|
1
1
|
import type { TrackItem, Reason } from '../types/PlayerQueue';
|
|
2
|
+
export interface TrackChangeResult {
|
|
3
|
+
track: TrackItem | null;
|
|
4
|
+
reason: Reason | undefined;
|
|
5
|
+
isReady: boolean;
|
|
6
|
+
}
|
|
2
7
|
/**
|
|
3
|
-
* Hook to
|
|
4
|
-
*
|
|
8
|
+
* Hook to subscribe to track changes.
|
|
9
|
+
*
|
|
10
|
+
* This hook provides real-time track change updates using native callbacks.
|
|
11
|
+
* Multiple components can use this hook simultaneously without interfering
|
|
12
|
+
* with each other.
|
|
13
|
+
*
|
|
14
|
+
* @returns Object with:
|
|
15
|
+
* - track: Current track or null if no track is playing
|
|
16
|
+
* - reason: Reason for the last track change
|
|
17
|
+
* - isReady: Whether the initial state has been loaded
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* ```tsx
|
|
21
|
+
* function NowPlaying() {
|
|
22
|
+
* const { track, reason, isReady } = useOnChangeTrack()
|
|
23
|
+
*
|
|
24
|
+
* if (!isReady) return <Text>Loading...</Text>
|
|
25
|
+
* if (!track) return <Text>No track playing</Text>
|
|
26
|
+
*
|
|
27
|
+
* return (
|
|
28
|
+
* <View>
|
|
29
|
+
* <Text>{track.title}</Text>
|
|
30
|
+
* <Text>{track.artist}</Text>
|
|
31
|
+
* </View>
|
|
32
|
+
* )
|
|
33
|
+
* }
|
|
34
|
+
* ```
|
|
5
35
|
*/
|
|
6
|
-
export declare function useOnChangeTrack():
|
|
7
|
-
track: TrackItem | undefined;
|
|
8
|
-
reason: Reason | undefined;
|
|
9
|
-
};
|
|
36
|
+
export declare function useOnChangeTrack(): TrackChangeResult;
|
|
@@ -1,17 +1,73 @@
|
|
|
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
|
/**
|
|
4
|
-
* Hook to
|
|
5
|
-
*
|
|
5
|
+
* Hook to subscribe to track changes.
|
|
6
|
+
*
|
|
7
|
+
* This hook provides real-time track change updates using native callbacks.
|
|
8
|
+
* Multiple components can use this hook simultaneously without interfering
|
|
9
|
+
* with each other.
|
|
10
|
+
*
|
|
11
|
+
* @returns Object with:
|
|
12
|
+
* - track: Current track or null if no track is playing
|
|
13
|
+
* - reason: Reason for the last track change
|
|
14
|
+
* - isReady: Whether the initial state has been loaded
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* ```tsx
|
|
18
|
+
* function NowPlaying() {
|
|
19
|
+
* const { track, reason, isReady } = useOnChangeTrack()
|
|
20
|
+
*
|
|
21
|
+
* if (!isReady) return <Text>Loading...</Text>
|
|
22
|
+
* if (!track) return <Text>No track playing</Text>
|
|
23
|
+
*
|
|
24
|
+
* return (
|
|
25
|
+
* <View>
|
|
26
|
+
* <Text>{track.title}</Text>
|
|
27
|
+
* <Text>{track.artist}</Text>
|
|
28
|
+
* </View>
|
|
29
|
+
* )
|
|
30
|
+
* }
|
|
31
|
+
* ```
|
|
6
32
|
*/
|
|
7
33
|
export function useOnChangeTrack() {
|
|
8
|
-
const [track, setTrack] = useState(
|
|
34
|
+
const [track, setTrack] = useState(null);
|
|
9
35
|
const [reason, setReason] = useState(undefined);
|
|
36
|
+
const [isReady, setIsReady] = useState(false);
|
|
37
|
+
const isMounted = useRef(true);
|
|
38
|
+
// Initialize with current track from the player
|
|
10
39
|
useEffect(() => {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
40
|
+
isMounted.current = true;
|
|
41
|
+
try {
|
|
42
|
+
const playerState = TrackPlayer.getState();
|
|
43
|
+
if (isMounted.current) {
|
|
44
|
+
setTrack(playerState.currentTrack);
|
|
45
|
+
setIsReady(true);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
catch (error) {
|
|
49
|
+
console.error('[useOnChangeTrack] Failed to get initial state:', error);
|
|
50
|
+
if (isMounted.current) {
|
|
51
|
+
setTrack(null);
|
|
52
|
+
setIsReady(true);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return () => {
|
|
56
|
+
isMounted.current = false;
|
|
57
|
+
};
|
|
15
58
|
}, []);
|
|
16
|
-
|
|
59
|
+
// Subscribe to track changes
|
|
60
|
+
useEffect(() => {
|
|
61
|
+
const handleTrackChange = (newTrack, newReason) => {
|
|
62
|
+
if (isMounted.current) {
|
|
63
|
+
setTrack(newTrack);
|
|
64
|
+
setReason(newReason);
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
const unsubscribe = callbackManager.subscribeToTrackChange(handleTrackChange);
|
|
68
|
+
return () => {
|
|
69
|
+
unsubscribe();
|
|
70
|
+
};
|
|
71
|
+
}, []);
|
|
72
|
+
return { track, reason, isReady };
|
|
17
73
|
}
|
|
@@ -1,9 +1,35 @@
|
|
|
1
1
|
import type { TrackPlayerState, Reason } from '../types/PlayerQueue';
|
|
2
|
+
export interface PlaybackStateResult {
|
|
3
|
+
state: TrackPlayerState;
|
|
4
|
+
reason: Reason | undefined;
|
|
5
|
+
isReady: boolean;
|
|
6
|
+
}
|
|
2
7
|
/**
|
|
3
|
-
* Hook to
|
|
4
|
-
*
|
|
8
|
+
* Hook to subscribe to playback state changes.
|
|
9
|
+
*
|
|
10
|
+
* This hook provides real-time playback state updates using native callbacks.
|
|
11
|
+
* Multiple components can use this hook simultaneously without interfering
|
|
12
|
+
* with each other.
|
|
13
|
+
*
|
|
14
|
+
* @returns Object with:
|
|
15
|
+
* - state: Current playback state ('playing' | 'paused' | 'stopped')
|
|
16
|
+
* - reason: Reason for the last state change
|
|
17
|
+
* - isReady: Whether the initial state has been loaded
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* ```tsx
|
|
21
|
+
* function PlaybackIndicator() {
|
|
22
|
+
* const { state, reason, isReady } = useOnPlaybackStateChange()
|
|
23
|
+
*
|
|
24
|
+
* if (!isReady) return <Text>Loading...</Text>
|
|
25
|
+
*
|
|
26
|
+
* return (
|
|
27
|
+
* <View>
|
|
28
|
+
* <Text>State: {state}</Text>
|
|
29
|
+
* {reason && <Text>Reason: {reason}</Text>}
|
|
30
|
+
* </View>
|
|
31
|
+
* )
|
|
32
|
+
* }
|
|
33
|
+
* ```
|
|
5
34
|
*/
|
|
6
|
-
export declare function useOnPlaybackStateChange():
|
|
7
|
-
state: TrackPlayerState | undefined;
|
|
8
|
-
reason: Reason | undefined;
|
|
9
|
-
};
|
|
35
|
+
export declare function useOnPlaybackStateChange(): PlaybackStateResult;
|
|
@@ -1,17 +1,73 @@
|
|
|
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
|
/**
|
|
4
|
-
* Hook to
|
|
5
|
-
*
|
|
5
|
+
* Hook to subscribe to playback state changes.
|
|
6
|
+
*
|
|
7
|
+
* This hook provides real-time playback state updates using native callbacks.
|
|
8
|
+
* Multiple components can use this hook simultaneously without interfering
|
|
9
|
+
* with each other.
|
|
10
|
+
*
|
|
11
|
+
* @returns Object with:
|
|
12
|
+
* - state: Current playback state ('playing' | 'paused' | 'stopped')
|
|
13
|
+
* - reason: Reason for the last state change
|
|
14
|
+
* - isReady: Whether the initial state has been loaded
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* ```tsx
|
|
18
|
+
* function PlaybackIndicator() {
|
|
19
|
+
* const { state, reason, isReady } = useOnPlaybackStateChange()
|
|
20
|
+
*
|
|
21
|
+
* if (!isReady) return <Text>Loading...</Text>
|
|
22
|
+
*
|
|
23
|
+
* return (
|
|
24
|
+
* <View>
|
|
25
|
+
* <Text>State: {state}</Text>
|
|
26
|
+
* {reason && <Text>Reason: {reason}</Text>}
|
|
27
|
+
* </View>
|
|
28
|
+
* )
|
|
29
|
+
* }
|
|
30
|
+
* ```
|
|
6
31
|
*/
|
|
7
32
|
export function useOnPlaybackStateChange() {
|
|
8
|
-
const [state, setState] = useState(
|
|
33
|
+
const [state, setState] = useState('stopped');
|
|
9
34
|
const [reason, setReason] = useState(undefined);
|
|
35
|
+
const [isReady, setIsReady] = useState(false);
|
|
36
|
+
const isMounted = useRef(true);
|
|
37
|
+
// Initialize with current state from the player
|
|
10
38
|
useEffect(() => {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
39
|
+
isMounted.current = true;
|
|
40
|
+
// Get initial state synchronously
|
|
41
|
+
try {
|
|
42
|
+
const playerState = TrackPlayer.getState();
|
|
43
|
+
if (isMounted.current) {
|
|
44
|
+
setState(playerState.currentState);
|
|
45
|
+
setIsReady(true);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
catch (error) {
|
|
49
|
+
console.error('[useOnPlaybackStateChange] Failed to get initial state:', error);
|
|
50
|
+
if (isMounted.current) {
|
|
51
|
+
setState('stopped');
|
|
52
|
+
setIsReady(true);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return () => {
|
|
56
|
+
isMounted.current = false;
|
|
57
|
+
};
|
|
15
58
|
}, []);
|
|
16
|
-
|
|
59
|
+
// Subscribe to playback state changes
|
|
60
|
+
useEffect(() => {
|
|
61
|
+
const handleStateChange = (newState, newReason) => {
|
|
62
|
+
if (isMounted.current) {
|
|
63
|
+
setState(newState);
|
|
64
|
+
setReason(newReason);
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
const unsubscribe = callbackManager.subscribeToPlaybackState(handleStateChange);
|
|
68
|
+
return () => {
|
|
69
|
+
unsubscribe();
|
|
70
|
+
};
|
|
71
|
+
}, []);
|
|
72
|
+
return { state, reason, isReady };
|
|
17
73
|
}
|