react-native-queue-player 1.0.6 → 1.0.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/android/src/main/java/com/margelo/nitro/queueplayer/AirPlayEngine.kt +1 -0
  2. package/android/src/main/java/com/margelo/nitro/queueplayer/CrossfadeEngine.kt +3 -0
  3. package/android/src/main/java/com/margelo/nitro/queueplayer/GaplessEngine.kt +3 -0
  4. package/android/src/main/java/com/margelo/nitro/queueplayer/MediaItemBuilder.kt +13 -0
  5. package/android/src/main/java/com/margelo/nitro/queueplayer/PlaybackEngine.kt +8 -0
  6. package/android/src/main/java/com/margelo/nitro/queueplayer/PlaybackService.kt +6 -42
  7. package/android/src/main/java/com/margelo/nitro/queueplayer/PlaybackServiceCallback.kt +7 -15
  8. package/android/src/main/java/com/margelo/nitro/queueplayer/TrackPlayer.kt +72 -23
  9. package/android/src/test/java/com/margelo/nitro/queueplayer/EqualizerKtTest.kt +1 -0
  10. package/android/src/test/java/com/margelo/nitro/queueplayer/MediaItemBuilderTest.kt +36 -0
  11. package/android/src/test/java/com/margelo/nitro/queueplayer/PlaybackEngineInterfaceTest.kt +1 -0
  12. package/android/src/test/java/com/margelo/nitro/queueplayer/PlaybackServiceCallbackBrowseTest.kt +8 -12
  13. package/android/src/test/java/com/margelo/nitro/queueplayer/PlaybackServiceCallbackSearchTest.kt +2 -3
  14. package/android/src/test/java/com/margelo/nitro/queueplayer/PlaybackServiceCallbackTest.kt +15 -19
  15. package/android/src/test/java/com/margelo/nitro/queueplayer/PlayerCommandsForTest.kt +9 -18
  16. package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerQueueChangeTest.kt +180 -0
  17. package/android/src/test/java/com/margelo/nitro/queueplayer/VisualizerKtTest.kt +1 -0
  18. package/ios/AVQueueBuilder.swift +29 -0
  19. package/ios/QueueState.swift +14 -2
  20. package/ios/Tests/AVQueueBuilderTests.swift +74 -0
  21. package/ios/Tests/QueueStateTests.swift +50 -0
  22. package/ios/TrackPlayer.swift +121 -14
  23. package/lib/module/hooks/index.js +1 -0
  24. package/lib/module/hooks/index.js.map +1 -1
  25. package/lib/module/hooks/useQueue.js +57 -0
  26. package/lib/module/hooks/useQueue.js.map +1 -0
  27. package/lib/module/index.js.map +1 -1
  28. package/lib/module/types.js +7 -0
  29. package/lib/module/types.js.map +1 -1
  30. package/lib/typescript/TrackPlayer.nitro.d.ts +27 -1
  31. package/lib/typescript/TrackPlayer.nitro.d.ts.map +1 -1
  32. package/lib/typescript/hooks/index.d.ts +2 -0
  33. package/lib/typescript/hooks/index.d.ts.map +1 -1
  34. package/lib/typescript/hooks/useQueue.d.ts +20 -0
  35. package/lib/typescript/hooks/useQueue.d.ts.map +1 -0
  36. package/lib/typescript/index.d.ts +1 -1
  37. package/lib/typescript/index.d.ts.map +1 -1
  38. package/lib/typescript/types.d.ts +5 -0
  39. package/lib/typescript/types.d.ts.map +1 -1
  40. package/nitrogen/generated/android/c++/JFunc_void_std__vector_TrackItem__double_QueueChangeReason.hpp +101 -0
  41. package/nitrogen/generated/android/c++/JHybridTrackPlayerSpec.cpp +18 -0
  42. package/nitrogen/generated/android/c++/JHybridTrackPlayerSpec.hpp +1 -0
  43. package/nitrogen/generated/android/c++/JQueueChangeReason.hpp +70 -0
  44. package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_std__vector_TrackItem__double_QueueChangeReason.kt +80 -0
  45. package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/HybridTrackPlayerSpec.kt +9 -0
  46. package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/QueueChangeReason.kt +27 -0
  47. package/nitrogen/generated/android/queueplayerOnLoad.cpp +2 -0
  48. package/nitrogen/generated/ios/QueuePlayer-Swift-Cxx-Bridge.cpp +8 -0
  49. package/nitrogen/generated/ios/QueuePlayer-Swift-Cxx-Bridge.hpp +25 -0
  50. package/nitrogen/generated/ios/QueuePlayer-Swift-Cxx-Umbrella.hpp +3 -0
  51. package/nitrogen/generated/ios/c++/HybridTrackPlayerSpecSwift.hpp +11 -0
  52. package/nitrogen/generated/ios/swift/Func_void_std__vector_TrackItem__double_QueueChangeReason.swift +46 -0
  53. package/nitrogen/generated/ios/swift/HybridTrackPlayerSpec.swift +1 -0
  54. package/nitrogen/generated/ios/swift/HybridTrackPlayerSpec_cxx.swift +26 -0
  55. package/nitrogen/generated/ios/swift/QueueChangeReason.swift +56 -0
  56. package/nitrogen/generated/shared/c++/HybridTrackPlayerSpec.cpp +1 -0
  57. package/nitrogen/generated/shared/c++/HybridTrackPlayerSpec.hpp +4 -0
  58. package/nitrogen/generated/shared/c++/QueueChangeReason.hpp +92 -0
  59. package/package.json +1 -1
  60. package/src/TrackPlayer.nitro.ts +33 -0
  61. package/src/hooks/index.ts +3 -0
  62. package/src/hooks/useQueue.ts +56 -0
  63. package/src/index.ts +1 -0
  64. package/src/types.ts +12 -0
@@ -41,6 +41,8 @@ namespace margelo::nitro::queueplayer { struct SleepTimerState; }
41
41
  namespace margelo::nitro::queueplayer { enum class StateChangeReason; }
42
42
  // Forward declaration of `TrackChangeReason` to properly resolve imports.
43
43
  namespace margelo::nitro::queueplayer { enum class TrackChangeReason; }
44
+ // Forward declaration of `QueueChangeReason` to properly resolve imports.
45
+ namespace margelo::nitro::queueplayer { enum class QueueChangeReason; }
44
46
  // Forward declaration of `PlayerProgress` to properly resolve imports.
45
47
  namespace margelo::nitro::queueplayer { struct PlayerProgress; }
46
48
  // Forward declaration of `PlaybackError` to properly resolve imports.
@@ -82,6 +84,7 @@ namespace margelo::nitro::queueplayer { struct NowPlayingFormat; }
82
84
  #include <functional>
83
85
  #include "StateChangeReason.hpp"
84
86
  #include "TrackChangeReason.hpp"
87
+ #include "QueueChangeReason.hpp"
85
88
  #include "PlayerProgress.hpp"
86
89
  #include "PlaybackError.hpp"
87
90
  #include "BrowseSnapshot.hpp"
@@ -171,6 +174,7 @@ namespace margelo::nitro::queueplayer {
171
174
  virtual SleepTimerState getSleepTimer() = 0;
172
175
  virtual std::function<void()> onStateChange(const std::function<void(PlayerState /* state */, StateChangeReason /* reason */)>& callback) = 0;
173
176
  virtual std::function<void()> onTrackChange(const std::function<void(const std::optional<TrackItem>& /* track */, double /* index */, TrackChangeReason /* reason */, std::optional<double> /* transitionGapMs */)>& callback) = 0;
177
+ virtual std::function<void()> onQueueChange(const std::function<void(const std::vector<TrackItem>& /* queue */, double /* currentIndex */, QueueChangeReason /* reason */)>& callback) = 0;
174
178
  virtual std::function<void()> onProgress(const std::function<void(const PlayerProgress& /* progress */)>& callback) = 0;
175
179
  virtual std::function<void()> onPlaybackMilestone(const std::function<void(double /* milestone */, double /* trackIndex */)>& callback) = 0;
176
180
  virtual std::function<void()> onError(const std::function<void(const PlaybackError& /* error */)>& callback) = 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.6",
3
+ "version": "1.0.8",
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",
@@ -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
@@ -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
@@ -18,6 +18,7 @@ export type {
18
18
  RemoteControlOptions,
19
19
  StateChangeReason,
20
20
  TrackChangeReason,
21
+ QueueChangeReason,
21
22
  TrackSource,
22
23
  PlayerProgress,
23
24
  PlaybackError,
package/src/types.ts CHANGED
@@ -417,6 +417,18 @@ export type TrackChangeReason =
417
417
  */
418
418
  | 'crossfade-cancelled';
419
419
 
420
+ /**
421
+ * Why the queue contents or order changed, reported by `onQueueChange`.
422
+ * Each value maps 1:1 to the public mutation that produced it.
423
+ */
424
+ export type QueueChangeReason =
425
+ | 'set-queue' // setQueue replaced the whole queue
426
+ | 'add' // addToQueue inserted track(s)
427
+ | 'remove' // removeFromQueue removed track(s)
428
+ | 'move' // moveInQueue reordered a track
429
+ | 'clear' // clearQueue emptied the queue
430
+ | 'shuffle'; // shuffleQueue reordered the queue
431
+
420
432
  /**
421
433
  * How the active track is being played:
422
434
  * - `'local'` — `track.url` is a `file://` URL.