react-native-audio-browser 0.2.0-next.12 → 0.2.0-next.14
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 +8 -1
- package/android/src/main/java/com/audiobrowser/browser/BrowserManager.kt +52 -0
- package/android/src/main/java/com/audiobrowser/browser/InertStyleDiagnostic.kt +267 -0
- package/android/src/main/java/com/audiobrowser/browser/JsonModels.kt +27 -11
- package/android/src/main/java/com/audiobrowser/browser/SectionScope.kt +1 -1
- 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/MediaSessionCallback.kt +21 -22
- 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 +16 -6
- package/android/src/main/res/values/strings.xml +37 -2
- package/android/src/test/java/com/audiobrowser/TestFixtures.kt +50 -5
- package/android/src/test/java/com/audiobrowser/browser/BrowserManagerLayerResolutionTest.kt +9 -0
- 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 +10 -9
- package/ios/Browser/BrowserManager.swift +106 -45
- package/ios/Browser/InertStyleDiagnostic.swift +279 -0
- package/ios/Browser/JsonModels.swift +81 -15
- 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/Section+Copying.swift +1 -3
- package/ios/Extension/UIApplication+ActiveScene.swift +27 -0
- package/ios/Http/HttpClient.swift +8 -2
- package/ios/HybridAudioBrowser.swift +56 -12
- package/ios/Model/NitroTypeStubs.swift +60 -1
- 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/ResolvedTrackCopyingTests.swift +2 -2
- 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/browser-config.js +1 -0
- package/lib/commonjs/features/browser-config.js.map +1 -1
- 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/browser-config.js +1 -0
- package/lib/module/features/browser-config.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/browser-config.d.ts.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-native.d.ts +2 -1
- package/lib/typescript/src/types/browser-native.d.ts.map +1 -1
- package/lib/typescript/src/types/browser-nodes.d.ts +116 -9
- package/lib/typescript/src/types/browser-nodes.d.ts.map +1 -1
- package/lib/typescript/src/types/browser.d.ts +36 -0
- package/lib/typescript/src/types/browser.d.ts.map +1 -1
- package/nitrogen/generated/android/AudioBrowserOnLoad.cpp +2 -0
- 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__string__.hpp +102 -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 +13 -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 +20 -1
- 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 +7 -5
- 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/Func_std__shared_ptr_Promise_std__string__.kt +80 -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/NativeBrowserConfiguration.kt +7 -4
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Section.kt +2 -5
- 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.cpp +9 -0
- package/nitrogen/generated/ios/AudioBrowser-Swift-Cxx-Bridge.hpp +104 -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/Func_std__shared_ptr_Promise_std__string__.swift +54 -0
- package/nitrogen/generated/ios/swift/GridTile.swift +44 -0
- package/nitrogen/generated/ios/swift/ImageShape.swift +40 -0
- package/nitrogen/generated/ios/swift/NativeBrowserConfiguration.swift +48 -1
- package/nitrogen/generated/ios/swift/Section.swift +1 -19
- 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++/NativeBrowserConfiguration.hpp +6 -2
- package/nitrogen/generated/shared/c++/Section.hpp +1 -5
- 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/browser-config.ts +1 -0
- 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-native.ts +5 -1
- package/src/types/browser-nodes.ts +124 -9
- package/src/types/browser.ts +38 -0
- 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,135 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: 'Read where audio is currently playing (speaker, Bluetooth, AirPlay, the car), react when the route changes, and open the system output switcher.'
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# Audio Output
|
|
6
|
+
|
|
7
|
+
Read where audio is currently playing (speaker, Bluetooth, AirPlay, the car…),
|
|
8
|
+
react when it changes, and open the system output switcher so the listener can
|
|
9
|
+
move playback to another device. Both layers are **cross-platform**:
|
|
10
|
+
|
|
11
|
+
- **Reading the current output** — `getOutput()` / `useOutput()` /
|
|
12
|
+
`onOutputChanged`. iOS reports the active route while a session is active;
|
|
13
|
+
Android reports the active output route from `AudioManager` (the `type` is
|
|
14
|
+
coarse — e.g. wired headphones may report as `speaker`).
|
|
15
|
+
- **Opening the switcher** — `openOutputPicker()` / `supportsOutputSwitcher()`:
|
|
16
|
+
the system route picker on iOS, the system Output Switcher on Android 11+.
|
|
17
|
+
|
|
18
|
+
::: tip Gate the switcher button on `supportsOutputSwitcher()`
|
|
19
|
+
Surface your output button only when `supportsOutputSwitcher()` is `true` — it's
|
|
20
|
+
`true` on iOS and Android 11+ (API 30), `false` on older Android and web.
|
|
21
|
+
Reading the current output works more broadly, but `getOutput()` can still be
|
|
22
|
+
`undefined` (before playback, or when unknown) — so guard for it.
|
|
23
|
+
:::
|
|
24
|
+
|
|
25
|
+
The UI snippets import `Text` / `Button` from `react-native`; everything else is
|
|
26
|
+
from `react-native-audio-browser`.
|
|
27
|
+
|
|
28
|
+
## The current output
|
|
29
|
+
|
|
30
|
+
[`useOutput()`](/api/features/output/#useoutput) returns the active output and
|
|
31
|
+
re-renders whenever it changes (AirPods connect, headphones unplug, a Bluetooth
|
|
32
|
+
speaker is selected):
|
|
33
|
+
|
|
34
|
+
```tsx
|
|
35
|
+
import { Text } from 'react-native'
|
|
36
|
+
import { useOutput } from 'react-native-audio-browser'
|
|
37
|
+
|
|
38
|
+
function OutputLabel() {
|
|
39
|
+
const output = useOutput()
|
|
40
|
+
if (!output) return null // not yet known
|
|
41
|
+
return <Text>Playing on {output.name}</Text>
|
|
42
|
+
}
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
An [`Output`](/api/features/output/#output) is:
|
|
46
|
+
|
|
47
|
+
| Field | Type | Meaning |
|
|
48
|
+
| ---------- | ------------ | ------------------------------------------------------------------------------- |
|
|
49
|
+
| `name` | `string` | Human-readable device name, e.g. `"AirPods Pro"`, `"Kitchen speaker"`. |
|
|
50
|
+
| `type` | `OutputType` | The output kind (see below). |
|
|
51
|
+
| `external` | `boolean` | `false` only for the built-in speaker/receiver; `true` for everything external. |
|
|
52
|
+
|
|
53
|
+
`type` is one of `'speaker'`, `'receiver'` (the iOS earpiece), `'headphones'`,
|
|
54
|
+
`'bluetooth'`, `'airplay'`, `'car'`, `'hdmi'`, `'usb'`, `'cast'` (a remote
|
|
55
|
+
speaker / TV), or `'other'`. Both platforms map their native ports/routes into
|
|
56
|
+
this shared set — e.g. iOS's separate Bluetooth ports all collapse to
|
|
57
|
+
`'bluetooth'`.
|
|
58
|
+
|
|
59
|
+
::: warning Android reports a coarser type
|
|
60
|
+
On Android the type comes from the system route and is coarser: wired headphones
|
|
61
|
+
often report as `'speaker'` (the built-in route), and really only `'bluetooth'`
|
|
62
|
+
is reliably distinguished from it (`'cast'` requires active route discovery,
|
|
63
|
+
which the reader doesn't run, so it won't appear here). The device **name** still
|
|
64
|
+
comes through.
|
|
65
|
+
:::
|
|
66
|
+
|
|
67
|
+
Outside React, read a snapshot with `getOutput()` or subscribe with
|
|
68
|
+
[`onOutputChanged`](/api/features/output/#onoutputchanged):
|
|
69
|
+
|
|
70
|
+
```ts
|
|
71
|
+
import { getOutput, onOutputChanged } from 'react-native-audio-browser'
|
|
72
|
+
|
|
73
|
+
getOutput() // Output | undefined
|
|
74
|
+
|
|
75
|
+
const unsubscribe = onOutputChanged.addListener((output) => {
|
|
76
|
+
console.log('now playing on', output.name)
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
// later:
|
|
80
|
+
unsubscribe()
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## Opening the output picker
|
|
84
|
+
|
|
85
|
+
[`openOutputPicker()`](/api/features/output/#openoutputpicker) presents the
|
|
86
|
+
system output switcher — the AirPlay / Bluetooth / speaker chooser on iOS, and
|
|
87
|
+
the Bluetooth / speaker / Cast switcher on Android (11+). Wire it to your own
|
|
88
|
+
output button, shown only when
|
|
89
|
+
[`supportsOutputSwitcher()`](/api/features/output/#supportsoutputswitcher) is
|
|
90
|
+
`true`:
|
|
91
|
+
|
|
92
|
+
```tsx
|
|
93
|
+
import { Button } from 'react-native'
|
|
94
|
+
import {
|
|
95
|
+
openOutputPicker,
|
|
96
|
+
supportsOutputSwitcher
|
|
97
|
+
} from 'react-native-audio-browser'
|
|
98
|
+
|
|
99
|
+
function OutputButton() {
|
|
100
|
+
if (!supportsOutputSwitcher()) return null // older Android / web
|
|
101
|
+
return <Button title="Output" onPress={() => openOutputPicker()} />
|
|
102
|
+
}
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
`openOutputPicker()` is safe to call unconditionally — it's a no-op where no
|
|
106
|
+
switcher exists — but gating the button on `supportsOutputSwitcher()` keeps you
|
|
107
|
+
from showing a control that would do nothing.
|
|
108
|
+
|
|
109
|
+
On Android the switcher is the system one (the picker from the media
|
|
110
|
+
notification); selecting a Bluetooth speaker there routes audio at the OS level.
|
|
111
|
+
The library can't force playback to a _specific_ Bluetooth device itself — route
|
|
112
|
+
selection is the system's job, which is what this hands off to. Sonos appears on
|
|
113
|
+
iOS (as an AirPlay target); on Android it isn't a system route, so it won't show.
|
|
114
|
+
|
|
115
|
+
## You may not need this for unplug handling
|
|
116
|
+
|
|
117
|
+
When the current output **disappears** — headphones unplugged, a Bluetooth
|
|
118
|
+
speaker powered off — the player **pauses automatically** rather than blaring out
|
|
119
|
+
of the built-in speaker (the convention every media app follows). It's a
|
|
120
|
+
deliberate pause: the listener presses play again when ready. So you don't need
|
|
121
|
+
to watch `onOutputChanged` just to handle unplugging — reach for these APIs to
|
|
122
|
+
_display_ the output or to _offer_ a switcher.
|
|
123
|
+
|
|
124
|
+
## API summary
|
|
125
|
+
|
|
126
|
+
| API | Platforms | Purpose |
|
|
127
|
+
| -------------------------- | ---------------- | -------------------------------------------------------------------------- |
|
|
128
|
+
| `openOutputPicker()` | iOS, Android 11+ | Present the system output switcher (Bluetooth / AirPlay / speaker / Cast). |
|
|
129
|
+
| `supportsOutputSwitcher()` | all | Whether a switcher can be shown — gate your output button on it. |
|
|
130
|
+
| `useOutput()` | iOS, Android | Reactive current output (`Output \| undefined`). |
|
|
131
|
+
| `getOutput()` | iOS, Android | One-off snapshot of the current output. |
|
|
132
|
+
| `onOutputChanged` | iOS, Android | Subscribe outside React; `addListener(cb)` returns an unsubscribe fn. |
|
|
133
|
+
|
|
134
|
+
The whole API is cross-platform; only the switcher needs Android 11+ (gate on
|
|
135
|
+
`supportsOutputSwitcher()`), and Android reports coarser output `type`s than iOS.
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: 'How CarPlay, Android Auto, and Android Automotive all reuse the browse tree, queue, now-playing metadata, and search source you have already built.'
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# In the car
|
|
6
|
+
|
|
7
|
+
CarPlay, Android Auto, and Android Automotive all show your content on the car
|
|
8
|
+
screen and play through the same player as your app. You don't build a separate
|
|
9
|
+
"car app" — the car surfaces are driven by the **same cross-cutting pieces you've
|
|
10
|
+
already built**:
|
|
11
|
+
|
|
12
|
+
- your [browse tree](/guide/browser) becomes the car's browsable menus,
|
|
13
|
+
- your [now-playing metadata](/guide/now-playing) fills the car's now-playing
|
|
14
|
+
screen,
|
|
15
|
+
- the [player and queue](/guide/playback) are shared, so playback is continuous
|
|
16
|
+
whether the user started it in-app or in the car,
|
|
17
|
+
- your [`search` source](/guide/search) answers in-car voice commands
|
|
18
|
+
("play jazz"),
|
|
19
|
+
- [favorites](/guide/favorites) surface a heart and a "play my favorites" intent.
|
|
20
|
+
|
|
21
|
+
Build those once and they light up every car surface. The per-platform pages
|
|
22
|
+
handle the platform-specific **setup**: **[Android Auto](/guide/android-auto)**
|
|
23
|
+
(also Android Automotive) and **[CarPlay](/guide/carplay)**.
|
|
24
|
+
|
|
25
|
+
## Shared vs platform-specific
|
|
26
|
+
|
|
27
|
+
| Concern | Where it's handled |
|
|
28
|
+
| ------------------------------------- | ------------------------------------------------------------------------------------------------------------------------ |
|
|
29
|
+
| Browse tree / menus | Shared — [Browser](/guide/browser) |
|
|
30
|
+
| Now-playing metadata & artwork | Shared — [Now Playing](/guide/now-playing) |
|
|
31
|
+
| Player, queue, playback | Shared — [Playback](/guide/playback) |
|
|
32
|
+
| Voice search ("play …") | Shared — your [`search`](/guide/search) source |
|
|
33
|
+
| Favorites heart & "play my favorites" | Shared — [Favorites](/guide/favorites) |
|
|
34
|
+
| App setup, entitlements, manifest | Per-platform — [Android Auto](/guide/android-auto) / [CarPlay](/guide/carplay) |
|
|
35
|
+
| Now-playing buttons | Per-platform — [Button layout](/guide/remote-controls#button-layout-android) (Android) / [CarPlay](/guide/carplay) (iOS) |
|
|
36
|
+
|
|
37
|
+
The takeaway: spend your effort on the shared content model; reach for a platform
|
|
38
|
+
page only for that platform's setup and buttons.
|
|
39
|
+
|
|
40
|
+
## Detecting a car connection
|
|
41
|
+
|
|
42
|
+
To adapt your in-app UI while connected to a car (a "now playing in your car"
|
|
43
|
+
state, a simplified screen), use
|
|
44
|
+
[`useCarConnected()`](/api/features/carConnection/#usecarconnected). It's `true`
|
|
45
|
+
for a CarPlay connection on iOS, and an Android Auto **or Android Automotive**
|
|
46
|
+
connection on Android (`false` on web):
|
|
47
|
+
|
|
48
|
+
```tsx
|
|
49
|
+
import { Text } from 'react-native'
|
|
50
|
+
import { useCarConnected } from 'react-native-audio-browser'
|
|
51
|
+
|
|
52
|
+
function CarBadge() {
|
|
53
|
+
const inCar = useCarConnected()
|
|
54
|
+
if (!inCar) return null
|
|
55
|
+
return <Text>Playing in your car</Text>
|
|
56
|
+
}
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Outside React, read a snapshot with
|
|
60
|
+
[`isCarConnected()`](/api/features/carConnection/#iscarconnected) or subscribe
|
|
61
|
+
with [`onCarConnectedChanged`](/api/features/carConnection/#oncarconnectedchanged)
|
|
62
|
+
(`addListener` returns an unsubscribe function):
|
|
63
|
+
|
|
64
|
+
```ts
|
|
65
|
+
import {
|
|
66
|
+
isCarConnected,
|
|
67
|
+
onCarConnectedChanged
|
|
68
|
+
} from 'react-native-audio-browser'
|
|
69
|
+
|
|
70
|
+
isCarConnected() // boolean
|
|
71
|
+
|
|
72
|
+
const unsubscribe = onCarConnectedChanged.addListener((connected) => {
|
|
73
|
+
// ...
|
|
74
|
+
})
|
|
75
|
+
// later: unsubscribe()
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## Next steps
|
|
79
|
+
|
|
80
|
+
1. Set up the platform(s) you ship to —
|
|
81
|
+
**[Android Auto](/guide/android-auto)** (and Android Automotive) and/or
|
|
82
|
+
**[CarPlay](/guide/carplay)**.
|
|
83
|
+
2. Make sure your [browse tree](/guide/browser) and
|
|
84
|
+
[now-playing](/guide/now-playing) are in good shape — that's what the car
|
|
85
|
+
renders.
|
|
86
|
+
3. Wire your [`search` source](/guide/search) so voice commands work in the car.
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: 'The browse tree in one page: browsable versus playable Tracks, setting up the player, declaring content, and playing it.'
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# Basic Usage
|
|
6
|
+
|
|
7
|
+
This guide covers the core idea — the **browse tree** — and the minimal code to set up the player, define some content, and play it. Installation is in [Getting Started](/guide/getting-started).
|
|
8
|
+
|
|
9
|
+
## The browse tree
|
|
10
|
+
|
|
11
|
+
The library models all content as one navigable tree of **Tracks**. A single Track is **browsable**, **playable**, or both, depending on which fields you set:
|
|
12
|
+
|
|
13
|
+
- **Browsable** — has a `path`; navigating into it resolves its children.
|
|
14
|
+
- **Playable** — has a `src`; the player can stream it.
|
|
15
|
+
|
|
16
|
+
Three fields at a glance — each answers a different question about a Track:
|
|
17
|
+
|
|
18
|
+
| Field | Answers | Meaning |
|
|
19
|
+
| ------ | ------------------ | --------------------------------------------------------- |
|
|
20
|
+
| `path` | where does it go? | the browse-tree address navigating opens |
|
|
21
|
+
| `src` | what does it play? | the media identifier the player streams |
|
|
22
|
+
| `id` | which item is it? | identity across surfaces — falls back to `src` when unset |
|
|
23
|
+
|
|
24
|
+
```mermaid
|
|
25
|
+
graph TD
|
|
26
|
+
Root[Root]
|
|
27
|
+
Root --> Favorites[Favorites]
|
|
28
|
+
Root --> Recent[Recently Played]
|
|
29
|
+
Root --> Browse[Browse]
|
|
30
|
+
|
|
31
|
+
Browse --> Jazz[Jazz]
|
|
32
|
+
Browse --> Rock[Rock]
|
|
33
|
+
|
|
34
|
+
Jazz --> J1[Smooth Floret FM]
|
|
35
|
+
Jazz --> J2[The Stalk 88.5]
|
|
36
|
+
Rock --> R90[Vintage]
|
|
37
|
+
Rock --> R1[Crunchy Greens]
|
|
38
|
+
Rock --> R2[Stem City Radio]
|
|
39
|
+
R90 --> R3[Funky Floret 101.3]
|
|
40
|
+
R90 --> R4[Wilted Greens FM]
|
|
41
|
+
|
|
42
|
+
classDef browsable fill:#e3f2fd,stroke:#1976d2
|
|
43
|
+
classDef playable fill:#e8f5e9,stroke:#388e3c
|
|
44
|
+
|
|
45
|
+
class Root,Favorites,Recent,Browse,Jazz,Rock,R90 browsable
|
|
46
|
+
class J1,J2,R1,R2,R3,R4 playable
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
<div style="display: flex; gap: 1rem; margin-top: 0.5rem; font-size: 0.9em;">
|
|
50
|
+
<span><span style="display: inline-block; width: 12px; height: 12px; background: #e3f2fd; border: 1px solid #1976d2; margin-right: 4px;"></span> Browsable: opens to children</span>
|
|
51
|
+
<span><span style="display: inline-block; width: 12px; height: 12px; background: #e8f5e9; border: 1px solid #388e3c; margin-right: 4px;"></span> Playable: streams audio</span>
|
|
52
|
+
</div>
|
|
53
|
+
|
|
54
|
+
One tree powers both your in-app UI and the native browse views on CarPlay and Android Auto.
|
|
55
|
+
|
|
56
|
+
## Set up the player
|
|
57
|
+
|
|
58
|
+
Call `setupPlayer()` once at startup to initialize the player:
|
|
59
|
+
|
|
60
|
+
```ts
|
|
61
|
+
import { setupPlayer } from 'react-native-audio-browser'
|
|
62
|
+
|
|
63
|
+
await setupPlayer()
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Define a browse tree
|
|
67
|
+
|
|
68
|
+
Describe the tree with `configureBrowser`: **tabs** along the top, and **routes** that resolve each path to its children. The simplest source is static data declared inline:
|
|
69
|
+
|
|
70
|
+
```ts
|
|
71
|
+
import { configureBrowser } from 'react-native-audio-browser'
|
|
72
|
+
|
|
73
|
+
configureBrowser({
|
|
74
|
+
tabs: [{ title: 'Browse', path: '/browse' }],
|
|
75
|
+
routes: {
|
|
76
|
+
'/browse': {
|
|
77
|
+
path: '/browse',
|
|
78
|
+
title: 'Browse',
|
|
79
|
+
children: [
|
|
80
|
+
{
|
|
81
|
+
title: 'Jazz',
|
|
82
|
+
// A path makes this browsable — tapping it opens that path.
|
|
83
|
+
path: '/browse/jazz'
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
title: 'Smooth Floret FM',
|
|
87
|
+
// A src makes this playable — tapping it streams the track.
|
|
88
|
+
src: 'https://example.com/floret.mp3'
|
|
89
|
+
}
|
|
90
|
+
]
|
|
91
|
+
},
|
|
92
|
+
'/browse/jazz': {
|
|
93
|
+
path: '/browse/jazz',
|
|
94
|
+
title: 'Jazz',
|
|
95
|
+
children: [
|
|
96
|
+
{
|
|
97
|
+
title: 'The Stalk 88.5',
|
|
98
|
+
src: 'https://example.com/stalk.mp3'
|
|
99
|
+
}
|
|
100
|
+
]
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
})
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
A route value is a [`BrowserSource`](/api/types/browser/#browsersource): the static page object shown above, an **async callback** that returns one (`'/path': async ({ routeParams }) => ({ title, children })`), or an HTTP request config that fetches it from your API — handy for trees too large to declare upfront.
|
|
107
|
+
|
|
108
|
+
A page can also group its tracks into titled, styled **sections** (`sections: [{ title, style, children }, …]`) — a flat `children` list like the ones above is shorthand for a single untitled section. See [Browser → How a path resolves](/guide/browser#how-a-path-resolves).
|
|
109
|
+
|
|
110
|
+
## Play a track
|
|
111
|
+
|
|
112
|
+
On CarPlay and Android Auto, tapping a playable Track plays it for you. To drive playback from your own UI, set a queue and call `play`:
|
|
113
|
+
|
|
114
|
+
```ts
|
|
115
|
+
import { setQueue, play } from 'react-native-audio-browser'
|
|
116
|
+
|
|
117
|
+
setQueue([{ title: 'Smooth Floret FM', src: 'https://example.com/floret.mp3' }])
|
|
118
|
+
play()
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
## The queue
|
|
122
|
+
|
|
123
|
+
The player works through a **queue** of Tracks. The Track at the current position is the **active track** — it's what plays, and what external next/previous controls (car, headphones, lock screen) move between.
|
|
124
|
+
|
|
125
|
+
**Now Playing** is the metadata shown on the lock screen, notification, and car surfaces. By default it mirrors the active track; for live streams you can override it as the current song changes — see [Now Playing](/guide/now-playing).
|
|
126
|
+
|
|
127
|
+
## Read state in your UI
|
|
128
|
+
|
|
129
|
+
Reactive hooks keep your in-app UI in sync with playback — they re-render automatically as it changes:
|
|
130
|
+
|
|
131
|
+
```tsx
|
|
132
|
+
import {
|
|
133
|
+
togglePlayback,
|
|
134
|
+
usePlayingState,
|
|
135
|
+
useActiveTrack
|
|
136
|
+
} from 'react-native-audio-browser'
|
|
137
|
+
|
|
138
|
+
function PlayPauseButton() {
|
|
139
|
+
const { playing } = usePlayingState()
|
|
140
|
+
const track = useActiveTrack()
|
|
141
|
+
|
|
142
|
+
return (
|
|
143
|
+
<Button
|
|
144
|
+
title={playing ? `Pause ${track?.title ?? ''}` : 'Play'}
|
|
145
|
+
onPress={() => togglePlayback()}
|
|
146
|
+
/>
|
|
147
|
+
)
|
|
148
|
+
}
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
See the [API reference](/api/) for the full set of hooks (`useProgress`, `useQueue`, `useNowPlaying`, and more).
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: 'Detect the Android 12+ battery restrictions that block the foreground service the player needs, and prompt the listener to fix their settings.'
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# Battery
|
|
6
|
+
|
|
7
|
+
On Android 12+, if the OS has put your app under battery restrictions, it
|
|
8
|
+
**blocks the foreground service** the player needs to start audio in the
|
|
9
|
+
background. So when a Bluetooth device or car head unit tries to resume playback
|
|
10
|
+
while your app is killed, nothing happens. This feature detects that block and
|
|
11
|
+
lets you prompt the user to fix their battery settings.
|
|
12
|
+
|
|
13
|
+
::: warning Android only
|
|
14
|
+
This is an **Android-only** concern (Android 12+ foreground-service limits).
|
|
15
|
+
On iOS every getter/hook here is inert: `getBatteryWarningPending()` is always
|
|
16
|
+
`false`, `getBatteryOptimizationStatus()` is always `'unrestricted'`, the events
|
|
17
|
+
never fire, and `dismissBatteryWarning()` / `openBatterySettings()` are no-ops.
|
|
18
|
+
So you can render the banner below unconditionally — it simply never shows on iOS.
|
|
19
|
+
:::
|
|
20
|
+
|
|
21
|
+
The UI snippet imports `View` / `Text` / `Button` from `react-native`; everything
|
|
22
|
+
else is from `react-native-audio-browser`.
|
|
23
|
+
|
|
24
|
+
## The warning banner
|
|
25
|
+
|
|
26
|
+
A warning becomes **pending** when a background resume is actually blocked. Show
|
|
27
|
+
a banner only while `pending` is true, offering to open settings or dismiss.
|
|
28
|
+
[`useBatteryWarning()`](/api/features/battery/#usebatterywarning) bundles
|
|
29
|
+
everything you need:
|
|
30
|
+
|
|
31
|
+
```tsx
|
|
32
|
+
import { View, Text, Button } from 'react-native'
|
|
33
|
+
import { useBatteryWarning } from 'react-native-audio-browser'
|
|
34
|
+
|
|
35
|
+
function BatteryWarningBanner() {
|
|
36
|
+
const { pending, status, dismiss, openSettings } = useBatteryWarning()
|
|
37
|
+
if (!pending) return null
|
|
38
|
+
|
|
39
|
+
return (
|
|
40
|
+
<View>
|
|
41
|
+
<Text>Playback was blocked by battery settings ({status}).</Text>
|
|
42
|
+
<Button title="Fix" onPress={openSettings} />
|
|
43
|
+
<Button title="Dismiss" onPress={dismiss} />
|
|
44
|
+
</View>
|
|
45
|
+
)
|
|
46
|
+
}
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
- `openSettings` ([`openBatterySettings`](/api/features/battery/#openbatterysettings))
|
|
50
|
+
opens this app's system battery settings. When the user returns having set the
|
|
51
|
+
app to unrestricted, the warning clears itself.
|
|
52
|
+
- `dismiss` ([`dismissBatteryWarning`](/api/features/battery/#dismissbatterywarning))
|
|
53
|
+
hides the warning without changing settings.
|
|
54
|
+
|
|
55
|
+
## When the warning clears
|
|
56
|
+
|
|
57
|
+
`pending` goes back to `false` when any of these happens — you don't clear it
|
|
58
|
+
manually:
|
|
59
|
+
|
|
60
|
+
- the user calls `dismiss`, or
|
|
61
|
+
- the battery status becomes `'unrestricted'` (e.g. they fixed it in settings).
|
|
62
|
+
|
|
63
|
+
## Battery optimization status
|
|
64
|
+
|
|
65
|
+
`status` is the device's current setting for your app:
|
|
66
|
+
|
|
67
|
+
| Status | Meaning |
|
|
68
|
+
| ---------------- | -------------------------------------------------------------------- |
|
|
69
|
+
| `'unrestricted'` | The user allowed the app to run freely in the background. |
|
|
70
|
+
| `'optimized'` | Default — the system may limit background work (Doze / App Standby). |
|
|
71
|
+
| `'restricted'` | Background work is severely limited; services are blocked. |
|
|
72
|
+
|
|
73
|
+
Background resume is reliable only when `'unrestricted'`, which is why the "Fix"
|
|
74
|
+
button sends users to settings to grant it.
|
|
75
|
+
|
|
76
|
+
## Reading state directly
|
|
77
|
+
|
|
78
|
+
If you don't want the bundled hook, the pieces are available individually:
|
|
79
|
+
|
|
80
|
+
```ts
|
|
81
|
+
import {
|
|
82
|
+
getBatteryWarningPending,
|
|
83
|
+
getBatteryOptimizationStatus,
|
|
84
|
+
onBatteryWarningPendingChanged,
|
|
85
|
+
onBatteryOptimizationStatusChanged
|
|
86
|
+
} from 'react-native-audio-browser'
|
|
87
|
+
|
|
88
|
+
getBatteryWarningPending() // boolean
|
|
89
|
+
getBatteryOptimizationStatus() // 'unrestricted' | 'optimized' | 'restricted'
|
|
90
|
+
|
|
91
|
+
const off = onBatteryWarningPendingChanged.addListener(({ pending }) => {})
|
|
92
|
+
const off2 = onBatteryOptimizationStatusChanged.addListener(({ status }) => {})
|
|
93
|
+
|
|
94
|
+
// each addListener returns an unsubscribe function:
|
|
95
|
+
off()
|
|
96
|
+
off2()
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
The reactive hooks [`useBatteryWarningPending()`](/api/features/battery/#usebatterywarningpending)
|
|
100
|
+
and [`useBatteryOptimizationStatus()`](/api/features/battery/#usebatteryoptimizationstatus)
|
|
101
|
+
wrap the getter+event pairs; `useBatteryWarning()` simply combines both with the
|
|
102
|
+
two actions.
|
|
103
|
+
|
|
104
|
+
## API summary
|
|
105
|
+
|
|
106
|
+
| API | Purpose |
|
|
107
|
+
| ----------------------------------------------------------------------- | ---------------------------------------------------------------------- |
|
|
108
|
+
| `useBatteryWarning()` | Everything for a banner: `{ pending, status, dismiss, openSettings }`. |
|
|
109
|
+
| `getBatteryWarningPending()` / `useBatteryWarningPending()` | Is a warning pending? (Android; `false` on iOS) |
|
|
110
|
+
| `getBatteryOptimizationStatus()` / `useBatteryOptimizationStatus()` | Current status (Android; `'unrestricted'` on iOS) |
|
|
111
|
+
| `onBatteryWarningPendingChanged` / `onBatteryOptimizationStatusChanged` | Subscribe outside React. |
|
|
112
|
+
| `openBatterySettings()` | Open the app's system battery settings (Android). |
|
|
113
|
+
| `dismissBatteryWarning()` | Dismiss the warning without changing settings (Android). |
|
|
114
|
+
|
|
115
|
+
All are **Android-only** — inert (`false` / `'unrestricted'` / no-op) on iOS.
|