react-native-queue-player 1.0.7 → 1.0.9
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/queueplayer/MediaItemBuilder.kt +21 -3
- package/android/src/main/java/com/margelo/nitro/queueplayer/PlaceholderArtwork.kt +30 -0
- package/android/src/main/java/com/margelo/nitro/queueplayer/PlaceholderFallbackBitmapLoader.kt +9 -6
- package/android/src/main/java/com/margelo/nitro/queueplayer/PlaybackService.kt +4 -1
- package/android/src/main/java/com/margelo/nitro/queueplayer/TrackPlayer.kt +48 -5
- package/android/src/test/java/com/margelo/nitro/queueplayer/MediaItemBuilderTest.kt +55 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/PlaceholderArtworkTest.kt +72 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/PlaceholderFallbackBitmapLoaderTest.kt +21 -9
- package/android/src/test/java/com/margelo/nitro/queueplayer/PlaybackServiceArtworkLoaderTest.kt +9 -9
- package/android/src/test/java/com/margelo/nitro/queueplayer/PlaybackServiceCallbackTest.kt +1 -1
- package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerAirPlayMetadataWiringTest.kt +1 -1
- package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerAudioFocusTest.kt +1 -1
- package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerEventsTest.kt +3 -3
- package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerLifecycleTest.kt +7 -7
- package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerLookaheadConfigTest.kt +6 -6
- package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerMutationTest.kt +2 -2
- package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerProgressThrottleTest.kt +1 -1
- package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerQueueChangeTest.kt +180 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerQueueTest.kt +1 -1
- package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerReadersTest.kt +1 -1
- package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerSkipCapabilityTest.kt +2 -2
- package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerSkipTest.kt +1 -1
- package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerTransportTest.kt +1 -1
- package/ios/AVQueueBuilder.swift +29 -0
- package/ios/Cast/Core/CastNowPlayingController.swift +7 -4
- package/ios/NowPlayingInfo.swift +7 -9
- package/ios/PlaceholderArtwork.swift +24 -1
- package/ios/QueueState.swift +14 -2
- package/ios/Tests/AVQueueBuilderTests.swift +83 -9
- package/ios/Tests/CastNowPlayingControllerTests.swift +33 -0
- package/ios/Tests/PlaceholderArtworkTests.swift +81 -0
- package/ios/Tests/QueueStateTests.swift +50 -0
- package/ios/TrackPlayer.swift +126 -15
- package/lib/module/hooks/index.js +1 -0
- package/lib/module/hooks/index.js.map +1 -1
- package/lib/module/hooks/useQueue.js +57 -0
- package/lib/module/hooks/useQueue.js.map +1 -0
- package/lib/module/index.js.map +1 -1
- package/lib/module/types.js +7 -0
- package/lib/module/types.js.map +1 -1
- package/lib/typescript/TrackPlayer.nitro.d.ts +27 -1
- package/lib/typescript/TrackPlayer.nitro.d.ts.map +1 -1
- package/lib/typescript/hooks/index.d.ts +2 -0
- package/lib/typescript/hooks/index.d.ts.map +1 -1
- package/lib/typescript/hooks/useQueue.d.ts +20 -0
- package/lib/typescript/hooks/useQueue.d.ts.map +1 -0
- package/lib/typescript/index.d.ts +1 -1
- package/lib/typescript/index.d.ts.map +1 -1
- package/lib/typescript/types.d.ts +22 -0
- package/lib/typescript/types.d.ts.map +1 -1
- package/nitrogen/generated/android/c++/JFunc_void_std__vector_TrackItem__double_QueueChangeReason.hpp +101 -0
- package/nitrogen/generated/android/c++/JHybridTrackPlayerSpec.cpp +18 -0
- package/nitrogen/generated/android/c++/JHybridTrackPlayerSpec.hpp +1 -0
- package/nitrogen/generated/android/c++/JPlayerConfig.hpp +5 -1
- package/nitrogen/generated/android/c++/JQueueChangeReason.hpp +70 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_std__vector_TrackItem__double_QueueChangeReason.kt +80 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/HybridTrackPlayerSpec.kt +9 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/PlayerConfig.kt +7 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/QueueChangeReason.kt +27 -0
- package/nitrogen/generated/android/queueplayerOnLoad.cpp +2 -0
- package/nitrogen/generated/ios/QueuePlayer-Swift-Cxx-Bridge.cpp +8 -0
- package/nitrogen/generated/ios/QueuePlayer-Swift-Cxx-Bridge.hpp +25 -0
- package/nitrogen/generated/ios/QueuePlayer-Swift-Cxx-Umbrella.hpp +3 -0
- package/nitrogen/generated/ios/c++/HybridTrackPlayerSpecSwift.hpp +11 -0
- package/nitrogen/generated/ios/swift/Func_void_std__vector_TrackItem__double_QueueChangeReason.swift +46 -0
- package/nitrogen/generated/ios/swift/HybridTrackPlayerSpec.swift +1 -0
- package/nitrogen/generated/ios/swift/HybridTrackPlayerSpec_cxx.swift +26 -0
- package/nitrogen/generated/ios/swift/PlayerConfig.swift +19 -1
- package/nitrogen/generated/ios/swift/QueueChangeReason.swift +56 -0
- package/nitrogen/generated/shared/c++/HybridTrackPlayerSpec.cpp +1 -0
- package/nitrogen/generated/shared/c++/HybridTrackPlayerSpec.hpp +4 -0
- package/nitrogen/generated/shared/c++/PlayerConfig.hpp +5 -1
- package/nitrogen/generated/shared/c++/QueueChangeReason.hpp +92 -0
- package/package.json +1 -1
- package/src/TrackPlayer.nitro.ts +33 -0
- package/src/hooks/index.ts +3 -0
- package/src/hooks/useQueue.ts +56 -0
- package/src/index.ts +1 -0
- package/src/types.ts +29 -0
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
///
|
|
2
|
+
/// QueueChangeReason.hpp
|
|
3
|
+
/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE.
|
|
4
|
+
/// https://github.com/mrousavy/nitro
|
|
5
|
+
/// Copyright © 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::queueplayer {
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* An enum which can be represented as a JavaScript union (QueueChangeReason).
|
|
30
|
+
*/
|
|
31
|
+
enum class QueueChangeReason {
|
|
32
|
+
SET_QUEUE SWIFT_NAME(setQueue) = 0,
|
|
33
|
+
ADD SWIFT_NAME(add) = 1,
|
|
34
|
+
REMOVE SWIFT_NAME(remove) = 2,
|
|
35
|
+
MOVE SWIFT_NAME(move) = 3,
|
|
36
|
+
CLEAR SWIFT_NAME(clear) = 4,
|
|
37
|
+
SHUFFLE SWIFT_NAME(shuffle) = 5,
|
|
38
|
+
} CLOSED_ENUM;
|
|
39
|
+
|
|
40
|
+
} // namespace margelo::nitro::queueplayer
|
|
41
|
+
|
|
42
|
+
namespace margelo::nitro {
|
|
43
|
+
|
|
44
|
+
// C++ QueueChangeReason <> JS QueueChangeReason (union)
|
|
45
|
+
template <>
|
|
46
|
+
struct JSIConverter<margelo::nitro::queueplayer::QueueChangeReason> final {
|
|
47
|
+
static inline margelo::nitro::queueplayer::QueueChangeReason fromJSI(jsi::Runtime& runtime, const jsi::Value& arg) {
|
|
48
|
+
std::string unionValue = JSIConverter<std::string>::fromJSI(runtime, arg);
|
|
49
|
+
switch (hashString(unionValue.c_str(), unionValue.size())) {
|
|
50
|
+
case hashString("set-queue"): return margelo::nitro::queueplayer::QueueChangeReason::SET_QUEUE;
|
|
51
|
+
case hashString("add"): return margelo::nitro::queueplayer::QueueChangeReason::ADD;
|
|
52
|
+
case hashString("remove"): return margelo::nitro::queueplayer::QueueChangeReason::REMOVE;
|
|
53
|
+
case hashString("move"): return margelo::nitro::queueplayer::QueueChangeReason::MOVE;
|
|
54
|
+
case hashString("clear"): return margelo::nitro::queueplayer::QueueChangeReason::CLEAR;
|
|
55
|
+
case hashString("shuffle"): return margelo::nitro::queueplayer::QueueChangeReason::SHUFFLE;
|
|
56
|
+
default: [[unlikely]]
|
|
57
|
+
throw std::invalid_argument("Cannot convert \"" + unionValue + "\" to enum QueueChangeReason - invalid value!");
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
static inline jsi::Value toJSI(jsi::Runtime& runtime, margelo::nitro::queueplayer::QueueChangeReason arg) {
|
|
61
|
+
switch (arg) {
|
|
62
|
+
case margelo::nitro::queueplayer::QueueChangeReason::SET_QUEUE: return JSIConverter<std::string>::toJSI(runtime, "set-queue");
|
|
63
|
+
case margelo::nitro::queueplayer::QueueChangeReason::ADD: return JSIConverter<std::string>::toJSI(runtime, "add");
|
|
64
|
+
case margelo::nitro::queueplayer::QueueChangeReason::REMOVE: return JSIConverter<std::string>::toJSI(runtime, "remove");
|
|
65
|
+
case margelo::nitro::queueplayer::QueueChangeReason::MOVE: return JSIConverter<std::string>::toJSI(runtime, "move");
|
|
66
|
+
case margelo::nitro::queueplayer::QueueChangeReason::CLEAR: return JSIConverter<std::string>::toJSI(runtime, "clear");
|
|
67
|
+
case margelo::nitro::queueplayer::QueueChangeReason::SHUFFLE: return JSIConverter<std::string>::toJSI(runtime, "shuffle");
|
|
68
|
+
default: [[unlikely]]
|
|
69
|
+
throw std::invalid_argument("Cannot convert QueueChangeReason to JS - invalid value: "
|
|
70
|
+
+ std::to_string(static_cast<int>(arg)) + "!");
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
static inline bool canConvert(jsi::Runtime& runtime, const jsi::Value& value) {
|
|
74
|
+
if (!value.isString()) {
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
std::string unionValue = JSIConverter<std::string>::fromJSI(runtime, value);
|
|
78
|
+
switch (hashString(unionValue.c_str(), unionValue.size())) {
|
|
79
|
+
case hashString("set-queue"):
|
|
80
|
+
case hashString("add"):
|
|
81
|
+
case hashString("remove"):
|
|
82
|
+
case hashString("move"):
|
|
83
|
+
case hashString("clear"):
|
|
84
|
+
case hashString("shuffle"):
|
|
85
|
+
return true;
|
|
86
|
+
default:
|
|
87
|
+
return false;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
} // namespace margelo::nitro
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-native-queue-player",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.9",
|
|
4
4
|
"description": "Nitro Modules audio playback library for React Native — gapless, EQ, crossfade, automotive, voice, casting",
|
|
5
5
|
"main": "./lib/module/index.js",
|
|
6
6
|
"types": "./lib/typescript/index.d.ts",
|
package/src/TrackPlayer.nitro.ts
CHANGED
|
@@ -9,6 +9,7 @@ import type {
|
|
|
9
9
|
RemoteControlOptions,
|
|
10
10
|
StateChangeReason,
|
|
11
11
|
TrackChangeReason,
|
|
12
|
+
QueueChangeReason,
|
|
12
13
|
TrackSource,
|
|
13
14
|
PlaybackError,
|
|
14
15
|
PlayerProgress,
|
|
@@ -372,6 +373,38 @@ export interface TrackPlayer
|
|
|
372
373
|
transitionGapMs: number | undefined
|
|
373
374
|
) => void
|
|
374
375
|
): () => void;
|
|
376
|
+
/**
|
|
377
|
+
* Fires when the queue **contents or order** change — `setQueue`,
|
|
378
|
+
* `addToQueue`, `removeFromQueue`, `moveInQueue`, `clearQueue`,
|
|
379
|
+
* `shuffleQueue`. Unlike `onTrackChange` (which fires only when the
|
|
380
|
+
* active *track* changes), this fires on queue-preserving mutations —
|
|
381
|
+
* e.g. inserting a "play next" item or removing an upcoming track — so
|
|
382
|
+
* a reactive queue UI stays in sync.
|
|
383
|
+
*
|
|
384
|
+
* The `(queue, currentIndex)` pair is delivered **atomically**: it is
|
|
385
|
+
* the post-mutation snapshot captured on the native side under the same
|
|
386
|
+
* mutation, so it never exhibits the one-tick skew of reading
|
|
387
|
+
* `getQueue()` + `getCurrentTrackIndex()` back-to-back. `currentIndex`
|
|
388
|
+
* is `-1` when the queue is empty; note it can shift without the active
|
|
389
|
+
* track changing (e.g. an insert before the current item), which is the
|
|
390
|
+
* case `onTrackChange` does not cover.
|
|
391
|
+
*
|
|
392
|
+
* Fires only when the queue actually changes — a no-op mutation (empty
|
|
393
|
+
* `addToQueue`, out-of-range `removeFromQueue`, `moveInQueue` with
|
|
394
|
+
* `from === to`) does not fire, and neither do current-index-only
|
|
395
|
+
* changes from auto-advance / skip (those are `onTrackChange`). Does not
|
|
396
|
+
* auto-fire on subscribe; seed initial state from `getQueue()` (the
|
|
397
|
+
* `useQueue()` hook does this). For a mutation that also changes the
|
|
398
|
+
* active track (`set-queue` / `clear` / `shuffle`) this fires first,
|
|
399
|
+
* then `onTrackChange` follows.
|
|
400
|
+
*/
|
|
401
|
+
onQueueChange(
|
|
402
|
+
callback: (
|
|
403
|
+
queue: TrackItem[],
|
|
404
|
+
currentIndex: number,
|
|
405
|
+
reason: QueueChangeReason
|
|
406
|
+
) => void
|
|
407
|
+
): () => void;
|
|
375
408
|
/**
|
|
376
409
|
* Subscribes to playback-progress ticks. Native side fires at ~2 Hz
|
|
377
410
|
* via `addPeriodicTimeObserver` (iOS) / a coroutine-driven handler
|
package/src/hooks/index.ts
CHANGED
|
@@ -7,6 +7,9 @@ export type { PlaybackStateSnapshot } from './usePlaybackState';
|
|
|
7
7
|
export { useActiveTrack } from './useActiveTrack';
|
|
8
8
|
export type { ActiveTrackSnapshot } from './useActiveTrack';
|
|
9
9
|
|
|
10
|
+
export { useQueue } from './useQueue';
|
|
11
|
+
export type { QueueSnapshot } from './useQueue';
|
|
12
|
+
|
|
10
13
|
export { useCanSkip } from './useCanSkip';
|
|
11
14
|
export type { CanSkipSnapshot } from './useCanSkip';
|
|
12
15
|
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { useEffect, useState } from 'react';
|
|
2
|
+
import { getTrackPlayer } from '../clients';
|
|
3
|
+
import type { TrackItem } from '../types';
|
|
4
|
+
|
|
5
|
+
export interface QueueSnapshot {
|
|
6
|
+
/** The current queue, in play order. */
|
|
7
|
+
queue: TrackItem[];
|
|
8
|
+
/** Index of the active track. `-1` when the queue is empty. */
|
|
9
|
+
currentIndex: number;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const INITIAL: QueueSnapshot = { queue: [], currentIndex: -1 };
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Subscribes to native `onQueueChange` for the live queue + current
|
|
16
|
+
* index, delivered as one atomic snapshot. Also subscribes to
|
|
17
|
+
* `onTrackChange` so `currentIndex` stays live on auto-advance / skip,
|
|
18
|
+
* which move the active position without changing the queue contents.
|
|
19
|
+
*
|
|
20
|
+
* The first-render seed reads `getQueue()` + `getCurrentTrackIndex()`
|
|
21
|
+
* separately — the pair is not atomic against a concurrent mutation, so
|
|
22
|
+
* it can briefly skew. Self-healing: the first `onQueueChange` /
|
|
23
|
+
* `onTrackChange` fire delivers a consistent snapshot.
|
|
24
|
+
*/
|
|
25
|
+
export function useQueue(): QueueSnapshot {
|
|
26
|
+
const [snapshot, setSnapshot] = useState<QueueSnapshot>(() => {
|
|
27
|
+
try {
|
|
28
|
+
const player = getTrackPlayer();
|
|
29
|
+
return {
|
|
30
|
+
queue: player.getQueue(),
|
|
31
|
+
currentIndex: player.getCurrentTrackIndex(),
|
|
32
|
+
};
|
|
33
|
+
} catch {
|
|
34
|
+
return INITIAL;
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
useEffect(() => {
|
|
39
|
+
const player = getTrackPlayer();
|
|
40
|
+
const offQueue = player.onQueueChange((queue, currentIndex) => {
|
|
41
|
+
setSnapshot({ queue, currentIndex });
|
|
42
|
+
});
|
|
43
|
+
// Keep `currentIndex` live when the active track changes without a
|
|
44
|
+
// queue-content change (auto-advance, skip). Preserves the queue from
|
|
45
|
+
// the last `onQueueChange`.
|
|
46
|
+
const offTrack = player.onTrackChange((_track, index) => {
|
|
47
|
+
setSnapshot((prev) => ({ queue: prev.queue, currentIndex: index }));
|
|
48
|
+
});
|
|
49
|
+
return () => {
|
|
50
|
+
offQueue();
|
|
51
|
+
offTrack();
|
|
52
|
+
};
|
|
53
|
+
}, []);
|
|
54
|
+
|
|
55
|
+
return snapshot;
|
|
56
|
+
}
|
package/src/index.ts
CHANGED
package/src/types.ts
CHANGED
|
@@ -69,6 +69,23 @@ export interface PlayerConfig {
|
|
|
69
69
|
httpHeaders?: Record<string, string>;
|
|
70
70
|
/** Overrides the default UA on network requests. */
|
|
71
71
|
userAgent?: string;
|
|
72
|
+
/**
|
|
73
|
+
* Replacement for the built-in RNQP cover-art placeholder (the image
|
|
74
|
+
* shown before real artwork loads and when a track has no artwork).
|
|
75
|
+
* MUST be a **local `file://` URI** — the built-in placeholder is a
|
|
76
|
+
* local file, so this must be too; remote URLs are not loaded (they
|
|
77
|
+
* would break offline playback and change behaviour). When set, this
|
|
78
|
+
* image is used in place of the built-in everywhere the built-in is
|
|
79
|
+
* used; unset, non-`file://`, or unloadable falls back to the built-in.
|
|
80
|
+
* Resolve a bundled asset to a `file://` path with expo-asset —
|
|
81
|
+
* `const a = Asset.fromModule(require('./logo.png')); await
|
|
82
|
+
* a.downloadAsync();` then pass `a.localUri` — which copies the packaged
|
|
83
|
+
* image onto the filesystem across dev and release.
|
|
84
|
+
* (`Image.resolveAssetSource(...).uri` is not suitable: it is an
|
|
85
|
+
* `http://` Metro URL in dev and a drawable resource name on Android
|
|
86
|
+
* release, neither of which is a `file://` path.)
|
|
87
|
+
*/
|
|
88
|
+
placeholderArtworkUri?: string;
|
|
72
89
|
/**
|
|
73
90
|
* Auto-retry attempts on transient errors (TIMEOUT / CONNECTION_LOST /
|
|
74
91
|
* NETWORK_UNREACHABLE / SOURCE_UNREACHABLE / CANNOT_OPEN). Clamped
|
|
@@ -417,6 +434,18 @@ export type TrackChangeReason =
|
|
|
417
434
|
*/
|
|
418
435
|
| 'crossfade-cancelled';
|
|
419
436
|
|
|
437
|
+
/**
|
|
438
|
+
* Why the queue contents or order changed, reported by `onQueueChange`.
|
|
439
|
+
* Each value maps 1:1 to the public mutation that produced it.
|
|
440
|
+
*/
|
|
441
|
+
export type QueueChangeReason =
|
|
442
|
+
| 'set-queue' // setQueue replaced the whole queue
|
|
443
|
+
| 'add' // addToQueue inserted track(s)
|
|
444
|
+
| 'remove' // removeFromQueue removed track(s)
|
|
445
|
+
| 'move' // moveInQueue reordered a track
|
|
446
|
+
| 'clear' // clearQueue emptied the queue
|
|
447
|
+
| 'shuffle'; // shuffleQueue reordered the queue
|
|
448
|
+
|
|
420
449
|
/**
|
|
421
450
|
* How the active track is being played:
|
|
422
451
|
* - `'local'` — `track.url` is a `file://` URL.
|