@siteed/expo-audio-stream 0.7.4 → 1.0.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/README.md +80 -4
- package/build/AudioRecorder.provider.d.ts +10 -0
- package/build/AudioRecorder.provider.d.ts.map +1 -0
- package/build/AudioRecorder.provider.js +23 -0
- package/build/AudioRecorder.provider.js.map +1 -0
- package/build/ExpoAudioStream.types.d.ts +2 -0
- package/build/ExpoAudioStream.types.d.ts.map +1 -1
- package/build/ExpoAudioStream.types.js.map +1 -1
- package/build/ExpoAudioStreamModule.web.d.ts +0 -2
- package/build/ExpoAudioStreamModule.web.d.ts.map +1 -1
- package/build/ExpoAudioStreamModule.web.js +0 -6
- package/build/ExpoAudioStreamModule.web.js.map +1 -1
- package/build/index.d.ts +4 -5
- package/build/index.d.ts.map +1 -1
- package/build/index.js +4 -7
- package/build/index.js.map +1 -1
- package/build/useAudioRecording.d.ts +4 -4
- package/build/useAudioRecording.d.ts.map +1 -1
- package/build/useAudioRecording.js +36 -37
- package/build/useAudioRecording.js.map +1 -1
- package/docs/demo.gif +0 -0
- package/package.json +1 -3
- package/src/AudioRecorder.provider.tsx +42 -0
- package/src/ExpoAudioStream.types.ts +3 -1
- package/src/ExpoAudioStreamModule.web.ts +0 -8
- package/src/index.ts +8 -12
- package/src/useAudioRecording.ts +49 -46
package/README.md
CHANGED
|
@@ -11,6 +11,23 @@
|
|
|
11
11
|
- Listeners for audio data events with detailed event payloads.
|
|
12
12
|
- Utility functions for recording control and file management.
|
|
13
13
|
|
|
14
|
+
## Example Application
|
|
15
|
+
|
|
16
|
+
The project comes with a fully functional example application that demonstrates how to use the library in a real-world scenario.
|
|
17
|
+
|
|
18
|
+

|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
To try it:
|
|
22
|
+
```bash
|
|
23
|
+
git clone https://github.com/deeeed/expo-audio-stream.git
|
|
24
|
+
cd expo-audio-stream
|
|
25
|
+
yarn
|
|
26
|
+
yarn example ios
|
|
27
|
+
yarn example android
|
|
28
|
+
yarn example web
|
|
29
|
+
```
|
|
30
|
+
|
|
14
31
|
## Installation
|
|
15
32
|
|
|
16
33
|
To install `@siteed/expo-audio-stream`, add it to your project using npm or Yarn:
|
|
@@ -41,9 +58,15 @@ Make sure to run `npx expo prebuild` after adding the plugin to your app.json fi
|
|
|
41
58
|
|
|
42
59
|
## Usage
|
|
43
60
|
|
|
44
|
-
|
|
61
|
+
This library provides two hooks: `useAudioRecorder` for standalone use and `useSharedAudioRecorder` for accessing shared recording state within a React context.
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
### Standalone Recording
|
|
65
|
+
|
|
66
|
+
The `example/` folder contains a fully functional React Native application demonstrating how to integrate and use `useAudioRecorder` from `@siteed/expo-audio-stream`. This includes starting and stopping recordings, handling permissions, and processing live audio data.
|
|
67
|
+
|
|
45
68
|
|
|
46
|
-
|
|
69
|
+
#### Standalone Usage
|
|
47
70
|
|
|
48
71
|
```tsx
|
|
49
72
|
import {
|
|
@@ -105,14 +128,67 @@ import { addAudioEventListener } from '@siteed/expo-audio-stream';
|
|
|
105
128
|
}, []);
|
|
106
129
|
```
|
|
107
130
|
|
|
108
|
-
### Recording
|
|
131
|
+
### Shared Recording
|
|
132
|
+
|
|
133
|
+
To facilitate state sharing across multiple components or screens, useSharedAudioRecorder can be used. It should be wrapped in a AudioRecorderProvider context provider to ensure state is managed at a higher level and shared appropriately.
|
|
134
|
+
|
|
135
|
+
#### Shared Recording Usage
|
|
136
|
+
|
|
137
|
+
```tsx
|
|
138
|
+
import { AudioRecorderProvider, useSharedAudioRecorder } from '@siteed/expo-audio-stream';
|
|
139
|
+
|
|
140
|
+
export default function ParentComponent() {
|
|
141
|
+
return (
|
|
142
|
+
<AudioRecorderProvider>
|
|
143
|
+
<ChildComponent />
|
|
144
|
+
</AudioRecorderProvider>
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function ChildComponent() {
|
|
149
|
+
const {
|
|
150
|
+
startRecording,
|
|
151
|
+
isRecording
|
|
152
|
+
} = useSharedAudioRecorder();
|
|
153
|
+
|
|
154
|
+
return (
|
|
155
|
+
<View>
|
|
156
|
+
<Text>{isRecording ? "Recording..." : "Ready to record"}</Text>
|
|
157
|
+
<Button title="Toggle Recording" onPress={startRecording} />
|
|
158
|
+
</View>
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
### Add Event Listener
|
|
164
|
+
|
|
165
|
+
You can also add an event listener to receive detailed audio event payloads, which is crucial for both standalone and shared usage scenarios.
|
|
166
|
+
|
|
167
|
+
```tsx
|
|
168
|
+
import { useEffect } from 'react';
|
|
169
|
+
import { addAudioEventListener } from '@siteed/expo-audio-stream';
|
|
170
|
+
|
|
171
|
+
function App() {
|
|
172
|
+
useEffect(() => {
|
|
173
|
+
const subscription = addAudioEventListener(event => {
|
|
174
|
+
console.log("Audio event received:", event);
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
return () => subscription.remove();
|
|
178
|
+
}, []);
|
|
179
|
+
|
|
180
|
+
// UI code here
|
|
181
|
+
}
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
## Recording configuration
|
|
109
185
|
|
|
110
186
|
- on Android and IOS, audio is recorded in wav format, 16khz sample rate, 16 bit depth, 1 channel.
|
|
111
187
|
- on web, it usually records in opus but it depends on the browser configuration.
|
|
112
188
|
|
|
113
189
|
If you want to process the audio livestream directly, I recommend having another encoding step to align the audio format across platforms.
|
|
114
190
|
|
|
115
|
-
|
|
191
|
+
## TODO
|
|
116
192
|
this package is still in development, and there are a few things that need to be done:
|
|
117
193
|
- Add resume (vs currently use start) support and implement pause on iOS.
|
|
118
194
|
- Multi format support: Extend support to other audio formats beyond WAV, such as MP3 or AAC
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { UseAudioRecorderProps, UseAudioRecorderState } from "./useAudioRecording";
|
|
3
|
+
interface AudioRecorderProviderProps {
|
|
4
|
+
children: React.ReactNode;
|
|
5
|
+
config?: UseAudioRecorderProps;
|
|
6
|
+
}
|
|
7
|
+
export declare const AudioRecorderProvider: React.FC<AudioRecorderProviderProps>;
|
|
8
|
+
export declare const useSharedAudioRecorder: () => UseAudioRecorderState;
|
|
9
|
+
export {};
|
|
10
|
+
//# sourceMappingURL=AudioRecorder.provider.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AudioRecorder.provider.d.ts","sourceRoot":"","sources":["../src/AudioRecorder.provider.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAoC,MAAM,OAAO,CAAC;AAEzD,OAAO,EACL,qBAAqB,EACrB,qBAAqB,EAEtB,MAAM,qBAAqB,CAAC;AAU7B,UAAU,0BAA0B;IAClC,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;IAC1B,MAAM,CAAC,EAAE,qBAAqB,CAAC;CAChC;AAED,eAAO,MAAM,qBAAqB,EAAE,KAAK,CAAC,EAAE,CAAC,0BAA0B,CAUtE,CAAC;AAEF,eAAO,MAAM,sBAAsB,6BAQlC,CAAC"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import React, { createContext, useContext } from "react";
|
|
2
|
+
import { useAudioRecorder, } from "./useAudioRecording";
|
|
3
|
+
const AudioRecorderContext = createContext({
|
|
4
|
+
isRecording: false,
|
|
5
|
+
isPaused: false,
|
|
6
|
+
duration: 0,
|
|
7
|
+
size: 0,
|
|
8
|
+
// other properties filled on useAudioRecorder
|
|
9
|
+
});
|
|
10
|
+
export const AudioRecorderProvider = ({ children, config = {}, }) => {
|
|
11
|
+
const audioRecorder = useAudioRecorder(config);
|
|
12
|
+
return (<AudioRecorderContext.Provider value={audioRecorder}>
|
|
13
|
+
{children}
|
|
14
|
+
</AudioRecorderContext.Provider>);
|
|
15
|
+
};
|
|
16
|
+
export const useSharedAudioRecorder = () => {
|
|
17
|
+
const context = useContext(AudioRecorderContext);
|
|
18
|
+
if (!context) {
|
|
19
|
+
throw new Error("useSharedAudioRecorder must be used within an AudioRecorderProvider");
|
|
20
|
+
}
|
|
21
|
+
return context;
|
|
22
|
+
};
|
|
23
|
+
//# sourceMappingURL=AudioRecorder.provider.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AudioRecorder.provider.js","sourceRoot":"","sources":["../src/AudioRecorder.provider.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,OAAO,CAAC;AAEzD,OAAO,EAGL,gBAAgB,GACjB,MAAM,qBAAqB,CAAC;AAE7B,MAAM,oBAAoB,GAAG,aAAa,CAAwB;IAChE,WAAW,EAAE,KAAK;IAClB,QAAQ,EAAE,KAAK;IACf,QAAQ,EAAE,CAAC;IACX,IAAI,EAAE,CAAC;IACP,8CAA8C;CACtB,CAAC,CAAC;AAO5B,MAAM,CAAC,MAAM,qBAAqB,GAAyC,CAAC,EAC1E,QAAQ,EACR,MAAM,GAAG,EAAE,GACZ,EAAE,EAAE;IACH,MAAM,aAAa,GAAG,gBAAgB,CAAC,MAAM,CAAC,CAAC;IAC/C,OAAO,CACL,CAAC,oBAAoB,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,aAAa,CAAC,CAClD;MAAA,CAAC,QAAQ,CACX;IAAA,EAAE,oBAAoB,CAAC,QAAQ,CAAC,CACjC,CAAC;AACJ,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,sBAAsB,GAAG,GAAG,EAAE;IACzC,MAAM,OAAO,GAAG,UAAU,CAAC,oBAAoB,CAAC,CAAC;IACjD,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,MAAM,IAAI,KAAK,CACb,qEAAqE,CACtE,CAAC;IACJ,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC,CAAC","sourcesContent":["import React, { createContext, useContext } from \"react\";\n\nimport {\n UseAudioRecorderProps,\n UseAudioRecorderState,\n useAudioRecorder,\n} from \"./useAudioRecording\";\n\nconst AudioRecorderContext = createContext<UseAudioRecorderState>({\n isRecording: false,\n isPaused: false,\n duration: 0,\n size: 0,\n // other properties filled on useAudioRecorder\n} as UseAudioRecorderState);\n\ninterface AudioRecorderProviderProps {\n children: React.ReactNode;\n config?: UseAudioRecorderProps;\n}\n\nexport const AudioRecorderProvider: React.FC<AudioRecorderProviderProps> = ({\n children,\n config = {},\n}) => {\n const audioRecorder = useAudioRecorder(config);\n return (\n <AudioRecorderContext.Provider value={audioRecorder}>\n {children}\n </AudioRecorderContext.Provider>\n );\n};\n\nexport const useSharedAudioRecorder = () => {\n const context = useContext(AudioRecorderContext);\n if (!context) {\n throw new Error(\n \"useSharedAudioRecorder must be used within an AudioRecorderProvider\",\n );\n }\n return context;\n};\n"]}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { AudioDataEvent } from "./useAudioRecording";
|
|
1
2
|
export interface AudioEventPayload {
|
|
2
3
|
encoded?: string;
|
|
3
4
|
buffer?: Blob;
|
|
@@ -39,5 +40,6 @@ export interface RecordingConfig {
|
|
|
39
40
|
channels?: 1 | 2;
|
|
40
41
|
encoding?: EncodingType;
|
|
41
42
|
interval?: number;
|
|
43
|
+
onAudioStream?: (_: AudioDataEvent) => Promise<void>;
|
|
42
44
|
}
|
|
43
45
|
//# sourceMappingURL=ExpoAudioStream.types.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ExpoAudioStream.types.d.ts","sourceRoot":"","sources":["../src/ExpoAudioStream.types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,iBAAiB;IAChC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,IAAI,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,eAAe,EAAE,MAAM,CAAC;IACxB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,sBAAsB;IACrC,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,iBAAiB;IAChC,WAAW,EAAE,OAAO,CAAC;IACrB,QAAQ,EAAE,OAAO,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,MAAM,YAAY,GAAG,WAAW,GAAG,UAAU,CAAC;
|
|
1
|
+
{"version":3,"file":"ExpoAudioStream.types.d.ts","sourceRoot":"","sources":["../src/ExpoAudioStream.types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAErD,MAAM,WAAW,iBAAiB;IAChC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,IAAI,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,eAAe,EAAE,MAAM,CAAC;IACxB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,sBAAsB;IACrC,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,iBAAiB;IAChC,WAAW,EAAE,OAAO,CAAC;IACrB,QAAQ,EAAE,OAAO,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,MAAM,YAAY,GAAG,WAAW,GAAG,UAAU,CAAC;AACpD,MAAM,WAAW,eAAe;IAC9B,UAAU,CAAC,EAAE,KAAK,GAAG,KAAK,GAAG,KAAK,CAAC;IACnC,QAAQ,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC;IACjB,QAAQ,CAAC,EAAE,YAAY,CAAC;IACxB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,aAAa,CAAC,EAAE,CAAC,CAAC,EAAE,cAAc,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CACtD"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ExpoAudioStream.types.js","sourceRoot":"","sources":["../src/ExpoAudioStream.types.ts"],"names":[],"mappings":"","sourcesContent":["
|
|
1
|
+
{"version":3,"file":"ExpoAudioStream.types.js","sourceRoot":"","sources":["../src/ExpoAudioStream.types.ts"],"names":[],"mappings":"","sourcesContent":["import { AudioDataEvent } from \"./useAudioRecording\";\n\nexport interface AudioEventPayload {\n encoded?: string;\n buffer?: Blob;\n fileUri: string;\n lastEmittedSize: number;\n position: number;\n deltaSize: number;\n totalSize: number;\n mimeType: string;\n streamUuid: string;\n}\n\nexport interface AudioStreamResult {\n fileUri: string;\n duration: number;\n size: number;\n mimeType: string;\n channels?: number;\n bitDepth?: number;\n sampleRate?: number;\n}\n\nexport interface StartAudioStreamResult {\n fileUri: string;\n mimeType: string;\n channels?: number;\n bitDepth?: number;\n sampleRate?: number;\n}\n\nexport interface AudioStreamStatus {\n isRecording: boolean;\n isPaused: boolean;\n duration: number;\n size: number;\n interval: number;\n mimeType: string;\n}\n\nexport type EncodingType = \"pcm_16bit\" | \"pcm_8bit\";\nexport interface RecordingConfig {\n sampleRate?: 16000 | 44100 | 48000;\n channels?: 1 | 2; // 1 or 2 MONO or STEREO\n encoding?: EncodingType;\n interval?: number;\n onAudioStream?: (_: AudioDataEvent) => Promise<void>;\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ExpoAudioStreamModule.web.d.ts","sourceRoot":"","sources":["../src/ExpoAudioStreamModule.web.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAEjD,OAAO,EAEL,iBAAiB,EACjB,eAAe,EACf,sBAAsB,EACvB,MAAM,yBAAyB,CAAC;AAGjC,cAAM,kBAAmB,SAAQ,YAAY;IAC3C,aAAa,EAAE,aAAa,GAAG,IAAI,CAAC;IACpC,WAAW,EAAE,IAAI,EAAE,CAAC;IACpB,WAAW,EAAE,OAAO,CAAC;IACrB,QAAQ,EAAE,OAAO,CAAC;IAClB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,UAAU,EAAE,MAAM,CAAC;IACnB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,WAAW,EAAE,MAAM,CAAC;IACpB,eAAe,EAAE,MAAM,CAAC;IACxB,eAAe,EAAE,MAAM,CAAC;IACxB,eAAe,EAAE,MAAM,CAAC;IACxB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;;IA4BpB,cAAc;IAUd,cAAc,CAAC,OAAO,GAAE,eAAoB;IAwBlD,uBAAuB;IA4BvB,cAAc,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE;QAAE,IAAI,EAAE,IAAI,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE;IAiBnE,YAAY;IAUN,aAAa,IAAI,OAAO,CAAC,iBAAiB,GAAG,IAAI,CAAC;IAelD,cAAc;IAcpB,MAAM;;;;;;;
|
|
1
|
+
{"version":3,"file":"ExpoAudioStreamModule.web.d.ts","sourceRoot":"","sources":["../src/ExpoAudioStreamModule.web.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAEjD,OAAO,EAEL,iBAAiB,EACjB,eAAe,EACf,sBAAsB,EACvB,MAAM,yBAAyB,CAAC;AAGjC,cAAM,kBAAmB,SAAQ,YAAY;IAC3C,aAAa,EAAE,aAAa,GAAG,IAAI,CAAC;IACpC,WAAW,EAAE,IAAI,EAAE,CAAC;IACpB,WAAW,EAAE,OAAO,CAAC;IACrB,QAAQ,EAAE,OAAO,CAAC;IAClB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,UAAU,EAAE,MAAM,CAAC;IACnB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,WAAW,EAAE,MAAM,CAAC;IACpB,eAAe,EAAE,MAAM,CAAC;IACxB,eAAe,EAAE,MAAM,CAAC;IACxB,eAAe,EAAE,MAAM,CAAC;IACxB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;;IA4BpB,cAAc;IAUd,cAAc,CAAC,OAAO,GAAE,eAAoB;IAwBlD,uBAAuB;IA4BvB,cAAc,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE;QAAE,IAAI,EAAE,IAAI,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE;IAiBnE,YAAY;IAUN,aAAa,IAAI,OAAO,CAAC,iBAAiB,GAAG,IAAI,CAAC;IAelD,cAAc;IAcpB,MAAM;;;;;;;CASP;;AAED,wBAAwC"}
|
|
@@ -151,12 +151,6 @@ class ExpoAudioStreamWeb extends EventEmitter {
|
|
|
151
151
|
interval: this.currentInterval,
|
|
152
152
|
};
|
|
153
153
|
}
|
|
154
|
-
listAudioFiles() {
|
|
155
|
-
// Not applicable on web
|
|
156
|
-
}
|
|
157
|
-
clearAudioFiles() {
|
|
158
|
-
// Not applicable on web
|
|
159
|
-
}
|
|
160
154
|
}
|
|
161
155
|
export default new ExpoAudioStreamWeb();
|
|
162
156
|
//# sourceMappingURL=ExpoAudioStreamModule.web.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ExpoAudioStreamModule.web.js","sourceRoot":"","sources":["../src/ExpoAudioStreamModule.web.ts"],"names":[],"mappings":"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","sourcesContent":["import debug from \"debug\";\nimport { EventEmitter } from \"expo-modules-core\";\n\nimport {\n AudioEventPayload,\n AudioStreamResult,\n RecordingConfig,\n StartAudioStreamResult,\n} from \"./ExpoAudioStream.types\";\n\nconst log = debug(\"expo-audio-stream:useAudioRecording\");\nclass ExpoAudioStreamWeb extends EventEmitter {\n mediaRecorder: MediaRecorder | null;\n audioChunks: Blob[];\n isRecording: boolean;\n isPaused: boolean;\n recordingStartTime: number;\n pausedTime: number;\n currentDurationMs: number;\n currentSize: number;\n currentInterval: number;\n lastEmittedSize: number;\n lastEmittedTime: number;\n streamUuid: string | null;\n\n constructor() {\n const mockNativeModule = {\n addListener: (eventName: string) => {\n // Not used on web\n },\n removeListeners: (count: number) => {\n // Not used on web\n },\n };\n super(mockNativeModule); // Pass the mock native module to the parent class\n\n this.mediaRecorder = null;\n this.audioChunks = [];\n this.isRecording = false;\n this.isPaused = false;\n this.recordingStartTime = 0;\n this.pausedTime = 0;\n this.currentDurationMs = 0;\n this.currentSize = 0;\n this.currentInterval = 1000; // Default interval in ms\n this.lastEmittedSize = 0;\n this.lastEmittedTime = 0;\n this.streamUuid = null; // Initialize UUID on first recording start\n }\n\n // Utility to handle user media stream\n async getMediaStream() {\n try {\n return await navigator.mediaDevices.getUserMedia({ audio: true });\n } catch (error) {\n console.error(\"Failed to get media stream:\", error);\n throw error;\n }\n }\n\n // Start recording with options\n async startRecording(options: RecordingConfig = {}) {\n if (this.isRecording) {\n throw new Error(\"Recording is already in progress\");\n }\n\n const stream = await this.getMediaStream();\n this.mediaRecorder = new MediaRecorder(stream);\n this.setupRecordingListeners();\n this.mediaRecorder.start(options.interval || this.currentInterval);\n this.isRecording = true;\n this.recordingStartTime = Date.now();\n this.pausedTime = 0;\n this.lastEmittedSize = 0;\n this.lastEmittedTime = 0;\n this.streamUuid = this.generateUUID(); // Generate a UUID for the new recording session\n const fileUri = `${this.streamUuid}.webm`;\n const streamConfig: StartAudioStreamResult = {\n fileUri,\n mimeType: \"audio/webm\",\n };\n return streamConfig;\n }\n\n // Setup listeners for the MediaRecorder\n setupRecordingListeners() {\n if (!this.mediaRecorder) {\n throw new Error(\"No active media recorder\");\n }\n this.mediaRecorder.ondataavailable = (event) => {\n this.audioChunks.push(event.data);\n this.currentSize += event.data.size; // Update the size of the recording\n\n this.emitAudioEvent({ data: event.data, position: this.lastEmittedTime });\n this.lastEmittedTime = event.timeStamp;\n this.lastEmittedSize = this.currentSize;\n };\n\n this.mediaRecorder.onstop = () => {\n this.isRecording = false;\n log(\"Recording stopped\", this.audioChunks);\n };\n\n this.mediaRecorder.onpause = () => {\n this.isPaused = true;\n };\n\n this.mediaRecorder.onresume = () => {\n this.isPaused = false;\n this.recordingStartTime += Date.now() - this.pausedTime; // Adjust start time after resuming\n };\n }\n\n emitAudioEvent({ data, position }: { data: Blob; position: number }) {\n const fileUri = `${this.streamUuid}.webm`;\n const audioEventPayload: AudioEventPayload = {\n fileUri,\n mimeType: \"audio/webm\",\n lastEmittedSize: this.lastEmittedSize, // Since this might be continuously streaming, adjust accordingly\n deltaSize: data.size,\n position,\n totalSize: this.currentSize,\n buffer: data,\n streamUuid: this.streamUuid ?? \"\", // Generate or manage UUID for stream identification\n };\n\n this.emit(\"AudioData\", audioEventPayload);\n }\n\n // Helper method to generate a UUID\n generateUUID() {\n // Implementation of UUID generation (use a library or custom method)\n return \"xxxx-xxxx-xxxx-xxxx\".replace(/[x]/g, (c) => {\n const r = (Math.random() * 16) | 0,\n v = c === \"x\" ? r : (r & 0x3) | 0x8;\n return v.toString(16);\n });\n }\n\n // Stop recording\n async stopRecording(): Promise<AudioStreamResult | null> {\n this.mediaRecorder?.stop();\n this.isRecording = false;\n this.currentDurationMs = Date.now() - this.recordingStartTime;\n const result: AudioStreamResult = {\n fileUri: `${this.streamUuid}.webm`,\n duration: this.currentDurationMs,\n size: this.currentSize,\n mimeType: \"audio/webm\",\n };\n\n return result;\n }\n\n // Pause recording\n async pauseRecording() {\n if (!this.mediaRecorder) {\n throw new Error(\"No active media recorder\");\n }\n\n if (this.isRecording && !this.isPaused) {\n this.mediaRecorder.pause();\n this.pausedTime = Date.now();\n } else {\n throw new Error(\"Recording is not active or already paused\");\n }\n }\n\n // Get current status\n status() {\n return {\n isRecording: this.isRecording,\n isPaused: this.isPaused,\n duration: Date.now() - this.recordingStartTime,\n size: this.currentSize,\n interval: this.currentInterval,\n };\n }\n\n listAudioFiles() {\n // Not applicable on web\n }\n\n clearAudioFiles() {\n // Not applicable on web\n }\n}\n\nexport default new ExpoAudioStreamWeb();\n"]}
|
|
1
|
+
{"version":3,"file":"ExpoAudioStreamModule.web.js","sourceRoot":"","sources":["../src/ExpoAudioStreamModule.web.ts"],"names":[],"mappings":"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","sourcesContent":["import debug from \"debug\";\nimport { EventEmitter } from \"expo-modules-core\";\n\nimport {\n AudioEventPayload,\n AudioStreamResult,\n RecordingConfig,\n StartAudioStreamResult,\n} from \"./ExpoAudioStream.types\";\n\nconst log = debug(\"expo-audio-stream:useAudioRecording\");\nclass ExpoAudioStreamWeb extends EventEmitter {\n mediaRecorder: MediaRecorder | null;\n audioChunks: Blob[];\n isRecording: boolean;\n isPaused: boolean;\n recordingStartTime: number;\n pausedTime: number;\n currentDurationMs: number;\n currentSize: number;\n currentInterval: number;\n lastEmittedSize: number;\n lastEmittedTime: number;\n streamUuid: string | null;\n\n constructor() {\n const mockNativeModule = {\n addListener: (eventName: string) => {\n // Not used on web\n },\n removeListeners: (count: number) => {\n // Not used on web\n },\n };\n super(mockNativeModule); // Pass the mock native module to the parent class\n\n this.mediaRecorder = null;\n this.audioChunks = [];\n this.isRecording = false;\n this.isPaused = false;\n this.recordingStartTime = 0;\n this.pausedTime = 0;\n this.currentDurationMs = 0;\n this.currentSize = 0;\n this.currentInterval = 1000; // Default interval in ms\n this.lastEmittedSize = 0;\n this.lastEmittedTime = 0;\n this.streamUuid = null; // Initialize UUID on first recording start\n }\n\n // Utility to handle user media stream\n async getMediaStream() {\n try {\n return await navigator.mediaDevices.getUserMedia({ audio: true });\n } catch (error) {\n console.error(\"Failed to get media stream:\", error);\n throw error;\n }\n }\n\n // Start recording with options\n async startRecording(options: RecordingConfig = {}) {\n if (this.isRecording) {\n throw new Error(\"Recording is already in progress\");\n }\n\n const stream = await this.getMediaStream();\n this.mediaRecorder = new MediaRecorder(stream);\n this.setupRecordingListeners();\n this.mediaRecorder.start(options.interval || this.currentInterval);\n this.isRecording = true;\n this.recordingStartTime = Date.now();\n this.pausedTime = 0;\n this.lastEmittedSize = 0;\n this.lastEmittedTime = 0;\n this.streamUuid = this.generateUUID(); // Generate a UUID for the new recording session\n const fileUri = `${this.streamUuid}.webm`;\n const streamConfig: StartAudioStreamResult = {\n fileUri,\n mimeType: \"audio/webm\",\n };\n return streamConfig;\n }\n\n // Setup listeners for the MediaRecorder\n setupRecordingListeners() {\n if (!this.mediaRecorder) {\n throw new Error(\"No active media recorder\");\n }\n this.mediaRecorder.ondataavailable = (event) => {\n this.audioChunks.push(event.data);\n this.currentSize += event.data.size; // Update the size of the recording\n\n this.emitAudioEvent({ data: event.data, position: this.lastEmittedTime });\n this.lastEmittedTime = event.timeStamp;\n this.lastEmittedSize = this.currentSize;\n };\n\n this.mediaRecorder.onstop = () => {\n this.isRecording = false;\n log(\"Recording stopped\", this.audioChunks);\n };\n\n this.mediaRecorder.onpause = () => {\n this.isPaused = true;\n };\n\n this.mediaRecorder.onresume = () => {\n this.isPaused = false;\n this.recordingStartTime += Date.now() - this.pausedTime; // Adjust start time after resuming\n };\n }\n\n emitAudioEvent({ data, position }: { data: Blob; position: number }) {\n const fileUri = `${this.streamUuid}.webm`;\n const audioEventPayload: AudioEventPayload = {\n fileUri,\n mimeType: \"audio/webm\",\n lastEmittedSize: this.lastEmittedSize, // Since this might be continuously streaming, adjust accordingly\n deltaSize: data.size,\n position,\n totalSize: this.currentSize,\n buffer: data,\n streamUuid: this.streamUuid ?? \"\", // Generate or manage UUID for stream identification\n };\n\n this.emit(\"AudioData\", audioEventPayload);\n }\n\n // Helper method to generate a UUID\n generateUUID() {\n // Implementation of UUID generation (use a library or custom method)\n return \"xxxx-xxxx-xxxx-xxxx\".replace(/[x]/g, (c) => {\n const r = (Math.random() * 16) | 0,\n v = c === \"x\" ? r : (r & 0x3) | 0x8;\n return v.toString(16);\n });\n }\n\n // Stop recording\n async stopRecording(): Promise<AudioStreamResult | null> {\n this.mediaRecorder?.stop();\n this.isRecording = false;\n this.currentDurationMs = Date.now() - this.recordingStartTime;\n const result: AudioStreamResult = {\n fileUri: `${this.streamUuid}.webm`,\n duration: this.currentDurationMs,\n size: this.currentSize,\n mimeType: \"audio/webm\",\n };\n\n return result;\n }\n\n // Pause recording\n async pauseRecording() {\n if (!this.mediaRecorder) {\n throw new Error(\"No active media recorder\");\n }\n\n if (this.isRecording && !this.isPaused) {\n this.mediaRecorder.pause();\n this.pausedTime = Date.now();\n } else {\n throw new Error(\"Recording is not active or already paused\");\n }\n }\n\n // Get current status\n status() {\n return {\n isRecording: this.isRecording,\n isPaused: this.isPaused,\n duration: Date.now() - this.recordingStartTime,\n size: this.currentSize,\n interval: this.currentInterval,\n };\n }\n}\n\nexport default new ExpoAudioStreamWeb();\n"]}
|
package/build/index.d.ts
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
import { type Subscription } from "expo-modules-core";
|
|
2
|
+
import { AudioRecorderProvider, useSharedAudioRecorder } from "./AudioRecorder.provider";
|
|
2
3
|
import { AudioEventPayload } from "./ExpoAudioStream.types";
|
|
3
|
-
import {
|
|
4
|
-
export declare function listAudioFiles(): Promise<string[]>;
|
|
5
|
-
export declare function clearAudioFiles(): Promise<void>;
|
|
4
|
+
import { AudioDataEvent, UseAudioRecorderState, useAudioRecorder } from "./useAudioRecording";
|
|
6
5
|
export declare function test(): void;
|
|
7
6
|
export declare function addAudioEventListener(listener: (event: AudioEventPayload) => Promise<void>): Subscription;
|
|
8
|
-
export
|
|
9
|
-
export {
|
|
7
|
+
export { AudioRecorderProvider, useAudioRecorder, useSharedAudioRecorder };
|
|
8
|
+
export type { AudioDataEvent, AudioEventPayload, UseAudioRecorderState };
|
|
10
9
|
//# sourceMappingURL=index.d.ts.map
|
package/build/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,YAAY,EAClB,MAAM,mBAAmB,CAAC;AAI3B,OAAO,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAE5D,OAAO,EACL,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,YAAY,EAClB,MAAM,mBAAmB,CAAC;AAI3B,OAAO,EACL,qBAAqB,EACrB,sBAAsB,EACvB,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAE5D,OAAO,EACL,cAAc,EACd,qBAAqB,EACrB,gBAAgB,EACjB,MAAM,qBAAqB,CAAC;AAM7B,wBAAgB,IAAI,IAAI,IAAI,CAE3B;AAED,wBAAgB,qBAAqB,CACnC,QAAQ,EAAE,CAAC,KAAK,EAAE,iBAAiB,KAAK,OAAO,CAAC,IAAI,CAAC,GACpD,YAAY,CAGd;AAED,OAAO,EAAE,qBAAqB,EAAE,gBAAgB,EAAE,sBAAsB,EAAE,CAAC;AAC3E,YAAY,EAAE,cAAc,EAAE,iBAAiB,EAAE,qBAAqB,EAAE,CAAC"}
|
package/build/index.js
CHANGED
|
@@ -1,13 +1,10 @@
|
|
|
1
1
|
import { EventEmitter, NativeModulesProxy, } from "expo-modules-core";
|
|
2
|
+
// Import the native module. On web, it will be resolved to ExpoAudioStream.web.ts
|
|
3
|
+
// and on native platforms to ExpoAudioStream.ts
|
|
4
|
+
import { AudioRecorderProvider, useSharedAudioRecorder, } from "./AudioRecorder.provider";
|
|
2
5
|
import ExpoAudioStreamModule from "./ExpoAudioStreamModule";
|
|
3
6
|
import { useAudioRecorder, } from "./useAudioRecording";
|
|
4
7
|
const emitter = new EventEmitter(ExpoAudioStreamModule ?? NativeModulesProxy.ExpoAudioStream);
|
|
5
|
-
export function listAudioFiles() {
|
|
6
|
-
return ExpoAudioStreamModule.listAudioFiles();
|
|
7
|
-
}
|
|
8
|
-
export function clearAudioFiles() {
|
|
9
|
-
return ExpoAudioStreamModule.clearAudioFiles();
|
|
10
|
-
}
|
|
11
8
|
export function test() {
|
|
12
9
|
return ExpoAudioStreamModule.test();
|
|
13
10
|
}
|
|
@@ -15,5 +12,5 @@ export function addAudioEventListener(listener) {
|
|
|
15
12
|
console.log(`addAudioEventListener`, listener);
|
|
16
13
|
return emitter.addListener("AudioData", listener);
|
|
17
14
|
}
|
|
18
|
-
export { useAudioRecorder };
|
|
15
|
+
export { AudioRecorderProvider, useAudioRecorder, useSharedAudioRecorder };
|
|
19
16
|
//# sourceMappingURL=index.js.map
|
package/build/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,YAAY,EACZ,kBAAkB,GAEnB,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,YAAY,EACZ,kBAAkB,GAEnB,MAAM,mBAAmB,CAAC;AAE3B,kFAAkF;AAClF,gDAAgD;AAChD,OAAO,EACL,qBAAqB,EACrB,sBAAsB,GACvB,MAAM,0BAA0B,CAAC;AAElC,OAAO,qBAAqB,MAAM,yBAAyB,CAAC;AAC5D,OAAO,EAGL,gBAAgB,GACjB,MAAM,qBAAqB,CAAC;AAE7B,MAAM,OAAO,GAAG,IAAI,YAAY,CAC9B,qBAAqB,IAAI,kBAAkB,CAAC,eAAe,CAC5D,CAAC;AAEF,MAAM,UAAU,IAAI;IAClB,OAAO,qBAAqB,CAAC,IAAI,EAAE,CAAC;AACtC,CAAC;AAED,MAAM,UAAU,qBAAqB,CACnC,QAAqD;IAErD,OAAO,CAAC,GAAG,CAAC,uBAAuB,EAAE,QAAQ,CAAC,CAAC;IAC/C,OAAO,OAAO,CAAC,WAAW,CAAoB,WAAW,EAAE,QAAQ,CAAC,CAAC;AACvE,CAAC;AAED,OAAO,EAAE,qBAAqB,EAAE,gBAAgB,EAAE,sBAAsB,EAAE,CAAC","sourcesContent":["import {\n EventEmitter,\n NativeModulesProxy,\n type Subscription,\n} from \"expo-modules-core\";\n\n// Import the native module. On web, it will be resolved to ExpoAudioStream.web.ts\n// and on native platforms to ExpoAudioStream.ts\nimport {\n AudioRecorderProvider,\n useSharedAudioRecorder,\n} from \"./AudioRecorder.provider\";\nimport { AudioEventPayload } from \"./ExpoAudioStream.types\";\nimport ExpoAudioStreamModule from \"./ExpoAudioStreamModule\";\nimport {\n AudioDataEvent,\n UseAudioRecorderState,\n useAudioRecorder,\n} from \"./useAudioRecording\";\n\nconst emitter = new EventEmitter(\n ExpoAudioStreamModule ?? NativeModulesProxy.ExpoAudioStream,\n);\n\nexport function test(): void {\n return ExpoAudioStreamModule.test();\n}\n\nexport function addAudioEventListener(\n listener: (event: AudioEventPayload) => Promise<void>,\n): Subscription {\n console.log(`addAudioEventListener`, listener);\n return emitter.addListener<AudioEventPayload>(\"AudioData\", listener);\n}\n\nexport { AudioRecorderProvider, useAudioRecorder, useSharedAudioRecorder };\nexport type { AudioDataEvent, AudioEventPayload, UseAudioRecorderState };\n"]}
|
|
@@ -6,6 +6,9 @@ export interface AudioDataEvent {
|
|
|
6
6
|
eventDataSize: number;
|
|
7
7
|
totalSize: number;
|
|
8
8
|
}
|
|
9
|
+
export interface UseAudioRecorderProps {
|
|
10
|
+
debug?: boolean;
|
|
11
|
+
}
|
|
9
12
|
export interface UseAudioRecorderState {
|
|
10
13
|
startRecording: (_: RecordingConfig) => Promise<StartAudioStreamResult>;
|
|
11
14
|
stopRecording: () => Promise<AudioStreamResult | null>;
|
|
@@ -16,8 +19,5 @@ export interface UseAudioRecorderState {
|
|
|
16
19
|
duration: number;
|
|
17
20
|
size: number;
|
|
18
21
|
}
|
|
19
|
-
export declare function useAudioRecorder({
|
|
20
|
-
onAudioStream?: (_: AudioDataEvent) => Promise<void>;
|
|
21
|
-
debug?: boolean;
|
|
22
|
-
}): UseAudioRecorderState;
|
|
22
|
+
export declare function useAudioRecorder({ debug, }?: UseAudioRecorderProps): UseAudioRecorderState;
|
|
23
23
|
//# sourceMappingURL=useAudioRecording.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useAudioRecording.d.ts","sourceRoot":"","sources":["../src/useAudioRecording.ts"],"names":[],"mappings":"AAIA,OAAO,EAEL,iBAAiB,EAEjB,eAAe,EACf,sBAAsB,EACvB,MAAM,yBAAyB,CAAC;AAGjC,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,aAAa,EAAE,MAAM,CAAC;IACtB,SAAS,EAAE,MAAM,CAAC;CACnB;AACD,MAAM,WAAW,qBAAqB;IACpC,cAAc,EAAE,CAAC,CAAC,EAAE,eAAe,KAAK,OAAO,CAAC,sBAAsB,CAAC,CAAC;IACxE,aAAa,EAAE,MAAM,OAAO,CAAC,iBAAiB,GAAG,IAAI,CAAC,CAAC;IACvD,cAAc,EAAE,MAAM,IAAI,CAAC;IAC3B,eAAe,EAAE,MAAM,IAAI,CAAC;IAC5B,WAAW,EAAE,OAAO,CAAC;IACrB,QAAQ,EAAE,OAAO,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;CACd;AA4CD,wBAAgB,gBAAgB,CAAC,EAC/B,
|
|
1
|
+
{"version":3,"file":"useAudioRecording.d.ts","sourceRoot":"","sources":["../src/useAudioRecording.ts"],"names":[],"mappings":"AAIA,OAAO,EAEL,iBAAiB,EAEjB,eAAe,EACf,sBAAsB,EACvB,MAAM,yBAAyB,CAAC;AAGjC,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,aAAa,EAAE,MAAM,CAAC;IACtB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,qBAAqB;IACpC,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AACD,MAAM,WAAW,qBAAqB;IACpC,cAAc,EAAE,CAAC,CAAC,EAAE,eAAe,KAAK,OAAO,CAAC,sBAAsB,CAAC,CAAC;IACxE,aAAa,EAAE,MAAM,OAAO,CAAC,iBAAiB,GAAG,IAAI,CAAC,CAAC;IACvD,cAAc,EAAE,MAAM,IAAI,CAAC;IAC3B,eAAe,EAAE,MAAM,IAAI,CAAC;IAC5B,WAAW,EAAE,OAAO,CAAC;IACrB,QAAQ,EAAE,OAAO,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;CACd;AA4CD,wBAAgB,gBAAgB,CAAC,EAC/B,KAAa,GACd,GAAE,qBAA0B,GAAG,qBAAqB,CA4LpD"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Platform } from "expo-modules-core";
|
|
2
|
-
import { useCallback, useEffect, useReducer } from "react";
|
|
2
|
+
import { useCallback, useEffect, useReducer, useRef } from "react";
|
|
3
3
|
import { addAudioEventListener } from ".";
|
|
4
4
|
import ExpoAudioStreamModule from "./ExpoAudioStreamModule";
|
|
5
5
|
function recorderReducer(state, action) {
|
|
@@ -29,24 +29,24 @@ function recorderReducer(state, action) {
|
|
|
29
29
|
}
|
|
30
30
|
}
|
|
31
31
|
const TAG = "[ useAudioRecorder ] ";
|
|
32
|
-
export function useAudioRecorder({
|
|
32
|
+
export function useAudioRecorder({ debug = false, } = {}) {
|
|
33
33
|
const [state, dispatch] = useReducer(recorderReducer, {
|
|
34
34
|
isRecording: false,
|
|
35
35
|
isPaused: false,
|
|
36
36
|
duration: 0,
|
|
37
37
|
size: 0,
|
|
38
38
|
});
|
|
39
|
-
|
|
40
|
-
const logDebug = (message, data) => {
|
|
39
|
+
const onAudioStreamRef = useRef(null);
|
|
40
|
+
const logDebug = useCallback((message, data) => {
|
|
41
41
|
if (debug) {
|
|
42
42
|
if (data) {
|
|
43
|
-
console.log(
|
|
43
|
+
console.log(`${TAG} ${message}`, data);
|
|
44
44
|
}
|
|
45
45
|
else {
|
|
46
|
-
console.log(
|
|
46
|
+
console.log(`${TAG} ${message}`);
|
|
47
47
|
}
|
|
48
48
|
}
|
|
49
|
-
};
|
|
49
|
+
}, [debug]);
|
|
50
50
|
const handleAudioEvent = useCallback(async (eventData) => {
|
|
51
51
|
const { fileUri, deltaSize, totalSize, lastEmittedSize, position, streamUuid, encoded, mimeType, buffer, } = eventData;
|
|
52
52
|
logDebug(`useAudioRecorder] Received audio event:`, {
|
|
@@ -63,41 +63,25 @@ export function useAudioRecorder({ onAudioStream, debug = false, }) {
|
|
|
63
63
|
// Ignore packet with no data
|
|
64
64
|
return;
|
|
65
65
|
}
|
|
66
|
-
// Add more detailed handling here
|
|
67
66
|
try {
|
|
68
67
|
// Coming from native ( ios / android ) otherwise buffer is set
|
|
69
68
|
if (Platform.OS !== "web") {
|
|
70
69
|
// Read the audio file as a base64 string for comparison
|
|
71
70
|
if (!encoded) {
|
|
72
|
-
console.error(
|
|
71
|
+
console.error(`${TAG} Encoded audio data is missing`);
|
|
73
72
|
throw new Error("Encoded audio data is missing");
|
|
74
73
|
}
|
|
75
|
-
|
|
74
|
+
onAudioStreamRef.current?.({
|
|
76
75
|
data: encoded,
|
|
77
76
|
position,
|
|
78
77
|
fileUri,
|
|
79
78
|
eventDataSize: deltaSize,
|
|
80
79
|
totalSize,
|
|
81
80
|
});
|
|
82
|
-
// Below code is optional, used to compare encoded data to audio on file system
|
|
83
|
-
// Fetch the audio data from the fileUri
|
|
84
|
-
// const options = {
|
|
85
|
-
// encoding: FileSystem.EncodingType.Base64,
|
|
86
|
-
// position: lastEmittedSize,
|
|
87
|
-
// length: deltaSize,
|
|
88
|
-
// };
|
|
89
|
-
// const base64Content = await FileSystem.readAsStringAsync(fileUri, options);
|
|
90
|
-
// const binaryData = atob(base64Content);
|
|
91
|
-
// const content = new Uint8Array(binaryData.length);
|
|
92
|
-
// for (let i = 0; i < binaryData.length; i++) {
|
|
93
|
-
// content[i] = binaryData.charCodeAt(i);
|
|
94
|
-
// }
|
|
95
|
-
// const audioBlob = new Blob([content], { type: 'application/octet-stream' }); // Create a Blob from the byte array
|
|
96
|
-
// console.debug(`Read audio file (len: ${content.length}) vs ${deltaSize}`)
|
|
97
81
|
}
|
|
98
82
|
else if (buffer) {
|
|
99
83
|
// Coming from web
|
|
100
|
-
|
|
84
|
+
onAudioStreamRef.current?.({
|
|
101
85
|
data: buffer,
|
|
102
86
|
position,
|
|
103
87
|
fileUri,
|
|
@@ -109,15 +93,16 @@ export function useAudioRecorder({ onAudioStream, debug = false, }) {
|
|
|
109
93
|
catch (error) {
|
|
110
94
|
console.error(`${TAG} Error processing audio event:`, error);
|
|
111
95
|
}
|
|
112
|
-
}, [logDebug
|
|
96
|
+
}, [logDebug]);
|
|
113
97
|
const checkStatus = useCallback(async () => {
|
|
114
98
|
try {
|
|
115
99
|
if (!state.isRecording) {
|
|
100
|
+
logDebug(`${TAG} Not recording, exiting status check.`);
|
|
116
101
|
return;
|
|
117
102
|
}
|
|
118
103
|
const status = ExpoAudioStreamModule.status();
|
|
119
104
|
if (debug) {
|
|
120
|
-
logDebug(
|
|
105
|
+
logDebug(`${TAG} Status:`, status);
|
|
121
106
|
}
|
|
122
107
|
if (!status.isRecording) {
|
|
123
108
|
dispatch({ type: "STOP" });
|
|
@@ -130,36 +115,50 @@ export function useAudioRecorder({ onAudioStream, debug = false, }) {
|
|
|
130
115
|
}
|
|
131
116
|
}
|
|
132
117
|
catch (error) {
|
|
133
|
-
console.error(
|
|
118
|
+
console.error(`${TAG} Error getting status:`, error);
|
|
134
119
|
}
|
|
135
120
|
}, [state.isRecording, logDebug]);
|
|
136
121
|
useEffect(() => {
|
|
137
|
-
|
|
138
|
-
|
|
122
|
+
let interval;
|
|
123
|
+
if (state.isRecording) {
|
|
124
|
+
interval = setInterval(checkStatus, 1000);
|
|
125
|
+
}
|
|
126
|
+
return () => {
|
|
127
|
+
if (interval) {
|
|
128
|
+
clearInterval(interval);
|
|
129
|
+
}
|
|
130
|
+
};
|
|
139
131
|
}, [checkStatus, state.isRecording]);
|
|
140
132
|
useEffect(() => {
|
|
141
|
-
|
|
142
|
-
return;
|
|
143
|
-
}
|
|
144
|
-
logDebug(`${TAG} Registering audio event listener`, onAudioStream);
|
|
133
|
+
logDebug(`${TAG} Registering audio event listener`);
|
|
145
134
|
const subscribe = addAudioEventListener(handleAudioEvent);
|
|
146
135
|
logDebug(`${TAG} Subscribed to audio event listener`, subscribe);
|
|
147
136
|
return () => {
|
|
148
137
|
logDebug(`${TAG} Removing audio event listener`);
|
|
149
138
|
subscribe.remove();
|
|
150
139
|
};
|
|
151
|
-
}, [
|
|
140
|
+
}, [handleAudioEvent, logDebug]);
|
|
152
141
|
const startRecording = useCallback(async (recordingOptions) => {
|
|
153
142
|
if (debug) {
|
|
154
143
|
logDebug(`${TAG} start recoding`, recordingOptions);
|
|
155
144
|
}
|
|
156
|
-
|
|
145
|
+
// remove onAudioStream from recordingOptions
|
|
146
|
+
const { onAudioStream, ...options } = recordingOptions;
|
|
147
|
+
if (typeof onAudioStream === "function") {
|
|
148
|
+
onAudioStreamRef.current = onAudioStream;
|
|
149
|
+
}
|
|
150
|
+
else {
|
|
151
|
+
console.warn(`${TAG} onAudioStream is not a function`, onAudioStream);
|
|
152
|
+
onAudioStreamRef.current = null;
|
|
153
|
+
}
|
|
154
|
+
const startResult = await ExpoAudioStreamModule.startRecording(options);
|
|
157
155
|
dispatch({ type: "START" });
|
|
158
156
|
return startResult;
|
|
159
157
|
}, [logDebug]);
|
|
160
158
|
const stopRecording = useCallback(async () => {
|
|
161
159
|
logDebug(`${TAG} stoping recording`);
|
|
162
160
|
const stopResult = await ExpoAudioStreamModule.stopRecording();
|
|
161
|
+
onAudioStreamRef.current = null;
|
|
163
162
|
logDebug(`${TAG} recording stopped`, stopResult);
|
|
164
163
|
dispatch({ type: "STOP" });
|
|
165
164
|
return stopResult;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useAudioRecording.js","sourceRoot":"","sources":["../src/useAudioRecording.ts"],"names":[],"mappings":"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","sourcesContent":["import { Platform } from \"expo-modules-core\";\nimport { useCallback, useEffect, useReducer } from \"react\";\n\nimport { addAudioEventListener } from \".\";\nimport {\n AudioEventPayload,\n AudioStreamResult,\n AudioStreamStatus,\n RecordingConfig,\n StartAudioStreamResult,\n} from \"./ExpoAudioStream.types\";\nimport ExpoAudioStreamModule from \"./ExpoAudioStreamModule\";\n\nexport interface AudioDataEvent {\n data: string | Blob;\n position: number;\n fileUri: string;\n eventDataSize: number;\n totalSize: number;\n}\nexport interface UseAudioRecorderState {\n startRecording: (_: RecordingConfig) => Promise<StartAudioStreamResult>;\n stopRecording: () => Promise<AudioStreamResult | null>;\n pauseRecording: () => void;\n resumeRecording: () => void;\n isRecording: boolean;\n isPaused: boolean;\n duration: number; // Duration of the recording\n size: number; // Size in bytes of the recorded audio\n}\n\ninterface RecorderState {\n isRecording: boolean;\n isPaused: boolean;\n duration: number;\n size: number;\n}\n\ntype RecorderAction =\n | { type: \"START\" | \"STOP\" | \"PAUSE\" | \"RESUME\" }\n | { type: \"UPDATE_STATUS\"; payload: { duration: number; size: number } };\n\nfunction recorderReducer(\n state: RecorderState,\n action: RecorderAction,\n): RecorderState {\n switch (action.type) {\n case \"START\":\n return {\n ...state,\n isRecording: true,\n isPaused: false,\n duration: 0,\n size: 0,\n };\n case \"STOP\":\n return { ...state, isRecording: false, isPaused: false };\n case \"PAUSE\":\n return { ...state, isPaused: true, isRecording: false };\n case \"RESUME\":\n return { ...state, isPaused: false, isRecording: true };\n case \"UPDATE_STATUS\":\n return {\n ...state,\n duration: action.payload.duration,\n size: action.payload.size,\n };\n default:\n return state;\n }\n}\nconst TAG = \"[ useAudioRecorder ] \";\n\nexport function useAudioRecorder({\n onAudioStream,\n debug = false,\n}: {\n onAudioStream?: (_: AudioDataEvent) => Promise<void>;\n debug?: boolean;\n}): UseAudioRecorderState {\n const [state, dispatch] = useReducer(recorderReducer, {\n isRecording: false,\n isPaused: false,\n duration: 0,\n size: 0,\n });\n\n console.log(`[useAudioRecorder] RENDERING state`, state);\n const logDebug = (message: string, data?: any) => {\n if (debug) {\n if (data) {\n console.log(`[useAudioRecorder] ${message}`, data);\n } else {\n console.log(`[useAudioRecorder] ${message}`);\n }\n }\n };\n\n const handleAudioEvent = useCallback(\n async (eventData: AudioEventPayload) => {\n const {\n fileUri,\n deltaSize,\n totalSize,\n lastEmittedSize,\n position,\n streamUuid,\n encoded,\n mimeType,\n buffer,\n } = eventData;\n logDebug(`useAudioRecorder] Received audio event:`, {\n fileUri,\n deltaSize,\n totalSize,\n position,\n mimeType,\n lastEmittedSize,\n streamUuid,\n encodedLength: encoded?.length,\n });\n if (deltaSize === 0) {\n // Ignore packet with no data\n return;\n }\n // Add more detailed handling here\n try {\n // Coming from native ( ios / android ) otherwise buffer is set\n if (Platform.OS !== \"web\") {\n // Read the audio file as a base64 string for comparison\n if (!encoded) {\n console.error(\"[useAudioRecorder] Encoded audio data is missing\");\n throw new Error(\"Encoded audio data is missing\");\n }\n await onAudioStream?.({\n data: encoded,\n position,\n fileUri,\n eventDataSize: deltaSize,\n totalSize,\n });\n\n // Below code is optional, used to compare encoded data to audio on file system\n // Fetch the audio data from the fileUri\n // const options = {\n // encoding: FileSystem.EncodingType.Base64,\n // position: lastEmittedSize,\n // length: deltaSize,\n // };\n // const base64Content = await FileSystem.readAsStringAsync(fileUri, options);\n // const binaryData = atob(base64Content);\n // const content = new Uint8Array(binaryData.length);\n // for (let i = 0; i < binaryData.length; i++) {\n // content[i] = binaryData.charCodeAt(i);\n // }\n // const audioBlob = new Blob([content], { type: 'application/octet-stream' }); // Create a Blob from the byte array\n // console.debug(`Read audio file (len: ${content.length}) vs ${deltaSize}`)\n } else if (buffer) {\n // Coming from web\n await onAudioStream?.({\n data: buffer,\n position,\n fileUri,\n eventDataSize: deltaSize,\n totalSize,\n });\n }\n } catch (error) {\n console.error(`${TAG} Error processing audio event:`, error);\n }\n },\n [logDebug, onAudioStream],\n );\n\n const checkStatus = useCallback(async () => {\n try {\n if (!state.isRecording) {\n return;\n }\n\n const status: AudioStreamStatus = ExpoAudioStreamModule.status();\n if (debug) {\n logDebug(\"[useAudioRecorder] Status:\", status);\n }\n\n if (!status.isRecording) {\n dispatch({ type: \"STOP\" });\n } else {\n dispatch({\n type: \"UPDATE_STATUS\",\n payload: { duration: status.duration, size: status.size },\n });\n }\n } catch (error) {\n console.error(`[useAudioRecorder] Error getting status:`, error);\n }\n }, [state.isRecording, logDebug]);\n\n useEffect(() => {\n const interval = state.isRecording ? setInterval(checkStatus, 1000) : null;\n return () => (interval ? clearInterval(interval) : undefined);\n }, [checkStatus, state.isRecording]);\n\n useEffect(() => {\n if (!onAudioStream) {\n return;\n }\n logDebug(`${TAG} Registering audio event listener`, onAudioStream);\n const subscribe = addAudioEventListener(handleAudioEvent);\n logDebug(`${TAG} Subscribed to audio event listener`, subscribe);\n\n return () => {\n logDebug(`${TAG} Removing audio event listener`);\n subscribe.remove();\n };\n }, [onAudioStream, handleAudioEvent, logDebug]);\n\n const startRecording = useCallback(\n async (recordingOptions: RecordingConfig) => {\n if (debug) {\n logDebug(`${TAG} start recoding`, recordingOptions);\n }\n const startResult: StartAudioStreamResult =\n await ExpoAudioStreamModule.startRecording(recordingOptions);\n dispatch({ type: \"START\" });\n\n return startResult;\n },\n [logDebug],\n );\n\n const stopRecording = useCallback(async () => {\n logDebug(`${TAG} stoping recording`);\n const stopResult: AudioStreamResult =\n await ExpoAudioStreamModule.stopRecording();\n logDebug(`${TAG} recording stopped`, stopResult);\n dispatch({ type: \"STOP\" });\n return stopResult;\n }, [logDebug]);\n\n const pauseRecording = useCallback(async () => {\n logDebug(`${TAG} pause recording`);\n const pauseResult = await ExpoAudioStreamModule.pauseRecording();\n dispatch({ type: \"PAUSE\" });\n return pauseResult;\n }, [logDebug]);\n\n const resumeRecording = useCallback(async () => {\n logDebug(`${TAG} resume recording`);\n const resumeResult = await ExpoAudioStreamModule.resumeRecording();\n dispatch({ type: \"RESUME\" });\n return resumeResult;\n }, [logDebug]);\n\n return {\n startRecording,\n stopRecording,\n pauseRecording,\n resumeRecording,\n isPaused: state.isPaused,\n isRecording: state.isRecording,\n duration: state.duration,\n size: state.size,\n };\n}\n"]}
|
|
1
|
+
{"version":3,"file":"useAudioRecording.js","sourceRoot":"","sources":["../src/useAudioRecording.ts"],"names":[],"mappings":"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","sourcesContent":["import { Platform } from \"expo-modules-core\";\nimport { useCallback, useEffect, useReducer, useRef } from \"react\";\n\nimport { addAudioEventListener } from \".\";\nimport {\n AudioEventPayload,\n AudioStreamResult,\n AudioStreamStatus,\n RecordingConfig,\n StartAudioStreamResult,\n} from \"./ExpoAudioStream.types\";\nimport ExpoAudioStreamModule from \"./ExpoAudioStreamModule\";\n\nexport interface AudioDataEvent {\n data: string | Blob;\n position: number;\n fileUri: string;\n eventDataSize: number;\n totalSize: number;\n}\n\nexport interface UseAudioRecorderProps {\n debug?: boolean;\n}\nexport interface UseAudioRecorderState {\n startRecording: (_: RecordingConfig) => Promise<StartAudioStreamResult>;\n stopRecording: () => Promise<AudioStreamResult | null>;\n pauseRecording: () => void;\n resumeRecording: () => void;\n isRecording: boolean;\n isPaused: boolean;\n duration: number; // Duration of the recording\n size: number; // Size in bytes of the recorded audio\n}\n\ninterface RecorderState {\n isRecording: boolean;\n isPaused: boolean;\n duration: number;\n size: number;\n}\n\ntype RecorderAction =\n | { type: \"START\" | \"STOP\" | \"PAUSE\" | \"RESUME\" }\n | { type: \"UPDATE_STATUS\"; payload: { duration: number; size: number } };\n\nfunction recorderReducer(\n state: RecorderState,\n action: RecorderAction,\n): RecorderState {\n switch (action.type) {\n case \"START\":\n return {\n ...state,\n isRecording: true,\n isPaused: false,\n duration: 0,\n size: 0,\n };\n case \"STOP\":\n return { ...state, isRecording: false, isPaused: false };\n case \"PAUSE\":\n return { ...state, isPaused: true, isRecording: false };\n case \"RESUME\":\n return { ...state, isPaused: false, isRecording: true };\n case \"UPDATE_STATUS\":\n return {\n ...state,\n duration: action.payload.duration,\n size: action.payload.size,\n };\n default:\n return state;\n }\n}\nconst TAG = \"[ useAudioRecorder ] \";\n\nexport function useAudioRecorder({\n debug = false,\n}: UseAudioRecorderProps = {}): UseAudioRecorderState {\n const [state, dispatch] = useReducer(recorderReducer, {\n isRecording: false,\n isPaused: false,\n duration: 0,\n size: 0,\n });\n\n const onAudioStreamRef = useRef<\n ((_: AudioDataEvent) => Promise<void>) | null\n >(null);\n\n const logDebug = useCallback(\n (message: string, data?: any) => {\n if (debug) {\n if (data) {\n console.log(`${TAG} ${message}`, data);\n } else {\n console.log(`${TAG} ${message}`);\n }\n }\n },\n [debug],\n );\n\n const handleAudioEvent = useCallback(\n async (eventData: AudioEventPayload) => {\n const {\n fileUri,\n deltaSize,\n totalSize,\n lastEmittedSize,\n position,\n streamUuid,\n encoded,\n mimeType,\n buffer,\n } = eventData;\n logDebug(`useAudioRecorder] Received audio event:`, {\n fileUri,\n deltaSize,\n totalSize,\n position,\n mimeType,\n lastEmittedSize,\n streamUuid,\n encodedLength: encoded?.length,\n });\n if (deltaSize === 0) {\n // Ignore packet with no data\n return;\n }\n try {\n // Coming from native ( ios / android ) otherwise buffer is set\n if (Platform.OS !== \"web\") {\n // Read the audio file as a base64 string for comparison\n if (!encoded) {\n console.error(`${TAG} Encoded audio data is missing`);\n throw new Error(\"Encoded audio data is missing\");\n }\n onAudioStreamRef.current?.({\n data: encoded,\n position,\n fileUri,\n eventDataSize: deltaSize,\n totalSize,\n });\n } else if (buffer) {\n // Coming from web\n onAudioStreamRef.current?.({\n data: buffer,\n position,\n fileUri,\n eventDataSize: deltaSize,\n totalSize,\n });\n }\n } catch (error) {\n console.error(`${TAG} Error processing audio event:`, error);\n }\n },\n [logDebug],\n );\n\n const checkStatus = useCallback(async () => {\n try {\n if (!state.isRecording) {\n logDebug(`${TAG} Not recording, exiting status check.`);\n return;\n }\n\n const status: AudioStreamStatus = ExpoAudioStreamModule.status();\n if (debug) {\n logDebug(`${TAG} Status:`, status);\n }\n\n if (!status.isRecording) {\n dispatch({ type: \"STOP\" });\n } else {\n dispatch({\n type: \"UPDATE_STATUS\",\n payload: { duration: status.duration, size: status.size },\n });\n }\n } catch (error) {\n console.error(`${TAG} Error getting status:`, error);\n }\n }, [state.isRecording, logDebug]);\n\n useEffect(() => {\n let interval: number;\n if (state.isRecording) {\n interval = setInterval(checkStatus, 1000);\n }\n return () => {\n if (interval) {\n clearInterval(interval);\n }\n };\n }, [checkStatus, state.isRecording]);\n\n useEffect(() => {\n logDebug(`${TAG} Registering audio event listener`);\n const subscribe = addAudioEventListener(handleAudioEvent);\n logDebug(`${TAG} Subscribed to audio event listener`, subscribe);\n\n return () => {\n logDebug(`${TAG} Removing audio event listener`);\n subscribe.remove();\n };\n }, [handleAudioEvent, logDebug]);\n\n const startRecording = useCallback(\n async (recordingOptions: RecordingConfig) => {\n if (debug) {\n logDebug(`${TAG} start recoding`, recordingOptions);\n }\n // remove onAudioStream from recordingOptions\n const { onAudioStream, ...options } = recordingOptions;\n if (typeof onAudioStream === \"function\") {\n onAudioStreamRef.current = onAudioStream;\n } else {\n console.warn(`${TAG} onAudioStream is not a function`, onAudioStream);\n onAudioStreamRef.current = null;\n }\n const startResult: StartAudioStreamResult =\n await ExpoAudioStreamModule.startRecording(options);\n dispatch({ type: \"START\" });\n\n return startResult;\n },\n [logDebug],\n );\n\n const stopRecording = useCallback(async () => {\n logDebug(`${TAG} stoping recording`);\n const stopResult: AudioStreamResult =\n await ExpoAudioStreamModule.stopRecording();\n onAudioStreamRef.current = null;\n logDebug(`${TAG} recording stopped`, stopResult);\n dispatch({ type: \"STOP\" });\n return stopResult;\n }, [logDebug]);\n\n const pauseRecording = useCallback(async () => {\n logDebug(`${TAG} pause recording`);\n const pauseResult = await ExpoAudioStreamModule.pauseRecording();\n dispatch({ type: \"PAUSE\" });\n return pauseResult;\n }, [logDebug]);\n\n const resumeRecording = useCallback(async () => {\n logDebug(`${TAG} resume recording`);\n const resumeResult = await ExpoAudioStreamModule.resumeRecording();\n dispatch({ type: \"RESUME\" });\n return resumeResult;\n }, [logDebug]);\n\n return {\n startRecording,\n stopRecording,\n pauseRecording,\n resumeRecording,\n isPaused: state.isPaused,\n isRecording: state.isRecording,\n duration: state.duration,\n size: state.size,\n };\n}\n"]}
|
package/docs/demo.gif
ADDED
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@siteed/expo-audio-stream",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "1.0.0",
|
|
4
4
|
"description": "stream audio crossplatform",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"main": "build/index.js",
|
|
@@ -49,7 +49,6 @@
|
|
|
49
49
|
"eslint-plugin-prettier": "^5.1.3",
|
|
50
50
|
"eslint-plugin-promise": "^6.1.1",
|
|
51
51
|
"eslint-plugin-react": "^7.34.1",
|
|
52
|
-
"expo-file-system": "^16.0.9",
|
|
53
52
|
"expo-module-scripts": "^3.4.2",
|
|
54
53
|
"expo-modules-core": "^1.11.12",
|
|
55
54
|
"prettier": "^3.2.5",
|
|
@@ -57,7 +56,6 @@
|
|
|
57
56
|
},
|
|
58
57
|
"peerDependencies": {
|
|
59
58
|
"expo": "*",
|
|
60
|
-
"expo-file-system": "*",
|
|
61
59
|
"react": "*",
|
|
62
60
|
"react-native": "*"
|
|
63
61
|
},
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import React, { createContext, useContext } from "react";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
UseAudioRecorderProps,
|
|
5
|
+
UseAudioRecorderState,
|
|
6
|
+
useAudioRecorder,
|
|
7
|
+
} from "./useAudioRecording";
|
|
8
|
+
|
|
9
|
+
const AudioRecorderContext = createContext<UseAudioRecorderState>({
|
|
10
|
+
isRecording: false,
|
|
11
|
+
isPaused: false,
|
|
12
|
+
duration: 0,
|
|
13
|
+
size: 0,
|
|
14
|
+
// other properties filled on useAudioRecorder
|
|
15
|
+
} as UseAudioRecorderState);
|
|
16
|
+
|
|
17
|
+
interface AudioRecorderProviderProps {
|
|
18
|
+
children: React.ReactNode;
|
|
19
|
+
config?: UseAudioRecorderProps;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export const AudioRecorderProvider: React.FC<AudioRecorderProviderProps> = ({
|
|
23
|
+
children,
|
|
24
|
+
config = {},
|
|
25
|
+
}) => {
|
|
26
|
+
const audioRecorder = useAudioRecorder(config);
|
|
27
|
+
return (
|
|
28
|
+
<AudioRecorderContext.Provider value={audioRecorder}>
|
|
29
|
+
{children}
|
|
30
|
+
</AudioRecorderContext.Provider>
|
|
31
|
+
);
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export const useSharedAudioRecorder = () => {
|
|
35
|
+
const context = useContext(AudioRecorderContext);
|
|
36
|
+
if (!context) {
|
|
37
|
+
throw new Error(
|
|
38
|
+
"useSharedAudioRecorder must be used within an AudioRecorderProvider",
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
return context;
|
|
42
|
+
};
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { AudioDataEvent } from "./useAudioRecording";
|
|
2
|
+
|
|
1
3
|
export interface AudioEventPayload {
|
|
2
4
|
encoded?: string;
|
|
3
5
|
buffer?: Blob;
|
|
@@ -38,10 +40,10 @@ export interface AudioStreamStatus {
|
|
|
38
40
|
}
|
|
39
41
|
|
|
40
42
|
export type EncodingType = "pcm_16bit" | "pcm_8bit";
|
|
41
|
-
|
|
42
43
|
export interface RecordingConfig {
|
|
43
44
|
sampleRate?: 16000 | 44100 | 48000;
|
|
44
45
|
channels?: 1 | 2; // 1 or 2 MONO or STEREO
|
|
45
46
|
encoding?: EncodingType;
|
|
46
47
|
interval?: number;
|
|
48
|
+
onAudioStream?: (_: AudioDataEvent) => Promise<void>;
|
|
47
49
|
}
|
|
@@ -176,14 +176,6 @@ class ExpoAudioStreamWeb extends EventEmitter {
|
|
|
176
176
|
interval: this.currentInterval,
|
|
177
177
|
};
|
|
178
178
|
}
|
|
179
|
-
|
|
180
|
-
listAudioFiles() {
|
|
181
|
-
// Not applicable on web
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
clearAudioFiles() {
|
|
185
|
-
// Not applicable on web
|
|
186
|
-
}
|
|
187
179
|
}
|
|
188
180
|
|
|
189
181
|
export default new ExpoAudioStreamWeb();
|
package/src/index.ts
CHANGED
|
@@ -6,26 +6,22 @@ import {
|
|
|
6
6
|
|
|
7
7
|
// Import the native module. On web, it will be resolved to ExpoAudioStream.web.ts
|
|
8
8
|
// and on native platforms to ExpoAudioStream.ts
|
|
9
|
+
import {
|
|
10
|
+
AudioRecorderProvider,
|
|
11
|
+
useSharedAudioRecorder,
|
|
12
|
+
} from "./AudioRecorder.provider";
|
|
9
13
|
import { AudioEventPayload } from "./ExpoAudioStream.types";
|
|
10
14
|
import ExpoAudioStreamModule from "./ExpoAudioStreamModule";
|
|
11
15
|
import {
|
|
12
|
-
useAudioRecorder,
|
|
13
|
-
UseAudioRecorderState,
|
|
14
16
|
AudioDataEvent,
|
|
17
|
+
UseAudioRecorderState,
|
|
18
|
+
useAudioRecorder,
|
|
15
19
|
} from "./useAudioRecording";
|
|
16
20
|
|
|
17
21
|
const emitter = new EventEmitter(
|
|
18
22
|
ExpoAudioStreamModule ?? NativeModulesProxy.ExpoAudioStream,
|
|
19
23
|
);
|
|
20
24
|
|
|
21
|
-
export function listAudioFiles(): Promise<string[]> {
|
|
22
|
-
return ExpoAudioStreamModule.listAudioFiles();
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export function clearAudioFiles(): Promise<void> {
|
|
26
|
-
return ExpoAudioStreamModule.clearAudioFiles();
|
|
27
|
-
}
|
|
28
|
-
|
|
29
25
|
export function test(): void {
|
|
30
26
|
return ExpoAudioStreamModule.test();
|
|
31
27
|
}
|
|
@@ -37,5 +33,5 @@ export function addAudioEventListener(
|
|
|
37
33
|
return emitter.addListener<AudioEventPayload>("AudioData", listener);
|
|
38
34
|
}
|
|
39
35
|
|
|
40
|
-
export
|
|
41
|
-
export {
|
|
36
|
+
export { AudioRecorderProvider, useAudioRecorder, useSharedAudioRecorder };
|
|
37
|
+
export type { AudioDataEvent, AudioEventPayload, UseAudioRecorderState };
|
package/src/useAudioRecording.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Platform } from "expo-modules-core";
|
|
2
|
-
import { useCallback, useEffect, useReducer } from "react";
|
|
2
|
+
import { useCallback, useEffect, useReducer, useRef } from "react";
|
|
3
3
|
|
|
4
4
|
import { addAudioEventListener } from ".";
|
|
5
5
|
import {
|
|
@@ -18,6 +18,10 @@ export interface AudioDataEvent {
|
|
|
18
18
|
eventDataSize: number;
|
|
19
19
|
totalSize: number;
|
|
20
20
|
}
|
|
21
|
+
|
|
22
|
+
export interface UseAudioRecorderProps {
|
|
23
|
+
debug?: boolean;
|
|
24
|
+
}
|
|
21
25
|
export interface UseAudioRecorderState {
|
|
22
26
|
startRecording: (_: RecordingConfig) => Promise<StartAudioStreamResult>;
|
|
23
27
|
stopRecording: () => Promise<AudioStreamResult | null>;
|
|
@@ -72,12 +76,8 @@ function recorderReducer(
|
|
|
72
76
|
const TAG = "[ useAudioRecorder ] ";
|
|
73
77
|
|
|
74
78
|
export function useAudioRecorder({
|
|
75
|
-
onAudioStream,
|
|
76
79
|
debug = false,
|
|
77
|
-
}: {
|
|
78
|
-
onAudioStream?: (_: AudioDataEvent) => Promise<void>;
|
|
79
|
-
debug?: boolean;
|
|
80
|
-
}): UseAudioRecorderState {
|
|
80
|
+
}: UseAudioRecorderProps = {}): UseAudioRecorderState {
|
|
81
81
|
const [state, dispatch] = useReducer(recorderReducer, {
|
|
82
82
|
isRecording: false,
|
|
83
83
|
isPaused: false,
|
|
@@ -85,16 +85,22 @@ export function useAudioRecorder({
|
|
|
85
85
|
size: 0,
|
|
86
86
|
});
|
|
87
87
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
88
|
+
const onAudioStreamRef = useRef<
|
|
89
|
+
((_: AudioDataEvent) => Promise<void>) | null
|
|
90
|
+
>(null);
|
|
91
|
+
|
|
92
|
+
const logDebug = useCallback(
|
|
93
|
+
(message: string, data?: any) => {
|
|
94
|
+
if (debug) {
|
|
95
|
+
if (data) {
|
|
96
|
+
console.log(`${TAG} ${message}`, data);
|
|
97
|
+
} else {
|
|
98
|
+
console.log(`${TAG} ${message}`);
|
|
99
|
+
}
|
|
95
100
|
}
|
|
96
|
-
}
|
|
97
|
-
|
|
101
|
+
},
|
|
102
|
+
[debug],
|
|
103
|
+
);
|
|
98
104
|
|
|
99
105
|
const handleAudioEvent = useCallback(
|
|
100
106
|
async (eventData: AudioEventPayload) => {
|
|
@@ -123,41 +129,24 @@ export function useAudioRecorder({
|
|
|
123
129
|
// Ignore packet with no data
|
|
124
130
|
return;
|
|
125
131
|
}
|
|
126
|
-
// Add more detailed handling here
|
|
127
132
|
try {
|
|
128
133
|
// Coming from native ( ios / android ) otherwise buffer is set
|
|
129
134
|
if (Platform.OS !== "web") {
|
|
130
135
|
// Read the audio file as a base64 string for comparison
|
|
131
136
|
if (!encoded) {
|
|
132
|
-
console.error(
|
|
137
|
+
console.error(`${TAG} Encoded audio data is missing`);
|
|
133
138
|
throw new Error("Encoded audio data is missing");
|
|
134
139
|
}
|
|
135
|
-
|
|
140
|
+
onAudioStreamRef.current?.({
|
|
136
141
|
data: encoded,
|
|
137
142
|
position,
|
|
138
143
|
fileUri,
|
|
139
144
|
eventDataSize: deltaSize,
|
|
140
145
|
totalSize,
|
|
141
146
|
});
|
|
142
|
-
|
|
143
|
-
// Below code is optional, used to compare encoded data to audio on file system
|
|
144
|
-
// Fetch the audio data from the fileUri
|
|
145
|
-
// const options = {
|
|
146
|
-
// encoding: FileSystem.EncodingType.Base64,
|
|
147
|
-
// position: lastEmittedSize,
|
|
148
|
-
// length: deltaSize,
|
|
149
|
-
// };
|
|
150
|
-
// const base64Content = await FileSystem.readAsStringAsync(fileUri, options);
|
|
151
|
-
// const binaryData = atob(base64Content);
|
|
152
|
-
// const content = new Uint8Array(binaryData.length);
|
|
153
|
-
// for (let i = 0; i < binaryData.length; i++) {
|
|
154
|
-
// content[i] = binaryData.charCodeAt(i);
|
|
155
|
-
// }
|
|
156
|
-
// const audioBlob = new Blob([content], { type: 'application/octet-stream' }); // Create a Blob from the byte array
|
|
157
|
-
// console.debug(`Read audio file (len: ${content.length}) vs ${deltaSize}`)
|
|
158
147
|
} else if (buffer) {
|
|
159
148
|
// Coming from web
|
|
160
|
-
|
|
149
|
+
onAudioStreamRef.current?.({
|
|
161
150
|
data: buffer,
|
|
162
151
|
position,
|
|
163
152
|
fileUri,
|
|
@@ -169,18 +158,19 @@ export function useAudioRecorder({
|
|
|
169
158
|
console.error(`${TAG} Error processing audio event:`, error);
|
|
170
159
|
}
|
|
171
160
|
},
|
|
172
|
-
[logDebug
|
|
161
|
+
[logDebug],
|
|
173
162
|
);
|
|
174
163
|
|
|
175
164
|
const checkStatus = useCallback(async () => {
|
|
176
165
|
try {
|
|
177
166
|
if (!state.isRecording) {
|
|
167
|
+
logDebug(`${TAG} Not recording, exiting status check.`);
|
|
178
168
|
return;
|
|
179
169
|
}
|
|
180
170
|
|
|
181
171
|
const status: AudioStreamStatus = ExpoAudioStreamModule.status();
|
|
182
172
|
if (debug) {
|
|
183
|
-
logDebug(
|
|
173
|
+
logDebug(`${TAG} Status:`, status);
|
|
184
174
|
}
|
|
185
175
|
|
|
186
176
|
if (!status.isRecording) {
|
|
@@ -192,20 +182,24 @@ export function useAudioRecorder({
|
|
|
192
182
|
});
|
|
193
183
|
}
|
|
194
184
|
} catch (error) {
|
|
195
|
-
console.error(
|
|
185
|
+
console.error(`${TAG} Error getting status:`, error);
|
|
196
186
|
}
|
|
197
187
|
}, [state.isRecording, logDebug]);
|
|
198
188
|
|
|
199
189
|
useEffect(() => {
|
|
200
|
-
|
|
201
|
-
|
|
190
|
+
let interval: number;
|
|
191
|
+
if (state.isRecording) {
|
|
192
|
+
interval = setInterval(checkStatus, 1000);
|
|
193
|
+
}
|
|
194
|
+
return () => {
|
|
195
|
+
if (interval) {
|
|
196
|
+
clearInterval(interval);
|
|
197
|
+
}
|
|
198
|
+
};
|
|
202
199
|
}, [checkStatus, state.isRecording]);
|
|
203
200
|
|
|
204
201
|
useEffect(() => {
|
|
205
|
-
|
|
206
|
-
return;
|
|
207
|
-
}
|
|
208
|
-
logDebug(`${TAG} Registering audio event listener`, onAudioStream);
|
|
202
|
+
logDebug(`${TAG} Registering audio event listener`);
|
|
209
203
|
const subscribe = addAudioEventListener(handleAudioEvent);
|
|
210
204
|
logDebug(`${TAG} Subscribed to audio event listener`, subscribe);
|
|
211
205
|
|
|
@@ -213,15 +207,23 @@ export function useAudioRecorder({
|
|
|
213
207
|
logDebug(`${TAG} Removing audio event listener`);
|
|
214
208
|
subscribe.remove();
|
|
215
209
|
};
|
|
216
|
-
}, [
|
|
210
|
+
}, [handleAudioEvent, logDebug]);
|
|
217
211
|
|
|
218
212
|
const startRecording = useCallback(
|
|
219
213
|
async (recordingOptions: RecordingConfig) => {
|
|
220
214
|
if (debug) {
|
|
221
215
|
logDebug(`${TAG} start recoding`, recordingOptions);
|
|
222
216
|
}
|
|
217
|
+
// remove onAudioStream from recordingOptions
|
|
218
|
+
const { onAudioStream, ...options } = recordingOptions;
|
|
219
|
+
if (typeof onAudioStream === "function") {
|
|
220
|
+
onAudioStreamRef.current = onAudioStream;
|
|
221
|
+
} else {
|
|
222
|
+
console.warn(`${TAG} onAudioStream is not a function`, onAudioStream);
|
|
223
|
+
onAudioStreamRef.current = null;
|
|
224
|
+
}
|
|
223
225
|
const startResult: StartAudioStreamResult =
|
|
224
|
-
await ExpoAudioStreamModule.startRecording(
|
|
226
|
+
await ExpoAudioStreamModule.startRecording(options);
|
|
225
227
|
dispatch({ type: "START" });
|
|
226
228
|
|
|
227
229
|
return startResult;
|
|
@@ -233,6 +235,7 @@ export function useAudioRecorder({
|
|
|
233
235
|
logDebug(`${TAG} stoping recording`);
|
|
234
236
|
const stopResult: AudioStreamResult =
|
|
235
237
|
await ExpoAudioStreamModule.stopRecording();
|
|
238
|
+
onAudioStreamRef.current = null;
|
|
236
239
|
logDebug(`${TAG} recording stopped`, stopResult);
|
|
237
240
|
dispatch({ type: "STOP" });
|
|
238
241
|
return stopResult;
|