@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
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
import type { AudioQuality, IOSOutputFormat } from './recording-presets';
|
|
2
|
+
/** Audio source information returned from native when reading sources from a queue. */
|
|
3
|
+
export type IAudioSourceInfo = {
|
|
4
|
+
uri?: string;
|
|
5
|
+
name?: string;
|
|
6
|
+
};
|
|
7
|
+
/**
|
|
8
|
+
* A URI string, an object describing a remote/local (by URI) audio source, or the numeric asset
|
|
9
|
+
* id `require('./song.mp3')` produces — resolved via `resolveSource`/`resolveSources`.
|
|
10
|
+
*/
|
|
11
|
+
export type IAudioSource = string | number | null | {
|
|
12
|
+
uri?: string;
|
|
13
|
+
headers?: Record<string, string>;
|
|
14
|
+
name?: string;
|
|
15
|
+
};
|
|
16
|
+
export type IAudioPlayerOptions = {
|
|
17
|
+
/** How often (in milliseconds) to emit playback status updates. @default 500 */
|
|
18
|
+
updateInterval?: number;
|
|
19
|
+
/** @platform web */
|
|
20
|
+
crossOrigin?: 'anonymous' | 'use-credentials';
|
|
21
|
+
/**
|
|
22
|
+
* If `true`, the audio session is not deactivated when this player pauses or finishes.
|
|
23
|
+
* @platform ios
|
|
24
|
+
* @default false
|
|
25
|
+
*/
|
|
26
|
+
keepAudioSessionActive?: boolean;
|
|
27
|
+
/**
|
|
28
|
+
* Seconds to buffer ahead of the current playback position.
|
|
29
|
+
* @platform ios
|
|
30
|
+
* @platform android
|
|
31
|
+
* @default 0
|
|
32
|
+
*/
|
|
33
|
+
preferredForwardBufferDuration?: number;
|
|
34
|
+
};
|
|
35
|
+
export type IPreloadOptions = {
|
|
36
|
+
/** @default 10 */
|
|
37
|
+
preferredForwardBufferDuration?: number;
|
|
38
|
+
};
|
|
39
|
+
export type IRecordingInput = {
|
|
40
|
+
name: string;
|
|
41
|
+
type: string;
|
|
42
|
+
uid: string;
|
|
43
|
+
};
|
|
44
|
+
export type IPitchCorrectionQuality = 'low' | 'medium' | 'high';
|
|
45
|
+
export type IAudioStatus = {
|
|
46
|
+
id: string;
|
|
47
|
+
currentTime: number;
|
|
48
|
+
playbackState: string;
|
|
49
|
+
timeControlStatus: string;
|
|
50
|
+
reasonForWaitingToPlay: string;
|
|
51
|
+
mute: boolean;
|
|
52
|
+
duration: number;
|
|
53
|
+
playing: boolean;
|
|
54
|
+
loop: boolean;
|
|
55
|
+
didJustFinish: boolean;
|
|
56
|
+
isBuffering: boolean;
|
|
57
|
+
isLoaded: boolean;
|
|
58
|
+
playbackRate: number;
|
|
59
|
+
shouldCorrectPitch: boolean;
|
|
60
|
+
/** @platform ios */
|
|
61
|
+
mediaServicesDidReset?: boolean;
|
|
62
|
+
isLive: boolean;
|
|
63
|
+
currentOffsetFromLive: number | null;
|
|
64
|
+
error: string | null;
|
|
65
|
+
};
|
|
66
|
+
export type IRecordingStatus = {
|
|
67
|
+
id: string;
|
|
68
|
+
isFinished: boolean;
|
|
69
|
+
hasError: boolean;
|
|
70
|
+
error: string | null;
|
|
71
|
+
url: string | null;
|
|
72
|
+
/** @platform ios */
|
|
73
|
+
mediaServicesDidReset?: boolean;
|
|
74
|
+
};
|
|
75
|
+
export type IRecorderState = {
|
|
76
|
+
canRecord: boolean;
|
|
77
|
+
isRecording: boolean;
|
|
78
|
+
durationMillis: number;
|
|
79
|
+
mediaServicesDidReset: boolean;
|
|
80
|
+
metering?: number;
|
|
81
|
+
url: string | null;
|
|
82
|
+
};
|
|
83
|
+
/** @platform android */
|
|
84
|
+
export type IAndroidOutputFormat = 'default' | '3gp' | 'mpeg4' | 'amrnb' | 'amrwb' | 'aac_adts' | 'mpeg2ts' | 'webm';
|
|
85
|
+
/** @platform android */
|
|
86
|
+
export type IAndroidAudioEncoder = 'default' | 'amr_nb' | 'amr_wb' | 'aac' | 'he_aac' | 'aac_eld';
|
|
87
|
+
export type IRecordingStartOptions = {
|
|
88
|
+
/** @platform ios @platform android @platform web */
|
|
89
|
+
forDuration?: number;
|
|
90
|
+
/** @platform ios */
|
|
91
|
+
atTime?: number;
|
|
92
|
+
};
|
|
93
|
+
/** @platform android @platform ios */
|
|
94
|
+
export type IRecordingDirectory = 'cache' | 'document';
|
|
95
|
+
export type IRecordingOptionsWeb = {
|
|
96
|
+
mimeType?: string;
|
|
97
|
+
bitsPerSecond?: number;
|
|
98
|
+
};
|
|
99
|
+
/** @platform ios */
|
|
100
|
+
export type IRecordingOptionsIos = {
|
|
101
|
+
extension?: string;
|
|
102
|
+
sampleRate?: number;
|
|
103
|
+
outputFormat?: string | IOSOutputFormat | number;
|
|
104
|
+
audioQuality: AudioQuality | number;
|
|
105
|
+
bitRateStrategy?: number;
|
|
106
|
+
bitDepthHint?: number;
|
|
107
|
+
linearPCMBitDepth?: number;
|
|
108
|
+
linearPCMIsBigEndian?: boolean;
|
|
109
|
+
linearPCMIsFloat?: boolean;
|
|
110
|
+
};
|
|
111
|
+
/** @platform android */
|
|
112
|
+
export type IRecordingSource = 'camcorder' | 'default' | 'mic' | 'remote_submix' | 'unprocessed' | 'voice_communication' | 'voice_performance' | 'voice_recognition';
|
|
113
|
+
/** @platform android */
|
|
114
|
+
export type IRecordingOptionsAndroid = {
|
|
115
|
+
extension?: string;
|
|
116
|
+
sampleRate?: number;
|
|
117
|
+
outputFormat: IAndroidOutputFormat;
|
|
118
|
+
audioEncoder: IAndroidAudioEncoder;
|
|
119
|
+
maxFileSize?: number;
|
|
120
|
+
audioSource?: IRecordingSource;
|
|
121
|
+
};
|
|
122
|
+
export type IRecordingOptions = {
|
|
123
|
+
/** @default 'cache' */
|
|
124
|
+
directory?: IRecordingDirectory;
|
|
125
|
+
isMeteringEnabled?: boolean;
|
|
126
|
+
extension: string;
|
|
127
|
+
sampleRate: number;
|
|
128
|
+
numberOfChannels: number;
|
|
129
|
+
bitRate: number;
|
|
130
|
+
android: IRecordingOptionsAndroid;
|
|
131
|
+
ios: IRecordingOptionsIos;
|
|
132
|
+
web: IRecordingOptionsWeb;
|
|
133
|
+
};
|
|
134
|
+
export type IAudioMode = {
|
|
135
|
+
/** @default true */
|
|
136
|
+
playsInSilentMode: boolean;
|
|
137
|
+
/** @default 'mixWithOthers' */
|
|
138
|
+
interruptionMode: IInterruptionMode;
|
|
139
|
+
/** @default false @platform ios */
|
|
140
|
+
allowsRecording: boolean;
|
|
141
|
+
/** @default false */
|
|
142
|
+
shouldPlayInBackground: boolean;
|
|
143
|
+
/** @default false */
|
|
144
|
+
shouldRouteThroughEarpiece: boolean;
|
|
145
|
+
/** @default false @platform ios @platform android */
|
|
146
|
+
allowsBackgroundRecording?: boolean;
|
|
147
|
+
};
|
|
148
|
+
export type IInterruptionMode = 'mixWithOthers' | 'doNotMix' | 'duckOthers';
|
|
149
|
+
export type IAudioMetadata = {
|
|
150
|
+
title?: string;
|
|
151
|
+
artist?: string;
|
|
152
|
+
albumTitle?: string;
|
|
153
|
+
artworkUrl?: string;
|
|
154
|
+
};
|
|
155
|
+
export type IAudioPlaylistLoopMode = 'none' | 'single' | 'all';
|
|
156
|
+
export type IAudioPlaylistOptions = {
|
|
157
|
+
/** @default [] */
|
|
158
|
+
sources?: IAudioSource[];
|
|
159
|
+
/** @default 500 */
|
|
160
|
+
updateInterval?: number;
|
|
161
|
+
/** @default 'none' */
|
|
162
|
+
loop?: IAudioPlaylistLoopMode;
|
|
163
|
+
/** @platform web */
|
|
164
|
+
crossOrigin?: 'anonymous' | 'use-credentials';
|
|
165
|
+
};
|
|
166
|
+
export type IAudioPlaylistStatus = {
|
|
167
|
+
id: string;
|
|
168
|
+
currentIndex: number;
|
|
169
|
+
trackCount: number;
|
|
170
|
+
currentTime: number;
|
|
171
|
+
duration: number;
|
|
172
|
+
playing: boolean;
|
|
173
|
+
isBuffering: boolean;
|
|
174
|
+
isLoaded: boolean;
|
|
175
|
+
playbackRate: number;
|
|
176
|
+
muted: boolean;
|
|
177
|
+
volume: number;
|
|
178
|
+
loop: IAudioPlaylistLoopMode;
|
|
179
|
+
didJustFinish: boolean;
|
|
180
|
+
};
|
|
181
|
+
/** From AudioConstants.ts — which lock-screen controls to show while a player is active. */
|
|
182
|
+
export type IAudioLockScreenOptions = {
|
|
183
|
+
showSeekForward?: boolean;
|
|
184
|
+
showSeekBackward?: boolean;
|
|
185
|
+
isLiveStream?: boolean;
|
|
186
|
+
};
|
|
187
|
+
export type IAudioSample = {
|
|
188
|
+
channels: {
|
|
189
|
+
frames: number[];
|
|
190
|
+
}[];
|
|
191
|
+
timestamp: number;
|
|
192
|
+
};
|
|
193
|
+
export type IAudioStreamEncoding = 'float32' | 'int16';
|
|
194
|
+
export type IAudioStreamOptions = {
|
|
195
|
+
/** @default 48000 */
|
|
196
|
+
sampleRate?: number;
|
|
197
|
+
/** @default 1 */
|
|
198
|
+
channels?: number;
|
|
199
|
+
/** @default 'float32' */
|
|
200
|
+
encoding?: IAudioStreamEncoding;
|
|
201
|
+
};
|
|
202
|
+
export type IAudioStreamBuffer = {
|
|
203
|
+
data: ArrayBuffer;
|
|
204
|
+
sampleRate: number;
|
|
205
|
+
channels: number;
|
|
206
|
+
timestamp: number;
|
|
207
|
+
};
|
|
208
|
+
export type IAudioStreamStatus = {
|
|
209
|
+
isStreaming: boolean;
|
|
210
|
+
};
|
|
211
|
+
/** From AudioEventKeys.ts — event names the `SharedObject` classes' `addListener` accepts. */
|
|
212
|
+
export declare const PLAYBACK_STATUS_UPDATE = "playbackStatusUpdate";
|
|
213
|
+
export declare const AUDIO_SAMPLE_UPDATE = "audioSampleUpdate";
|
|
214
|
+
export declare const RECORDING_STATUS_UPDATE = "recordingStatusUpdate";
|
|
215
|
+
export declare const PLAYLIST_STATUS_UPDATE = "playlistStatusUpdate";
|
|
216
|
+
export declare const TRACK_CHANGED = "trackChanged";
|
|
217
|
+
export declare const AUDIO_STREAM_BUFFER = "audioStreamBuffer";
|
|
218
|
+
export declare const AUDIO_STREAM_STATUS = "audioStreamStatus";
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/** From AudioEventKeys.ts — event names the `SharedObject` classes' `addListener` accepts. */
|
|
2
|
+
export const PLAYBACK_STATUS_UPDATE = 'playbackStatusUpdate';
|
|
3
|
+
export const AUDIO_SAMPLE_UPDATE = 'audioSampleUpdate';
|
|
4
|
+
export const RECORDING_STATUS_UPDATE = 'recordingStatusUpdate';
|
|
5
|
+
export const PLAYLIST_STATUS_UPDATE = 'playlistStatusUpdate';
|
|
6
|
+
export const TRACK_CHANGED = 'trackChanged';
|
|
7
|
+
export const AUDIO_STREAM_BUFFER = 'audioStreamBuffer';
|
|
8
|
+
export const AUDIO_STREAM_STATUS = 'audioStreamStatus';
|
|
9
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/core/types.ts"],"names":[],"mappings":"AAkRA,8FAA8F;AAC9F,MAAM,CAAC,MAAM,sBAAsB,GAAG,sBAAsB,CAAC;AAC7D,MAAM,CAAC,MAAM,mBAAmB,GAAG,mBAAmB,CAAC;AACvD,MAAM,CAAC,MAAM,uBAAuB,GAAG,uBAAuB,CAAC;AAC/D,MAAM,CAAC,MAAM,sBAAsB,GAAG,sBAAsB,CAAC;AAC7D,MAAM,CAAC,MAAM,aAAa,GAAG,cAAc,CAAC;AAC5C,MAAM,CAAC,MAAM,mBAAmB,GAAG,mBAAmB,CAAC;AACvD,MAAM,CAAC,MAAM,mBAAmB,GAAG,mBAAmB,CAAC"}
|
package/native-link.json
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
{
|
|
2
|
+
"android": {
|
|
3
|
+
"gradleProjectName": "expo-audio",
|
|
4
|
+
"modules": [
|
|
5
|
+
{
|
|
6
|
+
"importPath": "expo.modules.audio.AudioModule",
|
|
7
|
+
"className": "AudioModule",
|
|
8
|
+
"nativeName": "ExpoAudio"
|
|
9
|
+
}
|
|
10
|
+
],
|
|
11
|
+
"manifestPermissions": [
|
|
12
|
+
"android.permission.FOREGROUND_SERVICE",
|
|
13
|
+
"android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK"
|
|
14
|
+
],
|
|
15
|
+
"manifestServices": [
|
|
16
|
+
{
|
|
17
|
+
"name": "expo.modules.audio.service.AudioControlsService",
|
|
18
|
+
"foregroundServiceType": "mediaPlayback",
|
|
19
|
+
"intentFilterActions": ["androidx.media3.session.MediaSessionService"]
|
|
20
|
+
}
|
|
21
|
+
],
|
|
22
|
+
"optionalManifestBundles": [
|
|
23
|
+
{
|
|
24
|
+
"id": "recording",
|
|
25
|
+
"label": "Background audio recording",
|
|
26
|
+
"warning": "Requesting FOREGROUND_SERVICE_MICROPHONE triggers Play Console policy review — Google requires a clear, prominent in-app disclosure and justification before you can publish.",
|
|
27
|
+
"nextSteps": "Pass `allowsBackgroundRecording: true` to setAudioModeAsync so recording keeps running with the app backgrounded.",
|
|
28
|
+
"manifestPermissions": [
|
|
29
|
+
"android.permission.FOREGROUND_SERVICE_MICROPHONE",
|
|
30
|
+
"android.permission.POST_NOTIFICATIONS"
|
|
31
|
+
],
|
|
32
|
+
"manifestServices": [
|
|
33
|
+
{
|
|
34
|
+
"name": "expo.modules.audio.service.AudioRecordingService",
|
|
35
|
+
"foregroundServiceType": "microphone"
|
|
36
|
+
}
|
|
37
|
+
]
|
|
38
|
+
}
|
|
39
|
+
]
|
|
40
|
+
},
|
|
41
|
+
"ios": {
|
|
42
|
+
"infoPlistKeys": {
|
|
43
|
+
"NSMicrophoneUsageDescription": "This app uses the microphone to record audio."
|
|
44
|
+
},
|
|
45
|
+
"infoPlistArrayKeys": { "UIBackgroundModes": ["audio"] }
|
|
46
|
+
}
|
|
47
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@symbiote-native/audio",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "expo-audio wrapped for SymbioteNative — one framework-agnostic core, built once and reachable from the React, Vue, Svelte, Solid, and Angular adapters. AudioPlayer / AudioRecorder / AudioPlaylist / AudioStream shared-object classes, audio-session config, and recording permissions.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/OneEyed1366/symbiote-native.git",
|
|
9
|
+
"directory": "packages/audio"
|
|
10
|
+
},
|
|
11
|
+
"homepage": "https://github.com/OneEyed1366/symbiote-native/tree/master/packages/audio#readme",
|
|
12
|
+
"bugs": {
|
|
13
|
+
"url": "https://github.com/OneEyed1366/symbiote-native/issues"
|
|
14
|
+
},
|
|
15
|
+
"author": "Andrey Prokopenko <psevdoproger@gmail.com>",
|
|
16
|
+
"keywords": [
|
|
17
|
+
"react-native",
|
|
18
|
+
"symbiote-native",
|
|
19
|
+
"expo-audio",
|
|
20
|
+
"react",
|
|
21
|
+
"vue",
|
|
22
|
+
"svelte",
|
|
23
|
+
"solid",
|
|
24
|
+
"angular",
|
|
25
|
+
"audio",
|
|
26
|
+
"recording"
|
|
27
|
+
],
|
|
28
|
+
"type": "module",
|
|
29
|
+
"main": "./build/core/index.js",
|
|
30
|
+
"module": "./build/core/index.js",
|
|
31
|
+
"types": "./build/core/index.d.ts",
|
|
32
|
+
"exports": {
|
|
33
|
+
".": {
|
|
34
|
+
"types": "./build/core/index.d.ts",
|
|
35
|
+
"default": "./build/core/index.js"
|
|
36
|
+
},
|
|
37
|
+
"./vue": {
|
|
38
|
+
"types": "./build/core/index.d.ts",
|
|
39
|
+
"default": "./build/core/index.js"
|
|
40
|
+
},
|
|
41
|
+
"./react": {
|
|
42
|
+
"types": "./build/core/index.d.ts",
|
|
43
|
+
"default": "./build/core/index.js"
|
|
44
|
+
},
|
|
45
|
+
"./svelte": {
|
|
46
|
+
"types": "./build/core/index.d.ts",
|
|
47
|
+
"default": "./build/core/index.js"
|
|
48
|
+
},
|
|
49
|
+
"./solid": {
|
|
50
|
+
"types": "./build/core/index.d.ts",
|
|
51
|
+
"default": "./build/core/index.js"
|
|
52
|
+
},
|
|
53
|
+
"./angular": {
|
|
54
|
+
"types": "./build-ngc/angular/index.d.ts",
|
|
55
|
+
"react-native": "./build-ngc/angular/index.js",
|
|
56
|
+
"default": "./src/angular/index.ts"
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
"files": [
|
|
60
|
+
"src",
|
|
61
|
+
"build",
|
|
62
|
+
"build-ngc",
|
|
63
|
+
"native-link.json",
|
|
64
|
+
"!src/**/*.test.*",
|
|
65
|
+
"!src/**/*.spec.*",
|
|
66
|
+
"!src/**/*.detox.*"
|
|
67
|
+
],
|
|
68
|
+
"publishConfig": {
|
|
69
|
+
"access": "public"
|
|
70
|
+
},
|
|
71
|
+
"dependencies": {
|
|
72
|
+
"expo-audio": "57.0.4",
|
|
73
|
+
"expo-modules-core": "57.0.5"
|
|
74
|
+
},
|
|
75
|
+
"peerDependencies": {
|
|
76
|
+
"@angular/core": ">=20",
|
|
77
|
+
"@vue/runtime-core": "^3.5.13",
|
|
78
|
+
"react": ">=19.0.0",
|
|
79
|
+
"react-native": ">=0.86",
|
|
80
|
+
"solid-js": ">=1.9.0",
|
|
81
|
+
"svelte": ">=5.56.0",
|
|
82
|
+
"vue": ">=3.5.0",
|
|
83
|
+
"@symbiote-native/angular": "^3.1.0",
|
|
84
|
+
"@symbiote-native/react": "^3.0.2",
|
|
85
|
+
"@symbiote-native/engine": "^1.3.0",
|
|
86
|
+
"@symbiote-native/solid": "^3.0.2",
|
|
87
|
+
"@symbiote-native/svelte": "^3.0.2",
|
|
88
|
+
"@symbiote-native/vue": "^3.0.2"
|
|
89
|
+
},
|
|
90
|
+
"peerDependenciesMeta": {
|
|
91
|
+
"@symbiote-native/angular": {
|
|
92
|
+
"optional": true
|
|
93
|
+
},
|
|
94
|
+
"@symbiote-native/react": {
|
|
95
|
+
"optional": true
|
|
96
|
+
},
|
|
97
|
+
"@symbiote-native/solid": {
|
|
98
|
+
"optional": true
|
|
99
|
+
},
|
|
100
|
+
"@symbiote-native/svelte": {
|
|
101
|
+
"optional": true
|
|
102
|
+
},
|
|
103
|
+
"@symbiote-native/vue": {
|
|
104
|
+
"optional": true
|
|
105
|
+
},
|
|
106
|
+
"@angular/core": {
|
|
107
|
+
"optional": true
|
|
108
|
+
},
|
|
109
|
+
"@vue/runtime-core": {
|
|
110
|
+
"optional": true
|
|
111
|
+
},
|
|
112
|
+
"react": {
|
|
113
|
+
"optional": true
|
|
114
|
+
},
|
|
115
|
+
"solid-js": {
|
|
116
|
+
"optional": true
|
|
117
|
+
},
|
|
118
|
+
"svelte": {
|
|
119
|
+
"optional": true
|
|
120
|
+
},
|
|
121
|
+
"vue": {
|
|
122
|
+
"optional": true
|
|
123
|
+
}
|
|
124
|
+
},
|
|
125
|
+
"devDependencies": {
|
|
126
|
+
"@angular/compiler": "~22.0.8",
|
|
127
|
+
"@angular/compiler-cli": "~22.0.8",
|
|
128
|
+
"@angular/core": "~22.0.8",
|
|
129
|
+
"@types/node": "^26.0.0",
|
|
130
|
+
"@types/react": "^19.2.0",
|
|
131
|
+
"@vue/runtime-core": "^3.5.13",
|
|
132
|
+
"react": "19.2.3",
|
|
133
|
+
"solid-js": "^1.9.14",
|
|
134
|
+
"svelte": "^5.56.0",
|
|
135
|
+
"typescript": "~6.0.0",
|
|
136
|
+
"@symbiote-native/angular": "3.1.0",
|
|
137
|
+
"@symbiote-native/solid": "3.0.2",
|
|
138
|
+
"@symbiote-native/engine": "1.3.0",
|
|
139
|
+
"@symbiote-native/svelte": "3.0.2",
|
|
140
|
+
"@symbiote-native/react": "3.0.2",
|
|
141
|
+
"@symbiote-native/vue": "3.0.2",
|
|
142
|
+
"@symbiote-native/test-utils": "0.4.2"
|
|
143
|
+
},
|
|
144
|
+
"scripts": {
|
|
145
|
+
"typecheck": "tsc --build",
|
|
146
|
+
"clean": "rm -rf build-ngc",
|
|
147
|
+
"ng:build": "pnpm run clean && ngc -p tsconfig.angular.json",
|
|
148
|
+
"format": "prettier --write \"src/**/*.{ts,tsx}\""
|
|
149
|
+
}
|
|
150
|
+
}
|
|
@@ -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';
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { Platform, type PermissionResponse } from 'expo-modules-core';
|
|
2
|
+
import { expoAudio } from './native-module';
|
|
3
|
+
import { resolveSource } from './resolve-source';
|
|
4
|
+
import type { IAudioMode, IAudioSource, IPreloadOptions } from './types';
|
|
5
|
+
|
|
6
|
+
/** Enables or disables the audio subsystem globally — pauses all playback when set to `false`. */
|
|
7
|
+
export async function setIsAudioActiveAsync(active: boolean): Promise<void> {
|
|
8
|
+
return expoAudio.setIsAudioActiveAsync(active);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/** Configures the global audio session — background playback, mixing, interruption handling. */
|
|
12
|
+
export async function setAudioModeAsync(
|
|
13
|
+
mode: Partial<IAudioMode>,
|
|
14
|
+
): Promise<void> {
|
|
15
|
+
const audioMode: Partial<IAudioMode> =
|
|
16
|
+
Platform.OS === 'ios'
|
|
17
|
+
? mode
|
|
18
|
+
: {
|
|
19
|
+
shouldPlayInBackground: mode.shouldPlayInBackground,
|
|
20
|
+
shouldRouteThroughEarpiece: mode.shouldRouteThroughEarpiece,
|
|
21
|
+
interruptionMode: mode.interruptionMode,
|
|
22
|
+
allowsBackgroundRecording: mode.allowsBackgroundRecording,
|
|
23
|
+
playsInSilentMode: mode.playsInSilentMode,
|
|
24
|
+
};
|
|
25
|
+
return expoAudio.setAudioModeAsync(audioMode);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** Requests microphone-access permission required for recording. */
|
|
29
|
+
export async function requestRecordingPermissionsAsync(): Promise<PermissionResponse> {
|
|
30
|
+
return expoAudio.requestRecordingPermissionsAsync();
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Requests permission to post notifications on Android, needed for lock-screen/notification-shade
|
|
35
|
+
* playback controls. @platform android — throws on every other platform.
|
|
36
|
+
*/
|
|
37
|
+
export async function requestNotificationPermissionsAsync(): Promise<PermissionResponse> {
|
|
38
|
+
if (Platform.OS !== 'android') {
|
|
39
|
+
throw new Error(
|
|
40
|
+
'expo-audio: `requestNotificationPermissionsAsync` is only available on Android.',
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
return expoAudio.requestNotificationPermissionsAsync();
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** Checks the current recording-permission status without prompting. */
|
|
47
|
+
export async function getRecordingPermissionsAsync(): Promise<PermissionResponse> {
|
|
48
|
+
return expoAudio.getRecordingPermissionsAsync();
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Preloads an audio source for near-instant playback later — call at module scope, before any
|
|
53
|
+
* player is created from the same source.
|
|
54
|
+
*/
|
|
55
|
+
export async function preload(
|
|
56
|
+
source: IAudioSource,
|
|
57
|
+
options: IPreloadOptions = {},
|
|
58
|
+
): Promise<void> {
|
|
59
|
+
const resolved = resolveSource(source);
|
|
60
|
+
if (!resolved) return;
|
|
61
|
+
const { preferredForwardBufferDuration = 10 } = options;
|
|
62
|
+
return expoAudio.preload(resolved, preferredForwardBufferDuration);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** Releases a specific preloaded source (must match what was passed to `preload`). */
|
|
66
|
+
export async function clearPreloadedSource(
|
|
67
|
+
source: IAudioSource,
|
|
68
|
+
): Promise<void> {
|
|
69
|
+
const resolved = resolveSource(source);
|
|
70
|
+
if (!resolved) return;
|
|
71
|
+
return expoAudio.clearPreloadedSource(resolved);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** Releases every preloaded source. */
|
|
75
|
+
export async function clearAllPreloadedSources(): Promise<void> {
|
|
76
|
+
return expoAudio.clearAllPreloadedSources();
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** URIs of every source currently in the preload cache. */
|
|
80
|
+
export async function getPreloadedSources(): Promise<string[]> {
|
|
81
|
+
return expoAudio.getPreloadedSources();
|
|
82
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { Platform } from 'expo-modules-core';
|
|
2
|
+
import { expoAudio } from './native-module';
|
|
3
|
+
import { resolveSource } from './resolve-source';
|
|
4
|
+
import type {
|
|
5
|
+
IAudioPlayerOptions,
|
|
6
|
+
IAudioSource,
|
|
7
|
+
IPitchCorrectionQuality,
|
|
8
|
+
} from './types';
|
|
9
|
+
|
|
10
|
+
// Upstream shims these two methods onto `AudioModule.AudioPlayer.prototype` at module load
|
|
11
|
+
// (ExpoAudio.ts). We do the equivalent by subclassing instead of monkey-patching the native
|
|
12
|
+
// class's prototype — same shape packages/media-library/src/next/asset.ts uses for its own
|
|
13
|
+
// platform-conditional overrides.
|
|
14
|
+
export class AudioPlayer extends expoAudio.AudioPlayer {
|
|
15
|
+
override replace(source: IAudioSource): void {
|
|
16
|
+
super.replace(resolveSource(source));
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
override setPlaybackRate(
|
|
20
|
+
rate: number,
|
|
21
|
+
pitchCorrectionQuality?: IPitchCorrectionQuality,
|
|
22
|
+
): void {
|
|
23
|
+
if (Platform.OS === 'android') {
|
|
24
|
+
super.setPlaybackRate(rate);
|
|
25
|
+
} else {
|
|
26
|
+
super.setPlaybackRate(rate, pitchCorrectionQuality);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Creates an `AudioPlayer` that does not release automatically — call `.remove()` when done with
|
|
33
|
+
* it. There is no lifecycle wrapper here (framework-agnostic core, see the package README); each
|
|
34
|
+
* adapter's own hook/composable/service is responsible for releasing it on unmount, mirroring
|
|
35
|
+
* upstream's `useReleasingSharedObject`.
|
|
36
|
+
*/
|
|
37
|
+
export function createAudioPlayer(
|
|
38
|
+
source: IAudioSource = null,
|
|
39
|
+
options: IAudioPlayerOptions = {},
|
|
40
|
+
): AudioPlayer {
|
|
41
|
+
const {
|
|
42
|
+
updateInterval = 500,
|
|
43
|
+
keepAudioSessionActive = false,
|
|
44
|
+
preferredForwardBufferDuration = 0,
|
|
45
|
+
} = options;
|
|
46
|
+
const resolvedSource = resolveSource(source);
|
|
47
|
+
return new AudioPlayer(
|
|
48
|
+
resolvedSource,
|
|
49
|
+
updateInterval,
|
|
50
|
+
keepAudioSessionActive,
|
|
51
|
+
preferredForwardBufferDuration,
|
|
52
|
+
);
|
|
53
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { expoAudio } from './native-module';
|
|
2
|
+
import { resolveSources } from './resolve-source';
|
|
3
|
+
import type { IAudioPlaylistOptions } from './types';
|
|
4
|
+
|
|
5
|
+
// No prototype shim on `AudioPlaylist` upstream (unlike `AudioPlayer`/`AudioRecorder`) — its
|
|
6
|
+
// `add`/`insert` take an already-resolved source, so a plain re-export is faithful to upstream.
|
|
7
|
+
export const AudioPlaylist = expoAudio.AudioPlaylist;
|
|
8
|
+
export type AudioPlaylist = InstanceType<typeof AudioPlaylist>;
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Creates an `AudioPlaylist` that does not release automatically — call `.destroy()` when done
|
|
12
|
+
* with it. See `createAudioPlayer`'s doc comment for the same lifecycle note.
|
|
13
|
+
*/
|
|
14
|
+
export function createAudioPlaylist(
|
|
15
|
+
options: IAudioPlaylistOptions = {},
|
|
16
|
+
): AudioPlaylist {
|
|
17
|
+
const { sources = [], updateInterval = 500, loop = 'none' } = options;
|
|
18
|
+
const resolvedSources = resolveSources(sources);
|
|
19
|
+
return new AudioPlaylist(resolvedSources, updateInterval, loop);
|
|
20
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { Platform } from 'expo-modules-core';
|
|
2
|
+
import { expoAudio } from './native-module';
|
|
3
|
+
import type { IRecordingOptions } from './types';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Fills in the per-platform recording config from `RecordingOptions`'s common fields, ported
|
|
7
|
+
* from upstream's `utils/options.ts`. Only the fields the current platform's native recorder
|
|
8
|
+
* reads are kept, with `options.ios`/`options.android`/`options.web` spread on top so an
|
|
9
|
+
* explicit per-platform override always wins.
|
|
10
|
+
*/
|
|
11
|
+
function createRecordingOptions(options: IRecordingOptions) {
|
|
12
|
+
const commonOptions = {
|
|
13
|
+
extension: options.extension,
|
|
14
|
+
sampleRate: options.sampleRate,
|
|
15
|
+
numberOfChannels: options.numberOfChannels,
|
|
16
|
+
bitRate: options.bitRate,
|
|
17
|
+
isMeteringEnabled: options.isMeteringEnabled ?? false,
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
if (Platform.OS === 'ios') {
|
|
21
|
+
return { ...commonOptions, directory: options.directory, ...options.ios };
|
|
22
|
+
}
|
|
23
|
+
if (Platform.OS === 'android') {
|
|
24
|
+
return {
|
|
25
|
+
...commonOptions,
|
|
26
|
+
directory: options.directory,
|
|
27
|
+
...options.android,
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
return { ...commonOptions, ...options.web };
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Upstream shims `prepareToRecordAsync` onto `AudioModule.AudioRecorder.prototype` at module
|
|
34
|
+
// load, EXCEPT on tvOS (recording prototypes "should not be shimmed on tvOS, where they do not
|
|
35
|
+
// exist" — ExpoAudio.ts). tvOS is not a target platform of this project (root CLAUDE.md: iOS +
|
|
36
|
+
// Android only), so that branch is dropped — the options are always processed.
|
|
37
|
+
export class AudioRecorder extends expoAudio.AudioRecorder {
|
|
38
|
+
override prepareToRecordAsync(
|
|
39
|
+
options?: Partial<IRecordingOptions>,
|
|
40
|
+
): Promise<void> {
|
|
41
|
+
const processedOptions = options
|
|
42
|
+
? createRecordingOptions(options as IRecordingOptions)
|
|
43
|
+
: undefined;
|
|
44
|
+
return super.prepareToRecordAsync(processedOptions);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { expoAudio } from './native-module';
|
|
2
|
+
import type { IAudioStreamOptions } from './types';
|
|
3
|
+
|
|
4
|
+
// Plain re-export — upstream has no prototype shim for `AudioStream` either.
|
|
5
|
+
export const AudioStream = expoAudio.AudioStream;
|
|
6
|
+
export type AudioStream = InstanceType<typeof AudioStream>;
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Creates a native `AudioStream` for real-time PCM microphone capture. Call `.start()` to
|
|
10
|
+
* begin and `.stop()` to end; requires microphone permission — see
|
|
11
|
+
* `requestRecordingPermissionsAsync`. Upstream only exposes this via the `useAudioStream` hook
|
|
12
|
+
* (React-only, see the package README); this factory is the framework-agnostic equivalent — the
|
|
13
|
+
* hook itself just applies these same defaults and wires `addListener` for
|
|
14
|
+
* `AUDIO_STREAM_STATUS`/`AUDIO_STREAM_BUFFER` on top, which each adapter's own lifecycle wrapper
|
|
15
|
+
* is responsible for.
|
|
16
|
+
*/
|
|
17
|
+
export function createAudioStream(
|
|
18
|
+
options: IAudioStreamOptions = {},
|
|
19
|
+
): AudioStream {
|
|
20
|
+
const { sampleRate = 48000, channels = 1, encoding = 'float32' } = options;
|
|
21
|
+
return new AudioStream({ sampleRate, channels, encoding });
|
|
22
|
+
}
|