@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.
Files changed (70) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +249 -0
  3. package/build/angular/index.d.ts +1 -0
  4. package/build/angular/index.js +4 -0
  5. package/build/core/audio-module.d.ts +26 -0
  6. package/build/core/audio-module.js +64 -0
  7. package/build/core/audio-player.d.ts +13 -0
  8. package/build/core/audio-player.js +31 -0
  9. package/build/core/audio-playlist.d.ts +8 -0
  10. package/build/core/audio-playlist.js +14 -0
  11. package/build/core/audio-recorder.d.ts +5 -0
  12. package/build/core/audio-recorder.js +40 -0
  13. package/build/core/audio-stream.d.ts +13 -0
  14. package/build/core/audio-stream.js +16 -0
  15. package/build/core/index.d.ts +9 -0
  16. package/build/core/index.js +8 -0
  17. package/build/core/native-module.d.ts +134 -0
  18. package/build/core/native-module.js +2 -0
  19. package/build/core/recording-presets.d.ts +50 -0
  20. package/build/core/recording-presets.js +94 -0
  21. package/build/core/resolve-source.d.ts +10 -0
  22. package/build/core/resolve-source.js +31 -0
  23. package/build/core/types.d.ts +218 -0
  24. package/build/core/types.js +8 -0
  25. package/build-ngc/angular/index.d.ts +1 -0
  26. package/build-ngc/angular/index.js +5 -0
  27. package/build-ngc/angular/index.js.map +1 -0
  28. package/build-ngc/core/audio-module.d.ts +26 -0
  29. package/build-ngc/core/audio-module.js +65 -0
  30. package/build-ngc/core/audio-module.js.map +1 -0
  31. package/build-ngc/core/audio-player.d.ts +13 -0
  32. package/build-ngc/core/audio-player.js +32 -0
  33. package/build-ngc/core/audio-player.js.map +1 -0
  34. package/build-ngc/core/audio-playlist.d.ts +8 -0
  35. package/build-ngc/core/audio-playlist.js +15 -0
  36. package/build-ngc/core/audio-playlist.js.map +1 -0
  37. package/build-ngc/core/audio-recorder.d.ts +5 -0
  38. package/build-ngc/core/audio-recorder.js +41 -0
  39. package/build-ngc/core/audio-recorder.js.map +1 -0
  40. package/build-ngc/core/audio-stream.d.ts +13 -0
  41. package/build-ngc/core/audio-stream.js +17 -0
  42. package/build-ngc/core/audio-stream.js.map +1 -0
  43. package/build-ngc/core/index.d.ts +9 -0
  44. package/build-ngc/core/index.js +9 -0
  45. package/build-ngc/core/index.js.map +1 -0
  46. package/build-ngc/core/native-module.d.ts +134 -0
  47. package/build-ngc/core/native-module.js +3 -0
  48. package/build-ngc/core/native-module.js.map +1 -0
  49. package/build-ngc/core/recording-presets.d.ts +50 -0
  50. package/build-ngc/core/recording-presets.js +95 -0
  51. package/build-ngc/core/recording-presets.js.map +1 -0
  52. package/build-ngc/core/resolve-source.d.ts +10 -0
  53. package/build-ngc/core/resolve-source.js +32 -0
  54. package/build-ngc/core/resolve-source.js.map +1 -0
  55. package/build-ngc/core/types.d.ts +218 -0
  56. package/build-ngc/core/types.js +9 -0
  57. package/build-ngc/core/types.js.map +1 -0
  58. package/native-link.json +47 -0
  59. package/package.json +150 -0
  60. package/src/angular/index.ts +4 -0
  61. package/src/core/audio-module.ts +82 -0
  62. package/src/core/audio-player.ts +53 -0
  63. package/src/core/audio-playlist.ts +20 -0
  64. package/src/core/audio-recorder.ts +46 -0
  65. package/src/core/audio-stream.ts +22 -0
  66. package/src/core/index.ts +62 -0
  67. package/src/core/native-module.ts +181 -0
  68. package/src/core/recording-presets.ts +99 -0
  69. package/src/core/resolve-source.ts +41 -0
  70. 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,5 @@
1
+ import { expoAudio } from './native-module';
2
+ import type { IRecordingOptions } from './types';
3
+ export declare class AudioRecorder extends expoAudio.AudioRecorder {
4
+ prepareToRecordAsync(options?: Partial<IRecordingOptions>): Promise<void>;
5
+ }
@@ -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';