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
|
@@ -190,6 +190,46 @@ open class HybridTrackPlayerSpec_cxx {
|
|
|
190
190
|
}
|
|
191
191
|
}
|
|
192
192
|
|
|
193
|
+
@inline(__always)
|
|
194
|
+
public final func addToUpNext(trackId: std.string) -> bridge.Result_void_ {
|
|
195
|
+
do {
|
|
196
|
+
try self.__implementation.addToUpNext(trackId: String(trackId))
|
|
197
|
+
return bridge.create_Result_void_()
|
|
198
|
+
} catch (let __error) {
|
|
199
|
+
let __exceptionPtr = __error.toCpp()
|
|
200
|
+
return bridge.create_Result_void_(__exceptionPtr)
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
@inline(__always)
|
|
205
|
+
public final func playNext(trackId: std.string) -> bridge.Result_void_ {
|
|
206
|
+
do {
|
|
207
|
+
try self.__implementation.playNext(trackId: String(trackId))
|
|
208
|
+
return bridge.create_Result_void_()
|
|
209
|
+
} catch (let __error) {
|
|
210
|
+
let __exceptionPtr = __error.toCpp()
|
|
211
|
+
return bridge.create_Result_void_(__exceptionPtr)
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
@inline(__always)
|
|
216
|
+
public final func getActualQueue() -> bridge.Result_std__vector_TrackItem__ {
|
|
217
|
+
do {
|
|
218
|
+
let __result = try self.__implementation.getActualQueue()
|
|
219
|
+
let __resultCpp = { () -> bridge.std__vector_TrackItem_ in
|
|
220
|
+
var __vector = bridge.create_std__vector_TrackItem_(__result.count)
|
|
221
|
+
for __item in __result {
|
|
222
|
+
__vector.push_back(__item)
|
|
223
|
+
}
|
|
224
|
+
return __vector
|
|
225
|
+
}()
|
|
226
|
+
return bridge.create_Result_std__vector_TrackItem__(__resultCpp)
|
|
227
|
+
} catch (let __error) {
|
|
228
|
+
let __exceptionPtr = __error.toCpp()
|
|
229
|
+
return bridge.create_Result_std__vector_TrackItem__(__exceptionPtr)
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
193
233
|
@inline(__always)
|
|
194
234
|
public final func getState() -> bridge.Result_PlayerState_ {
|
|
195
235
|
do {
|
|
@@ -202,6 +242,18 @@ open class HybridTrackPlayerSpec_cxx {
|
|
|
202
242
|
}
|
|
203
243
|
}
|
|
204
244
|
|
|
245
|
+
@inline(__always)
|
|
246
|
+
public final func setRepeatMode(mode: Int32) -> bridge.Result_bool_ {
|
|
247
|
+
do {
|
|
248
|
+
let __result = try self.__implementation.setRepeatMode(mode: margelo.nitro.nitroplayer.RepeatMode(rawValue: mode)!)
|
|
249
|
+
let __resultCpp = __result
|
|
250
|
+
return bridge.create_Result_bool_(__resultCpp)
|
|
251
|
+
} catch (let __error) {
|
|
252
|
+
let __exceptionPtr = __error.toCpp()
|
|
253
|
+
return bridge.create_Result_bool_(__exceptionPtr)
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
205
257
|
@inline(__always)
|
|
206
258
|
public final func configure(config: PlayerConfig) -> bridge.Result_void_ {
|
|
207
259
|
do {
|
|
@@ -322,4 +374,16 @@ open class HybridTrackPlayerSpec_cxx {
|
|
|
322
374
|
return bridge.create_Result_bool_(__exceptionPtr)
|
|
323
375
|
}
|
|
324
376
|
}
|
|
377
|
+
|
|
378
|
+
@inline(__always)
|
|
379
|
+
public final func setVolume(volume: Double) -> bridge.Result_bool_ {
|
|
380
|
+
do {
|
|
381
|
+
let __result = try self.__implementation.setVolume(volume: volume)
|
|
382
|
+
let __resultCpp = __result
|
|
383
|
+
return bridge.create_Result_bool_(__resultCpp)
|
|
384
|
+
} catch (let __error) {
|
|
385
|
+
let __exceptionPtr = __error.toCpp()
|
|
386
|
+
return bridge.create_Result_bool_(__exceptionPtr)
|
|
387
|
+
}
|
|
388
|
+
}
|
|
325
389
|
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
///
|
|
2
|
+
/// RepeatMode.swift
|
|
3
|
+
/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE.
|
|
4
|
+
/// https://github.com/mrousavy/nitro
|
|
5
|
+
/// Copyright © 2026 Marc Rousavy @ Margelo
|
|
6
|
+
///
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Represents the JS union `RepeatMode`, backed by a C++ enum.
|
|
10
|
+
*/
|
|
11
|
+
public typealias RepeatMode = margelo.nitro.nitroplayer.RepeatMode
|
|
12
|
+
|
|
13
|
+
public extension RepeatMode {
|
|
14
|
+
/**
|
|
15
|
+
* Get a RepeatMode for the given String value, or
|
|
16
|
+
* return `nil` if the given value was invalid/unknown.
|
|
17
|
+
*/
|
|
18
|
+
init?(fromString string: String) {
|
|
19
|
+
switch string {
|
|
20
|
+
case "off":
|
|
21
|
+
self = .off
|
|
22
|
+
case "Playlist":
|
|
23
|
+
self = .playlist
|
|
24
|
+
case "track":
|
|
25
|
+
self = .track
|
|
26
|
+
default:
|
|
27
|
+
return nil
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Get the String value this RepeatMode represents.
|
|
33
|
+
*/
|
|
34
|
+
var stringValue: String {
|
|
35
|
+
switch self {
|
|
36
|
+
case .off:
|
|
37
|
+
return "off"
|
|
38
|
+
case .playlist:
|
|
39
|
+
return "Playlist"
|
|
40
|
+
case .track:
|
|
41
|
+
return "track"
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -20,7 +20,11 @@ namespace margelo::nitro::nitroplayer {
|
|
|
20
20
|
prototype.registerHybridMethod("skipToNext", &HybridTrackPlayerSpec::skipToNext);
|
|
21
21
|
prototype.registerHybridMethod("skipToPrevious", &HybridTrackPlayerSpec::skipToPrevious);
|
|
22
22
|
prototype.registerHybridMethod("seek", &HybridTrackPlayerSpec::seek);
|
|
23
|
+
prototype.registerHybridMethod("addToUpNext", &HybridTrackPlayerSpec::addToUpNext);
|
|
24
|
+
prototype.registerHybridMethod("playNext", &HybridTrackPlayerSpec::playNext);
|
|
25
|
+
prototype.registerHybridMethod("getActualQueue", &HybridTrackPlayerSpec::getActualQueue);
|
|
23
26
|
prototype.registerHybridMethod("getState", &HybridTrackPlayerSpec::getState);
|
|
27
|
+
prototype.registerHybridMethod("setRepeatMode", &HybridTrackPlayerSpec::setRepeatMode);
|
|
24
28
|
prototype.registerHybridMethod("configure", &HybridTrackPlayerSpec::configure);
|
|
25
29
|
prototype.registerHybridMethod("onChangeTrack", &HybridTrackPlayerSpec::onChangeTrack);
|
|
26
30
|
prototype.registerHybridMethod("onPlaybackStateChange", &HybridTrackPlayerSpec::onPlaybackStateChange);
|
|
@@ -28,6 +32,7 @@ namespace margelo::nitro::nitroplayer {
|
|
|
28
32
|
prototype.registerHybridMethod("onPlaybackProgressChange", &HybridTrackPlayerSpec::onPlaybackProgressChange);
|
|
29
33
|
prototype.registerHybridMethod("onAndroidAutoConnectionChange", &HybridTrackPlayerSpec::onAndroidAutoConnectionChange);
|
|
30
34
|
prototype.registerHybridMethod("isAndroidAutoConnected", &HybridTrackPlayerSpec::isAndroidAutoConnected);
|
|
35
|
+
prototype.registerHybridMethod("setVolume", &HybridTrackPlayerSpec::setVolume);
|
|
31
36
|
});
|
|
32
37
|
}
|
|
33
38
|
|
|
@@ -13,12 +13,14 @@
|
|
|
13
13
|
#error NitroModules cannot be found! Are you sure you installed NitroModules properly?
|
|
14
14
|
#endif
|
|
15
15
|
|
|
16
|
+
// Forward declaration of `TrackItem` to properly resolve imports.
|
|
17
|
+
namespace margelo::nitro::nitroplayer { struct TrackItem; }
|
|
16
18
|
// Forward declaration of `PlayerState` to properly resolve imports.
|
|
17
19
|
namespace margelo::nitro::nitroplayer { struct PlayerState; }
|
|
20
|
+
// Forward declaration of `RepeatMode` to properly resolve imports.
|
|
21
|
+
namespace margelo::nitro::nitroplayer { enum class RepeatMode; }
|
|
18
22
|
// Forward declaration of `PlayerConfig` to properly resolve imports.
|
|
19
23
|
namespace margelo::nitro::nitroplayer { struct PlayerConfig; }
|
|
20
|
-
// Forward declaration of `TrackItem` to properly resolve imports.
|
|
21
|
-
namespace margelo::nitro::nitroplayer { struct TrackItem; }
|
|
22
24
|
// Forward declaration of `Reason` to properly resolve imports.
|
|
23
25
|
namespace margelo::nitro::nitroplayer { enum class Reason; }
|
|
24
26
|
// Forward declaration of `TrackPlayerState` to properly resolve imports.
|
|
@@ -26,9 +28,11 @@ namespace margelo::nitro::nitroplayer { enum class TrackPlayerState; }
|
|
|
26
28
|
|
|
27
29
|
#include <string>
|
|
28
30
|
#include <optional>
|
|
31
|
+
#include "TrackItem.hpp"
|
|
32
|
+
#include <vector>
|
|
29
33
|
#include "PlayerState.hpp"
|
|
34
|
+
#include "RepeatMode.hpp"
|
|
30
35
|
#include "PlayerConfig.hpp"
|
|
31
|
-
#include "TrackItem.hpp"
|
|
32
36
|
#include "Reason.hpp"
|
|
33
37
|
#include <functional>
|
|
34
38
|
#include "TrackPlayerState.hpp"
|
|
@@ -70,7 +74,11 @@ namespace margelo::nitro::nitroplayer {
|
|
|
70
74
|
virtual void skipToNext() = 0;
|
|
71
75
|
virtual void skipToPrevious() = 0;
|
|
72
76
|
virtual void seek(double position) = 0;
|
|
77
|
+
virtual void addToUpNext(const std::string& trackId) = 0;
|
|
78
|
+
virtual void playNext(const std::string& trackId) = 0;
|
|
79
|
+
virtual std::vector<TrackItem> getActualQueue() = 0;
|
|
73
80
|
virtual PlayerState getState() = 0;
|
|
81
|
+
virtual bool setRepeatMode(RepeatMode mode) = 0;
|
|
74
82
|
virtual void configure(const PlayerConfig& config) = 0;
|
|
75
83
|
virtual void onChangeTrack(const std::function<void(const TrackItem& /* track */, std::optional<Reason> /* reason */)>& callback) = 0;
|
|
76
84
|
virtual void onPlaybackStateChange(const std::function<void(TrackPlayerState /* state */, std::optional<Reason> /* reason */)>& callback) = 0;
|
|
@@ -78,6 +86,7 @@ namespace margelo::nitro::nitroplayer {
|
|
|
78
86
|
virtual void onPlaybackProgressChange(const std::function<void(double /* position */, double /* totalDuration */, std::optional<bool> /* isManuallySeeked */)>& callback) = 0;
|
|
79
87
|
virtual void onAndroidAutoConnectionChange(const std::function<void(bool /* connected */)>& callback) = 0;
|
|
80
88
|
virtual bool isAndroidAutoConnected() = 0;
|
|
89
|
+
virtual bool setVolume(double volume) = 0;
|
|
81
90
|
|
|
82
91
|
protected:
|
|
83
92
|
// Hybrid Setup
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
///
|
|
2
|
+
/// RepeatMode.hpp
|
|
3
|
+
/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE.
|
|
4
|
+
/// https://github.com/mrousavy/nitro
|
|
5
|
+
/// Copyright © 2026 Marc Rousavy @ Margelo
|
|
6
|
+
///
|
|
7
|
+
|
|
8
|
+
#pragma once
|
|
9
|
+
|
|
10
|
+
#if __has_include(<NitroModules/NitroHash.hpp>)
|
|
11
|
+
#include <NitroModules/NitroHash.hpp>
|
|
12
|
+
#else
|
|
13
|
+
#error NitroModules cannot be found! Are you sure you installed NitroModules properly?
|
|
14
|
+
#endif
|
|
15
|
+
#if __has_include(<NitroModules/JSIConverter.hpp>)
|
|
16
|
+
#include <NitroModules/JSIConverter.hpp>
|
|
17
|
+
#else
|
|
18
|
+
#error NitroModules cannot be found! Are you sure you installed NitroModules properly?
|
|
19
|
+
#endif
|
|
20
|
+
#if __has_include(<NitroModules/NitroDefines.hpp>)
|
|
21
|
+
#include <NitroModules/NitroDefines.hpp>
|
|
22
|
+
#else
|
|
23
|
+
#error NitroModules cannot be found! Are you sure you installed NitroModules properly?
|
|
24
|
+
#endif
|
|
25
|
+
|
|
26
|
+
namespace margelo::nitro::nitroplayer {
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* An enum which can be represented as a JavaScript union (RepeatMode).
|
|
30
|
+
*/
|
|
31
|
+
enum class RepeatMode {
|
|
32
|
+
OFF SWIFT_NAME(off) = 0,
|
|
33
|
+
PLAYLIST SWIFT_NAME(playlist) = 1,
|
|
34
|
+
TRACK SWIFT_NAME(track) = 2,
|
|
35
|
+
} CLOSED_ENUM;
|
|
36
|
+
|
|
37
|
+
} // namespace margelo::nitro::nitroplayer
|
|
38
|
+
|
|
39
|
+
namespace margelo::nitro {
|
|
40
|
+
|
|
41
|
+
// C++ RepeatMode <> JS RepeatMode (union)
|
|
42
|
+
template <>
|
|
43
|
+
struct JSIConverter<margelo::nitro::nitroplayer::RepeatMode> final {
|
|
44
|
+
static inline margelo::nitro::nitroplayer::RepeatMode fromJSI(jsi::Runtime& runtime, const jsi::Value& arg) {
|
|
45
|
+
std::string unionValue = JSIConverter<std::string>::fromJSI(runtime, arg);
|
|
46
|
+
switch (hashString(unionValue.c_str(), unionValue.size())) {
|
|
47
|
+
case hashString("off"): return margelo::nitro::nitroplayer::RepeatMode::OFF;
|
|
48
|
+
case hashString("Playlist"): return margelo::nitro::nitroplayer::RepeatMode::PLAYLIST;
|
|
49
|
+
case hashString("track"): return margelo::nitro::nitroplayer::RepeatMode::TRACK;
|
|
50
|
+
default: [[unlikely]]
|
|
51
|
+
throw std::invalid_argument("Cannot convert \"" + unionValue + "\" to enum RepeatMode - invalid value!");
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
static inline jsi::Value toJSI(jsi::Runtime& runtime, margelo::nitro::nitroplayer::RepeatMode arg) {
|
|
55
|
+
switch (arg) {
|
|
56
|
+
case margelo::nitro::nitroplayer::RepeatMode::OFF: return JSIConverter<std::string>::toJSI(runtime, "off");
|
|
57
|
+
case margelo::nitro::nitroplayer::RepeatMode::PLAYLIST: return JSIConverter<std::string>::toJSI(runtime, "Playlist");
|
|
58
|
+
case margelo::nitro::nitroplayer::RepeatMode::TRACK: return JSIConverter<std::string>::toJSI(runtime, "track");
|
|
59
|
+
default: [[unlikely]]
|
|
60
|
+
throw std::invalid_argument("Cannot convert RepeatMode to JS - invalid value: "
|
|
61
|
+
+ std::to_string(static_cast<int>(arg)) + "!");
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
static inline bool canConvert(jsi::Runtime& runtime, const jsi::Value& value) {
|
|
65
|
+
if (!value.isString()) {
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
std::string unionValue = JSIConverter<std::string>::fromJSI(runtime, value);
|
|
69
|
+
switch (hashString(unionValue.c_str(), unionValue.size())) {
|
|
70
|
+
case hashString("off"):
|
|
71
|
+
case hashString("Playlist"):
|
|
72
|
+
case hashString("track"):
|
|
73
|
+
return true;
|
|
74
|
+
default:
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
} // namespace margelo::nitro
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-native-nitro-player",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.3.0-alpha.10",
|
|
4
4
|
"description": "react-native-nitro-player",
|
|
5
5
|
"main": "lib/index",
|
|
6
6
|
"module": "lib/index",
|
|
@@ -30,12 +30,14 @@
|
|
|
30
30
|
"scripts": {
|
|
31
31
|
"postinstall": "tsc || exit 0;",
|
|
32
32
|
"typecheck": "tsc --noEmit",
|
|
33
|
-
"clean": "rm -rf android/build node_modules/**/android/build lib",
|
|
33
|
+
"clean": "rm -rf android/build node_modules/**/android/build lib nitrogen tsconfig.tsbuildinfo",
|
|
34
34
|
"lint": "eslint \"**/*.{js,ts,tsx}\" --fix",
|
|
35
35
|
"lint-ci": "eslint \"**/*.{js,ts,tsx}\" -f @jamesacarr/github-actions",
|
|
36
36
|
"typescript": "tsc",
|
|
37
|
-
"specs": "tsc
|
|
38
|
-
"
|
|
37
|
+
"specs": "tsc && nitrogen --logLevel=\"debug\"",
|
|
38
|
+
"copy-readme": "cp ../README.md README.md",
|
|
39
|
+
"release": "release-it",
|
|
40
|
+
"build": "bun run clean && bun run specs && bun run typescript && bun run copy-readme"
|
|
39
41
|
},
|
|
40
42
|
"keywords": [
|
|
41
43
|
"react-native",
|
|
@@ -43,14 +45,14 @@
|
|
|
43
45
|
],
|
|
44
46
|
"repository": {
|
|
45
47
|
"type": "git",
|
|
46
|
-
"url": "git+https://github.com/
|
|
48
|
+
"url": "git+https://github.com/riteshshukla04/react-native-nitro-player"
|
|
47
49
|
},
|
|
48
|
-
"author": "
|
|
50
|
+
"author": "Ritesh Shukla <riteshshukla2381@gmail.com> (https://github.com/riteshshukla04)",
|
|
49
51
|
"license": "MIT",
|
|
50
52
|
"bugs": {
|
|
51
|
-
"url": "https://github.com/
|
|
53
|
+
"url": "https://github.com/riteshshukla04/react-native-nitro-player/issues"
|
|
52
54
|
},
|
|
53
|
-
"homepage": "https://github.com/
|
|
55
|
+
"homepage": "https://github.com/riteshshukla04/react-native-nitro-player#readme",
|
|
54
56
|
"publishConfig": {
|
|
55
57
|
"registry": "https://registry.npmjs.org/"
|
|
56
58
|
},
|
|
@@ -122,14 +124,13 @@
|
|
|
122
124
|
"github": {
|
|
123
125
|
"release": true
|
|
124
126
|
},
|
|
127
|
+
"hooks": {
|
|
128
|
+
"before:release": "bun run clean && bun run specs && bun run typescript && bun run copy-readme"
|
|
129
|
+
},
|
|
125
130
|
"plugins": {
|
|
126
131
|
"@release-it/bumper": {
|
|
127
132
|
"in": "package.json",
|
|
128
133
|
"out": [
|
|
129
|
-
{
|
|
130
|
-
"file": "package.json",
|
|
131
|
-
"path": "version"
|
|
132
|
-
},
|
|
133
134
|
{
|
|
134
135
|
"file": "../package.json",
|
|
135
136
|
"path": "version"
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { TrackPlayer } from '../index'
|
|
2
|
+
import type { TrackItem, TrackPlayerState, Reason } from '../types/PlayerQueue'
|
|
3
|
+
|
|
4
|
+
type PlaybackStateCallback = (state: TrackPlayerState, reason?: Reason) => void
|
|
5
|
+
type TrackChangeCallback = (track: TrackItem, reason?: Reason) => void
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Internal subscription manager that allows multiple hooks to subscribe
|
|
9
|
+
* to a single native callback. This solves the problem where registering
|
|
10
|
+
* a new callback overwrites the previous one.
|
|
11
|
+
*/
|
|
12
|
+
class CallbackSubscriptionManager {
|
|
13
|
+
private playbackStateSubscribers = new Set<PlaybackStateCallback>()
|
|
14
|
+
private trackChangeSubscribers = new Set<TrackChangeCallback>()
|
|
15
|
+
private isPlaybackStateRegistered = false
|
|
16
|
+
private isTrackChangeRegistered = false
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Subscribe to playback state changes
|
|
20
|
+
* @returns Unsubscribe function
|
|
21
|
+
*/
|
|
22
|
+
subscribeToPlaybackState(callback: PlaybackStateCallback): () => void {
|
|
23
|
+
this.playbackStateSubscribers.add(callback)
|
|
24
|
+
this.ensurePlaybackStateRegistered()
|
|
25
|
+
|
|
26
|
+
return () => {
|
|
27
|
+
this.playbackStateSubscribers.delete(callback)
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Subscribe to track changes
|
|
33
|
+
* @returns Unsubscribe function
|
|
34
|
+
*/
|
|
35
|
+
subscribeToTrackChange(callback: TrackChangeCallback): () => void {
|
|
36
|
+
this.trackChangeSubscribers.add(callback)
|
|
37
|
+
this.ensureTrackChangeRegistered()
|
|
38
|
+
|
|
39
|
+
return () => {
|
|
40
|
+
this.trackChangeSubscribers.delete(callback)
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
private ensurePlaybackStateRegistered(): void {
|
|
45
|
+
if (this.isPlaybackStateRegistered) return
|
|
46
|
+
|
|
47
|
+
try {
|
|
48
|
+
TrackPlayer.onPlaybackStateChange((state, reason) => {
|
|
49
|
+
this.playbackStateSubscribers.forEach((subscriber) => {
|
|
50
|
+
try {
|
|
51
|
+
subscriber(state, reason)
|
|
52
|
+
} catch (error) {
|
|
53
|
+
console.error(
|
|
54
|
+
'[CallbackManager] Error in playback state subscriber:',
|
|
55
|
+
error
|
|
56
|
+
)
|
|
57
|
+
}
|
|
58
|
+
})
|
|
59
|
+
})
|
|
60
|
+
this.isPlaybackStateRegistered = true
|
|
61
|
+
} catch (error) {
|
|
62
|
+
console.error(
|
|
63
|
+
'[CallbackManager] Failed to register playback state callback:',
|
|
64
|
+
error
|
|
65
|
+
)
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
private ensureTrackChangeRegistered(): void {
|
|
70
|
+
if (this.isTrackChangeRegistered) return
|
|
71
|
+
|
|
72
|
+
try {
|
|
73
|
+
TrackPlayer.onChangeTrack((track, reason) => {
|
|
74
|
+
this.trackChangeSubscribers.forEach((subscriber) => {
|
|
75
|
+
try {
|
|
76
|
+
subscriber(track, reason)
|
|
77
|
+
} catch (error) {
|
|
78
|
+
console.error(
|
|
79
|
+
'[CallbackManager] Error in track change subscriber:',
|
|
80
|
+
error
|
|
81
|
+
)
|
|
82
|
+
}
|
|
83
|
+
})
|
|
84
|
+
})
|
|
85
|
+
this.isTrackChangeRegistered = true
|
|
86
|
+
} catch (error) {
|
|
87
|
+
console.error(
|
|
88
|
+
'[CallbackManager] Failed to register track change callback:',
|
|
89
|
+
error
|
|
90
|
+
)
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Export singleton instance
|
|
96
|
+
export const callbackManager = new CallbackSubscriptionManager()
|
package/src/hooks/index.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'
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { useEffect, useState, useRef, useCallback } from 'react'
|
|
2
|
+
import { TrackPlayer } from '../index'
|
|
3
|
+
import { callbackManager } from './callbackManager'
|
|
4
|
+
import type { TrackItem } from '../types/PlayerQueue'
|
|
5
|
+
|
|
6
|
+
export interface UseActualQueueResult {
|
|
7
|
+
/** The current queue in playback order */
|
|
8
|
+
queue: TrackItem[]
|
|
9
|
+
/** Manually refresh the queue */
|
|
10
|
+
refreshQueue: () => void
|
|
11
|
+
/** Whether the queue is currently loading */
|
|
12
|
+
isLoading: boolean
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Hook to get the actual playback queue including temporary tracks
|
|
17
|
+
*
|
|
18
|
+
* Returns the complete queue in playback order:
|
|
19
|
+
* [tracks_before_current] + [current] + [playNext_stack] + [upNext_queue] + [remaining_tracks]
|
|
20
|
+
*
|
|
21
|
+
* Auto-updates when:
|
|
22
|
+
* - Track changes
|
|
23
|
+
* - Playback state changes
|
|
24
|
+
*
|
|
25
|
+
* Call `refreshQueue()` after adding tracks via `playNext()` or `addToUpNext()`
|
|
26
|
+
* to immediately see the updated queue.
|
|
27
|
+
*
|
|
28
|
+
* @returns Object containing queue array, refresh function, and loading state
|
|
29
|
+
*
|
|
30
|
+
* @example
|
|
31
|
+
* ```tsx
|
|
32
|
+
* function QueueView() {
|
|
33
|
+
* const { queue, refreshQueue, isLoading } = useActualQueue();
|
|
34
|
+
*
|
|
35
|
+
* const handleAddToUpNext = (trackId: string) => {
|
|
36
|
+
* TrackPlayer.addToUpNext(trackId);
|
|
37
|
+
* // Refresh queue after adding track
|
|
38
|
+
* setTimeout(refreshQueue, 100);
|
|
39
|
+
* };
|
|
40
|
+
*
|
|
41
|
+
* return (
|
|
42
|
+
* <ScrollView>
|
|
43
|
+
* {queue.map((track, index) => (
|
|
44
|
+
* <Text key={track.id}>
|
|
45
|
+
* {index + 1}. {track.title}
|
|
46
|
+
* </Text>
|
|
47
|
+
* ))}
|
|
48
|
+
* </ScrollView>
|
|
49
|
+
* );
|
|
50
|
+
* }
|
|
51
|
+
* ```
|
|
52
|
+
*/
|
|
53
|
+
export function useActualQueue(): UseActualQueueResult {
|
|
54
|
+
const [queue, setQueue] = useState<TrackItem[]>([])
|
|
55
|
+
const [isLoading, setIsLoading] = useState(true)
|
|
56
|
+
const isMounted = useRef(true)
|
|
57
|
+
|
|
58
|
+
const updateQueue = useCallback(() => {
|
|
59
|
+
if (!isMounted.current) return
|
|
60
|
+
|
|
61
|
+
try {
|
|
62
|
+
const actualQueue = TrackPlayer.getActualQueue()
|
|
63
|
+
if (isMounted.current) {
|
|
64
|
+
setQueue(actualQueue)
|
|
65
|
+
setIsLoading(false)
|
|
66
|
+
}
|
|
67
|
+
} catch (error) {
|
|
68
|
+
console.error('[useActualQueue] Error getting queue:', error)
|
|
69
|
+
if (isMounted.current) {
|
|
70
|
+
setQueue([])
|
|
71
|
+
setIsLoading(false)
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}, [])
|
|
75
|
+
|
|
76
|
+
const refreshQueue = useCallback(() => {
|
|
77
|
+
if (!isMounted.current) return
|
|
78
|
+
setIsLoading(true)
|
|
79
|
+
updateQueue()
|
|
80
|
+
}, [updateQueue])
|
|
81
|
+
|
|
82
|
+
// Initialize queue
|
|
83
|
+
useEffect(() => {
|
|
84
|
+
isMounted.current = true
|
|
85
|
+
updateQueue()
|
|
86
|
+
|
|
87
|
+
return () => {
|
|
88
|
+
isMounted.current = false
|
|
89
|
+
}
|
|
90
|
+
}, [updateQueue])
|
|
91
|
+
|
|
92
|
+
// Update queue on track changes (with slight delay to ensure native side has updated)
|
|
93
|
+
useEffect(() => {
|
|
94
|
+
const unsubscribe = callbackManager.subscribeToTrackChange(() => {
|
|
95
|
+
// Small delay to ensure native queue is updated
|
|
96
|
+
setTimeout(updateQueue, 50)
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
return () => {
|
|
100
|
+
unsubscribe()
|
|
101
|
+
}
|
|
102
|
+
}, [updateQueue])
|
|
103
|
+
|
|
104
|
+
// Update queue on playback state changes
|
|
105
|
+
useEffect(() => {
|
|
106
|
+
const unsubscribe = callbackManager.subscribeToPlaybackState(() => {
|
|
107
|
+
updateQueue()
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
return () => {
|
|
111
|
+
unsubscribe()
|
|
112
|
+
}
|
|
113
|
+
}, [updateQueue])
|
|
114
|
+
|
|
115
|
+
return { queue, refreshQueue, isLoading }
|
|
116
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { useEffect, useState, useRef, useCallback } from 'react'
|
|
2
|
+
import { TrackPlayer } from '../index'
|
|
3
|
+
import { callbackManager } from './callbackManager'
|
|
4
|
+
import type { PlayerState } from '../types/PlayerQueue'
|
|
5
|
+
|
|
6
|
+
const DEFAULT_STATE: PlayerState = {
|
|
7
|
+
currentTrack: null,
|
|
8
|
+
currentPosition: 0,
|
|
9
|
+
totalDuration: 0,
|
|
10
|
+
currentState: 'stopped',
|
|
11
|
+
currentPlaylistId: null,
|
|
12
|
+
currentIndex: -1,
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Hook to get the current player state (same as TrackPlayer.getState())
|
|
17
|
+
*
|
|
18
|
+
* This hook provides all player state information including:
|
|
19
|
+
* - Current track
|
|
20
|
+
* - Current position and duration
|
|
21
|
+
* - Playback state (playing, paused, stopped)
|
|
22
|
+
* - Current playlist ID
|
|
23
|
+
* - Current track index
|
|
24
|
+
*
|
|
25
|
+
* The hook uses native callbacks for immediate updates when state changes.
|
|
26
|
+
* Multiple components can use this hook simultaneously.
|
|
27
|
+
*
|
|
28
|
+
* @returns PlayerState object with all current player information
|
|
29
|
+
*
|
|
30
|
+
* @example
|
|
31
|
+
* ```tsx
|
|
32
|
+
* function PlayerComponent() {
|
|
33
|
+
* const state = useNowPlaying()
|
|
34
|
+
*
|
|
35
|
+
* return (
|
|
36
|
+
* <View>
|
|
37
|
+
* {state.currentTrack && (
|
|
38
|
+
* <Text>Now Playing: {state.currentTrack.title}</Text>
|
|
39
|
+
* )}
|
|
40
|
+
* <Text>Position: {state.currentPosition} / {state.totalDuration}</Text>
|
|
41
|
+
* <Text>State: {state.currentState}</Text>
|
|
42
|
+
* <Text>Playlist: {state.currentPlaylistId || 'None'}</Text>
|
|
43
|
+
* <Text>Index: {state.currentIndex}</Text>
|
|
44
|
+
* </View>
|
|
45
|
+
* )
|
|
46
|
+
* }
|
|
47
|
+
* ```
|
|
48
|
+
*/
|
|
49
|
+
export function useNowPlaying(): PlayerState {
|
|
50
|
+
const [state, setState] = useState<PlayerState>(DEFAULT_STATE)
|
|
51
|
+
const isMounted = useRef(true)
|
|
52
|
+
|
|
53
|
+
const updateState = useCallback(() => {
|
|
54
|
+
if (!isMounted.current) return
|
|
55
|
+
|
|
56
|
+
try {
|
|
57
|
+
const newState = TrackPlayer.getState()
|
|
58
|
+
setState(newState)
|
|
59
|
+
} catch (error) {
|
|
60
|
+
console.error('[useNowPlaying] Error updating player state:', error)
|
|
61
|
+
}
|
|
62
|
+
}, [])
|
|
63
|
+
|
|
64
|
+
// Initialize with current state
|
|
65
|
+
useEffect(() => {
|
|
66
|
+
isMounted.current = true
|
|
67
|
+
updateState()
|
|
68
|
+
|
|
69
|
+
return () => {
|
|
70
|
+
isMounted.current = false
|
|
71
|
+
}
|
|
72
|
+
}, [updateState])
|
|
73
|
+
|
|
74
|
+
// Subscribe to track changes
|
|
75
|
+
useEffect(() => {
|
|
76
|
+
const unsubscribe = callbackManager.subscribeToTrackChange(() => {
|
|
77
|
+
updateState()
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
return () => {
|
|
81
|
+
unsubscribe()
|
|
82
|
+
}
|
|
83
|
+
}, [updateState])
|
|
84
|
+
|
|
85
|
+
// Subscribe to playback state changes
|
|
86
|
+
useEffect(() => {
|
|
87
|
+
const unsubscribe = callbackManager.subscribeToPlaybackState(() => {
|
|
88
|
+
updateState()
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
return () => {
|
|
92
|
+
unsubscribe()
|
|
93
|
+
}
|
|
94
|
+
}, [updateState])
|
|
95
|
+
|
|
96
|
+
return state
|
|
97
|
+
}
|