@idealyst/audio 1.2.48
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/package.json +69 -0
- package/src/constants.ts +161 -0
- package/src/context/AudioContext.native.ts +84 -0
- package/src/context/AudioContext.web.ts +97 -0
- package/src/context/index.native.ts +1 -0
- package/src/context/index.ts +1 -0
- package/src/hooks/index.ts +3 -0
- package/src/hooks/useAudio.ts +129 -0
- package/src/hooks/usePlayer.ts +247 -0
- package/src/hooks/useRecorder.ts +176 -0
- package/src/index.native.ts +114 -0
- package/src/index.ts +114 -0
- package/src/index.web.ts +8 -0
- package/src/playback/Player.native.ts +517 -0
- package/src/playback/Player.web.ts +518 -0
- package/src/playback/index.native.ts +1 -0
- package/src/playback/index.ts +1 -0
- package/src/recording/Recorder.native.ts +330 -0
- package/src/recording/Recorder.web.ts +399 -0
- package/src/recording/index.native.ts +1 -0
- package/src/recording/index.ts +1 -0
- package/src/session/AudioSession.native.ts +204 -0
- package/src/session/AudioSession.web.ts +69 -0
- package/src/session/index.native.ts +5 -0
- package/src/session/index.ts +1 -0
- package/src/types.ts +470 -0
- package/src/utils.ts +379 -0
package/src/types.ts
ADDED
|
@@ -0,0 +1,470 @@
|
|
|
1
|
+
// ============================================
|
|
2
|
+
// AUDIO CONFIGURATION
|
|
3
|
+
// ============================================
|
|
4
|
+
|
|
5
|
+
export type SampleRate = 8000 | 16000 | 22050 | 44100 | 48000;
|
|
6
|
+
export type BitDepth = 8 | 16 | 32;
|
|
7
|
+
export type ChannelCount = 1 | 2;
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Audio configuration for recording and playback.
|
|
11
|
+
*/
|
|
12
|
+
export interface AudioConfig {
|
|
13
|
+
/** Sample rate in Hz. Default: 16000 */
|
|
14
|
+
sampleRate: SampleRate;
|
|
15
|
+
|
|
16
|
+
/** Number of audio channels. Default: 1 (mono) */
|
|
17
|
+
channels: ChannelCount;
|
|
18
|
+
|
|
19
|
+
/** Bits per sample. Default: 16 */
|
|
20
|
+
bitDepth: BitDepth;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// ============================================
|
|
24
|
+
// AUDIO SESSION (iOS/Android)
|
|
25
|
+
// ============================================
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* iOS AVAudioSession categories
|
|
29
|
+
*/
|
|
30
|
+
export type AudioSessionCategory =
|
|
31
|
+
| 'ambient'
|
|
32
|
+
| 'soloAmbient'
|
|
33
|
+
| 'playback'
|
|
34
|
+
| 'record'
|
|
35
|
+
| 'playAndRecord'
|
|
36
|
+
| 'multiRoute';
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* iOS AVAudioSession category options
|
|
40
|
+
*/
|
|
41
|
+
export type AudioSessionCategoryOption =
|
|
42
|
+
| 'mixWithOthers'
|
|
43
|
+
| 'duckOthers'
|
|
44
|
+
| 'allowBluetooth'
|
|
45
|
+
| 'allowBluetoothA2DP'
|
|
46
|
+
| 'allowAirPlay'
|
|
47
|
+
| 'defaultToSpeaker'
|
|
48
|
+
| 'interruptSpokenAudioAndMixWithOthers';
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* iOS AVAudioSession modes
|
|
52
|
+
*/
|
|
53
|
+
export type AudioSessionMode =
|
|
54
|
+
| 'default'
|
|
55
|
+
| 'voiceChat'
|
|
56
|
+
| 'gameChat'
|
|
57
|
+
| 'videoRecording'
|
|
58
|
+
| 'measurement'
|
|
59
|
+
| 'moviePlayback'
|
|
60
|
+
| 'videoChat'
|
|
61
|
+
| 'spokenAudio';
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Audio session configuration
|
|
65
|
+
*/
|
|
66
|
+
export interface AudioSessionConfig {
|
|
67
|
+
category: AudioSessionCategory;
|
|
68
|
+
categoryOptions?: AudioSessionCategoryOption[];
|
|
69
|
+
mode?: AudioSessionMode;
|
|
70
|
+
active?: boolean;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Audio session state
|
|
75
|
+
*/
|
|
76
|
+
export interface AudioSessionState {
|
|
77
|
+
isActive: boolean;
|
|
78
|
+
category: AudioSessionCategory;
|
|
79
|
+
mode: AudioSessionMode;
|
|
80
|
+
categoryOptions: AudioSessionCategoryOption[];
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Audio session interruption info
|
|
85
|
+
*/
|
|
86
|
+
export interface AudioSessionInterruption {
|
|
87
|
+
type: 'began' | 'ended';
|
|
88
|
+
shouldResume?: boolean;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Audio session route change info
|
|
93
|
+
*/
|
|
94
|
+
export interface AudioSessionRouteChange {
|
|
95
|
+
reason: string;
|
|
96
|
+
previousOutputs: string[];
|
|
97
|
+
currentOutputs: string[];
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// ============================================
|
|
101
|
+
// PCM DATA
|
|
102
|
+
// ============================================
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* PCM audio data packet
|
|
106
|
+
*/
|
|
107
|
+
export interface PCMData {
|
|
108
|
+
/** Raw audio buffer */
|
|
109
|
+
buffer: ArrayBuffer;
|
|
110
|
+
|
|
111
|
+
/** Typed array of samples */
|
|
112
|
+
samples: Int8Array | Int16Array | Float32Array;
|
|
113
|
+
|
|
114
|
+
/** Capture/playback timestamp */
|
|
115
|
+
timestamp: number;
|
|
116
|
+
|
|
117
|
+
/** Audio configuration */
|
|
118
|
+
config: AudioConfig;
|
|
119
|
+
|
|
120
|
+
/** Convert to Base64 string */
|
|
121
|
+
toBase64(): string;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Audio level information
|
|
126
|
+
*/
|
|
127
|
+
export interface AudioLevel {
|
|
128
|
+
/** Current level (0.0 - 1.0) */
|
|
129
|
+
current: number;
|
|
130
|
+
|
|
131
|
+
/** Peak level since last reset (0.0 - 1.0) */
|
|
132
|
+
peak: number;
|
|
133
|
+
|
|
134
|
+
/** RMS (root mean square) */
|
|
135
|
+
rms: number;
|
|
136
|
+
|
|
137
|
+
/** Decibel value (-Infinity to 0) */
|
|
138
|
+
db: number;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// ============================================
|
|
142
|
+
// RECORDER TYPES
|
|
143
|
+
// ============================================
|
|
144
|
+
|
|
145
|
+
export type RecorderState =
|
|
146
|
+
| 'idle'
|
|
147
|
+
| 'requesting_permission'
|
|
148
|
+
| 'starting'
|
|
149
|
+
| 'recording'
|
|
150
|
+
| 'paused'
|
|
151
|
+
| 'stopping'
|
|
152
|
+
| 'error';
|
|
153
|
+
|
|
154
|
+
export type PermissionStatus = 'undetermined' | 'granted' | 'denied' | 'blocked';
|
|
155
|
+
|
|
156
|
+
export interface RecorderStatus {
|
|
157
|
+
state: RecorderState;
|
|
158
|
+
isRecording: boolean;
|
|
159
|
+
isPaused: boolean;
|
|
160
|
+
permission: PermissionStatus;
|
|
161
|
+
duration: number;
|
|
162
|
+
level: AudioLevel;
|
|
163
|
+
config: AudioConfig;
|
|
164
|
+
error?: AudioError;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
export type RecorderDataCallback = (data: PCMData) => void;
|
|
168
|
+
export type RecorderLevelCallback = (level: AudioLevel) => void;
|
|
169
|
+
export type RecorderStateCallback = (status: RecorderStatus) => void;
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Recorder interface
|
|
173
|
+
*/
|
|
174
|
+
export interface IRecorder {
|
|
175
|
+
readonly status: RecorderStatus;
|
|
176
|
+
|
|
177
|
+
checkPermission(): Promise<PermissionStatus>;
|
|
178
|
+
requestPermission(): Promise<PermissionStatus>;
|
|
179
|
+
|
|
180
|
+
start(config?: Partial<AudioConfig>): Promise<void>;
|
|
181
|
+
stop(): Promise<void>;
|
|
182
|
+
pause(): Promise<void>;
|
|
183
|
+
resume(): Promise<void>;
|
|
184
|
+
|
|
185
|
+
onData(callback: RecorderDataCallback): () => void;
|
|
186
|
+
onLevel(callback: RecorderLevelCallback, intervalMs?: number): () => void;
|
|
187
|
+
onStateChange(callback: RecorderStateCallback): () => void;
|
|
188
|
+
|
|
189
|
+
resetPeakLevel(): void;
|
|
190
|
+
dispose(): void;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// ============================================
|
|
194
|
+
// PLAYER TYPES
|
|
195
|
+
// ============================================
|
|
196
|
+
|
|
197
|
+
export type PlayerState =
|
|
198
|
+
| 'idle'
|
|
199
|
+
| 'loading'
|
|
200
|
+
| 'ready'
|
|
201
|
+
| 'playing'
|
|
202
|
+
| 'paused'
|
|
203
|
+
| 'stopped'
|
|
204
|
+
| 'error';
|
|
205
|
+
|
|
206
|
+
export interface PlayerStatus {
|
|
207
|
+
state: PlayerState;
|
|
208
|
+
isPlaying: boolean;
|
|
209
|
+
isPaused: boolean;
|
|
210
|
+
duration: number;
|
|
211
|
+
position: number;
|
|
212
|
+
buffered: number;
|
|
213
|
+
volume: number;
|
|
214
|
+
muted: boolean;
|
|
215
|
+
error?: AudioError;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
export type PlayerStateCallback = (status: PlayerStatus) => void;
|
|
219
|
+
export type PlayerPositionCallback = (position: number) => void;
|
|
220
|
+
export type PlayerBufferCallback = (buffered: number) => void;
|
|
221
|
+
export type PlayerEndedCallback = () => void;
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Player interface
|
|
225
|
+
*/
|
|
226
|
+
export interface IPlayer {
|
|
227
|
+
readonly status: PlayerStatus;
|
|
228
|
+
|
|
229
|
+
// File playback
|
|
230
|
+
loadFile(uri: string): Promise<void>;
|
|
231
|
+
unload(): void;
|
|
232
|
+
|
|
233
|
+
// PCM streaming
|
|
234
|
+
loadPCMStream(config: AudioConfig): Promise<void>;
|
|
235
|
+
feedPCMData(data: ArrayBuffer | Int16Array): void;
|
|
236
|
+
flush(): Promise<void>;
|
|
237
|
+
|
|
238
|
+
// Playback control
|
|
239
|
+
play(): Promise<void>;
|
|
240
|
+
pause(): void;
|
|
241
|
+
stop(): void;
|
|
242
|
+
seek(positionMs: number): Promise<void>;
|
|
243
|
+
|
|
244
|
+
// Volume
|
|
245
|
+
setVolume(volume: number): void;
|
|
246
|
+
setMuted(muted: boolean): void;
|
|
247
|
+
|
|
248
|
+
// Events
|
|
249
|
+
onStateChange(callback: PlayerStateCallback): () => void;
|
|
250
|
+
onPosition(callback: PlayerPositionCallback, intervalMs?: number): () => void;
|
|
251
|
+
onBufferChange(callback: PlayerBufferCallback): () => void;
|
|
252
|
+
onEnded(callback: PlayerEndedCallback): () => void;
|
|
253
|
+
|
|
254
|
+
dispose(): void;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// ============================================
|
|
258
|
+
// AUDIO CONTEXT
|
|
259
|
+
// ============================================
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* Shared audio context interface
|
|
263
|
+
*/
|
|
264
|
+
export interface IAudioContext {
|
|
265
|
+
/** Native sample rate of the audio context */
|
|
266
|
+
readonly sampleRate: number;
|
|
267
|
+
|
|
268
|
+
/** Current time in seconds */
|
|
269
|
+
readonly currentTime: number;
|
|
270
|
+
|
|
271
|
+
/** Whether the context is initialized */
|
|
272
|
+
readonly isInitialized: boolean;
|
|
273
|
+
|
|
274
|
+
/** Initialize the audio context (call before using recorder/player) */
|
|
275
|
+
initialize(): Promise<void>;
|
|
276
|
+
|
|
277
|
+
/** Get the underlying platform audio context */
|
|
278
|
+
getContext(): AudioContext | null;
|
|
279
|
+
|
|
280
|
+
/** Suspend the audio context */
|
|
281
|
+
suspend(): Promise<void>;
|
|
282
|
+
|
|
283
|
+
/** Resume the audio context */
|
|
284
|
+
resume(): Promise<void>;
|
|
285
|
+
|
|
286
|
+
/** Close and dispose the audio context */
|
|
287
|
+
close(): Promise<void>;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* Audio session manager interface
|
|
292
|
+
*/
|
|
293
|
+
export interface IAudioSessionManager {
|
|
294
|
+
readonly state: AudioSessionState;
|
|
295
|
+
|
|
296
|
+
configure(config: Partial<AudioSessionConfig>): Promise<void>;
|
|
297
|
+
activate(): Promise<void>;
|
|
298
|
+
deactivate(notifyOthers?: boolean): Promise<void>;
|
|
299
|
+
|
|
300
|
+
onInterruption(callback: (interruption: AudioSessionInterruption) => void): () => void;
|
|
301
|
+
onRouteChange(callback: (change: AudioSessionRouteChange) => void): () => void;
|
|
302
|
+
|
|
303
|
+
getCurrentOutputs(): string[];
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// ============================================
|
|
307
|
+
// ERROR TYPES
|
|
308
|
+
// ============================================
|
|
309
|
+
|
|
310
|
+
export type AudioErrorCode =
|
|
311
|
+
// Permission errors
|
|
312
|
+
| 'PERMISSION_DENIED'
|
|
313
|
+
| 'PERMISSION_BLOCKED'
|
|
314
|
+
// Device errors
|
|
315
|
+
| 'DEVICE_NOT_FOUND'
|
|
316
|
+
| 'DEVICE_IN_USE'
|
|
317
|
+
| 'NOT_SUPPORTED'
|
|
318
|
+
// Playback errors
|
|
319
|
+
| 'SOURCE_NOT_FOUND'
|
|
320
|
+
| 'FORMAT_NOT_SUPPORTED'
|
|
321
|
+
| 'DECODE_ERROR'
|
|
322
|
+
| 'PLAYBACK_ERROR'
|
|
323
|
+
| 'BUFFER_UNDERRUN'
|
|
324
|
+
// Recording errors
|
|
325
|
+
| 'RECORDING_ERROR'
|
|
326
|
+
// General errors
|
|
327
|
+
| 'INITIALIZATION_FAILED'
|
|
328
|
+
| 'INVALID_STATE'
|
|
329
|
+
| 'INVALID_CONFIG'
|
|
330
|
+
| 'UNKNOWN';
|
|
331
|
+
|
|
332
|
+
export interface AudioError {
|
|
333
|
+
code: AudioErrorCode;
|
|
334
|
+
message: string;
|
|
335
|
+
originalError?: Error;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
// ============================================
|
|
339
|
+
// HOOK TYPES
|
|
340
|
+
// ============================================
|
|
341
|
+
|
|
342
|
+
export interface UseAudioOptions {
|
|
343
|
+
/** Audio session configuration (native only) */
|
|
344
|
+
session?: Partial<AudioSessionConfig>;
|
|
345
|
+
|
|
346
|
+
/** Whether to initialize on mount. Default: true */
|
|
347
|
+
initializeOnMount?: boolean;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
export interface UseAudioResult {
|
|
351
|
+
/** Whether the audio context is initialized */
|
|
352
|
+
isInitialized: boolean;
|
|
353
|
+
|
|
354
|
+
/** Audio session state (native) */
|
|
355
|
+
sessionState: AudioSessionState;
|
|
356
|
+
|
|
357
|
+
/** Current audio outputs */
|
|
358
|
+
outputs: string[];
|
|
359
|
+
|
|
360
|
+
/** Initialize audio (call before using recorder/player) */
|
|
361
|
+
initialize: () => Promise<void>;
|
|
362
|
+
|
|
363
|
+
/** Configure audio session (native) */
|
|
364
|
+
configureSession: (config: Partial<AudioSessionConfig>) => Promise<void>;
|
|
365
|
+
|
|
366
|
+
/** Suspend audio context */
|
|
367
|
+
suspend: () => Promise<void>;
|
|
368
|
+
|
|
369
|
+
/** Resume audio context */
|
|
370
|
+
resume: () => Promise<void>;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
export interface UseRecorderOptions {
|
|
374
|
+
/** Audio configuration */
|
|
375
|
+
config?: Partial<AudioConfig>;
|
|
376
|
+
|
|
377
|
+
/** Auto request permission on mount */
|
|
378
|
+
autoRequestPermission?: boolean;
|
|
379
|
+
|
|
380
|
+
/** Level update interval in ms. Default: 100 */
|
|
381
|
+
levelUpdateInterval?: number;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
export interface UseRecorderResult {
|
|
385
|
+
// State
|
|
386
|
+
status: RecorderStatus;
|
|
387
|
+
isRecording: boolean;
|
|
388
|
+
isPaused: boolean;
|
|
389
|
+
permission: PermissionStatus;
|
|
390
|
+
duration: number;
|
|
391
|
+
level: AudioLevel;
|
|
392
|
+
error: AudioError | null;
|
|
393
|
+
|
|
394
|
+
// Actions
|
|
395
|
+
start: (config?: Partial<AudioConfig>) => Promise<void>;
|
|
396
|
+
stop: () => Promise<void>;
|
|
397
|
+
pause: () => Promise<void>;
|
|
398
|
+
resume: () => Promise<void>;
|
|
399
|
+
|
|
400
|
+
// Permissions
|
|
401
|
+
checkPermission: () => Promise<PermissionStatus>;
|
|
402
|
+
requestPermission: () => Promise<PermissionStatus>;
|
|
403
|
+
|
|
404
|
+
// Data subscription
|
|
405
|
+
subscribeToData: (callback: RecorderDataCallback) => () => void;
|
|
406
|
+
|
|
407
|
+
// Utilities
|
|
408
|
+
resetPeakLevel: () => void;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
export interface UsePlayerOptions {
|
|
412
|
+
/** Auto play when source is loaded */
|
|
413
|
+
autoPlay?: boolean;
|
|
414
|
+
|
|
415
|
+
/** Initial volume (0.0 - 1.0) */
|
|
416
|
+
volume?: number;
|
|
417
|
+
|
|
418
|
+
/** Position update interval in ms. Default: 100 */
|
|
419
|
+
positionUpdateInterval?: number;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
export interface UsePlayerResult {
|
|
423
|
+
// State
|
|
424
|
+
status: PlayerStatus;
|
|
425
|
+
isPlaying: boolean;
|
|
426
|
+
isPaused: boolean;
|
|
427
|
+
isLoading: boolean;
|
|
428
|
+
position: number;
|
|
429
|
+
duration: number;
|
|
430
|
+
volume: number;
|
|
431
|
+
error: AudioError | null;
|
|
432
|
+
|
|
433
|
+
// File playback
|
|
434
|
+
loadFile: (uri: string) => Promise<void>;
|
|
435
|
+
unload: () => void;
|
|
436
|
+
|
|
437
|
+
// PCM streaming
|
|
438
|
+
loadPCMStream: (config: AudioConfig) => Promise<void>;
|
|
439
|
+
feedPCMData: (data: ArrayBuffer | Int16Array) => void;
|
|
440
|
+
flush: () => Promise<void>;
|
|
441
|
+
|
|
442
|
+
// Playback control
|
|
443
|
+
play: () => Promise<void>;
|
|
444
|
+
pause: () => void;
|
|
445
|
+
stop: () => void;
|
|
446
|
+
seek: (positionMs: number) => Promise<void>;
|
|
447
|
+
|
|
448
|
+
// Volume
|
|
449
|
+
setVolume: (volume: number) => void;
|
|
450
|
+
toggleMute: () => void;
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
// ============================================
|
|
454
|
+
// PRESET TYPES
|
|
455
|
+
// ============================================
|
|
456
|
+
|
|
457
|
+
export interface AudioProfiles {
|
|
458
|
+
speech: AudioConfig;
|
|
459
|
+
highQuality: AudioConfig;
|
|
460
|
+
studio: AudioConfig;
|
|
461
|
+
phone: AudioConfig;
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
export interface SessionPresets {
|
|
465
|
+
playback: AudioSessionConfig;
|
|
466
|
+
record: AudioSessionConfig;
|
|
467
|
+
voiceChat: AudioSessionConfig;
|
|
468
|
+
ambient: AudioSessionConfig;
|
|
469
|
+
default: AudioSessionConfig;
|
|
470
|
+
}
|