@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.
@@ -0,0 +1,247 @@
1
+ /**
2
+ * usePlayer Hook
3
+ *
4
+ * Provides audio playback functionality for files and PCM streaming.
5
+ */
6
+
7
+ import { useState, useEffect, useCallback, useRef } from 'react';
8
+ import type {
9
+ UsePlayerOptions,
10
+ UsePlayerResult,
11
+ PlayerStatus,
12
+ AudioError,
13
+ AudioConfig,
14
+ } from '../types';
15
+ import {
16
+ DEFAULT_PLAYER_STATUS,
17
+ DEFAULT_POSITION_UPDATE_INTERVAL,
18
+ } from '../constants';
19
+ import { getAudioContext } from '../context';
20
+ import { createPlayer } from '../playback';
21
+
22
+ export function usePlayer(options: UsePlayerOptions = {}): UsePlayerResult {
23
+ const {
24
+ autoPlay = false,
25
+ volume: initialVolume = 1.0,
26
+ positionUpdateInterval = DEFAULT_POSITION_UPDATE_INTERVAL,
27
+ } = options;
28
+
29
+ const [status, setStatus] = useState<PlayerStatus>({
30
+ ...DEFAULT_PLAYER_STATUS,
31
+ volume: initialVolume,
32
+ });
33
+
34
+ const audioContextRef = useRef(getAudioContext());
35
+ const playerRef = useRef<ReturnType<typeof createPlayer> | null>(null);
36
+ const mountedRef = useRef(true);
37
+ const autoPlayPendingRef = useRef(false);
38
+
39
+ // Initialize player lazily
40
+ const getPlayer = useCallback(() => {
41
+ if (!playerRef.current) {
42
+ playerRef.current = createPlayer(audioContextRef.current);
43
+ // Set initial volume
44
+ playerRef.current.setVolume(initialVolume);
45
+ }
46
+ return playerRef.current;
47
+ }, [initialVolume]);
48
+
49
+ // Load file
50
+ const loadFile = useCallback(async (uri: string) => {
51
+ const player = getPlayer();
52
+ const audioContext = audioContextRef.current;
53
+
54
+ // Ensure audio context is initialized
55
+ if (!audioContext.isInitialized) {
56
+ await audioContext.initialize();
57
+ }
58
+
59
+ await player.loadFile(uri);
60
+
61
+ if (autoPlay) {
62
+ autoPlayPendingRef.current = true;
63
+ }
64
+ }, [getPlayer, autoPlay]);
65
+
66
+ // Unload
67
+ const unload = useCallback(() => {
68
+ const player = playerRef.current;
69
+ if (player) {
70
+ player.unload();
71
+ }
72
+ }, []);
73
+
74
+ // Load PCM stream
75
+ const loadPCMStream = useCallback(async (config: AudioConfig) => {
76
+ const player = getPlayer();
77
+ const audioContext = audioContextRef.current;
78
+
79
+ // Ensure audio context is initialized
80
+ if (!audioContext.isInitialized) {
81
+ await audioContext.initialize();
82
+ }
83
+
84
+ await player.loadPCMStream(config);
85
+
86
+ if (autoPlay) {
87
+ autoPlayPendingRef.current = true;
88
+ }
89
+ }, [getPlayer, autoPlay]);
90
+
91
+ // Feed PCM data
92
+ const feedPCMData = useCallback((data: ArrayBuffer | Int16Array) => {
93
+ const player = playerRef.current;
94
+ if (player) {
95
+ player.feedPCMData(data);
96
+ }
97
+ }, []);
98
+
99
+ // Flush
100
+ const flush = useCallback(async () => {
101
+ const player = playerRef.current;
102
+ if (player) {
103
+ await player.flush();
104
+ }
105
+ }, []);
106
+
107
+ // Play
108
+ const play = useCallback(async () => {
109
+ const player = getPlayer();
110
+ await player.play();
111
+ }, [getPlayer]);
112
+
113
+ // Pause
114
+ const pause = useCallback(() => {
115
+ const player = playerRef.current;
116
+ if (player) {
117
+ player.pause();
118
+ }
119
+ }, []);
120
+
121
+ // Stop
122
+ const stop = useCallback(() => {
123
+ const player = playerRef.current;
124
+ if (player) {
125
+ player.stop();
126
+ }
127
+ }, []);
128
+
129
+ // Seek
130
+ const seek = useCallback(async (positionMs: number) => {
131
+ const player = playerRef.current;
132
+ if (player) {
133
+ await player.seek(positionMs);
134
+ }
135
+ }, []);
136
+
137
+ // Set volume
138
+ const setVolume = useCallback((vol: number) => {
139
+ const player = playerRef.current;
140
+ if (player) {
141
+ player.setVolume(vol);
142
+ }
143
+ }, []);
144
+
145
+ // Toggle mute
146
+ const toggleMute = useCallback(() => {
147
+ const player = playerRef.current;
148
+ if (player) {
149
+ player.setMuted(!status.muted);
150
+ }
151
+ }, [status.muted]);
152
+
153
+ // Setup on mount
154
+ useEffect(() => {
155
+ mountedRef.current = true;
156
+
157
+ const player = getPlayer();
158
+
159
+ // Subscribe to state changes
160
+ const unsubState = player.onStateChange((newStatus) => {
161
+ if (mountedRef.current) {
162
+ setStatus(newStatus);
163
+
164
+ // Handle autoplay
165
+ if (autoPlayPendingRef.current && newStatus.state === 'ready') {
166
+ autoPlayPendingRef.current = false;
167
+ player.play().catch(console.error);
168
+ }
169
+ }
170
+ });
171
+
172
+ // Subscribe to position updates
173
+ const unsubPosition = player.onPosition((position) => {
174
+ if (mountedRef.current) {
175
+ setStatus((prev) => ({ ...prev, position }));
176
+ }
177
+ }, positionUpdateInterval);
178
+
179
+ // Subscribe to buffer changes
180
+ const unsubBuffer = player.onBufferChange((buffered) => {
181
+ if (mountedRef.current) {
182
+ setStatus((prev) => ({ ...prev, buffered }));
183
+ }
184
+ });
185
+
186
+ // Subscribe to ended event
187
+ const unsubEnded = player.onEnded(() => {
188
+ if (mountedRef.current) {
189
+ // Status will be updated via onStateChange
190
+ }
191
+ });
192
+
193
+ return () => {
194
+ mountedRef.current = false;
195
+ unsubState();
196
+ unsubPosition();
197
+ unsubBuffer();
198
+ unsubEnded();
199
+
200
+ // Dispose player on unmount
201
+ if (playerRef.current) {
202
+ playerRef.current.dispose();
203
+ playerRef.current = null;
204
+ }
205
+ };
206
+ }, [getPlayer, positionUpdateInterval]);
207
+
208
+ // Derived state
209
+ const isPlaying = status.isPlaying;
210
+ const isPaused = status.isPaused;
211
+ const isLoading = status.state === 'loading';
212
+ const position = status.position;
213
+ const duration = status.duration;
214
+ const volume = status.volume;
215
+ const error = status.error || null;
216
+
217
+ return {
218
+ // State
219
+ status,
220
+ isPlaying,
221
+ isPaused,
222
+ isLoading,
223
+ position,
224
+ duration,
225
+ volume,
226
+ error,
227
+
228
+ // File playback
229
+ loadFile,
230
+ unload,
231
+
232
+ // PCM streaming
233
+ loadPCMStream,
234
+ feedPCMData,
235
+ flush,
236
+
237
+ // Playback control
238
+ play,
239
+ pause,
240
+ stop,
241
+ seek,
242
+
243
+ // Volume
244
+ setVolume,
245
+ toggleMute,
246
+ };
247
+ }
@@ -0,0 +1,176 @@
1
+ /**
2
+ * useRecorder Hook
3
+ *
4
+ * Provides recording functionality with PCM data streaming.
5
+ */
6
+
7
+ import { useState, useEffect, useCallback, useRef } from 'react';
8
+ import type {
9
+ UseRecorderOptions,
10
+ UseRecorderResult,
11
+ RecorderStatus,
12
+ PermissionStatus,
13
+ AudioLevel,
14
+ AudioError,
15
+ AudioConfig,
16
+ RecorderDataCallback,
17
+ } from '../types';
18
+ import {
19
+ DEFAULT_RECORDER_STATUS,
20
+ DEFAULT_AUDIO_LEVEL,
21
+ DEFAULT_LEVEL_UPDATE_INTERVAL,
22
+ } from '../constants';
23
+ import { getAudioContext } from '../context';
24
+ import { createRecorder } from '../recording';
25
+
26
+ export function useRecorder(options: UseRecorderOptions = {}): UseRecorderResult {
27
+ const {
28
+ config,
29
+ autoRequestPermission = false,
30
+ levelUpdateInterval = DEFAULT_LEVEL_UPDATE_INTERVAL,
31
+ } = options;
32
+
33
+ const [status, setStatus] = useState<RecorderStatus>(DEFAULT_RECORDER_STATUS);
34
+
35
+ const audioContextRef = useRef(getAudioContext());
36
+ const recorderRef = useRef<ReturnType<typeof createRecorder> | null>(null);
37
+ const mountedRef = useRef(true);
38
+
39
+ // Initialize recorder lazily
40
+ const getRecorder = useCallback(() => {
41
+ if (!recorderRef.current) {
42
+ recorderRef.current = createRecorder(audioContextRef.current);
43
+ }
44
+ return recorderRef.current;
45
+ }, []);
46
+
47
+ // Check permission
48
+ const checkPermission = useCallback(async (): Promise<PermissionStatus> => {
49
+ const recorder = getRecorder();
50
+ return recorder.checkPermission();
51
+ }, [getRecorder]);
52
+
53
+ // Request permission
54
+ const requestPermission = useCallback(async (): Promise<PermissionStatus> => {
55
+ const recorder = getRecorder();
56
+ return recorder.requestPermission();
57
+ }, [getRecorder]);
58
+
59
+ // Start recording
60
+ const start = useCallback(async (startConfig?: Partial<AudioConfig>) => {
61
+ const recorder = getRecorder();
62
+ const audioContext = audioContextRef.current;
63
+
64
+ // Ensure audio context is initialized
65
+ if (!audioContext.isInitialized) {
66
+ await audioContext.initialize();
67
+ }
68
+
69
+ await recorder.start(startConfig || config);
70
+ }, [getRecorder, config]);
71
+
72
+ // Stop recording
73
+ const stop = useCallback(async () => {
74
+ const recorder = getRecorder();
75
+ await recorder.stop();
76
+ }, [getRecorder]);
77
+
78
+ // Pause recording
79
+ const pause = useCallback(async () => {
80
+ const recorder = getRecorder();
81
+ await recorder.pause();
82
+ }, [getRecorder]);
83
+
84
+ // Resume recording
85
+ const resume = useCallback(async () => {
86
+ const recorder = getRecorder();
87
+ await recorder.resume();
88
+ }, [getRecorder]);
89
+
90
+ // Subscribe to data
91
+ const subscribeToData = useCallback((callback: RecorderDataCallback): (() => void) => {
92
+ const recorder = getRecorder();
93
+ return recorder.onData(callback);
94
+ }, [getRecorder]);
95
+
96
+ // Reset peak level
97
+ const resetPeakLevel = useCallback(() => {
98
+ const recorder = recorderRef.current;
99
+ if (recorder) {
100
+ recorder.resetPeakLevel();
101
+ }
102
+ }, []);
103
+
104
+ // Setup on mount
105
+ useEffect(() => {
106
+ mountedRef.current = true;
107
+
108
+ const recorder = getRecorder();
109
+
110
+ // Subscribe to state changes
111
+ const unsubState = recorder.onStateChange((newStatus) => {
112
+ if (mountedRef.current) {
113
+ setStatus(newStatus);
114
+ }
115
+ });
116
+
117
+ // Subscribe to level updates
118
+ const unsubLevel = recorder.onLevel((level) => {
119
+ if (mountedRef.current) {
120
+ setStatus((prev) => ({ ...prev, level }));
121
+ }
122
+ }, levelUpdateInterval);
123
+
124
+ // Auto request permission if enabled
125
+ if (autoRequestPermission) {
126
+ requestPermission().catch(console.error);
127
+ }
128
+
129
+ return () => {
130
+ mountedRef.current = false;
131
+ unsubState();
132
+ unsubLevel();
133
+
134
+ // Dispose recorder on unmount
135
+ if (recorderRef.current) {
136
+ recorderRef.current.dispose();
137
+ recorderRef.current = null;
138
+ }
139
+ };
140
+ }, [getRecorder, autoRequestPermission, requestPermission, levelUpdateInterval]);
141
+
142
+ // Derived state
143
+ const isRecording = status.isRecording;
144
+ const isPaused = status.isPaused;
145
+ const permission = status.permission;
146
+ const duration = status.duration;
147
+ const level = status.level;
148
+ const error = status.error || null;
149
+
150
+ return {
151
+ // State
152
+ status,
153
+ isRecording,
154
+ isPaused,
155
+ permission,
156
+ duration,
157
+ level,
158
+ error,
159
+
160
+ // Actions
161
+ start,
162
+ stop,
163
+ pause,
164
+ resume,
165
+
166
+ // Permissions
167
+ checkPermission,
168
+ requestPermission,
169
+
170
+ // Data subscription
171
+ subscribeToData,
172
+
173
+ // Utilities
174
+ resetPeakLevel,
175
+ };
176
+ }
@@ -0,0 +1,114 @@
1
+ /**
2
+ * @idealyst/audio
3
+ *
4
+ * Unified audio package for recording and playback on web and native.
5
+ * Native implementation using react-native-audio-api.
6
+ */
7
+
8
+ // Types
9
+ export type {
10
+ // Audio configuration
11
+ SampleRate,
12
+ BitDepth,
13
+ ChannelCount,
14
+ AudioConfig,
15
+
16
+ // Audio session (native)
17
+ AudioSessionCategory,
18
+ AudioSessionCategoryOption,
19
+ AudioSessionMode,
20
+ AudioSessionConfig,
21
+ AudioSessionState,
22
+ AudioSessionInterruption,
23
+ AudioSessionRouteChange,
24
+
25
+ // PCM data
26
+ PCMData,
27
+ AudioLevel,
28
+
29
+ // Recorder
30
+ RecorderState,
31
+ PermissionStatus,
32
+ RecorderStatus,
33
+ RecorderDataCallback,
34
+ RecorderLevelCallback,
35
+ RecorderStateCallback,
36
+ IRecorder,
37
+
38
+ // Player
39
+ PlayerState,
40
+ PlayerStatus,
41
+ PlayerStateCallback,
42
+ PlayerPositionCallback,
43
+ PlayerBufferCallback,
44
+ PlayerEndedCallback,
45
+ IPlayer,
46
+
47
+ // Audio context
48
+ IAudioContext,
49
+ IAudioSessionManager,
50
+
51
+ // Errors
52
+ AudioErrorCode,
53
+ AudioError,
54
+
55
+ // Hook types
56
+ UseAudioOptions,
57
+ UseAudioResult,
58
+ UseRecorderOptions,
59
+ UseRecorderResult,
60
+ UsePlayerOptions,
61
+ UsePlayerResult,
62
+
63
+ // Presets
64
+ AudioProfiles,
65
+ SessionPresets,
66
+ } from './types';
67
+
68
+ // Constants
69
+ export {
70
+ // Audio profiles
71
+ AUDIO_PROFILES,
72
+
73
+ // Session presets
74
+ SESSION_PRESETS,
75
+
76
+ // Defaults
77
+ DEFAULT_AUDIO_CONFIG,
78
+ DEFAULT_AUDIO_LEVEL,
79
+ DEFAULT_RECORDER_STATUS,
80
+ DEFAULT_PLAYER_STATUS,
81
+ DEFAULT_SESSION_STATE,
82
+ DEFAULT_LEVEL_UPDATE_INTERVAL,
83
+ DEFAULT_POSITION_UPDATE_INTERVAL,
84
+ } from './constants';
85
+
86
+ // Context (native)
87
+ export { getAudioContext, NativeAudioContextManager } from './context/index.native';
88
+
89
+ // Session (native)
90
+ export { getAudioSessionManager, NativeAudioSessionManager } from './session/index.native';
91
+
92
+ // Recording (native)
93
+ export { createRecorder, NativeRecorder } from './recording/index.native';
94
+
95
+ // Playback (native)
96
+ export { createPlayer, NativePlayer } from './playback/index.native';
97
+
98
+ // Hooks
99
+ export { useAudio, useRecorder, usePlayer } from './hooks';
100
+
101
+ // Utilities
102
+ export {
103
+ createAudioError,
104
+ pcmToFloat32,
105
+ float32ToInt16,
106
+ resampleLinear,
107
+ calculateAudioLevels,
108
+ clamp,
109
+ durationToSamples,
110
+ samplesToDuration,
111
+ createWavHeader,
112
+ arrayBufferToBase64,
113
+ base64ToArrayBuffer,
114
+ } from './utils';
package/src/index.ts ADDED
@@ -0,0 +1,114 @@
1
+ /**
2
+ * @idealyst/audio
3
+ *
4
+ * Unified audio package for recording and playback on web and native.
5
+ * Web implementation using Web Audio API.
6
+ */
7
+
8
+ // Types
9
+ export type {
10
+ // Audio configuration
11
+ SampleRate,
12
+ BitDepth,
13
+ ChannelCount,
14
+ AudioConfig,
15
+
16
+ // Audio session (native)
17
+ AudioSessionCategory,
18
+ AudioSessionCategoryOption,
19
+ AudioSessionMode,
20
+ AudioSessionConfig,
21
+ AudioSessionState,
22
+ AudioSessionInterruption,
23
+ AudioSessionRouteChange,
24
+
25
+ // PCM data
26
+ PCMData,
27
+ AudioLevel,
28
+
29
+ // Recorder
30
+ RecorderState,
31
+ PermissionStatus,
32
+ RecorderStatus,
33
+ RecorderDataCallback,
34
+ RecorderLevelCallback,
35
+ RecorderStateCallback,
36
+ IRecorder,
37
+
38
+ // Player
39
+ PlayerState,
40
+ PlayerStatus,
41
+ PlayerStateCallback,
42
+ PlayerPositionCallback,
43
+ PlayerBufferCallback,
44
+ PlayerEndedCallback,
45
+ IPlayer,
46
+
47
+ // Audio context
48
+ IAudioContext,
49
+ IAudioSessionManager,
50
+
51
+ // Errors
52
+ AudioErrorCode,
53
+ AudioError,
54
+
55
+ // Hook types
56
+ UseAudioOptions,
57
+ UseAudioResult,
58
+ UseRecorderOptions,
59
+ UseRecorderResult,
60
+ UsePlayerOptions,
61
+ UsePlayerResult,
62
+
63
+ // Presets
64
+ AudioProfiles,
65
+ SessionPresets,
66
+ } from './types';
67
+
68
+ // Constants
69
+ export {
70
+ // Audio profiles
71
+ AUDIO_PROFILES,
72
+
73
+ // Session presets
74
+ SESSION_PRESETS,
75
+
76
+ // Defaults
77
+ DEFAULT_AUDIO_CONFIG,
78
+ DEFAULT_AUDIO_LEVEL,
79
+ DEFAULT_RECORDER_STATUS,
80
+ DEFAULT_PLAYER_STATUS,
81
+ DEFAULT_SESSION_STATE,
82
+ DEFAULT_LEVEL_UPDATE_INTERVAL,
83
+ DEFAULT_POSITION_UPDATE_INTERVAL,
84
+ } from './constants';
85
+
86
+ // Context
87
+ export { getAudioContext, WebAudioContextManager } from './context';
88
+
89
+ // Session
90
+ export { getAudioSessionManager, WebAudioSessionManager } from './session';
91
+
92
+ // Recording
93
+ export { createRecorder, WebRecorder } from './recording';
94
+
95
+ // Playback
96
+ export { createPlayer, WebPlayer } from './playback';
97
+
98
+ // Hooks
99
+ export { useAudio, useRecorder, usePlayer } from './hooks';
100
+
101
+ // Utilities
102
+ export {
103
+ createAudioError,
104
+ pcmToFloat32,
105
+ float32ToInt16,
106
+ resampleLinear,
107
+ calculateAudioLevels,
108
+ clamp,
109
+ durationToSamples,
110
+ samplesToDuration,
111
+ createWavHeader,
112
+ arrayBufferToBase64,
113
+ base64ToArrayBuffer,
114
+ } from './utils';
@@ -0,0 +1,8 @@
1
+ /**
2
+ * @idealyst/audio
3
+ *
4
+ * Unified audio package for recording and playback on web and native.
5
+ * Explicit web export.
6
+ */
7
+
8
+ export * from './index';