@symbiote-native/audio 0.1.0
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/LICENSE +21 -0
- package/README.md +249 -0
- package/build/angular/index.d.ts +1 -0
- package/build/angular/index.js +4 -0
- package/build/core/audio-module.d.ts +26 -0
- package/build/core/audio-module.js +64 -0
- package/build/core/audio-player.d.ts +13 -0
- package/build/core/audio-player.js +31 -0
- package/build/core/audio-playlist.d.ts +8 -0
- package/build/core/audio-playlist.js +14 -0
- package/build/core/audio-recorder.d.ts +5 -0
- package/build/core/audio-recorder.js +40 -0
- package/build/core/audio-stream.d.ts +13 -0
- package/build/core/audio-stream.js +16 -0
- package/build/core/index.d.ts +9 -0
- package/build/core/index.js +8 -0
- package/build/core/native-module.d.ts +134 -0
- package/build/core/native-module.js +2 -0
- package/build/core/recording-presets.d.ts +50 -0
- package/build/core/recording-presets.js +94 -0
- package/build/core/resolve-source.d.ts +10 -0
- package/build/core/resolve-source.js +31 -0
- package/build/core/types.d.ts +218 -0
- package/build/core/types.js +8 -0
- package/build-ngc/angular/index.d.ts +1 -0
- package/build-ngc/angular/index.js +5 -0
- package/build-ngc/angular/index.js.map +1 -0
- package/build-ngc/core/audio-module.d.ts +26 -0
- package/build-ngc/core/audio-module.js +65 -0
- package/build-ngc/core/audio-module.js.map +1 -0
- package/build-ngc/core/audio-player.d.ts +13 -0
- package/build-ngc/core/audio-player.js +32 -0
- package/build-ngc/core/audio-player.js.map +1 -0
- package/build-ngc/core/audio-playlist.d.ts +8 -0
- package/build-ngc/core/audio-playlist.js +15 -0
- package/build-ngc/core/audio-playlist.js.map +1 -0
- package/build-ngc/core/audio-recorder.d.ts +5 -0
- package/build-ngc/core/audio-recorder.js +41 -0
- package/build-ngc/core/audio-recorder.js.map +1 -0
- package/build-ngc/core/audio-stream.d.ts +13 -0
- package/build-ngc/core/audio-stream.js +17 -0
- package/build-ngc/core/audio-stream.js.map +1 -0
- package/build-ngc/core/index.d.ts +9 -0
- package/build-ngc/core/index.js +9 -0
- package/build-ngc/core/index.js.map +1 -0
- package/build-ngc/core/native-module.d.ts +134 -0
- package/build-ngc/core/native-module.js +3 -0
- package/build-ngc/core/native-module.js.map +1 -0
- package/build-ngc/core/recording-presets.d.ts +50 -0
- package/build-ngc/core/recording-presets.js +95 -0
- package/build-ngc/core/recording-presets.js.map +1 -0
- package/build-ngc/core/resolve-source.d.ts +10 -0
- package/build-ngc/core/resolve-source.js +32 -0
- package/build-ngc/core/resolve-source.js.map +1 -0
- package/build-ngc/core/types.d.ts +218 -0
- package/build-ngc/core/types.js +9 -0
- package/build-ngc/core/types.js.map +1 -0
- package/native-link.json +47 -0
- package/package.json +150 -0
- package/src/angular/index.ts +4 -0
- package/src/core/audio-module.ts +82 -0
- package/src/core/audio-player.ts +53 -0
- package/src/core/audio-playlist.ts +20 -0
- package/src/core/audio-recorder.ts +46 -0
- package/src/core/audio-stream.ts +22 -0
- package/src/core/index.ts +62 -0
- package/src/core/native-module.ts +181 -0
- package/src/core/recording-presets.ts +99 -0
- package/src/core/resolve-source.ts +41 -0
- package/src/core/types.ts +282 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 A. Prokopenko
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
# @symbiote-native/audio
|
|
2
|
+
|
|
3
|
+
A wrapper package for [SymbioteNative](../../README.md) that makes
|
|
4
|
+
[`expo-audio`](https://github.com/expo/expo/tree/main/packages/expo-audio) usable from **every**
|
|
5
|
+
adapter — React, Vue, Svelte, Solid, and Angular. `AudioPlayer`, `AudioRecorder`, `AudioPlaylist`,
|
|
6
|
+
and `AudioStream` are JSI-backed `SharedObject` instances — per-instance native objects with real
|
|
7
|
+
state and methods, not one-shot functions — so there is no hook/composable/service wrapper here
|
|
8
|
+
either: the React, Vue, Svelte, Solid, and Angular entry points are plain re-exports of the same
|
|
9
|
+
`core`, and each adapter's own lifecycle code (the equivalent of upstream's `useAudioPlayer`)
|
|
10
|
+
decides when to create one and when to call `.remove()` / `.destroy()`.
|
|
11
|
+
|
|
12
|
+
## Install
|
|
13
|
+
|
|
14
|
+
**New app:**
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
npx @symbiote-native/cli new my-app --audio
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
**Existing SymbioteNative app:**
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
npx @symbiote-native/cli add --audio
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Either way: installs `@symbiote-native/audio`, wires the native autolinking automatically, and — since
|
|
27
|
+
this package has an optional, policy-sensitive Android bundle — asks at the end whether to grant
|
|
28
|
+
background recording too (see the note below the manual-install block). See
|
|
29
|
+
[`@symbiote-native/cli`](../cli).
|
|
30
|
+
|
|
31
|
+
<details>
|
|
32
|
+
<summary>Manual install (no CLI — installing and wiring native autolinking by hand)</summary>
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
npm install @symbiote-native/audio
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
`expo-audio` and `expo-modules-core` come along as regular dependencies, pinned to exact
|
|
39
|
+
versions — never install either yourself, and never add the `expo` meta-package to your project
|
|
40
|
+
(it bundles its own Metro/Babel pipeline, which conflicts with this project's own).
|
|
41
|
+
|
|
42
|
+
## Required one-time step: native autolinking wiring
|
|
43
|
+
|
|
44
|
+
Same one-time step as every other `expo-modules-core` package this project ships — see
|
|
45
|
+
[`@symbiote-native/local-auth`'s README](../local-auth/README.md#required-one-time-step-native-autolinking-wiring)
|
|
46
|
+
and the `symbiote-expo-native-module` project skill. Nothing package-specific beyond one Info.plist
|
|
47
|
+
string:
|
|
48
|
+
|
|
49
|
+
- iOS — `NSMicrophoneUsageDescription` in `Info.plist`, wired automatically by
|
|
50
|
+
`@symbiote-native/expo-modules-link`'s aggregator (`native-link.json`'s `ios.infoPlistKeys`) the
|
|
51
|
+
next time it runs — override the default text by adding the key yourself first. Same aggregator
|
|
52
|
+
also adds `UIBackgroundModes: audio` (`ios.infoPlistArrayKeys`) — upstream's own `withAudio.ts`
|
|
53
|
+
config plugin defaults `enableBackgroundPlayback` to `true`, and without this key iOS suspends
|
|
54
|
+
playback the moment the app backgrounds.
|
|
55
|
+
- Android — `RECORD_AUDIO` / `MODIFY_AUDIO_SETTINGS` already ship in `expo-audio`'s own
|
|
56
|
+
`AndroidManifest.xml` and merge automatically, same as every other autolinked permission.
|
|
57
|
+
Background **playback** is wired the same way as iOS: `native-link.json`'s
|
|
58
|
+
`android.manifestPermissions` adds `FOREGROUND_SERVICE`/`FOREGROUND_SERVICE_MEDIA_PLAYBACK`, and
|
|
59
|
+
`android.manifestServices` declares `AudioControlsService` (`foregroundServiceType="mediaPlayback"`)
|
|
60
|
+
— matching upstream's own `withAudio.ts` config plugin, whose `enableBackgroundPlayback` defaults
|
|
61
|
+
to `true`. Background **recording** (`allowsBackgroundRecording: true`) is opt-in, matching
|
|
62
|
+
upstream's own `enableBackgroundRecording` default of `false` — requesting the microphone
|
|
63
|
+
foreground-service type is exactly the kind of thing that should be a deliberate app choice, not
|
|
64
|
+
a package side effect (same reasoning as `@symbiote-native/location`'s background permission).
|
|
65
|
+
|
|
66
|
+
</details>
|
|
67
|
+
|
|
68
|
+
**Background recording — opt-in, asked for you.** `new --audio`/`add --audio` above already ask,
|
|
69
|
+
interactively, whether to grant it; say yes and `FOREGROUND_SERVICE_MICROPHONE`/
|
|
70
|
+
`POST_NOTIFICATIONS` plus the `AudioRecordingService` (`foregroundServiceType="microphone"`)
|
|
71
|
+
`<service>` land in your `AndroidManifest.xml` for you, along with the `allowsBackgroundRecording`
|
|
72
|
+
call reminder you still need to make in JS. Said no, or ran non-interactively (CI, piped stdin)?
|
|
73
|
+
Run it any time after:
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
npx @symbiote-native/cli grant audio
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Idempotent — safe to run again even if already granted.
|
|
80
|
+
|
|
81
|
+
## Shape
|
|
82
|
+
|
|
83
|
+
```
|
|
84
|
+
src/core/ AudioPlayer / AudioRecorder / AudioPlaylist / AudioStream classes (each a thin
|
|
85
|
+
subclass of the native SharedObject, adding only the JS-side logic upstream's own
|
|
86
|
+
ExpoAudio.ts shims onto the prototype — source resolution on `replace()`, the
|
|
87
|
+
Android arg-count fix for `setPlaybackRate()`, per-platform option processing for
|
|
88
|
+
`prepareToRecordAsync()`), the createAudioPlayer / createAudioPlaylist /
|
|
89
|
+
createAudioStream factories, the audio-session/permission/preload module functions,
|
|
90
|
+
RecordingPresets, and the event-name constants `addListener` accepts.
|
|
91
|
+
native-module.ts resolves the native module via expo-modules-core's
|
|
92
|
+
requireNativeModule.
|
|
93
|
+
src/angular/ @symbiote-native/audio/angular — export * from '../core'
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
`./react`, `./vue`, `./svelte`, and `./solid` are `exports`-map aliases straight onto
|
|
97
|
+
`src/core/`. `./angular` stays a physical file/subpath since Angular ships through a separate
|
|
98
|
+
`ngc`/AOT build (`build-ngc/`).
|
|
99
|
+
|
|
100
|
+
## Use it
|
|
101
|
+
|
|
102
|
+
```ts
|
|
103
|
+
import {
|
|
104
|
+
createAudioPlayer,
|
|
105
|
+
PLAYBACK_STATUS_UPDATE,
|
|
106
|
+
setAudioModeAsync,
|
|
107
|
+
} from '@symbiote-native/audio';
|
|
108
|
+
|
|
109
|
+
await setAudioModeAsync({
|
|
110
|
+
playsInSilentMode: true,
|
|
111
|
+
shouldPlayInBackground: false,
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
const player = createAudioPlayer('https://example.com/track.mp3');
|
|
115
|
+
player.play();
|
|
116
|
+
|
|
117
|
+
const subscription = player.addListener(PLAYBACK_STATUS_UPDATE, status => {
|
|
118
|
+
console.log(status.currentTime, status.duration, status.playing);
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
// later, e.g. on unmount:
|
|
122
|
+
subscription.remove();
|
|
123
|
+
player.remove();
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
Recording:
|
|
127
|
+
|
|
128
|
+
```ts
|
|
129
|
+
import {
|
|
130
|
+
AudioRecorder,
|
|
131
|
+
RECORDING_STATUS_UPDATE,
|
|
132
|
+
RecordingPresets,
|
|
133
|
+
requestRecordingPermissionsAsync,
|
|
134
|
+
} from '@symbiote-native/audio';
|
|
135
|
+
|
|
136
|
+
const { granted } = await requestRecordingPermissionsAsync();
|
|
137
|
+
if (!granted) throw new Error('Microphone permission denied');
|
|
138
|
+
|
|
139
|
+
const recorder = new AudioRecorder(RecordingPresets.HIGH_QUALITY);
|
|
140
|
+
await recorder.prepareToRecordAsync();
|
|
141
|
+
recorder.record();
|
|
142
|
+
recorder.addListener(RECORDING_STATUS_UPDATE, status => console.log(status));
|
|
143
|
+
// … later
|
|
144
|
+
await recorder.stop();
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
Playlist and real-time PCM streaming follow the same `create*` + `addListener` shape:
|
|
148
|
+
`createAudioPlaylist({ sources: [...], loop: 'all' })` and `createAudioStream()` — the stream
|
|
149
|
+
factory takes no `onBuffer` callback (unlike upstream's `useAudioStream` hook); subscribe with
|
|
150
|
+
`stream.addListener(AUDIO_STREAM_BUFFER, ...)` after `stream.start()`, same as every other event
|
|
151
|
+
on these classes.
|
|
152
|
+
|
|
153
|
+
Identical import surface on every adapter — `@symbiote-native/audio/react`, `/vue`, `/svelte`,
|
|
154
|
+
`/solid`, `/angular` all re-export the same classes and functions.
|
|
155
|
+
|
|
156
|
+
## API
|
|
157
|
+
|
|
158
|
+
```ts
|
|
159
|
+
class AudioPlayer extends SharedObject {
|
|
160
|
+
id: string; playing: boolean; muted: boolean; loop: boolean; paused: boolean; isLoaded: boolean;
|
|
161
|
+
isAudioSamplingSupported: boolean; isBuffering: boolean; currentTime: number; duration: number;
|
|
162
|
+
volume: number; playbackRate: number; shouldCorrectPitch: boolean; currentStatus: IAudioStatus;
|
|
163
|
+
play(): void; pause(): void; replace(source: IAudioSource): void;
|
|
164
|
+
seekTo(seconds: number, toleranceMillisBefore?: number, toleranceMillisAfter?: number): Promise<void>;
|
|
165
|
+
setPlaybackRate(rate: number, pitchCorrectionQuality?: IPitchCorrectionQuality): void;
|
|
166
|
+
setAudioSamplingEnabled(enabled: boolean): void;
|
|
167
|
+
setActiveForLockScreen(active: boolean, metadata?: IAudioMetadata, options?: IAudioLockScreenOptions): void;
|
|
168
|
+
updateLockScreenMetadata(metadata: IAudioMetadata): void;
|
|
169
|
+
clearLockScreenControls(): void;
|
|
170
|
+
remove(): void;
|
|
171
|
+
}
|
|
172
|
+
createAudioPlayer(source?: IAudioSource, options?: IAudioPlayerOptions): AudioPlayer
|
|
173
|
+
|
|
174
|
+
class AudioRecorder extends SharedObject {
|
|
175
|
+
id: string; currentTime: number; isRecording: boolean; uri: string | null;
|
|
176
|
+
record(options?: IRecordingStartOptions): void; stop(): Promise<void>; pause(): void;
|
|
177
|
+
getAvailableInputs(): IRecordingInput[]; getCurrentInput(): Promise<IRecordingInput>;
|
|
178
|
+
setInput(inputUid: string): void; getStatus(): IRecorderState;
|
|
179
|
+
prepareToRecordAsync(options?: Partial<IRecordingOptions>): Promise<void>;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
class AudioPlaylist extends SharedObject {
|
|
183
|
+
id: string; readonly currentIndex: number; readonly trackCount: number;
|
|
184
|
+
readonly sources: IAudioSourceInfo[]; playing: boolean; muted: boolean; isLoaded: boolean;
|
|
185
|
+
isBuffering: boolean; currentTime: number; duration: number; volume: number; playbackRate: number;
|
|
186
|
+
loop: IAudioPlaylistLoopMode; currentStatus: IAudioPlaylistStatus;
|
|
187
|
+
play(): void; pause(): void; next(): void; previous(): void; skipTo(index: number): void;
|
|
188
|
+
seekTo(seconds: number): Promise<void>; add(source: IAudioSource): void;
|
|
189
|
+
insert(source: IAudioSource, index: number): void; remove(index: number): void;
|
|
190
|
+
clear(): void; destroy(): void;
|
|
191
|
+
}
|
|
192
|
+
createAudioPlaylist(options?: IAudioPlaylistOptions): AudioPlaylist
|
|
193
|
+
|
|
194
|
+
class AudioStream extends SharedObject {
|
|
195
|
+
id: string; readonly sampleRate: number; readonly channels: number; readonly isStreaming: boolean;
|
|
196
|
+
start(): Promise<void>; stop(): void;
|
|
197
|
+
}
|
|
198
|
+
createAudioStream(options?: IAudioStreamOptions): AudioStream
|
|
199
|
+
|
|
200
|
+
setIsAudioActiveAsync(active: boolean): Promise<void>
|
|
201
|
+
setAudioModeAsync(mode: Partial<IAudioMode>): Promise<void>
|
|
202
|
+
requestRecordingPermissionsAsync(): Promise<PermissionResponse>
|
|
203
|
+
requestNotificationPermissionsAsync(): Promise<PermissionResponse> // Android only, throws elsewhere
|
|
204
|
+
getRecordingPermissionsAsync(): Promise<PermissionResponse>
|
|
205
|
+
preload(source: IAudioSource, options?: IPreloadOptions): Promise<void>
|
|
206
|
+
clearPreloadedSource(source: IAudioSource): Promise<void>
|
|
207
|
+
clearAllPreloadedSources(): Promise<void>
|
|
208
|
+
getPreloadedSources(): Promise<string[]>
|
|
209
|
+
|
|
210
|
+
RecordingPresets: { HIGH_QUALITY: IRecordingOptions; LOW_QUALITY: IRecordingOptions }
|
|
211
|
+
IOSOutputFormat, AudioQuality // enums, ported from RecordingConstants.ts
|
|
212
|
+
|
|
213
|
+
PLAYBACK_STATUS_UPDATE, AUDIO_SAMPLE_UPDATE, RECORDING_STATUS_UPDATE, PLAYLIST_STATUS_UPDATE,
|
|
214
|
+
TRACK_CHANGED, AUDIO_STREAM_BUFFER, AUDIO_STREAM_STATUS // event names for .addListener()
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
Plus the full `I`-prefixed type surface ported from upstream's `Audio.types.ts` /
|
|
218
|
+
`AudioModule.types.ts` / `AudioStream.types.ts` / `AudioConstants.ts`, re-exported from the
|
|
219
|
+
barrel — see `src/core/types.ts`.
|
|
220
|
+
|
|
221
|
+
## Deliberately not ported
|
|
222
|
+
|
|
223
|
+
- **`useAudioPlayer` / `useAudioPlayerStatus` / `useAudioSampleListener` / `useAudioRecorder` /
|
|
224
|
+
`useAudioRecorderState` / `useAudioPlaylist` / `useAudioPlaylistStatus` / `useAudioStream`** —
|
|
225
|
+
React hooks, framework-specific by construction. `createAudioPlayer` / `createAudioPlaylist` /
|
|
226
|
+
`createAudioStream` are the framework-agnostic equivalents each already exposes (upstream's own
|
|
227
|
+
hooks are thin wrappers over these plus `useReleasingSharedObject` for cleanup-on-unmount); each
|
|
228
|
+
adapter's own lifecycle wrapper is where a hook/composable/service belongs, per
|
|
229
|
+
`<components_split_logic_view_lifecycle>` in the root project CLAUDE.md. None ship yet.
|
|
230
|
+
- **The `Asset`-instance form of `AudioSource`, and the `downloadFirst` player option.** Upstream
|
|
231
|
+
resolves both through `expo-asset` (`Asset.fromModule` / `Asset.downloadAsync`), which depends
|
|
232
|
+
on `expo-constants` and peers on the `expo` meta-package — this project never depends on `expo`
|
|
233
|
+
(root CLAUDE.md's dependency-scope invariant, and the `symbiote-expo-native-module` skill §1).
|
|
234
|
+
The plain `number` form (`require('./song.mp3')`) **is** supported — `resolveSource`/
|
|
235
|
+
`resolveSources` resolve it through RN's own generic `resolveAssetSource`, wired by
|
|
236
|
+
`bootstrapHost`, no `expo-asset` needed.
|
|
237
|
+
- **`interruptionModeAndroid`** on `IAudioMode` — upstream marks it `@deprecated`, superseded by
|
|
238
|
+
the cross-platform `interruptionMode`.
|
|
239
|
+
- **Web-only surfaces** (`ExpoAudio.web.ts`, `AudioPlayer.web.ts`, `AudioStream.web.ts`,
|
|
240
|
+
`MediaSessionController.web.ts`) — this project targets iOS + Android only.
|
|
241
|
+
|
|
242
|
+
## Test it
|
|
243
|
+
|
|
244
|
+
No Fabric/Descriptor angle at all — every class here is a `SharedObject` with no visual
|
|
245
|
+
component. Tests fake the native `AudioPlayer`/`AudioRecorder`/`AudioPlaylist`/`AudioStream`
|
|
246
|
+
classes `expoAudio` exposes (real prototype methods, so the subclass `super.*()` calls in
|
|
247
|
+
`audio-player.ts`/`audio-recorder.ts` resolve correctly) and the plain async module functions
|
|
248
|
+
(`src/core/audio-classes.test.ts`, `src/core/audio-module.test.ts`, `src/core/
|
|
249
|
+
resolve-source.test.ts`) — no `installFabric()`, no ViewConfig.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from '../core';
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
// @symbiote-native/audio/angular: the Angular entry over the framework-agnostic core. Same
|
|
2
|
+
// reasoning as the React/Vue entries — every export here is a plain class/function with no
|
|
3
|
+
// framework-specific lifecycle to wrap, so this is a plain re-export.
|
|
4
|
+
export * from '../core/index.js';
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { type PermissionResponse } from 'expo-modules-core';
|
|
2
|
+
import type { IAudioMode, IAudioSource, IPreloadOptions } from './types';
|
|
3
|
+
/** Enables or disables the audio subsystem globally — pauses all playback when set to `false`. */
|
|
4
|
+
export declare function setIsAudioActiveAsync(active: boolean): Promise<void>;
|
|
5
|
+
/** Configures the global audio session — background playback, mixing, interruption handling. */
|
|
6
|
+
export declare function setAudioModeAsync(mode: Partial<IAudioMode>): Promise<void>;
|
|
7
|
+
/** Requests microphone-access permission required for recording. */
|
|
8
|
+
export declare function requestRecordingPermissionsAsync(): Promise<PermissionResponse>;
|
|
9
|
+
/**
|
|
10
|
+
* Requests permission to post notifications on Android, needed for lock-screen/notification-shade
|
|
11
|
+
* playback controls. @platform android — throws on every other platform.
|
|
12
|
+
*/
|
|
13
|
+
export declare function requestNotificationPermissionsAsync(): Promise<PermissionResponse>;
|
|
14
|
+
/** Checks the current recording-permission status without prompting. */
|
|
15
|
+
export declare function getRecordingPermissionsAsync(): Promise<PermissionResponse>;
|
|
16
|
+
/**
|
|
17
|
+
* Preloads an audio source for near-instant playback later — call at module scope, before any
|
|
18
|
+
* player is created from the same source.
|
|
19
|
+
*/
|
|
20
|
+
export declare function preload(source: IAudioSource, options?: IPreloadOptions): Promise<void>;
|
|
21
|
+
/** Releases a specific preloaded source (must match what was passed to `preload`). */
|
|
22
|
+
export declare function clearPreloadedSource(source: IAudioSource): Promise<void>;
|
|
23
|
+
/** Releases every preloaded source. */
|
|
24
|
+
export declare function clearAllPreloadedSources(): Promise<void>;
|
|
25
|
+
/** URIs of every source currently in the preload cache. */
|
|
26
|
+
export declare function getPreloadedSources(): Promise<string[]>;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { Platform } from 'expo-modules-core';
|
|
2
|
+
import { expoAudio } from './native-module.js';
|
|
3
|
+
import { resolveSource } from './resolve-source.js';
|
|
4
|
+
/** Enables or disables the audio subsystem globally — pauses all playback when set to `false`. */
|
|
5
|
+
export async function setIsAudioActiveAsync(active) {
|
|
6
|
+
return expoAudio.setIsAudioActiveAsync(active);
|
|
7
|
+
}
|
|
8
|
+
/** Configures the global audio session — background playback, mixing, interruption handling. */
|
|
9
|
+
export async function setAudioModeAsync(mode) {
|
|
10
|
+
const audioMode = Platform.OS === 'ios'
|
|
11
|
+
? mode
|
|
12
|
+
: {
|
|
13
|
+
shouldPlayInBackground: mode.shouldPlayInBackground,
|
|
14
|
+
shouldRouteThroughEarpiece: mode.shouldRouteThroughEarpiece,
|
|
15
|
+
interruptionMode: mode.interruptionMode,
|
|
16
|
+
allowsBackgroundRecording: mode.allowsBackgroundRecording,
|
|
17
|
+
playsInSilentMode: mode.playsInSilentMode,
|
|
18
|
+
};
|
|
19
|
+
return expoAudio.setAudioModeAsync(audioMode);
|
|
20
|
+
}
|
|
21
|
+
/** Requests microphone-access permission required for recording. */
|
|
22
|
+
export async function requestRecordingPermissionsAsync() {
|
|
23
|
+
return expoAudio.requestRecordingPermissionsAsync();
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Requests permission to post notifications on Android, needed for lock-screen/notification-shade
|
|
27
|
+
* playback controls. @platform android — throws on every other platform.
|
|
28
|
+
*/
|
|
29
|
+
export async function requestNotificationPermissionsAsync() {
|
|
30
|
+
if (Platform.OS !== 'android') {
|
|
31
|
+
throw new Error('expo-audio: `requestNotificationPermissionsAsync` is only available on Android.');
|
|
32
|
+
}
|
|
33
|
+
return expoAudio.requestNotificationPermissionsAsync();
|
|
34
|
+
}
|
|
35
|
+
/** Checks the current recording-permission status without prompting. */
|
|
36
|
+
export async function getRecordingPermissionsAsync() {
|
|
37
|
+
return expoAudio.getRecordingPermissionsAsync();
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Preloads an audio source for near-instant playback later — call at module scope, before any
|
|
41
|
+
* player is created from the same source.
|
|
42
|
+
*/
|
|
43
|
+
export async function preload(source, options = {}) {
|
|
44
|
+
const resolved = resolveSource(source);
|
|
45
|
+
if (!resolved)
|
|
46
|
+
return;
|
|
47
|
+
const { preferredForwardBufferDuration = 10 } = options;
|
|
48
|
+
return expoAudio.preload(resolved, preferredForwardBufferDuration);
|
|
49
|
+
}
|
|
50
|
+
/** Releases a specific preloaded source (must match what was passed to `preload`). */
|
|
51
|
+
export async function clearPreloadedSource(source) {
|
|
52
|
+
const resolved = resolveSource(source);
|
|
53
|
+
if (!resolved)
|
|
54
|
+
return;
|
|
55
|
+
return expoAudio.clearPreloadedSource(resolved);
|
|
56
|
+
}
|
|
57
|
+
/** Releases every preloaded source. */
|
|
58
|
+
export async function clearAllPreloadedSources() {
|
|
59
|
+
return expoAudio.clearAllPreloadedSources();
|
|
60
|
+
}
|
|
61
|
+
/** URIs of every source currently in the preload cache. */
|
|
62
|
+
export async function getPreloadedSources() {
|
|
63
|
+
return expoAudio.getPreloadedSources();
|
|
64
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { expoAudio } from './native-module';
|
|
2
|
+
import type { IAudioPlayerOptions, IAudioSource, IPitchCorrectionQuality } from './types';
|
|
3
|
+
export declare class AudioPlayer extends expoAudio.AudioPlayer {
|
|
4
|
+
replace(source: IAudioSource): void;
|
|
5
|
+
setPlaybackRate(rate: number, pitchCorrectionQuality?: IPitchCorrectionQuality): void;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Creates an `AudioPlayer` that does not release automatically — call `.remove()` when done with
|
|
9
|
+
* it. There is no lifecycle wrapper here (framework-agnostic core, see the package README); each
|
|
10
|
+
* adapter's own hook/composable/service is responsible for releasing it on unmount, mirroring
|
|
11
|
+
* upstream's `useReleasingSharedObject`.
|
|
12
|
+
*/
|
|
13
|
+
export declare function createAudioPlayer(source?: IAudioSource, options?: IAudioPlayerOptions): AudioPlayer;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { Platform } from 'expo-modules-core';
|
|
2
|
+
import { expoAudio } from './native-module.js';
|
|
3
|
+
import { resolveSource } from './resolve-source.js';
|
|
4
|
+
// Upstream shims these two methods onto `AudioModule.AudioPlayer.prototype` at module load
|
|
5
|
+
// (ExpoAudio.ts). We do the equivalent by subclassing instead of monkey-patching the native
|
|
6
|
+
// class's prototype — same shape packages/media-library/src/next/asset.ts uses for its own
|
|
7
|
+
// platform-conditional overrides.
|
|
8
|
+
export class AudioPlayer extends expoAudio.AudioPlayer {
|
|
9
|
+
replace(source) {
|
|
10
|
+
super.replace(resolveSource(source));
|
|
11
|
+
}
|
|
12
|
+
setPlaybackRate(rate, pitchCorrectionQuality) {
|
|
13
|
+
if (Platform.OS === 'android') {
|
|
14
|
+
super.setPlaybackRate(rate);
|
|
15
|
+
}
|
|
16
|
+
else {
|
|
17
|
+
super.setPlaybackRate(rate, pitchCorrectionQuality);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Creates an `AudioPlayer` that does not release automatically — call `.remove()` when done with
|
|
23
|
+
* it. There is no lifecycle wrapper here (framework-agnostic core, see the package README); each
|
|
24
|
+
* adapter's own hook/composable/service is responsible for releasing it on unmount, mirroring
|
|
25
|
+
* upstream's `useReleasingSharedObject`.
|
|
26
|
+
*/
|
|
27
|
+
export function createAudioPlayer(source = null, options = {}) {
|
|
28
|
+
const { updateInterval = 500, keepAudioSessionActive = false, preferredForwardBufferDuration = 0, } = options;
|
|
29
|
+
const resolvedSource = resolveSource(source);
|
|
30
|
+
return new AudioPlayer(resolvedSource, updateInterval, keepAudioSessionActive, preferredForwardBufferDuration);
|
|
31
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { IAudioPlaylistOptions } from './types';
|
|
2
|
+
export declare const AudioPlaylist: typeof import("./native-module").NativeAudioPlaylist;
|
|
3
|
+
export type AudioPlaylist = InstanceType<typeof AudioPlaylist>;
|
|
4
|
+
/**
|
|
5
|
+
* Creates an `AudioPlaylist` that does not release automatically — call `.destroy()` when done
|
|
6
|
+
* with it. See `createAudioPlayer`'s doc comment for the same lifecycle note.
|
|
7
|
+
*/
|
|
8
|
+
export declare function createAudioPlaylist(options?: IAudioPlaylistOptions): AudioPlaylist;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { expoAudio } from './native-module.js';
|
|
2
|
+
import { resolveSources } from './resolve-source.js';
|
|
3
|
+
// No prototype shim on `AudioPlaylist` upstream (unlike `AudioPlayer`/`AudioRecorder`) — its
|
|
4
|
+
// `add`/`insert` take an already-resolved source, so a plain re-export is faithful to upstream.
|
|
5
|
+
export const AudioPlaylist = expoAudio.AudioPlaylist;
|
|
6
|
+
/**
|
|
7
|
+
* Creates an `AudioPlaylist` that does not release automatically — call `.destroy()` when done
|
|
8
|
+
* with it. See `createAudioPlayer`'s doc comment for the same lifecycle note.
|
|
9
|
+
*/
|
|
10
|
+
export function createAudioPlaylist(options = {}) {
|
|
11
|
+
const { sources = [], updateInterval = 500, loop = 'none' } = options;
|
|
12
|
+
const resolvedSources = resolveSources(sources);
|
|
13
|
+
return new AudioPlaylist(resolvedSources, updateInterval, loop);
|
|
14
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { Platform } from 'expo-modules-core';
|
|
2
|
+
import { expoAudio } from './native-module.js';
|
|
3
|
+
/**
|
|
4
|
+
* Fills in the per-platform recording config from `RecordingOptions`'s common fields, ported
|
|
5
|
+
* from upstream's `utils/options.ts`. Only the fields the current platform's native recorder
|
|
6
|
+
* reads are kept, with `options.ios`/`options.android`/`options.web` spread on top so an
|
|
7
|
+
* explicit per-platform override always wins.
|
|
8
|
+
*/
|
|
9
|
+
function createRecordingOptions(options) {
|
|
10
|
+
const commonOptions = {
|
|
11
|
+
extension: options.extension,
|
|
12
|
+
sampleRate: options.sampleRate,
|
|
13
|
+
numberOfChannels: options.numberOfChannels,
|
|
14
|
+
bitRate: options.bitRate,
|
|
15
|
+
isMeteringEnabled: options.isMeteringEnabled ?? false,
|
|
16
|
+
};
|
|
17
|
+
if (Platform.OS === 'ios') {
|
|
18
|
+
return { ...commonOptions, directory: options.directory, ...options.ios };
|
|
19
|
+
}
|
|
20
|
+
if (Platform.OS === 'android') {
|
|
21
|
+
return {
|
|
22
|
+
...commonOptions,
|
|
23
|
+
directory: options.directory,
|
|
24
|
+
...options.android,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
return { ...commonOptions, ...options.web };
|
|
28
|
+
}
|
|
29
|
+
// Upstream shims `prepareToRecordAsync` onto `AudioModule.AudioRecorder.prototype` at module
|
|
30
|
+
// load, EXCEPT on tvOS (recording prototypes "should not be shimmed on tvOS, where they do not
|
|
31
|
+
// exist" — ExpoAudio.ts). tvOS is not a target platform of this project (root CLAUDE.md: iOS +
|
|
32
|
+
// Android only), so that branch is dropped — the options are always processed.
|
|
33
|
+
export class AudioRecorder extends expoAudio.AudioRecorder {
|
|
34
|
+
prepareToRecordAsync(options) {
|
|
35
|
+
const processedOptions = options
|
|
36
|
+
? createRecordingOptions(options)
|
|
37
|
+
: undefined;
|
|
38
|
+
return super.prepareToRecordAsync(processedOptions);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { IAudioStreamOptions } from './types';
|
|
2
|
+
export declare const AudioStream: typeof import("./native-module").NativeAudioStream;
|
|
3
|
+
export type AudioStream = InstanceType<typeof AudioStream>;
|
|
4
|
+
/**
|
|
5
|
+
* Creates a native `AudioStream` for real-time PCM microphone capture. Call `.start()` to
|
|
6
|
+
* begin and `.stop()` to end; requires microphone permission — see
|
|
7
|
+
* `requestRecordingPermissionsAsync`. Upstream only exposes this via the `useAudioStream` hook
|
|
8
|
+
* (React-only, see the package README); this factory is the framework-agnostic equivalent — the
|
|
9
|
+
* hook itself just applies these same defaults and wires `addListener` for
|
|
10
|
+
* `AUDIO_STREAM_STATUS`/`AUDIO_STREAM_BUFFER` on top, which each adapter's own lifecycle wrapper
|
|
11
|
+
* is responsible for.
|
|
12
|
+
*/
|
|
13
|
+
export declare function createAudioStream(options?: IAudioStreamOptions): AudioStream;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { expoAudio } from './native-module.js';
|
|
2
|
+
// Plain re-export — upstream has no prototype shim for `AudioStream` either.
|
|
3
|
+
export const AudioStream = expoAudio.AudioStream;
|
|
4
|
+
/**
|
|
5
|
+
* Creates a native `AudioStream` for real-time PCM microphone capture. Call `.start()` to
|
|
6
|
+
* begin and `.stop()` to end; requires microphone permission — see
|
|
7
|
+
* `requestRecordingPermissionsAsync`. Upstream only exposes this via the `useAudioStream` hook
|
|
8
|
+
* (React-only, see the package README); this factory is the framework-agnostic equivalent — the
|
|
9
|
+
* hook itself just applies these same defaults and wires `addListener` for
|
|
10
|
+
* `AUDIO_STREAM_STATUS`/`AUDIO_STREAM_BUFFER` on top, which each adapter's own lifecycle wrapper
|
|
11
|
+
* is responsible for.
|
|
12
|
+
*/
|
|
13
|
+
export function createAudioStream(options = {}) {
|
|
14
|
+
const { sampleRate = 48000, channels = 1, encoding = 'float32' } = options;
|
|
15
|
+
return new AudioStream({ sampleRate, channels, encoding });
|
|
16
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export { AudioPlayer, createAudioPlayer } from './audio-player';
|
|
2
|
+
export { AudioRecorder } from './audio-recorder';
|
|
3
|
+
export { AudioPlaylist, createAudioPlaylist } from './audio-playlist';
|
|
4
|
+
export { AudioStream, createAudioStream } from './audio-stream';
|
|
5
|
+
export { setIsAudioActiveAsync, setAudioModeAsync, requestRecordingPermissionsAsync, requestNotificationPermissionsAsync, getRecordingPermissionsAsync, preload, clearPreloadedSource, clearAllPreloadedSources, getPreloadedSources, } from './audio-module';
|
|
6
|
+
export { resolveSource, resolveSources } from './resolve-source';
|
|
7
|
+
export { RecordingPresets, IOSOutputFormat, AudioQuality, } from './recording-presets';
|
|
8
|
+
export { PLAYBACK_STATUS_UPDATE, AUDIO_SAMPLE_UPDATE, RECORDING_STATUS_UPDATE, PLAYLIST_STATUS_UPDATE, TRACK_CHANGED, AUDIO_STREAM_BUFFER, AUDIO_STREAM_STATUS, } from './types';
|
|
9
|
+
export type { IAudioSource, IAudioSourceInfo, IAudioPlayerOptions, IPreloadOptions, IRecordingInput, IPitchCorrectionQuality, IAudioStatus, IRecordingStatus, IRecorderState, IAndroidOutputFormat, IAndroidAudioEncoder, IRecordingStartOptions, IRecordingDirectory, IRecordingOptions, IRecordingOptionsWeb, IRecordingOptionsIos, IRecordingOptionsAndroid, IRecordingSource, IAudioMode, IInterruptionMode, IAudioMetadata, IAudioPlaylistLoopMode, IAudioPlaylistOptions, IAudioPlaylistStatus, IAudioLockScreenOptions, IAudioSample, IAudioStreamEncoding, IAudioStreamOptions, IAudioStreamBuffer, IAudioStreamStatus, } from './types';
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export { AudioPlayer, createAudioPlayer } from './audio-player.js';
|
|
2
|
+
export { AudioRecorder } from './audio-recorder.js';
|
|
3
|
+
export { AudioPlaylist, createAudioPlaylist } from './audio-playlist.js';
|
|
4
|
+
export { AudioStream, createAudioStream } from './audio-stream.js';
|
|
5
|
+
export { setIsAudioActiveAsync, setAudioModeAsync, requestRecordingPermissionsAsync, requestNotificationPermissionsAsync, getRecordingPermissionsAsync, preload, clearPreloadedSource, clearAllPreloadedSources, getPreloadedSources, } from './audio-module.js';
|
|
6
|
+
export { resolveSource, resolveSources } from './resolve-source.js';
|
|
7
|
+
export { RecordingPresets, IOSOutputFormat, AudioQuality, } from './recording-presets.js';
|
|
8
|
+
export { PLAYBACK_STATUS_UPDATE, AUDIO_SAMPLE_UPDATE, RECORDING_STATUS_UPDATE, PLAYLIST_STATUS_UPDATE, TRACK_CHANGED, AUDIO_STREAM_BUFFER, AUDIO_STREAM_STATUS, } from './types.js';
|