react-native-audio-browser 0.2.0-next.12 → 0.2.0-next.13
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 +10 -0
- package/android/build.gradle +20 -1
- package/android/src/main/java/com/audiobrowser/AudioBrowser.kt +5 -1
- package/android/src/main/java/com/audiobrowser/browser/BrowserManager.kt +8 -0
- package/android/src/main/java/com/audiobrowser/browser/InertStyleDiagnostic.kt +267 -0
- package/android/src/main/java/com/audiobrowser/browser/JsonModels.kt +27 -9
- package/android/src/main/java/com/audiobrowser/browser/StyleResolver.kt +11 -0
- package/android/src/main/java/com/audiobrowser/extension/ResolvedTrackExt.kt +11 -1
- package/android/src/main/java/com/audiobrowser/model/PlayerUpdateOptions.kt +1 -1
- package/android/src/main/java/com/audiobrowser/player/PlaybackStateStore.kt +17 -2
- package/android/src/main/java/com/audiobrowser/player/Player.kt +3 -0
- package/android/src/main/java/com/audiobrowser/player/ShuffleOrderLedBy.kt +30 -0
- package/android/src/main/java/com/audiobrowser/util/StyleWire.kt +73 -0
- package/android/src/main/java/com/audiobrowser/util/TrackFactory.kt +8 -1
- package/android/src/test/java/com/audiobrowser/TestFixtures.kt +49 -3
- package/android/src/test/java/com/audiobrowser/browser/BrowserManagerTest.kt +3 -2
- package/android/src/test/java/com/audiobrowser/browser/InertStyleDiagnosticTest.kt +277 -0
- package/android/src/test/java/com/audiobrowser/browser/JsonStyleTest.kt +56 -0
- package/android/src/test/java/com/audiobrowser/browser/SectionScopeTest.kt +4 -4
- package/android/src/test/java/com/audiobrowser/browser/StyleResolverTest.kt +77 -10
- package/android/src/test/java/com/audiobrowser/player/PlaybackStateStoreTrackJsonTest.kt +2 -2
- package/android/src/test/java/com/audiobrowser/player/ShuffleOrderLedByTest.kt +79 -0
- package/android/src/test/java/com/audiobrowser/util/MediaExtrasBuilderTest.kt +8 -8
- package/ios/Browser/BrowserManager.swift +106 -45
- package/ios/Browser/InertStyleDiagnostic.swift +279 -0
- package/ios/Browser/JsonModels.swift +81 -10
- package/ios/Browser/SectionPresentation.swift +30 -0
- package/ios/Browser/StyleResolver.swift +11 -0
- package/ios/Browser/TabBarEntries.swift +7 -1
- package/ios/CarPlay/CarPlayController.swift +139 -35
- package/ios/CarPlay/CarPlayListItemFactory.swift +99 -27
- package/ios/Extension/UIApplication+ActiveScene.swift +27 -0
- package/ios/Http/HttpClient.swift +8 -2
- package/ios/HybridAudioBrowser.swift +54 -11
- package/ios/Model/NitroTypeStubs.swift +60 -0
- package/ios/Player/MediaIntentCriteria.swift +13 -5
- package/ios/Player/MediaLoader.swift +33 -9
- package/ios/Player/NowPlayingUpdater.swift +53 -5
- package/ios/Player/PlaybackCoordinator.swift +11 -4
- package/ios/Player/PlaybackTimer.swift +8 -2
- package/ios/Player/QueueManager.swift +36 -10
- package/ios/Player/ShuffleOrder.swift +23 -0
- package/ios/Player/SleepTimerHandling.swift +13 -0
- package/ios/RemoteCommand/RemoteCommandController.swift +53 -18
- package/ios/Tests/InertStyleDiagnosticTests.swift +200 -0
- package/ios/Tests/JsonModelsTests.swift +76 -1
- package/ios/Tests/QueueManagerTests.swift +72 -6
- package/ios/Tests/RemoteCommandControllerTests.swift +16 -0
- package/ios/Tests/SectionPresentationTests.swift +29 -0
- package/ios/Tests/StyleResolverTests.swift +60 -0
- package/ios/Tests/Support/TestTypes.swift +7 -0
- package/ios/TrackPlayer.swift +57 -34
- package/ios/Util/NetworkMonitor.swift +43 -26
- package/ios/Util/SFSymbolRenderer.swift +24 -2
- package/ios/Util/SVGProcessor.swift +9 -2
- package/lib/commonjs/features/playback/progress.js +2 -1
- package/lib/commonjs/features/playback/progress.js.map +1 -1
- package/lib/commonjs/features/playback/rate.js +8 -1
- package/lib/commonjs/features/playback/rate.js.map +1 -1
- package/lib/commonjs/web/TrackPlayer/QueueManager.js +14 -0
- package/lib/commonjs/web/TrackPlayer/QueueManager.js.map +1 -1
- package/lib/module/features/playback/progress.js +2 -1
- package/lib/module/features/playback/progress.js.map +1 -1
- package/lib/module/features/playback/rate.js +8 -1
- package/lib/module/features/playback/rate.js.map +1 -1
- package/lib/module/web/TrackPlayer/QueueManager.js +14 -0
- package/lib/module/web/TrackPlayer/QueueManager.js.map +1 -1
- package/lib/typescript/src/features/playback/progress.d.ts.map +1 -1
- package/lib/typescript/src/features/playback/rate.d.ts +8 -1
- package/lib/typescript/src/features/playback/rate.d.ts.map +1 -1
- package/lib/typescript/src/types/browser-nodes.d.ts +104 -2
- package/lib/typescript/src/types/browser-nodes.d.ts.map +1 -1
- package/nitrogen/generated/android/c++/JArtworkRequestConfig.hpp +4 -0
- package/nitrogen/generated/android/c++/JBrowseResult.hpp +6 -0
- package/nitrogen/generated/android/c++/JCardImage.hpp +58 -0
- package/nitrogen/generated/android/c++/JFavoriteChangedEvent.hpp +4 -0
- package/nitrogen/generated/android/c++/JFormatNowPlayingParams.hpp +4 -0
- package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_RequestConfig___Track.hpp +4 -0
- package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__optional_NowPlayingUpdate____FormatNowPlayingParams.hpp +4 -0
- package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__optional_std__string____Track.hpp +4 -0
- package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__shared_ptr_Promise_RequestConfig_____Track.hpp +4 -0
- package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__shared_ptr_Promise_std__vector_Track______SearchParams.hpp +4 -0
- package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__shared_ptr_Promise_void_____TrackLoadEvent.hpp +4 -0
- package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__variant_ResolvedTrack__BrowseError__std__shared_ptr_Promise_std__variant_ResolvedTrack__BrowseError_______BrowserSourceCallbackParam.hpp +6 -0
- package/nitrogen/generated/android/c++/JFunc_void_FavoriteChangedEvent.hpp +4 -0
- package/nitrogen/generated/android/c++/JFunc_void_PlaybackActiveTrackChangedEvent.hpp +4 -0
- package/nitrogen/generated/android/c++/JFunc_void_std__optional_ResolvedTrack_.hpp +6 -0
- package/nitrogen/generated/android/c++/JFunc_void_std__vector_Track_.hpp +4 -0
- package/nitrogen/generated/android/c++/JGridTile.hpp +61 -0
- package/nitrogen/generated/android/c++/JHybridAudioBrowserSpec.cpp +12 -0
- package/nitrogen/generated/android/c++/JImageShape.hpp +58 -0
- package/nitrogen/generated/android/c++/JMediaRequestConfig.hpp +4 -0
- package/nitrogen/generated/android/c++/JNativeBrowserConfiguration.hpp +6 -0
- package/nitrogen/generated/android/c++/JNativeRouteEntry.hpp +6 -0
- package/nitrogen/generated/android/c++/JNativeSetupPlayerOptions.hpp +4 -0
- package/nitrogen/generated/android/c++/JPlaybackActiveTrackChangedEvent.hpp +4 -0
- package/nitrogen/generated/android/c++/JResolvedTrack.hpp +6 -0
- package/nitrogen/generated/android/c++/JSection.hpp +6 -0
- package/nitrogen/generated/android/c++/JSectionStyle.hpp +30 -3
- package/nitrogen/generated/android/c++/JTrack.hpp +4 -0
- package/nitrogen/generated/android/c++/JTrackLoadEvent.hpp +4 -0
- package/nitrogen/generated/android/c++/JTrackStyle.hpp +24 -3
- package/nitrogen/generated/android/c++/JVariant_ResolvedTrack_BrowseError_Promise_BrowseResult_.hpp +6 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/CardImage.kt +23 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/GridTile.kt +24 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/ImageShape.kt +23 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/SectionStyle.kt +18 -3
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/TrackStyle.kt +15 -3
- package/nitrogen/generated/ios/AudioBrowser-Swift-Cxx-Bridge.hpp +54 -0
- package/nitrogen/generated/ios/AudioBrowser-Swift-Cxx-Umbrella.hpp +9 -0
- package/nitrogen/generated/ios/c++/HybridAudioBrowserSpecSwift.hpp +9 -0
- package/nitrogen/generated/ios/swift/CardImage.swift +40 -0
- package/nitrogen/generated/ios/swift/GridTile.swift +44 -0
- package/nitrogen/generated/ios/swift/ImageShape.swift +40 -0
- package/nitrogen/generated/ios/swift/SectionStyle.swift +70 -1
- package/nitrogen/generated/ios/swift/TrackStyle.swift +59 -1
- package/nitrogen/generated/shared/c++/CardImage.hpp +76 -0
- package/nitrogen/generated/shared/c++/GridTile.hpp +80 -0
- package/nitrogen/generated/shared/c++/ImageShape.hpp +76 -0
- package/nitrogen/generated/shared/c++/SectionStyle.hpp +32 -2
- package/nitrogen/generated/shared/c++/TrackStyle.hpp +25 -2
- package/package.json +7 -2
- package/src/features/playback/progress.ts +2 -1
- package/src/features/playback/rate.ts +8 -1
- package/src/style-resolution.test.ts +72 -1
- package/src/types/browser-nodes.ts +112 -2
- package/src/web/TrackPlayer/QueueManager.test.ts +29 -0
- package/src/web/TrackPlayer/QueueManager.ts +14 -0
- package/website/guide/android-auto.md +114 -0
- package/website/guide/android-certificates.md +409 -0
- package/website/guide/artwork.md +255 -0
- package/website/guide/audio-output.md +135 -0
- package/website/guide/automotive.md +86 -0
- package/website/guide/basic-usage.md +151 -0
- package/website/guide/battery.md +115 -0
- package/website/guide/browser.md +581 -0
- package/website/guide/carplay.md +620 -0
- package/website/guide/configuration.md +173 -0
- package/website/guide/equalizer.md +231 -0
- package/website/guide/errors.md +310 -0
- package/website/guide/favorites.md +154 -0
- package/website/guide/gate.md +143 -0
- package/website/guide/getting-started.md +46 -0
- package/website/guide/hooks.md +238 -0
- package/website/guide/live-streams.md +125 -0
- package/website/guide/metadata.md +239 -0
- package/website/guide/migrating-from-track-player.md +244 -0
- package/website/guide/native-callback-fetch.md +127 -0
- package/website/guide/network.md +86 -0
- package/website/guide/now-playing.md +175 -0
- package/website/guide/playback.md +328 -0
- package/website/guide/queue.md +300 -0
- package/website/guide/remote-controls.md +256 -0
- package/website/guide/search.md +255 -0
- package/website/guide/sleep-timer.md +268 -0
- package/website/guide/track.md +246 -0
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: 'Control the active track — play, pause, stop, seek, playback state, progress, rate, and volume — the toolkit for building your own player UI.'
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# Playback
|
|
6
|
+
|
|
7
|
+
This guide covers **controlling the active track** — transport (play / pause /
|
|
8
|
+
stop), seeking, playback state, progress, rate, and volume. It's the toolkit for
|
|
9
|
+
building your own player UI.
|
|
10
|
+
|
|
11
|
+
Playback acts on whatever the **active track** is. _Which_ track that is — and
|
|
12
|
+
moving between tracks with next/previous — belongs to the [queue](/guide/basic-usage#the-queue);
|
|
13
|
+
this page is about playing the one that's already active. The system surfaces
|
|
14
|
+
(lock screen, notification, car) are covered in [Now Playing](/guide/now-playing).
|
|
15
|
+
|
|
16
|
+
> All functions here are synchronous and safe to call before a track is loaded —
|
|
17
|
+
> they're no-ops or update intent rather than throwing.
|
|
18
|
+
|
|
19
|
+
The UI snippets below import `useState` from `react`, `Text` / `Button` /
|
|
20
|
+
`Switch` from `react-native`, and `Slider` from
|
|
21
|
+
`@react-native-community/slider`; those import lines are omitted for brevity.
|
|
22
|
+
Everything else comes from `react-native-audio-browser`.
|
|
23
|
+
|
|
24
|
+
## Transport controls
|
|
25
|
+
|
|
26
|
+
```ts
|
|
27
|
+
import { play, pause, togglePlayback, stop } from 'react-native-audio-browser'
|
|
28
|
+
|
|
29
|
+
play() // play or resume the active track
|
|
30
|
+
pause() // pause, keeping position
|
|
31
|
+
togglePlayback() // flip between play and pause
|
|
32
|
+
stop() // stop and reset position to the start
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
`stop` keeps the track loaded — `play()` afterwards starts it again from the
|
|
36
|
+
beginning. (For live streams there's no position to reset, so it just stops.)
|
|
37
|
+
That's different from `reset`, which stops playback **and clears the queue**:
|
|
38
|
+
|
|
39
|
+
```ts
|
|
40
|
+
import { reset } from 'react-native-audio-browser'
|
|
41
|
+
|
|
42
|
+
reset() // tear down: stop + empty the queue
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
| | Position | Track stays loaded | Queue |
|
|
46
|
+
| --------- | -------------- | ------------------ | ------- |
|
|
47
|
+
| `pause()` | kept | yes | kept |
|
|
48
|
+
| `stop()` | reset to start | yes | kept |
|
|
49
|
+
| `reset()` | — | no | cleared |
|
|
50
|
+
|
|
51
|
+
## Seeking
|
|
52
|
+
|
|
53
|
+
Seek within the active track by an absolute position or a relative offset, both
|
|
54
|
+
in **seconds**:
|
|
55
|
+
|
|
56
|
+
```ts
|
|
57
|
+
import { seekTo, seekBy } from 'react-native-audio-browser'
|
|
58
|
+
|
|
59
|
+
seekTo(90) // jump to 1:30
|
|
60
|
+
seekBy(30) // skip ahead 30s
|
|
61
|
+
seekBy(-15) // back 15s
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
For live streams, jump back to the live edge (a no-op for non-live tracks):
|
|
65
|
+
|
|
66
|
+
```ts
|
|
67
|
+
import { seekToLiveEdge } from 'react-native-audio-browser'
|
|
68
|
+
|
|
69
|
+
seekToLiveEdge()
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Moving between tracks (next / previous / skip) is part of the
|
|
73
|
+
[queue](/guide/basic-usage#the-queue), not seeking.
|
|
74
|
+
|
|
75
|
+
## Playback state
|
|
76
|
+
|
|
77
|
+
`usePlayback` returns the current `{ state, error }`. The `state` is one of:
|
|
78
|
+
|
|
79
|
+
| State | Meaning |
|
|
80
|
+
| ------------- | ---------------------------------------------- |
|
|
81
|
+
| `'none'` | Idle — no track loaded (initial state). |
|
|
82
|
+
| `'loading'` | Loading the item before playback can begin. |
|
|
83
|
+
| `'ready'` | Track loaded and ready, currently paused. |
|
|
84
|
+
| `'playing'` | Currently playing. |
|
|
85
|
+
| `'buffering'` | Loading more data before it can continue. |
|
|
86
|
+
| `'paused'` | Paused. |
|
|
87
|
+
| `'stopped'` | Stopped. |
|
|
88
|
+
| `'ended'` | Reached the end of the queue. |
|
|
89
|
+
| `'error'` | Playback failed — see [Errors](/guide/errors). |
|
|
90
|
+
|
|
91
|
+
```tsx
|
|
92
|
+
import { usePlayback, retry } from 'react-native-audio-browser'
|
|
93
|
+
|
|
94
|
+
function PlayerStatus() {
|
|
95
|
+
const { state } = usePlayback() // `error` is also here — see Errors
|
|
96
|
+
|
|
97
|
+
if (state === 'error') {
|
|
98
|
+
return <Button title="Retry" onPress={() => retry()} />
|
|
99
|
+
}
|
|
100
|
+
return <Text>{state}</Text>
|
|
101
|
+
}
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
`retry()` only does something while the state is `'error'`; it re-attempts the
|
|
105
|
+
current item. See [Errors](/guide/errors) for the error shape and handling.
|
|
106
|
+
|
|
107
|
+
### Just playing or buffering?
|
|
108
|
+
|
|
109
|
+
For a play/pause button, drive it off `usePlayingState` — not
|
|
110
|
+
`state === 'playing'` — so it isn't thrown off by the `loading`/`buffering`
|
|
111
|
+
states. It gives you two booleans and re-renders only when they flip:
|
|
112
|
+
|
|
113
|
+
```tsx
|
|
114
|
+
import { usePlayingState, togglePlayback } from 'react-native-audio-browser'
|
|
115
|
+
|
|
116
|
+
function PlayPauseButton() {
|
|
117
|
+
const { playing, buffering } = usePlayingState()
|
|
118
|
+
|
|
119
|
+
return (
|
|
120
|
+
<Button
|
|
121
|
+
title={buffering ? 'Loading…' : playing ? 'Pause' : 'Play'}
|
|
122
|
+
onPress={() => togglePlayback()}
|
|
123
|
+
/>
|
|
124
|
+
)
|
|
125
|
+
}
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
### Play-when-ready
|
|
129
|
+
|
|
130
|
+
`playWhenReady` is the **intent** to play, separate from whether audio is
|
|
131
|
+
actually coming out yet (it may still be loading or buffering). Setting it is
|
|
132
|
+
equivalent to calling `play()` / `pause()`:
|
|
133
|
+
|
|
134
|
+
```tsx
|
|
135
|
+
import { setPlayWhenReady, usePlayWhenReady } from 'react-native-audio-browser'
|
|
136
|
+
|
|
137
|
+
setPlayWhenReady(true) // same as play()
|
|
138
|
+
|
|
139
|
+
function PlayToggle() {
|
|
140
|
+
const wantsToPlay = usePlayWhenReady()
|
|
141
|
+
return (
|
|
142
|
+
<Switch
|
|
143
|
+
value={wantsToPlay}
|
|
144
|
+
onValueChange={(next) => setPlayWhenReady(next)}
|
|
145
|
+
/>
|
|
146
|
+
)
|
|
147
|
+
}
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
Use `usePlayWhenReady` when your control should reflect the user's _intent_
|
|
151
|
+
immediately (the toggle flips the instant they tap, even while buffering); use
|
|
152
|
+
`usePlayingState` when it should reflect whether sound is actually playing.
|
|
153
|
+
|
|
154
|
+
## Progress
|
|
155
|
+
|
|
156
|
+
Progress is `{ position, duration, buffered }`, all in seconds. There are two
|
|
157
|
+
hooks, and the difference matters:
|
|
158
|
+
|
|
159
|
+
- **`usePolledProgress(intervalMs?)`** — polls on a timer (default 1000 **ms**).
|
|
160
|
+
Works out of the box, and automatically pauses while the app is backgrounded.
|
|
161
|
+
**Start here.**
|
|
162
|
+
- **`useProgress()`** — driven by native progress events. These are
|
|
163
|
+
**disabled by default** (`progressUpdateEventInterval` is `null`), so this
|
|
164
|
+
hook won't update until you turn them on (see below).
|
|
165
|
+
|
|
166
|
+
> **Mind the units.** `usePolledProgress` takes its interval in
|
|
167
|
+
> **milliseconds**; `progressUpdateEventInterval` (below) is in **seconds**.
|
|
168
|
+
|
|
169
|
+
```tsx
|
|
170
|
+
import { usePolledProgress, seekTo } from 'react-native-audio-browser'
|
|
171
|
+
|
|
172
|
+
function Scrubber() {
|
|
173
|
+
const { position, duration } = usePolledProgress()
|
|
174
|
+
|
|
175
|
+
return (
|
|
176
|
+
<Slider
|
|
177
|
+
minimumValue={0}
|
|
178
|
+
maximumValue={duration || 1}
|
|
179
|
+
value={position}
|
|
180
|
+
// Seek when the user releases the thumb.
|
|
181
|
+
onSlidingComplete={(seconds) => seekTo(seconds)}
|
|
182
|
+
/>
|
|
183
|
+
)
|
|
184
|
+
}
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
To use the event-based `useProgress` instead, enable progress events once at
|
|
188
|
+
setup by setting how often they fire (in seconds):
|
|
189
|
+
|
|
190
|
+
```ts
|
|
191
|
+
import { setupPlayer } from 'react-native-audio-browser'
|
|
192
|
+
|
|
193
|
+
await setupPlayer({ progressUpdateEventInterval: 0.5 })
|
|
194
|
+
// or later: updateOptions({ progressUpdateEventInterval: 0.5 })
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
Outside React, `getProgress()` returns a one-off snapshot of the current
|
|
198
|
+
position/duration/buffered — it's a plain getter and is **never** gated, so it
|
|
199
|
+
works even with progress events off. Only the `onProgressUpdated` event (and
|
|
200
|
+
therefore `useProgress`) depends on `progressUpdateEventInterval`.
|
|
201
|
+
|
|
202
|
+
## Playback rate
|
|
203
|
+
|
|
204
|
+
Rate is a multiplier: `1` is normal, `0.5` half speed, `2` double. Handy for
|
|
205
|
+
podcasts and audiobooks:
|
|
206
|
+
|
|
207
|
+
```ts
|
|
208
|
+
import { getRate, setRate } from 'react-native-audio-browser'
|
|
209
|
+
|
|
210
|
+
setRate(1.5) // 1.5× speed
|
|
211
|
+
getRate() // → current rate
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
Rate is a speed multiplier, not a transport control: it's independent of
|
|
215
|
+
play/pause, and setting it while paused doesn't start playback. Pass a value
|
|
216
|
+
greater than zero — the platforms disagree on what a non-positive rate does
|
|
217
|
+
(iOS ignores it, Android throws, web stalls the audio element), so treat it as
|
|
218
|
+
unsupported rather than as a way to pause.
|
|
219
|
+
|
|
220
|
+
There's no reactive hook for rate (it only changes when you set it), so a
|
|
221
|
+
control that displays the current speed just holds it in local state:
|
|
222
|
+
|
|
223
|
+
```tsx
|
|
224
|
+
function SpeedControl() {
|
|
225
|
+
const [rate, setRateState] = useState(() => getRate())
|
|
226
|
+
const cycle = () => {
|
|
227
|
+
const next = rate >= 2 ? 0.5 : rate + 0.5
|
|
228
|
+
setRate(next) // tell the player
|
|
229
|
+
setRateState(next) // update the label
|
|
230
|
+
}
|
|
231
|
+
return <Button title={`${rate.toFixed(1)}×`} onPress={cycle} />
|
|
232
|
+
}
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
## Volume
|
|
236
|
+
|
|
237
|
+
There are two distinct volumes:
|
|
238
|
+
|
|
239
|
+
- **Player volume** — this player's own level, `0`–`1`. Use it for in-app
|
|
240
|
+
controls, ducking, or fades.
|
|
241
|
+
- **System volume** — the device's media volume, `0`–`1`.
|
|
242
|
+
|
|
243
|
+
Player volume has no reactive hook (it only changes when you set it), so a
|
|
244
|
+
controlled `<Slider>` holds the value in local state — seed it from `getVolume()`
|
|
245
|
+
and write through to `setVolume`:
|
|
246
|
+
|
|
247
|
+
```tsx
|
|
248
|
+
import { getVolume, setVolume } from 'react-native-audio-browser'
|
|
249
|
+
|
|
250
|
+
function VolumeSlider() {
|
|
251
|
+
const [volume, setVolumeState] = useState(() => getVolume())
|
|
252
|
+
return (
|
|
253
|
+
<Slider
|
|
254
|
+
minimumValue={0}
|
|
255
|
+
maximumValue={1}
|
|
256
|
+
value={volume}
|
|
257
|
+
onValueChange={(next) => {
|
|
258
|
+
setVolume(next) // apply to the player
|
|
259
|
+
setVolumeState(next) // keep the slider in sync
|
|
260
|
+
}}
|
|
261
|
+
/>
|
|
262
|
+
)
|
|
263
|
+
}
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
System volume _does_ have a hook, since the hardware buttons can change it
|
|
267
|
+
behind your back:
|
|
268
|
+
|
|
269
|
+
```tsx
|
|
270
|
+
import { useSystemVolume } from 'react-native-audio-browser'
|
|
271
|
+
|
|
272
|
+
function SystemVolumeLabel() {
|
|
273
|
+
const volume = useSystemVolume() // re-renders on hardware volume keys
|
|
274
|
+
return <Text>{Math.round(volume * 100)}%</Text>
|
|
275
|
+
}
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
`setSystemVolume` is a **no-op on iOS** — Apple provides no public API to set
|
|
279
|
+
the system volume (use the player volume there instead). Reading it and
|
|
280
|
+
`useSystemVolume` work on both platforms.
|
|
281
|
+
|
|
282
|
+
## Measuring listening time
|
|
283
|
+
|
|
284
|
+
`trackPlaybackTime` calls you back every N seconds of **actual playback** — the
|
|
285
|
+
clock advances only while the state is `'playing'` and freezes on
|
|
286
|
+
pause/buffering/stop, so paused time never inflates the count. It's built for
|
|
287
|
+
"still listening" analytics pings:
|
|
288
|
+
|
|
289
|
+
```ts
|
|
290
|
+
import { trackPlaybackTime } from 'react-native-audio-browser'
|
|
291
|
+
|
|
292
|
+
// Fire every 30 seconds of real listening. `sendHeartbeat` is your own
|
|
293
|
+
// analytics call. The callback also gets `sinceLast` (seconds since the
|
|
294
|
+
// previous fire) if you'd rather report deltas than the running total.
|
|
295
|
+
const cancel = trackPlaybackTime(({ total, sinceLast, track }) => {
|
|
296
|
+
sendHeartbeat({ seconds: total, trackId: track?.id })
|
|
297
|
+
}, 30)
|
|
298
|
+
|
|
299
|
+
// later:
|
|
300
|
+
cancel()
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
`period` must be `>= 1` (it throws otherwise). The clock is cumulative across
|
|
304
|
+
the whole session and
|
|
305
|
+
counts _all_ play time, not per-track — `total` keeps climbing through track
|
|
306
|
+
changes. To measure per track, cancel and re-subscribe whenever the
|
|
307
|
+
[active track changes](/guide/now-playing). It's a coarse, ~1-second-resolution
|
|
308
|
+
signal, not a precise timer.
|
|
309
|
+
|
|
310
|
+
## API summary
|
|
311
|
+
|
|
312
|
+
| API | Purpose |
|
|
313
|
+
| ----------------------------------------------- | ----------------------------------------------------------------------- |
|
|
314
|
+
| `play()` / `pause()` / `togglePlayback()` | Start, pause, or flip playback. |
|
|
315
|
+
| `stop()` | Stop and reset position; track stays loaded. |
|
|
316
|
+
| `reset()` | Stop **and** clear the queue. |
|
|
317
|
+
| `seekTo(seconds)` / `seekBy(offset)` | Seek to an absolute / relative position. |
|
|
318
|
+
| `seekToLiveEdge()` | Jump to the live edge (no-op if not live). |
|
|
319
|
+
| `usePlayback()` / `getPlayback()` | Full `{ state, error }`; subscribe via `onPlaybackChanged`. |
|
|
320
|
+
| `retry()` | Re-attempt the current item while in the `'error'` state. |
|
|
321
|
+
| `usePlayingState()` | `{ playing, buffering }` booleans for a play/pause button. |
|
|
322
|
+
| `setPlayWhenReady(bool)` / `usePlayWhenReady()` | Play/pause _intent_, independent of buffering. |
|
|
323
|
+
| `usePolledProgress(intervalMs?)` | `{ position, duration, buffered }` via polling (ms) — works by default. |
|
|
324
|
+
| `useProgress()` / `getProgress()` | Progress via events (enable `progressUpdateEventInterval` first). |
|
|
325
|
+
| `getRate()` / `setRate(rate)` | Playback speed multiplier (`1` = normal). |
|
|
326
|
+
| `getVolume()` / `setVolume(0..1)` | This player's volume. |
|
|
327
|
+
| `getSystemVolume()` / `useSystemVolume()` | Device media volume (`setSystemVolume` is iOS no-op). |
|
|
328
|
+
| `trackPlaybackTime(cb, period)` | Heartbeat every `period` seconds of real playback. |
|
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: 'Build and edit the queue, move between tracks with next and previous, and read it back for an "up next" UI.'
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# Queue
|
|
6
|
+
|
|
7
|
+
The **queue** is the ordered list of tracks the player works through. The track
|
|
8
|
+
at the current position is the **active track** — it's what plays, and what
|
|
9
|
+
next/previous move between. This guide covers building the queue, editing it,
|
|
10
|
+
moving between tracks, and reading it back for an "up next" UI.
|
|
11
|
+
|
|
12
|
+
For the first-time setup and the browse-tree concept, see
|
|
13
|
+
[Basic Usage](/guide/basic-usage#the-queue). For transport (play / pause / seek
|
|
14
|
+
_within_ the active track), see [Playback](/guide/playback). The system
|
|
15
|
+
surfaces (lock screen, car) are in [Now Playing](/guide/now-playing).
|
|
16
|
+
|
|
17
|
+
The UI snippets below import `View` / `Text` / `Button` / `FlatList` from
|
|
18
|
+
`react-native`; those import lines are omitted for brevity. Everything else
|
|
19
|
+
comes from `react-native-audio-browser`.
|
|
20
|
+
|
|
21
|
+
## Setting the queue
|
|
22
|
+
|
|
23
|
+
[`setQueue`](/api/features/queue/#setqueue) replaces the entire queue.
|
|
24
|
+
Optionally pass which track to make active and where to start it:
|
|
25
|
+
|
|
26
|
+
```ts
|
|
27
|
+
import { setQueue, play } from 'react-native-audio-browser'
|
|
28
|
+
|
|
29
|
+
setQueue(tracks) // replace; first track becomes active
|
|
30
|
+
setQueue(tracks, 2) // start on index 2
|
|
31
|
+
setQueue(tracks, 2, 30) // ...30 seconds in
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
One thing to know:
|
|
35
|
+
|
|
36
|
+
- **It doesn't start playback.** `setQueue` leaves the play/pause state as-is —
|
|
37
|
+
if the player was paused it stays paused. Call
|
|
38
|
+
[`play()`](/api/features/playback/#play) to begin (see [Playback](/guide/playback)).
|
|
39
|
+
|
|
40
|
+
So the usual "load these and play" is two calls:
|
|
41
|
+
|
|
42
|
+
```ts
|
|
43
|
+
setQueue(tracks) // or setQueue(tracks, startIndex)
|
|
44
|
+
play() // start playback
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Reading the queue
|
|
48
|
+
|
|
49
|
+
[`useQueue`](/api/features/queue/#usequeue) returns the current
|
|
50
|
+
[`Track[]`](/api/types/browser-nodes/#track) and re-renders whenever it changes
|
|
51
|
+
(add, remove, reorder, or metadata update):
|
|
52
|
+
|
|
53
|
+
```tsx
|
|
54
|
+
import { useQueue } from 'react-native-audio-browser'
|
|
55
|
+
|
|
56
|
+
function QueueList() {
|
|
57
|
+
const queue = useQueue()
|
|
58
|
+
return (
|
|
59
|
+
<FlatList
|
|
60
|
+
data={queue}
|
|
61
|
+
keyExtractor={(track, i) => track.id ?? String(i)}
|
|
62
|
+
renderItem={({ item }) => <Text>{item.title}</Text>}
|
|
63
|
+
/>
|
|
64
|
+
)
|
|
65
|
+
}
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Outside React, read a snapshot with
|
|
69
|
+
[`getQueue()`](/api/features/queue/#getqueue), or a single entry with
|
|
70
|
+
[`getTrack(index)`](/api/features/queue/#gettrack) (returns `undefined` if the
|
|
71
|
+
index is empty). To react to changes without a hook, subscribe to
|
|
72
|
+
[`onQueueChanged`](/api/features/queue/#onqueuechanged) — it fires with the new
|
|
73
|
+
queue on every add, remove, reorder, or metadata update.
|
|
74
|
+
|
|
75
|
+
## The active track
|
|
76
|
+
|
|
77
|
+
[`useActiveTrack`](/api/features/queue/#useactivetrack) returns the currently
|
|
78
|
+
playing [`Track`](/api/types/browser-nodes/#track) (or `undefined` when nothing
|
|
79
|
+
is loaded):
|
|
80
|
+
|
|
81
|
+
```tsx
|
|
82
|
+
import { useActiveTrack } from 'react-native-audio-browser'
|
|
83
|
+
|
|
84
|
+
function NowPlaying() {
|
|
85
|
+
const track = useActiveTrack()
|
|
86
|
+
return <Text>{track?.title ?? 'Nothing playing'}</Text>
|
|
87
|
+
}
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Outside React, use [`getActiveTrack()`](/api/features/queue/#getactivetrack) and
|
|
91
|
+
[`getActiveTrackIndex()`](/api/features/queue/#getactivetrackindex). To react to
|
|
92
|
+
_changes_ — e.g. for per-track analytics — subscribe to
|
|
93
|
+
[`onActiveTrackChanged`](/api/features/queue/#onactivetrackchanged), which also
|
|
94
|
+
reports the track you're leaving:
|
|
95
|
+
|
|
96
|
+
```ts
|
|
97
|
+
import { onActiveTrackChanged } from 'react-native-audio-browser'
|
|
98
|
+
|
|
99
|
+
const unsubscribe = onActiveTrackChanged.addListener((event) => {
|
|
100
|
+
console.log('now playing', event.track?.title)
|
|
101
|
+
console.log('left off', event.lastTrack?.title, 'at', event.lastPosition)
|
|
102
|
+
})
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
## Moving between tracks
|
|
106
|
+
|
|
107
|
+
Jump to a track by index, or step to the next/previous one. Each optionally
|
|
108
|
+
takes a start position **in seconds**:
|
|
109
|
+
|
|
110
|
+
```ts
|
|
111
|
+
import { skip, skipToNext, skipToPrevious } from 'react-native-audio-browser'
|
|
112
|
+
|
|
113
|
+
skip(3) // jump to index 3
|
|
114
|
+
skipToNext() // next track
|
|
115
|
+
skipToPrevious() // previous track
|
|
116
|
+
skipToNext(10) // next track, starting 10 seconds in
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
This is how you move _between_ tracks
|
|
120
|
+
([`skip`](/api/features/queue/#skip),
|
|
121
|
+
[`skipToNext`](/api/features/queue/#skiptonext),
|
|
122
|
+
[`skipToPrevious`](/api/features/queue/#skiptoprevious)); seeking _within_ the
|
|
123
|
+
active track (`seekTo` / `seekBy`) lives in [Playback](/guide/playback).
|
|
124
|
+
|
|
125
|
+
## Editing the queue
|
|
126
|
+
|
|
127
|
+
[`add`](/api/features/queue/#add), [`move`](/api/features/queue/#move),
|
|
128
|
+
[`remove`](/api/features/queue/#remove),
|
|
129
|
+
[`removeUpcomingTracks`](/api/features/queue/#removeupcomingtracks), and
|
|
130
|
+
[`load`](/api/features/queue/#load) edit the queue without rebuilding it.
|
|
131
|
+
Indexes refer to positions in the current queue (the same order `useQueue`
|
|
132
|
+
returns).
|
|
133
|
+
|
|
134
|
+
```ts
|
|
135
|
+
import {
|
|
136
|
+
add,
|
|
137
|
+
move,
|
|
138
|
+
remove,
|
|
139
|
+
removeUpcomingTracks,
|
|
140
|
+
load
|
|
141
|
+
} from 'react-native-audio-browser'
|
|
142
|
+
|
|
143
|
+
add(track) // append one track (also accepts a Track[])
|
|
144
|
+
add(track, 0) // insert before index 0 (front of the queue)
|
|
145
|
+
|
|
146
|
+
move(4, 1) // move index 4 to index 1
|
|
147
|
+
// toIndex past the end moves the track to the end.
|
|
148
|
+
|
|
149
|
+
remove(2) // remove index 2 (also accepts number[])
|
|
150
|
+
removeUpcomingTracks() // drop everything after the active track
|
|
151
|
+
|
|
152
|
+
load(track) // replace the active track (or start the queue if empty)
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
The second argument to `add` is an **absolute** index, not "after the active
|
|
156
|
+
track". For a real **play-next** button, insert right after the active track:
|
|
157
|
+
|
|
158
|
+
```ts
|
|
159
|
+
import { add, getActiveTrackIndex } from 'react-native-audio-browser'
|
|
160
|
+
|
|
161
|
+
const i = getActiveTrackIndex()
|
|
162
|
+
add(track, i === undefined ? 0 : i + 1)
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
**Removing the active track** doesn't stop playback: the next track becomes
|
|
166
|
+
active, or — if you removed the last one — the first track does.
|
|
167
|
+
|
|
168
|
+
## Repeat and shuffle
|
|
169
|
+
|
|
170
|
+
[Repeat mode](/api/features/queue/#userepeatmode) is one of `'off'`, `'track'`,
|
|
171
|
+
or `'queue'`:
|
|
172
|
+
|
|
173
|
+
| Mode | Behavior |
|
|
174
|
+
| --------- | ------------------------------------------------ |
|
|
175
|
+
| `'off'` | Stop when the last track finishes (the default). |
|
|
176
|
+
| `'track'` | Repeat the active track forever. |
|
|
177
|
+
| `'queue'` | Loop the whole queue. |
|
|
178
|
+
|
|
179
|
+
```tsx
|
|
180
|
+
import {
|
|
181
|
+
setRepeatMode,
|
|
182
|
+
useRepeatMode,
|
|
183
|
+
toggleShuffle,
|
|
184
|
+
useShuffle
|
|
185
|
+
} from 'react-native-audio-browser'
|
|
186
|
+
|
|
187
|
+
function RepeatButton() {
|
|
188
|
+
const mode = useRepeatMode()
|
|
189
|
+
const next = mode === 'off' ? 'queue' : mode === 'queue' ? 'track' : 'off'
|
|
190
|
+
return (
|
|
191
|
+
<Button title={`Repeat: ${mode}`} onPress={() => setRepeatMode(next)} />
|
|
192
|
+
)
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function ShuffleButton() {
|
|
196
|
+
const on = useShuffle()
|
|
197
|
+
return (
|
|
198
|
+
<Button
|
|
199
|
+
title={on ? 'Shuffle: on' : 'Shuffle: off'}
|
|
200
|
+
onPress={() => toggleShuffle()}
|
|
201
|
+
/>
|
|
202
|
+
)
|
|
203
|
+
}
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
[`setShuffle(true | false)`](/api/features/queue/#setshuffle) sets it directly;
|
|
207
|
+
[`toggleShuffle()`](/api/features/queue/#toggleshuffle) flips it.
|
|
208
|
+
|
|
209
|
+
## When the queue ends
|
|
210
|
+
|
|
211
|
+
With repeat `'off'`, playback pauses when the last track finishes and the
|
|
212
|
+
playback state becomes `'ended'` (see [Playback](/guide/playback#playback-state)).
|
|
213
|
+
To react — autoplay more, log a session, show a prompt — subscribe to
|
|
214
|
+
[`onQueueEnded`](/api/features/queue/#onqueueended):
|
|
215
|
+
|
|
216
|
+
```ts
|
|
217
|
+
import { onQueueEnded } from 'react-native-audio-browser'
|
|
218
|
+
|
|
219
|
+
const unsubscribe = onQueueEnded.addListener(({ track, position }) => {
|
|
220
|
+
// `track` is the index that was active; `position` is where it stopped.
|
|
221
|
+
loadMoreAndPlay()
|
|
222
|
+
})
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
## A complete "Up Next" screen
|
|
226
|
+
|
|
227
|
+
`useQueue` plus `useActiveTrack` is enough for a reorderable list with
|
|
228
|
+
tap-to-play and remove:
|
|
229
|
+
|
|
230
|
+
```tsx
|
|
231
|
+
import { View, Text, Button, FlatList } from 'react-native'
|
|
232
|
+
import {
|
|
233
|
+
useQueue,
|
|
234
|
+
useActiveTrack,
|
|
235
|
+
getActiveTrackIndex,
|
|
236
|
+
skip,
|
|
237
|
+
move,
|
|
238
|
+
remove
|
|
239
|
+
} from 'react-native-audio-browser'
|
|
240
|
+
|
|
241
|
+
function UpNext() {
|
|
242
|
+
const queue = useQueue()
|
|
243
|
+
// `useActiveTrack` re-renders the list when the active track changes; we
|
|
244
|
+
// compare each row by index (more robust than by id, which is optional on a
|
|
245
|
+
// Track). `getActiveTrackIndex()` is fresh on each render.
|
|
246
|
+
const active = useActiveTrack()
|
|
247
|
+
const activeIndex = active ? getActiveTrackIndex() : undefined
|
|
248
|
+
|
|
249
|
+
return (
|
|
250
|
+
<FlatList
|
|
251
|
+
data={queue}
|
|
252
|
+
keyExtractor={(track, i) => track.id ?? String(i)}
|
|
253
|
+
renderItem={({ item, index }) => (
|
|
254
|
+
<View style={{ flexDirection: 'row', alignItems: 'center' }}>
|
|
255
|
+
<Text
|
|
256
|
+
onPress={() => skip(index)}
|
|
257
|
+
style={index === activeIndex ? { fontWeight: 'bold' } : null}
|
|
258
|
+
>
|
|
259
|
+
{item.title}
|
|
260
|
+
</Text>
|
|
261
|
+
{index > 0 && (
|
|
262
|
+
<Button title="↑" onPress={() => move(index, index - 1)} />
|
|
263
|
+
)}
|
|
264
|
+
<Button title="✕" onPress={() => remove(index)} />
|
|
265
|
+
</View>
|
|
266
|
+
)}
|
|
267
|
+
/>
|
|
268
|
+
)
|
|
269
|
+
}
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
A `<Button>` can't be a child of `<Text>`, so each row is a `<View>` with the
|
|
273
|
+
title and buttons as siblings. The `keyExtractor` falls back to the array index
|
|
274
|
+
when a track has no `id` — fine for a static list, but for a **reorderable**
|
|
275
|
+
list like this one, give your tracks a stable `id` so `move`/`remove` don't
|
|
276
|
+
confuse row identity.
|
|
277
|
+
|
|
278
|
+
## A note on units
|
|
279
|
+
|
|
280
|
+
All position arguments are **seconds** — `seekTo`, `seekBy`, the
|
|
281
|
+
`initialPosition` on `skip` / `skipToNext` / `skipToPrevious`, and `setQueue`'s
|
|
282
|
+
`startPosition`.
|
|
283
|
+
|
|
284
|
+
## API summary
|
|
285
|
+
|
|
286
|
+
| API | Purpose |
|
|
287
|
+
| ----------------------------------------------------------------- | --------------------------------------------------------------- |
|
|
288
|
+
| `setQueue(tracks, startIndex?, startPosition?)` | Replace the queue (doesn't auto-play; position in **seconds**). |
|
|
289
|
+
| `add(track \| tracks, insertBeforeIndex?)` | Append, or insert before an index. |
|
|
290
|
+
| `load(track)` | Replace the active track (or start the queue if empty). |
|
|
291
|
+
| `move(from, to)` | Reorder; `to` past the end → end. |
|
|
292
|
+
| `remove(index \| indexes)` | Remove track(s); active removal advances. |
|
|
293
|
+
| `removeUpcomingTracks()` | Drop everything after the active track. |
|
|
294
|
+
| `skip(index, initialPosition?)` | Jump to a track (position in **seconds**). |
|
|
295
|
+
| `skipToNext()` / `skipToPrevious()` | Step between tracks. |
|
|
296
|
+
| `useQueue()` / `getQueue()` / `getTrack(i)` | Read the queue. |
|
|
297
|
+
| `useActiveTrack()` / `getActiveTrack()` / `getActiveTrackIndex()` | Read the active track. |
|
|
298
|
+
| `onActiveTrackChanged` / `onQueueChanged` / `onQueueEnded` | Subscribe to changes. |
|
|
299
|
+
| `setRepeatMode(mode)` / `useRepeatMode()` | `'off'` / `'track'` / `'queue'`. |
|
|
300
|
+
| `setShuffle(bool)` / `toggleShuffle()` / `useShuffle()` | Shuffle order. |
|