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,255 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: 'Set Track.artwork once and render it everywhere: the fetch and transform pipeline, per-surface sizing, SF Symbols, tinting, light/dark pairs, Android vector icons, and SVG.'
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# Artwork
|
|
6
|
+
|
|
7
|
+
**Artwork** is the image shown for a track — the thumbnail in a browse list, the cover on the lock screen, the icon on a CarPlay row. You set one field, `artwork`, on a [`Track`](/guide/track); the library resolves and transforms it per surface and per requested size, and hands the ready-to-render result back on `artworkSource`.
|
|
8
|
+
|
|
9
|
+
This guide covers the mental model (the two configs and the output field), **which artwork renders where**, the **transform pipeline** that turns your URL into a sized request, and the platform-specific bits: SF Symbols on iOS, tinting, light/dark artwork, Android vector icons, and SVG.
|
|
10
|
+
|
|
11
|
+
## Mental model
|
|
12
|
+
|
|
13
|
+
Three names do all the work:
|
|
14
|
+
|
|
15
|
+
| Name | What it is | You |
|
|
16
|
+
| -------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------- |
|
|
17
|
+
| `Track.artwork` | The image **you set** — an `https` URL, an `sf:` symbol (iOS), a platform URI, or a `{ light, dark }` pair. | set it |
|
|
18
|
+
| `artwork` / `nowPlayingArtwork` config | How the library **fetches** that image (base URL, headers, signing, sizing). | configure once (override per route / for now-playing) |
|
|
19
|
+
| `Track.artworkSource` | The resolved, ready-to-render [`ImageSource`](/api/types/browser-nodes/#imagesource) (URL + headers) the library **produces**. | read it |
|
|
20
|
+
|
|
21
|
+
`artwork` is the only field you set per track. `artworkSource` is **output-only** — the library populates it; never set it yourself.
|
|
22
|
+
|
|
23
|
+
```tsx
|
|
24
|
+
import { useActiveTrack } from 'react-native-audio-browser'
|
|
25
|
+
import { Image } from 'react-native'
|
|
26
|
+
|
|
27
|
+
function Cover() {
|
|
28
|
+
const track = useActiveTrack()
|
|
29
|
+
// artworkSource is the transformed result, shaped for <Image>
|
|
30
|
+
return <Image source={track?.artworkSource} />
|
|
31
|
+
}
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Which artwork renders where
|
|
35
|
+
|
|
36
|
+
The library publishes art to several surfaces; each gets its image from a field/config and (where the surface knows its display size) a size hint:
|
|
37
|
+
|
|
38
|
+
| Surface | Image from | Size hint | Tinting |
|
|
39
|
+
| ----------------------------------------------- | --------------------------------------------- | --------- | ------------------------------------------------------------------------------ |
|
|
40
|
+
| **Browse rows / tabs** (CarPlay / Android Auto) | `artwork` (per-route override allowed) | no | CarPlay: `style: { artworkRendering: 'stencil' }`, or a `{ light, dark }` pair |
|
|
41
|
+
| **In-app `<Image>`** | `artworkSource` (output) | no | your UI |
|
|
42
|
+
| **iOS lock screen / Control Center** | `nowPlayingArtwork` → falls back to `artwork` | yes | — |
|
|
43
|
+
| **CarPlay Now Playing** | `nowPlayingArtwork` → `artwork` | yes | — |
|
|
44
|
+
| **Android notification / Android Auto** | `nowPlayingArtwork` → `artwork` | yes | — (Android Auto is dark-only) |
|
|
45
|
+
| **CarPlay tile sections** (`display: 'grid'`) | `artwork` (each tile is a Track) | yes | CarPlay: `style: { artworkRendering: 'stencil' }`, or a `{ light, dark }` pair |
|
|
46
|
+
|
|
47
|
+
**Size hint** = the surface tells the library the pixels it needs, delivered as an [`ImageContext`](/api/types/browser/#imagecontext) to your `transform` / `imageQueryParams`. Browse-time resolution has none.
|
|
48
|
+
|
|
49
|
+
Two things to take from the table:
|
|
50
|
+
|
|
51
|
+
- **One image by default.** With only `artwork` set, the same source feeds browse rows _and_ every now-playing surface. Set `nowPlayingArtwork` only when the now-playing image should differ (a larger, lock-screen-quality cover without bloating list thumbnails).
|
|
52
|
+
- **Size hints exist only where the surface knows its size.** Browse-time resolution has no size, so `imageQueryParams` and `context` (below) do nothing there; they kick in at load time on CarPlay / Android Auto / now-playing.
|
|
53
|
+
- **Tinting is for browse icons only.** `artworkRendering: 'stencil'` is applied when CarPlay renders browse content — list rows, tabs, and tiles — not to now-playing cover art.
|
|
54
|
+
|
|
55
|
+
## The transform pipeline
|
|
56
|
+
|
|
57
|
+
`artwork` is an [`ArtworkRequestConfig`](/api/types/browser/#artworkrequestconfig). When the library needs a track's image, it builds the request in layers, each overriding the last:
|
|
58
|
+
|
|
59
|
+
1. **`request`** — shared base config (user agent, common headers).
|
|
60
|
+
2. **`artwork`** — the static image config (`baseUrl`, `headers`, …).
|
|
61
|
+
3. **`resolve(track)` / `resolveSync(track)`** — per-track overrides built from the track's metadata.
|
|
62
|
+
4. **`imageQueryParams`** — appends the surface's requested size (when it reports one).
|
|
63
|
+
5. **`transform({ request, context })` / `transformSync`** — final tweak, with the requested size in `context`.
|
|
64
|
+
|
|
65
|
+
To build a per-track URL, use `resolve` (the track is the argument). The `{id}` token is **not** a general feature — only `nowPlayingArtwork` templates it (see below); static values in `artwork` are sent verbatim.
|
|
66
|
+
|
|
67
|
+
A typical CDN setup never needs more than `baseUrl` plus `imageQueryParams`:
|
|
68
|
+
|
|
69
|
+
```ts
|
|
70
|
+
import { configureBrowser } from 'react-native-audio-browser'
|
|
71
|
+
|
|
72
|
+
configureBrowser({
|
|
73
|
+
// ...the rest of your config (tabs, routes, …)
|
|
74
|
+
artwork: {
|
|
75
|
+
baseUrl: 'https://images.example.com',
|
|
76
|
+
// tell the library which query params carry the requested size
|
|
77
|
+
imageQueryParams: { width: 'w', height: 'h' }
|
|
78
|
+
}
|
|
79
|
+
})
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
With [`imageQueryParams`](/api/types/browser/#imagequeryparams) the requested size from CarPlay / Android Auto is appended automatically — `?w=200&h=200` — so each surface gets right-sized art. Omit `height` to send width only. This only fires on surfaces that report a size; browse rows (no size hint) get the URL without these params.
|
|
83
|
+
|
|
84
|
+
For anything `imageQueryParams` can't express — per-track paths, signed URLs — use `resolve` and `transform` (or their sync variants):
|
|
85
|
+
|
|
86
|
+
```ts
|
|
87
|
+
import { configureBrowser } from 'react-native-audio-browser'
|
|
88
|
+
import HmacSHA256 from 'crypto-js/hmac-sha256' // add: yarn add crypto-js
|
|
89
|
+
|
|
90
|
+
const CDN_KEY = process.env.CDN_KEY ?? '' // your CDN signing key
|
|
91
|
+
|
|
92
|
+
// sign the path locally with your key — a pure, sync function
|
|
93
|
+
const signUrl = (path: string) => HmacSHA256(path, CDN_KEY).toString()
|
|
94
|
+
|
|
95
|
+
configureBrowser({
|
|
96
|
+
artwork: {
|
|
97
|
+
baseUrl: 'https://images.example.com',
|
|
98
|
+
// build the path from the track (sync — no await needed)
|
|
99
|
+
resolveSync: (track) => ({ path: `/covers/${track.id}.jpg` }),
|
|
100
|
+
// then sign it + size it. both are sync, so no Promise is
|
|
101
|
+
// allocated per image
|
|
102
|
+
transformSync: ({ request, context }) => ({
|
|
103
|
+
...request,
|
|
104
|
+
query: {
|
|
105
|
+
...request.query,
|
|
106
|
+
w: context?.width ? String(context.width) : '600',
|
|
107
|
+
// request.path is optional — guard it before signing
|
|
108
|
+
sig: signUrl(request.path ?? '')
|
|
109
|
+
}
|
|
110
|
+
})
|
|
111
|
+
}
|
|
112
|
+
})
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
A `transform` (or `transformSync`) **replaces** the request — it doesn't merge — so spread `...request` (and `...request.query`) or you'll drop the `baseUrl` and other layers. The [`context`](/api/types/browser/#imagecontext) is an `ImageContext` with `width` / `height` in **pixels**, present only when the surface knows its display size (so guard with `context?.width`). `request.path` is also optional — it's only set if a `resolve` ran first — so guard it before signing (as above).
|
|
116
|
+
|
|
117
|
+
`resolveSync` / `transformSync` are no-Promise variants of `resolve` / `transform`. Reach for them when the per-track config or final tweak needs no `await` — a synced query param, a locally-computed path — to skip a Promise allocation per image (the example above is fully sync for that reason). If you set _both_ the sync and async form of the **same** stage (e.g. `transform` _and_ `transformSync`), the async one runs first and the sync one tweaks its result; `resolve` and `transform` are independent stages that both always run.
|
|
118
|
+
|
|
119
|
+
::: tip Per-route artwork
|
|
120
|
+
A route can override the global `artwork` with its own config — `routes: { '/premium': { artwork: { baseUrl: '…' } } }` — so different content can load images from different hosts. See the [Browser](/guide/browser) guide.
|
|
121
|
+
:::
|
|
122
|
+
|
|
123
|
+
## SF Symbols (iOS)
|
|
124
|
+
|
|
125
|
+
Prefix `artwork` with `sf:` to render an Apple [SF Symbol](https://developer.apple.com/sf-symbols/) instead of fetching an image — ideal for tab and category icons. Optional `bg` (background, transparent if omitted) and `fg` (symbol color, black if omitted) query params set the colors:
|
|
126
|
+
|
|
127
|
+
```ts
|
|
128
|
+
{
|
|
129
|
+
title: 'Favorites',
|
|
130
|
+
path: '/favorites',
|
|
131
|
+
artwork: 'sf:heart.fill?bg=#FF0090&fg=#fff'
|
|
132
|
+
}
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
On CarPlay, an SF Symbol with **no explicit colors** is handed to CarPlay untinted, so CarPlay renders it in the appearance's own color (adapting to light/dark); give it a `bg`/`fg` and you get exactly those colors instead. The library renders the symbol to an image on device and caches it. This is iOS-only; on Android an `sf:` value is not a valid image.
|
|
136
|
+
|
|
137
|
+
## Tinting and platform icons
|
|
138
|
+
|
|
139
|
+
For monochrome **icons** (not full-color album art), let the system tint them to stay legible on either appearance:
|
|
140
|
+
|
|
141
|
+
- **iOS CarPlay** — declare [`style: { artworkRendering: 'stencil' }`](/api/types/browser-nodes/#trackstyle) on the track. Inside a page it's an inherited style property (a section- or page-wide value covers every item); tabs render without a section context, so a tab icon needs the declaration on the tab track itself. CarPlay tints the glyph per appearance: black in light mode, white in dark.
|
|
142
|
+
- **Android Auto** is dark-only, so there's no per-appearance tinting — ship an appropriately colored (e.g. white) icon. When a layout is declared (`display` on the section, page, or parent item), an `android.resource://…/drawable/…` artwork URI switches Android Auto to the **category** variant of that layout (which adds icon margins and lets Android Auto render it as an icon). The library only sets the category style; the visual treatment is Android Auto's — see [Browse display](/guide/android-auto#browse-display).
|
|
143
|
+
|
|
144
|
+
```ts
|
|
145
|
+
{
|
|
146
|
+
title: 'Settings',
|
|
147
|
+
path: '/settings',
|
|
148
|
+
artwork: 'sf:gear',
|
|
149
|
+
style: { artworkRendering: 'stencil' }
|
|
150
|
+
}
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
**SVG** is supported on both platforms and detected by a `.svg` URL suffix — serve SVGs with a path ending in `.svg` and they decode as vectors rather than raster images.
|
|
154
|
+
|
|
155
|
+
## Light and dark artwork
|
|
156
|
+
|
|
157
|
+
Tinting recolors _one_ image. When the two appearances need genuinely **different** images — a logo whose colors change, a mark with a light-on-dark counterpart — set `artwork` to an [`ArtworkVariants`](/api/types/browser-nodes/#artworkvariants) pair instead of a URL:
|
|
158
|
+
|
|
159
|
+
```ts
|
|
160
|
+
{
|
|
161
|
+
title: 'Playlists',
|
|
162
|
+
path: '/playlists',
|
|
163
|
+
artwork: {
|
|
164
|
+
light: 'https://images.example.com/playlists-light.png',
|
|
165
|
+
dark: 'https://images.example.com/playlists-dark.png'
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
Both fields are required — a pair with one side missing has no sensible fallback at render time.
|
|
171
|
+
|
|
172
|
+
On CarPlay browse rows and tabs the library fetches both and registers them as a single adaptive image, so switching appearance mid-drive swaps the image **in place** — no re-fetch, and no re-query of the browse tree. Each URL runs through the [transform pipeline](#the-transform-pipeline) independently, so `baseUrl`, headers, `imageQueryParams` and `resolve` / `transform` apply to both.
|
|
173
|
+
|
|
174
|
+
Everywhere a single image is required, a pair resolves to its `dark` URL:
|
|
175
|
+
|
|
176
|
+
| Surface | Uses |
|
|
177
|
+
| ---------------------------------------- | ----------------------------- |
|
|
178
|
+
| **CarPlay browse rows / tabs** | both, adapting per appearance |
|
|
179
|
+
| **Android Auto** | `dark` (dark-only platform) |
|
|
180
|
+
| **Now-playing** (all platforms) | `dark` |
|
|
181
|
+
| **`artworkSource`** (your own `<Image>`) | `dark` |
|
|
182
|
+
|
|
183
|
+
::: tip Pair or tint?
|
|
184
|
+
Prefer [`artworkRendering: 'stencil'`](#tinting-and-platform-icons) when recoloring the same shape gives the right result: it's one fetch instead of two, the variants can't drift apart, and it works from the image's alpha so the source color is irrelevant. Reach for a pair only when the appearances need different artwork.
|
|
185
|
+
:::
|
|
186
|
+
|
|
187
|
+
::: warning Android Auto fetches only `http(s)` artwork
|
|
188
|
+
Browse artwork is served to Android Auto through an internal content provider that only fetches `http`/`https` URLs. `android.resource://` and `file://` URIs pass through and render directly; other custom schemes won't load there.
|
|
189
|
+
:::
|
|
190
|
+
|
|
191
|
+
## Now-playing artwork
|
|
192
|
+
|
|
193
|
+
By default the now-playing image (lock screen, Control Center, CarPlay / Android Auto Now Playing) is the active track's `artwork` — the same image as its browse-row thumbnail. To resolve it differently, set `nowPlayingArtwork`, which builds the now-playing image from its **own** request and uniquely supports an `{id}` template filled from the active track's `id`:
|
|
194
|
+
|
|
195
|
+
```ts
|
|
196
|
+
import { configureBrowser } from 'react-native-audio-browser'
|
|
197
|
+
|
|
198
|
+
configureBrowser({
|
|
199
|
+
nowPlayingArtwork: {
|
|
200
|
+
baseUrl: 'https://images.example.com',
|
|
201
|
+
// {id} is replaced with the active track's id
|
|
202
|
+
path: '/artwork/{id}',
|
|
203
|
+
// ask the CDN for a lock-screen-quality variant
|
|
204
|
+
imageQueryParams: { width: 'w', height: 'h' }
|
|
205
|
+
}
|
|
206
|
+
})
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
Now-playing surfaces request a **much larger** image than list thumbnails — on iOS, up to the screen width in pixels, capped at 1200px — so with `imageQueryParams` set you fetch a fittingly large variant rather than upscaling a tiny thumbnail.
|
|
210
|
+
|
|
211
|
+
Two separate ideas here: the **`{id}` template** is replaced with the active track's `id`; and if you _don't_ set `nowPlayingArtwork` at all, now-playing surfaces fall back to the `artwork` config (and to `track.artwork`).
|
|
212
|
+
|
|
213
|
+
`nowPlayingArtwork` is an `ArtworkRequestConfig` like `artwork`, so it takes the same `resolve` / `transform` / `imageQueryParams`. It is **native-only**: the `{id}` template and the `nowPlayingArtwork` config are not applied by the web implementation, where now-playing artwork simply uses the `artwork` config like any other surface. Artwork is also the one now-playing field the text layers can't touch — the formatter / override / flash carry only `title` / `artist` / `album`. See [Now Playing](/guide/now-playing#now-playing-artwork) for the metadata side.
|
|
214
|
+
|
|
215
|
+
::: warning Now-playing artwork follows the active track
|
|
216
|
+
Now-playing artwork is resolved **once per active track**, keyed on its `id` — so it won't re-resolve while the same track keeps playing. There is no imperative way to swap _only_ the image mid-stream: `updateNowPlaying()` overrides `title` / `artist` / `album`, not artwork. Changing it means making a _new_ active track (a different `id` plus the new `artwork`) the current one, which reloads playback — so it's not a fit for, say, updating live-radio cover art on each song. An in-place update API is tracked in [issue #76](https://github.com/radio-garden/react-native-audio-browser/issues/76).
|
|
217
|
+
:::
|
|
218
|
+
|
|
219
|
+
## Tile sections
|
|
220
|
+
|
|
221
|
+
A page's [`Section`](/api/types/browser-nodes/#section) can render its tracks as tappable artwork tiles by declaring `style: { display: 'grid' }` (wrapping; add `gridWrap: false` for a single line of tiles):
|
|
222
|
+
|
|
223
|
+
```ts
|
|
224
|
+
{
|
|
225
|
+
path: '/home',
|
|
226
|
+
title: 'Home',
|
|
227
|
+
sections: [
|
|
228
|
+
{
|
|
229
|
+
title: 'Featured',
|
|
230
|
+
style: { display: 'grid', gridWrap: false },
|
|
231
|
+
path: '/browse/featured', // header / "view all" target
|
|
232
|
+
children: [
|
|
233
|
+
{ title: 'Jazz', path: '/browse/jazz', artwork: 'https://…/jazz.jpg' },
|
|
234
|
+
{ title: 'Rock', path: '/browse/rock', artwork: 'https://…/rock.jpg' }
|
|
235
|
+
]
|
|
236
|
+
}
|
|
237
|
+
]
|
|
238
|
+
}
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
Each tile is an ordinary Track, so its `artwork` runs through the same pipeline as any other. Tile styles presume artwork: a track without any renders as a placeholder tile plus its title — the artwork `resolve` hook is the place to supply fallback art. On CarPlay a single-line grid (`gridWrap: false`) shows the tiles that fit (roughly eight, width-dependent; the rest are truncated) and a wrapping grid takes as many lines as it needs on iOS 26+ (rendering as a list before that); Android Auto's grid always wraps, so it renders both identically. See [Browser → Presentation](/guide/browser#presentation) for the full per-surface rundown.
|
|
242
|
+
|
|
243
|
+
## API summary
|
|
244
|
+
|
|
245
|
+
| Symbol | Purpose |
|
|
246
|
+
| --------------------------------------------------------------------- | ------------------------------------------------------------------------------------- |
|
|
247
|
+
| [`Track.artwork`](/api/types/browser-nodes/#track) | The image you set — `https` URL, `sf:` symbol (iOS), or platform URI. |
|
|
248
|
+
| [`Track.artworkSource`](/api/types/browser-nodes/#track) | Output-only resolved `ImageSource` for your own `<Image>`. |
|
|
249
|
+
| [`TrackStyle.artworkRendering`](/api/types/browser-nodes/#trackstyle) | `'stencil'` tints a monochrome icon per CarPlay light/dark (iOS only). |
|
|
250
|
+
| [`ArtworkVariants`](/api/types/browser-nodes/#artworkvariants) | A `{ light, dark }` pair set on `artwork` when the appearances need different images. |
|
|
251
|
+
| [`artwork`](/api/types/browser/#artworkrequestconfig) | Config for browse-row image requests (and the default everywhere). |
|
|
252
|
+
| `nowPlayingArtwork` | Separate config for now-playing art; supports `{id}`; native-only. |
|
|
253
|
+
| [`imageQueryParams`](/api/types/browser/#imagequeryparams) | Map the surface's requested size to your CDN's query params. |
|
|
254
|
+
| [`ImageContext`](/api/types/browser/#imagecontext) | Requested `width`/`height` in pixels, passed to `transform`. |
|
|
255
|
+
| [`Section.style`](/api/types/browser-nodes/#sectionstyle) | `display: 'grid'` (± `gridWrap: false`) renders a section's tracks as artwork tiles. |
|
|
@@ -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).
|