@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
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { NativeRecorder, createRecorder } from './Recorder.native';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { WebRecorder, createRecorder } from './Recorder.web';
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Native Audio Session Manager
|
|
3
|
+
*
|
|
4
|
+
* Manages iOS AVAudioSession through react-native-audio-api's AudioManager.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { AudioManager } from 'react-native-audio-api';
|
|
8
|
+
import type {
|
|
9
|
+
IAudioSessionManager,
|
|
10
|
+
AudioSessionConfig,
|
|
11
|
+
AudioSessionState,
|
|
12
|
+
AudioSessionCategory,
|
|
13
|
+
AudioSessionMode,
|
|
14
|
+
AudioSessionCategoryOption,
|
|
15
|
+
AudioSessionInterruption,
|
|
16
|
+
AudioSessionRouteChange,
|
|
17
|
+
} from '../types';
|
|
18
|
+
import { DEFAULT_SESSION_STATE } from '../constants';
|
|
19
|
+
|
|
20
|
+
// Map our types to react-native-audio-api types
|
|
21
|
+
const CATEGORY_MAP: Record<AudioSessionCategory, string> = {
|
|
22
|
+
ambient: 'ambient',
|
|
23
|
+
soloAmbient: 'soloAmbient',
|
|
24
|
+
playback: 'playback',
|
|
25
|
+
record: 'record',
|
|
26
|
+
playAndRecord: 'playAndRecord',
|
|
27
|
+
multiRoute: 'multiRoute',
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const MODE_MAP: Record<AudioSessionMode, string> = {
|
|
31
|
+
default: 'default',
|
|
32
|
+
voiceChat: 'voiceChat',
|
|
33
|
+
gameChat: 'gameChat',
|
|
34
|
+
videoRecording: 'videoRecording',
|
|
35
|
+
measurement: 'measurement',
|
|
36
|
+
moviePlayback: 'moviePlayback',
|
|
37
|
+
videoChat: 'videoChat',
|
|
38
|
+
spokenAudio: 'spokenAudio',
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const OPTION_MAP: Record<AudioSessionCategoryOption, string> = {
|
|
42
|
+
mixWithOthers: 'mixWithOthers',
|
|
43
|
+
duckOthers: 'duckOthers',
|
|
44
|
+
allowBluetooth: 'allowBluetooth',
|
|
45
|
+
allowBluetoothA2DP: 'allowBluetoothA2DP',
|
|
46
|
+
allowAirPlay: 'allowAirPlay',
|
|
47
|
+
defaultToSpeaker: 'defaultToSpeaker',
|
|
48
|
+
interruptSpokenAudioAndMixWithOthers: 'interruptSpokenAudioAndMixWithOthers',
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
class NativeAudioSessionManager implements IAudioSessionManager {
|
|
52
|
+
private _state: AudioSessionState = { ...DEFAULT_SESSION_STATE };
|
|
53
|
+
private interruptionCallbacks: Set<(interruption: AudioSessionInterruption) => void> = new Set();
|
|
54
|
+
private routeChangeCallbacks: Set<(change: AudioSessionRouteChange) => void> = new Set();
|
|
55
|
+
private interruptionSubscription: { remove: () => void } | null = null;
|
|
56
|
+
private routeChangeSubscription: { remove: () => void } | null = null;
|
|
57
|
+
|
|
58
|
+
constructor() {
|
|
59
|
+
this.setupEventListeners();
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
private setupEventListeners(): void {
|
|
63
|
+
try {
|
|
64
|
+
AudioManager.observeAudioInterruptions(true);
|
|
65
|
+
|
|
66
|
+
this.interruptionSubscription = AudioManager.addSystemEventListener(
|
|
67
|
+
'interruption',
|
|
68
|
+
(event: any) => {
|
|
69
|
+
const interruption: AudioSessionInterruption = {
|
|
70
|
+
type: event.type === 'began' ? 'began' : 'ended',
|
|
71
|
+
shouldResume: event.type === 'ended' ? event.shouldResume : undefined,
|
|
72
|
+
};
|
|
73
|
+
this.interruptionCallbacks.forEach((cb) => cb(interruption));
|
|
74
|
+
}
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
this.routeChangeSubscription = AudioManager.addSystemEventListener(
|
|
78
|
+
'routeChange',
|
|
79
|
+
(event: any) => {
|
|
80
|
+
const change: AudioSessionRouteChange = {
|
|
81
|
+
reason: event.reason || 'unknown',
|
|
82
|
+
previousOutputs: event.previousOutputs || [],
|
|
83
|
+
currentOutputs: event.currentOutputs || [],
|
|
84
|
+
};
|
|
85
|
+
this.routeChangeCallbacks.forEach((cb) => cb(change));
|
|
86
|
+
}
|
|
87
|
+
);
|
|
88
|
+
} catch (error) {
|
|
89
|
+
console.warn('[AudioSession] Failed to setup event listeners:', error);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
get state(): AudioSessionState {
|
|
94
|
+
return { ...this._state };
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
async configure(config: Partial<AudioSessionConfig>): Promise<void> {
|
|
98
|
+
const category = config.category || this._state.category;
|
|
99
|
+
const mode = config.mode || this._state.mode;
|
|
100
|
+
const options = config.categoryOptions || this._state.categoryOptions;
|
|
101
|
+
|
|
102
|
+
const iosCategory = CATEGORY_MAP[category];
|
|
103
|
+
const iosMode = MODE_MAP[mode];
|
|
104
|
+
const iosOptions = options.map((opt) => OPTION_MAP[opt]).filter(Boolean);
|
|
105
|
+
|
|
106
|
+
try {
|
|
107
|
+
AudioManager.setAudioSessionOptions({
|
|
108
|
+
iosCategory,
|
|
109
|
+
iosMode,
|
|
110
|
+
iosOptions,
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
this._state.category = category;
|
|
114
|
+
this._state.mode = mode;
|
|
115
|
+
this._state.categoryOptions = options;
|
|
116
|
+
|
|
117
|
+
if (config.active !== false) {
|
|
118
|
+
await this.activate();
|
|
119
|
+
}
|
|
120
|
+
} catch (error) {
|
|
121
|
+
console.error('[AudioSession] Failed to configure:', error);
|
|
122
|
+
throw error;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
async activate(): Promise<void> {
|
|
127
|
+
try {
|
|
128
|
+
AudioManager.setAudioSessionActivity(true);
|
|
129
|
+
this._state.isActive = true;
|
|
130
|
+
} catch (error) {
|
|
131
|
+
console.error('[AudioSession] Failed to activate:', error);
|
|
132
|
+
throw error;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
async deactivate(_notifyOthers: boolean = true): Promise<void> {
|
|
137
|
+
try {
|
|
138
|
+
AudioManager.setAudioSessionActivity(false);
|
|
139
|
+
this._state.isActive = false;
|
|
140
|
+
} catch (error) {
|
|
141
|
+
console.error('[AudioSession] Failed to deactivate:', error);
|
|
142
|
+
throw error;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
onInterruption(callback: (interruption: AudioSessionInterruption) => void): () => void {
|
|
147
|
+
this.interruptionCallbacks.add(callback);
|
|
148
|
+
return () => {
|
|
149
|
+
this.interruptionCallbacks.delete(callback);
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
onRouteChange(callback: (change: AudioSessionRouteChange) => void): () => void {
|
|
154
|
+
this.routeChangeCallbacks.add(callback);
|
|
155
|
+
return () => {
|
|
156
|
+
this.routeChangeCallbacks.delete(callback);
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
getCurrentOutputs(): string[] {
|
|
161
|
+
try {
|
|
162
|
+
const devicesInfo = AudioManager.getDevicesInfo();
|
|
163
|
+
if (devicesInfo && devicesInfo.outputs) {
|
|
164
|
+
return devicesInfo.outputs.map((output: any) => output.name || output.type || 'Unknown');
|
|
165
|
+
}
|
|
166
|
+
return ['Speaker'];
|
|
167
|
+
} catch (error) {
|
|
168
|
+
console.warn('[AudioSession] Failed to get outputs:', error);
|
|
169
|
+
return ['Speaker'];
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
dispose(): void {
|
|
174
|
+
if (this.interruptionSubscription) {
|
|
175
|
+
this.interruptionSubscription.remove();
|
|
176
|
+
this.interruptionSubscription = null;
|
|
177
|
+
}
|
|
178
|
+
if (this.routeChangeSubscription) {
|
|
179
|
+
this.routeChangeSubscription.remove();
|
|
180
|
+
this.routeChangeSubscription = null;
|
|
181
|
+
}
|
|
182
|
+
this.interruptionCallbacks.clear();
|
|
183
|
+
this.routeChangeCallbacks.clear();
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// Singleton instance
|
|
188
|
+
let instance: NativeAudioSessionManager | null = null;
|
|
189
|
+
|
|
190
|
+
export function getAudioSessionManager(): IAudioSessionManager {
|
|
191
|
+
if (!instance) {
|
|
192
|
+
instance = new NativeAudioSessionManager();
|
|
193
|
+
}
|
|
194
|
+
return instance;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
export function disposeAudioSessionManager(): void {
|
|
198
|
+
if (instance) {
|
|
199
|
+
instance.dispose();
|
|
200
|
+
instance = null;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
export { NativeAudioSessionManager };
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Web Audio Session Manager (no-op)
|
|
3
|
+
*
|
|
4
|
+
* On web, audio sessions are managed automatically by the browser.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type {
|
|
8
|
+
IAudioSessionManager,
|
|
9
|
+
AudioSessionConfig,
|
|
10
|
+
AudioSessionState,
|
|
11
|
+
AudioSessionInterruption,
|
|
12
|
+
AudioSessionRouteChange,
|
|
13
|
+
} from '../types';
|
|
14
|
+
import { DEFAULT_SESSION_STATE } from '../constants';
|
|
15
|
+
|
|
16
|
+
class WebAudioSessionManager implements IAudioSessionManager {
|
|
17
|
+
private _state: AudioSessionState = { ...DEFAULT_SESSION_STATE, isActive: true };
|
|
18
|
+
|
|
19
|
+
get state(): AudioSessionState {
|
|
20
|
+
return { ...this._state };
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
async configure(config: Partial<AudioSessionConfig>): Promise<void> {
|
|
24
|
+
// No-op on web, just update internal state for consistency
|
|
25
|
+
if (config.category) {
|
|
26
|
+
this._state.category = config.category;
|
|
27
|
+
}
|
|
28
|
+
if (config.mode) {
|
|
29
|
+
this._state.mode = config.mode;
|
|
30
|
+
}
|
|
31
|
+
if (config.categoryOptions) {
|
|
32
|
+
this._state.categoryOptions = config.categoryOptions;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async activate(): Promise<void> {
|
|
37
|
+
this._state.isActive = true;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async deactivate(_notifyOthers?: boolean): Promise<void> {
|
|
41
|
+
this._state.isActive = false;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
onInterruption(_callback: (interruption: AudioSessionInterruption) => void): () => void {
|
|
45
|
+
// No-op on web
|
|
46
|
+
return () => {};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
onRouteChange(_callback: (change: AudioSessionRouteChange) => void): () => void {
|
|
50
|
+
// No-op on web
|
|
51
|
+
return () => {};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
getCurrentOutputs(): string[] {
|
|
55
|
+
return ['Default'];
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Singleton instance
|
|
60
|
+
let instance: WebAudioSessionManager | null = null;
|
|
61
|
+
|
|
62
|
+
export function getAudioSessionManager(): IAudioSessionManager {
|
|
63
|
+
if (!instance) {
|
|
64
|
+
instance = new WebAudioSessionManager();
|
|
65
|
+
}
|
|
66
|
+
return instance;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export { WebAudioSessionManager };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { getAudioSessionManager, WebAudioSessionManager } from './AudioSession.web';
|