@siteed/expo-audio-stream 0.2.0 → 0.2.1
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 +5 -10
- package/build/ExpoAudioStream.types.js +0 -1
- package/build/ExpoAudioStream.types.js.map +1 -1
- package/build/ExpoAudioStreamModule.js +2 -2
- package/build/ExpoAudioStreamModule.js.map +1 -1
- package/build/ExpoAudioStreamModule.web.d.ts.map +1 -1
- package/build/ExpoAudioStreamModule.web.js +15 -15
- package/build/ExpoAudioStreamModule.web.js.map +1 -1
- package/build/index.d.ts +3 -3
- package/build/index.d.ts.map +1 -1
- package/build/index.js +4 -6
- package/build/index.js.map +1 -1
- package/build/useAudioRecording.d.ts +1 -1
- package/build/useAudioRecording.d.ts.map +1 -1
- package/build/useAudioRecording.js +21 -14
- package/build/useAudioRecording.js.map +1 -1
- package/package.json +1 -1
- package/plugin/build/index.d.ts +1 -1
- package/plugin/build/index.js +4 -4
- package/plugin/src/index.ts +14 -8
- package/src/ExpoAudioStream.types.ts +8 -8
- package/src/ExpoAudioStreamModule.ts +2 -2
- package/src/ExpoAudioStreamModule.web.ts +165 -160
- package/src/index.ts +15 -7
- package/src/useAudioRecording.ts +144 -118
package/README.md
CHANGED
|
@@ -39,20 +39,16 @@ Add the plugin to your app.json like so:
|
|
|
39
39
|
|
|
40
40
|
## Usage
|
|
41
41
|
|
|
42
|
+
The `example/` folder contains a fully functional React Native application that demonstrates how to integrate and use the `@siteed/expo-audio-stream` library in a real-world scenario. This sample application includes features such as starting and stopping audio recordings, handling permissions, and processing live audio data.
|
|
43
|
+
|
|
42
44
|
### Importing the module
|
|
43
45
|
|
|
44
46
|
```tsx
|
|
45
47
|
import {
|
|
46
48
|
useAudioRecorder,
|
|
49
|
+
AudioStreamResult,
|
|
47
50
|
} from 'expo-audio-stream';
|
|
48
51
|
|
|
49
|
-
export interface AudioStreamResult {
|
|
50
|
-
fileUrl: string;
|
|
51
|
-
duration: number;
|
|
52
|
-
size: number;
|
|
53
|
-
mimeType: string;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
52
|
export default function App() {
|
|
57
53
|
const { startRecording, stopRecording, duration, size, isRecording } = useAudioRecorder({
|
|
58
54
|
onAudioStream: (audioData: Blob) => {
|
|
@@ -150,10 +146,9 @@ This library uses the npm `debug` package, to enable logging you can:
|
|
|
150
146
|
```
|
|
151
147
|
localStorage.debug = 'expo-audio-stream:*'
|
|
152
148
|
```
|
|
153
|
-
|
|
149
|
+
or set the DEBUG environment variable to `expo-audio-stream:*`
|
|
154
150
|
|
|
155
151
|
### TODO
|
|
156
152
|
this package is still in development, and there are a few things that need to be done:
|
|
157
|
-
-
|
|
158
|
-
- add multiple format of audio stream (wav, mp3, opus)
|
|
153
|
+
- add multiple format for native audio stream (wav, mp3, opus)
|
|
159
154
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ExpoAudioStream.types.js","sourceRoot":"","sources":["../src/ExpoAudioStream.types.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"ExpoAudioStream.types.js","sourceRoot":"","sources":["../src/ExpoAudioStream.types.ts"],"names":[],"mappings":"","sourcesContent":["export interface AudioEventPayload {\n encoded?: string;\n buffer?: Blob;\n fileUri: string;\n from: 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}\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 interface RecordingOptions {\n // TODO align Android and IOS options\n // sampleRate?: number;\n // channelConfig?: number; // numberOfChannel\n // audioFormat?: number; // bitDepth (ENCODING_PCM_16BIT --> 2)\n interval?: number;\n}\n"]}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { requireNativeModule } from
|
|
1
|
+
import { requireNativeModule } from "expo-modules-core";
|
|
2
2
|
// It loads the native module object from the JSI or falls back to
|
|
3
3
|
// the bridge module (from NativeModulesProxy) if the remote debugger is on.
|
|
4
|
-
export default requireNativeModule(
|
|
4
|
+
export default requireNativeModule("ExpoAudioStream");
|
|
5
5
|
//# sourceMappingURL=ExpoAudioStreamModule.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ExpoAudioStreamModule.js","sourceRoot":"","sources":["../src/ExpoAudioStreamModule.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAExD,kEAAkE;AAClE,4EAA4E;AAC5E,eAAe,mBAAmB,CAAC,iBAAiB,CAAC,CAAC","sourcesContent":["import { requireNativeModule } from
|
|
1
|
+
{"version":3,"file":"ExpoAudioStreamModule.js","sourceRoot":"","sources":["../src/ExpoAudioStreamModule.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAExD,kEAAkE;AAClE,4EAA4E;AAC5E,eAAe,mBAAmB,CAAC,iBAAiB,CAAC,CAAC","sourcesContent":["import { requireNativeModule } from \"expo-modules-core\";\n\n// It loads the native module object from the JSI or falls back to\n// the bridge module (from NativeModulesProxy) if the remote debugger is on.\nexport default requireNativeModule(\"ExpoAudioStream\");\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ExpoAudioStreamModule.web.d.ts","sourceRoot":"","sources":["../src/ExpoAudioStreamModule.web.ts"],"names":[],"mappings":"
|
|
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,gBAAgB,EACjB,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,eAAe,EAAE,MAAM,CAAC;IACxB,WAAW,EAAE,MAAM,CAAC;IACpB,eAAe,EAAE,MAAM,CAAC;IACxB,eAAe,EAAE,MAAM,CAAC;IACxB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;;IA2BpB,cAAc;IAUd,cAAc,CAAC,OAAO,GAAE,gBAAqB;IAmBnD,uBAAuB;IA0BvB,cAAc,CAAC,IAAI,EAAE,IAAI;IAgBzB,YAAY;IAUN,aAAa,IAAI,OAAO,CAAC,iBAAiB,GAAG,IAAI,CAAC;IAelD,cAAc;IAcpB,MAAM;;;;;;;IAUN,cAAc;IAId,eAAe;CAGhB;;AAED,wBAAwC"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
+
import debug from "debug";
|
|
1
2
|
import { EventEmitter } from "expo-modules-core";
|
|
2
|
-
import debug from 'debug';
|
|
3
3
|
const log = debug("expo-audio-stream:useAudioRecording");
|
|
4
4
|
class ExpoAudioStreamWeb extends EventEmitter {
|
|
5
5
|
mediaRecorder;
|
|
@@ -20,7 +20,7 @@ class ExpoAudioStreamWeb extends EventEmitter {
|
|
|
20
20
|
},
|
|
21
21
|
removeListeners: (count) => {
|
|
22
22
|
// Not used on web
|
|
23
|
-
}
|
|
23
|
+
},
|
|
24
24
|
};
|
|
25
25
|
super(mockNativeModule); // Pass the mock native module to the parent class
|
|
26
26
|
this.mediaRecorder = null;
|
|
@@ -48,7 +48,7 @@ class ExpoAudioStreamWeb extends EventEmitter {
|
|
|
48
48
|
// Start recording with options
|
|
49
49
|
async startRecording(options = {}) {
|
|
50
50
|
if (this.isRecording) {
|
|
51
|
-
throw new Error(
|
|
51
|
+
throw new Error("Recording is already in progress");
|
|
52
52
|
}
|
|
53
53
|
const stream = await this.getMediaStream();
|
|
54
54
|
this.mediaRecorder = new MediaRecorder(stream);
|
|
@@ -65,7 +65,7 @@ class ExpoAudioStreamWeb extends EventEmitter {
|
|
|
65
65
|
// Setup listeners for the MediaRecorder
|
|
66
66
|
setupRecordingListeners() {
|
|
67
67
|
if (!this.mediaRecorder) {
|
|
68
|
-
throw new Error(
|
|
68
|
+
throw new Error("No active media recorder");
|
|
69
69
|
}
|
|
70
70
|
this.mediaRecorder.ondataavailable = (event) => {
|
|
71
71
|
this.audioChunks.push(event.data);
|
|
@@ -75,34 +75,34 @@ class ExpoAudioStreamWeb extends EventEmitter {
|
|
|
75
75
|
};
|
|
76
76
|
this.mediaRecorder.onstop = () => {
|
|
77
77
|
this.isRecording = false;
|
|
78
|
-
log(
|
|
78
|
+
log("Recording stopped", this.audioChunks);
|
|
79
79
|
};
|
|
80
80
|
this.mediaRecorder.onpause = () => {
|
|
81
81
|
this.isPaused = true;
|
|
82
82
|
};
|
|
83
83
|
this.mediaRecorder.onresume = () => {
|
|
84
84
|
this.isPaused = false;
|
|
85
|
-
this.recordingStartTime +=
|
|
85
|
+
this.recordingStartTime += Date.now() - this.pausedTime; // Adjust start time after resuming
|
|
86
86
|
};
|
|
87
87
|
}
|
|
88
88
|
emitAudioEvent(data) {
|
|
89
89
|
const fileUri = `${this.streamUuid}.webm`;
|
|
90
90
|
const audioEventPayload = {
|
|
91
|
-
fileUri
|
|
92
|
-
mimeType:
|
|
91
|
+
fileUri,
|
|
92
|
+
mimeType: "audio/webm",
|
|
93
93
|
from: this.lastEmittedSize, // Since this might be continuously streaming, adjust accordingly
|
|
94
94
|
deltaSize: data.size,
|
|
95
95
|
totalSize: this.currentSize,
|
|
96
96
|
buffer: data,
|
|
97
|
-
streamUuid: this.streamUuid ??
|
|
97
|
+
streamUuid: this.streamUuid ?? "", // Generate or manage UUID for stream identification
|
|
98
98
|
};
|
|
99
|
-
this.emit(
|
|
99
|
+
this.emit("AudioData", audioEventPayload);
|
|
100
100
|
}
|
|
101
101
|
// Helper method to generate a UUID
|
|
102
102
|
generateUUID() {
|
|
103
103
|
// Implementation of UUID generation (use a library or custom method)
|
|
104
|
-
return
|
|
105
|
-
const r = Math.random() * 16 | 0, v = c
|
|
104
|
+
return "xxxx-xxxx-xxxx-xxxx".replace(/[x]/g, (c) => {
|
|
105
|
+
const r = (Math.random() * 16) | 0, v = c === "x" ? r : (r & 0x3) | 0x8;
|
|
106
106
|
return v.toString(16);
|
|
107
107
|
});
|
|
108
108
|
}
|
|
@@ -115,21 +115,21 @@ class ExpoAudioStreamWeb extends EventEmitter {
|
|
|
115
115
|
fileUri: `${this.streamUuid}.webm`,
|
|
116
116
|
duration: this.currentDuration,
|
|
117
117
|
size: this.currentSize,
|
|
118
|
-
mimeType:
|
|
118
|
+
mimeType: "audio/webm",
|
|
119
119
|
};
|
|
120
120
|
return result;
|
|
121
121
|
}
|
|
122
122
|
// Pause recording
|
|
123
123
|
async pauseRecording() {
|
|
124
124
|
if (!this.mediaRecorder) {
|
|
125
|
-
throw new Error(
|
|
125
|
+
throw new Error("No active media recorder");
|
|
126
126
|
}
|
|
127
127
|
if (this.isRecording && !this.isPaused) {
|
|
128
128
|
this.mediaRecorder.pause();
|
|
129
129
|
this.pausedTime = Date.now();
|
|
130
130
|
}
|
|
131
131
|
else {
|
|
132
|
-
throw new Error(
|
|
132
|
+
throw new Error("Recording is not active or already paused");
|
|
133
133
|
}
|
|
134
134
|
}
|
|
135
135
|
// Get current status
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ExpoAudioStreamModule.web.js","sourceRoot":"","sources":["../src/ExpoAudioStreamModule.web.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAEjD,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,MAAM,GAAG,GAAI,KAAK,CAAC,qCAAqC,CAAC,CAAC;AAC1D,MAAM,kBAAmB,SAAQ,YAAY;IACzC,aAAa,CAAuB;IACpC,WAAW,CAAS;IACpB,WAAW,CAAU;IACrB,QAAQ,CAAU;IAClB,kBAAkB,CAAS;IAC3B,UAAU,CAAS;IACnB,eAAe,CAAS;IACxB,WAAW,CAAS;IACpB,eAAe,CAAS;IACxB,eAAe,CAAS;IACxB,UAAU,CAAgB;IAE1B;QACI,MAAM,gBAAgB,GAAG;YACrB,WAAW,EAAE,CAAC,SAAiB,EAAE,EAAE;gBAC/B,kBAAkB;YACtB,CAAC;YACD,eAAe,EAAE,CAAC,KAAa,EAAE,EAAE;gBAC/B,kBAAkB;YACtB,CAAC;SACJ,CAAC;QACF,KAAK,CAAC,gBAAgB,CAAC,CAAC,CAAC,kDAAkD;QAG3E,IAAI,CAAC,aAAa,GAAG,IAAI,CAAC;QAC1B,IAAI,CAAC,WAAW,GAAG,EAAE,CAAC;QACtB,IAAI,CAAC,WAAW,GAAG,KAAK,CAAC;QACzB,IAAI,CAAC,QAAQ,GAAG,KAAK,CAAC;QACtB,IAAI,CAAC,kBAAkB,GAAG,CAAC,CAAC;QAC5B,IAAI,CAAC,UAAU,GAAG,CAAC,CAAC;QACpB,IAAI,CAAC,eAAe,GAAG,CAAC,CAAC;QACzB,IAAI,CAAC,WAAW,GAAG,CAAC,CAAC;QACrB,IAAI,CAAC,eAAe,GAAG,IAAI,CAAC,CAAC,yBAAyB;QACtD,IAAI,CAAC,eAAe,GAAG,CAAC,CAAC;QACzB,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC,CAAC,2CAA2C;IACvE,CAAC;IAED,sCAAsC;IACtC,KAAK,CAAC,cAAc;QAChB,IAAI,CAAC;YACD,OAAO,MAAM,SAAS,CAAC,YAAY,CAAC,YAAY,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;QACtE,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACb,OAAO,CAAC,KAAK,CAAC,6BAA6B,EAAE,KAAK,CAAC,CAAC;YACpD,MAAM,KAAK,CAAC;QAChB,CAAC;IACL,CAAC;IAED,+BAA+B;IAC/B,KAAK,CAAC,cAAc,CAAC,UAA4B,EAAE;QAC/C,IAAI,IAAI,CAAC,WAAW,EAAE,CAAC;YACnB,MAAM,IAAI,KAAK,CAAC,kCAAkC,CAAC,CAAC;QACxD,CAAC;QAED,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,cAAc,EAAE,CAAC;QAC3C,IAAI,CAAC,aAAa,GAAG,IAAI,aAAa,CAAC,MAAM,CAAC,CAAC;QAC/C,IAAI,CAAC,uBAAuB,EAAE,CAAC;QAC/B,IAAI,CAAC,aAAa,CAAC,KAAK,CAAC,OAAO,CAAC,QAAQ,IAAI,IAAI,CAAC,eAAe,CAAC,CAAC;QACnE,IAAI,CAAC,WAAW,GAAG,IAAI,CAAC;QACxB,IAAI,CAAC,kBAAkB,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACrC,IAAI,CAAC,UAAU,GAAG,CAAC,CAAC;QACpB,IAAI,CAAC,eAAe,GAAG,CAAC,CAAC;QACzB,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC,YAAY,EAAE,CAAC,CAAC,gDAAgD;QACvF,MAAM,OAAO,GAAG,GAAG,IAAI,CAAC,UAAU,OAAO,CAAC;QAC1C,OAAO,OAAO,CAAC;IACnB,CAAC;IAED,wCAAwC;IACxC,uBAAuB;QACnB,IAAI,CAAC,IAAI,CAAC,aAAa,EAAE,CAAC;YACtB,MAAM,IAAI,KAAK,CAAC,0BAA0B,CAAC,CAAC;QAChD,CAAC;QACD,IAAI,CAAC,aAAa,CAAC,eAAe,GAAG,CAAC,KAAK,EAAE,EAAE;YAC3C,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;YAClC,IAAI,CAAC,WAAW,IAAI,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,mCAAmC;YACxE,IAAI,CAAC,cAAc,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAE,0CAA0C;YAC5E,IAAI,CAAC,eAAe,GAAG,IAAI,CAAC,WAAW,CAAC;QAC5C,CAAC,CAAC;QAEF,IAAI,CAAC,aAAa,CAAC,MAAM,GAAG,GAAG,EAAE;YAC7B,IAAI,CAAC,WAAW,GAAG,KAAK,CAAC;YACzB,GAAG,CAAC,mBAAmB,EAAE,IAAI,CAAC,WAAW,CAAC,CAAC;QAC/C,CAAC,CAAC;QAEF,IAAI,CAAC,aAAa,CAAC,OAAO,GAAG,GAAG,EAAE;YAC9B,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC;QACzB,CAAC,CAAC;QAEF,IAAI,CAAC,aAAa,CAAC,QAAQ,GAAG,GAAG,EAAE;YAC/B,IAAI,CAAC,QAAQ,GAAG,KAAK,CAAC;YACtB,IAAI,CAAC,kBAAkB,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,mCAAmC;QAClG,CAAC,CAAC;IACN,CAAC;IAED,cAAc,CAAC,IAAU;QACrB,MAAM,OAAO,GAAG,GAAG,IAAI,CAAC,UAAU,OAAO,CAAC;QAC1C,MAAM,iBAAiB,GAAsB;YACzC,OAAO,EAAE,OAAO;YAChB,QAAQ,EAAE,YAAY;YACtB,IAAI,EAAE,IAAI,CAAC,eAAe,EAAG,iEAAiE;YAC9F,SAAS,EAAE,IAAI,CAAC,IAAI;YACpB,SAAS,EAAE,IAAI,CAAC,WAAW;YAC3B,MAAM,EAAE,IAAI;YACZ,UAAU,EAAE,IAAI,CAAC,UAAU,IAAI,EAAE,EAAG,oDAAoD;SAC3F,CAAC;QAEF,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,iBAAiB,CAAC,CAAC;IAC9C,CAAC;IAEA,mCAAmC;IACnC,YAAY;QACT,qEAAqE;QACrE,OAAO,qBAAqB,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC,EAAE,EAAE;YAC/C,MAAM,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,GAAG,EAAE,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,IAAI,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,GAAG,GAAG,GAAG,CAAC,CAAC;YACrE,OAAO,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;QAC1B,CAAC,CAAC,CAAC;IACP,CAAC;IAED,iBAAiB;IACjB,KAAK,CAAC,aAAa;QACf,IAAI,CAAC,aAAa,EAAE,IAAI,EAAE,CAAC;QAC3B,IAAI,CAAC,WAAW,GAAG,KAAK,CAAC;QACzB,IAAI,CAAC,eAAe,GAAG,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,kBAAkB,CAAC,GAAG,IAAI,CAAC;QACrE,MAAM,MAAM,GAAsB;YAC9B,OAAO,EAAE,GAAG,IAAI,CAAC,UAAU,OAAO;YAClC,QAAQ,EAAE,IAAI,CAAC,eAAe;YAC9B,IAAI,EAAE,IAAI,CAAC,WAAW;YACtB,QAAQ,EAAE,YAAY;SACzB,CAAA;QAED,OAAO,MAAM,CAAC;IAClB,CAAC;IAED,kBAAkB;IAClB,KAAK,CAAC,cAAc;QAChB,IAAI,CAAC,IAAI,CAAC,aAAa,EAAE,CAAC;YACtB,MAAM,IAAI,KAAK,CAAC,0BAA0B,CAAC,CAAC;QAChD,CAAC;QAED,IAAI,IAAI,CAAC,WAAW,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC;YACrC,IAAI,CAAC,aAAa,CAAC,KAAK,EAAE,CAAC;YAC3B,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACjC,CAAC;aAAM,CAAC;YACJ,MAAM,IAAI,KAAK,CAAC,2CAA2C,CAAC,CAAC;QACjE,CAAC;IACL,CAAC;IAED,qBAAqB;IACrB,MAAM;QACF,OAAO;YACH,WAAW,EAAE,IAAI,CAAC,WAAW;YAC7B,QAAQ,EAAE,IAAI,CAAC,QAAQ;YACvB,QAAQ,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,kBAAkB;YAC9C,IAAI,EAAE,IAAI,CAAC,WAAW;YACtB,QAAQ,EAAE,IAAI,CAAC,eAAe;SACjC,CAAC;IACN,CAAC;IAED,cAAc;QACV,wBAAwB;IAC5B,CAAC;IAED,eAAe;QACX,wBAAwB;IAC5B,CAAC;CACJ;AAED,eAAe,IAAI,kBAAkB,EAAE,CAAC","sourcesContent":["import { EventEmitter } from \"expo-modules-core\";\nimport { AudioEventPayload, AudioStreamResult, RecordingOptions } from \"./ExpoAudioStream.types\";\nimport debug from 'debug';\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 currentDuration: number;\n currentSize: number;\n currentInterval: number;\n lastEmittedSize: 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\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.currentDuration = 0;\n this.currentSize = 0;\n this.currentInterval = 1000; // Default interval in ms\n this.lastEmittedSize = 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: RecordingOptions = {}) {\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.streamUuid = this.generateUUID(); // Generate a UUID for the new recording session\n const fileUri = `${this.streamUuid}.webm`;\n return fileUri;\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 this.emitAudioEvent(event.data); // Emit the event with the correct payload\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: Blob) {\n const fileUri = `${this.streamUuid}.webm`;\n const audioEventPayload: AudioEventPayload = {\n fileUri: fileUri,\n mimeType: 'audio/webm',\n from: this.lastEmittedSize, // Since this might be continuously streaming, adjust accordingly\n deltaSize: data.size,\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, 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.currentDuration = (Date.now() - this.recordingStartTime) / 1000;\n const result: AudioStreamResult = {\n fileUri: `${this.streamUuid}.webm`,\n duration: this.currentDuration,\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();"]}
|
|
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 RecordingOptions,\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 currentDuration: number;\n currentSize: number;\n currentInterval: number;\n lastEmittedSize: 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.currentDuration = 0;\n this.currentSize = 0;\n this.currentInterval = 1000; // Default interval in ms\n this.lastEmittedSize = 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: RecordingOptions = {}) {\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.streamUuid = this.generateUUID(); // Generate a UUID for the new recording session\n const fileUri = `${this.streamUuid}.webm`;\n return fileUri;\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 this.emitAudioEvent(event.data); // Emit the event with the correct payload\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: Blob) {\n const fileUri = `${this.streamUuid}.webm`;\n const audioEventPayload: AudioEventPayload = {\n fileUri,\n mimeType: \"audio/webm\",\n from: this.lastEmittedSize, // Since this might be continuously streaming, adjust accordingly\n deltaSize: data.size,\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.currentDuration = (Date.now() - this.recordingStartTime) / 1000;\n const result: AudioStreamResult = {\n fileUri: `${this.streamUuid}.webm`,\n duration: this.currentDuration,\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"]}
|
package/build/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { type Subscription } from
|
|
2
|
-
import { AudioEventPayload } from
|
|
3
|
-
import { useAudioRecorder } from
|
|
1
|
+
import { type Subscription } from "expo-modules-core";
|
|
2
|
+
import { AudioEventPayload } from "./ExpoAudioStream.types";
|
|
3
|
+
import { useAudioRecorder } from "./useAudioRecording";
|
|
4
4
|
export declare function listAudioFiles(): Promise<string[]>;
|
|
5
5
|
export declare function clearAudioFiles(): Promise<void>;
|
|
6
6
|
export declare function addAudioEventListener(listener: (event: AudioEventPayload) => void): Subscription;
|
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,
|
|
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,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAMvD,wBAAgB,cAAc,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC,CAElD;AAED,wBAAgB,eAAe,IAAI,OAAO,CAAC,IAAI,CAAC,CAE/C;AAED,wBAAgB,qBAAqB,CACnC,QAAQ,EAAE,CAAC,KAAK,EAAE,iBAAiB,KAAK,IAAI,GAC3C,YAAY,CAEd;AAED,OAAO,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,CAAC"}
|
package/build/index.js
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
|
-
import { NativeModulesProxy,
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
import ExpoAudioStreamModule from './ExpoAudioStreamModule';
|
|
5
|
-
import { useAudioRecorder } from './useAudioRecording';
|
|
1
|
+
import { EventEmitter, NativeModulesProxy, } from "expo-modules-core";
|
|
2
|
+
import ExpoAudioStreamModule from "./ExpoAudioStreamModule";
|
|
3
|
+
import { useAudioRecorder } from "./useAudioRecording";
|
|
6
4
|
const emitter = new EventEmitter(ExpoAudioStreamModule ?? NativeModulesProxy.ExpoAudioStream);
|
|
7
5
|
export function listAudioFiles() {
|
|
8
6
|
return ExpoAudioStreamModule.listAudioFiles();
|
|
@@ -11,7 +9,7 @@ export function clearAudioFiles() {
|
|
|
11
9
|
return ExpoAudioStreamModule.clearAudioFiles();
|
|
12
10
|
}
|
|
13
11
|
export function addAudioEventListener(listener) {
|
|
14
|
-
return emitter.addListener(
|
|
12
|
+
return emitter.addListener("AudioData", listener);
|
|
15
13
|
}
|
|
16
14
|
export { useAudioRecorder };
|
|
17
15
|
//# 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,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,YAAY,EACZ,kBAAkB,GAEnB,MAAM,mBAAmB,CAAC;AAK3B,OAAO,qBAAqB,MAAM,yBAAyB,CAAC;AAC5D,OAAO,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAEvD,MAAM,OAAO,GAAG,IAAI,YAAY,CAC9B,qBAAqB,IAAI,kBAAkB,CAAC,eAAe,CAC5D,CAAC;AAEF,MAAM,UAAU,cAAc;IAC5B,OAAO,qBAAqB,CAAC,cAAc,EAAE,CAAC;AAChD,CAAC;AAED,MAAM,UAAU,eAAe;IAC7B,OAAO,qBAAqB,CAAC,eAAe,EAAE,CAAC;AACjD,CAAC;AAED,MAAM,UAAU,qBAAqB,CACnC,QAA4C;IAE5C,OAAO,OAAO,CAAC,WAAW,CAAoB,WAAW,EAAE,QAAQ,CAAC,CAAC;AACvE,CAAC;AAED,OAAO,EAAqB,gBAAgB,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 { AudioEventPayload } from \"./ExpoAudioStream.types\";\nimport ExpoAudioStreamModule from \"./ExpoAudioStreamModule\";\nimport { useAudioRecorder } from \"./useAudioRecording\";\n\nconst emitter = new EventEmitter(\n ExpoAudioStreamModule ?? NativeModulesProxy.ExpoAudioStream,\n);\n\nexport function listAudioFiles(): Promise<string[]> {\n return ExpoAudioStreamModule.listAudioFiles();\n}\n\nexport function clearAudioFiles(): Promise<void> {\n return ExpoAudioStreamModule.clearAudioFiles();\n}\n\nexport function addAudioEventListener(\n listener: (event: AudioEventPayload) => void,\n): Subscription {\n return emitter.addListener<AudioEventPayload>(\"AudioData\", listener);\n}\n\nexport { AudioEventPayload, useAudioRecorder };\n"]}
|
|
@@ -8,7 +8,7 @@ interface UseAudioRecorderState {
|
|
|
8
8
|
duration: number;
|
|
9
9
|
size: number;
|
|
10
10
|
}
|
|
11
|
-
export declare function useAudioRecorder({ onAudioStream }: {
|
|
11
|
+
export declare function useAudioRecorder({ onAudioStream, }: {
|
|
12
12
|
onAudioStream?: (buffer: Blob) => void;
|
|
13
13
|
}): UseAudioRecorderState;
|
|
14
14
|
export {};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useAudioRecording.d.ts","sourceRoot":"","sources":["../src/useAudioRecording.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"useAudioRecording.d.ts","sourceRoot":"","sources":["../src/useAudioRecording.ts"],"names":[],"mappings":"AAMA,OAAO,EACL,iBAAiB,EAEjB,gBAAgB,EACjB,MAAM,yBAAyB,CAAC;AAKjC,UAAU,qBAAqB;IAC7B,cAAc,EAAE,CAAC,CAAC,EAAE,gBAAgB,KAAK,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IAChE,aAAa,EAAE,MAAM,OAAO,CAAC,iBAAiB,GAAG,IAAI,CAAC,CAAC;IACvD,cAAc,EAAE,MAAM,IAAI,CAAC;IAC3B,WAAW,EAAE,OAAO,CAAC;IACrB,QAAQ,EAAE,OAAO,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;CACd;AAED,wBAAgB,gBAAgB,CAAC,EAC/B,aAAa,GACd,EAAE;IACD,aAAa,CAAC,EAAE,CAAC,MAAM,EAAE,IAAI,KAAK,IAAI,CAAC;CACxC,GAAG,qBAAqB,CAkIxB"}
|
|
@@ -1,12 +1,11 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { decode as atob } from "base-64";
|
|
2
|
+
import debug from "debug";
|
|
3
|
+
import { Platform } from "expo-modules-core";
|
|
2
4
|
import { useCallback, useEffect, useState } from "react";
|
|
3
|
-
import
|
|
4
|
-
import
|
|
5
|
-
import { decode as atob } from 'base-64';
|
|
6
|
-
import debug from 'debug';
|
|
7
|
-
const emitter = new EventEmitter(ExpoAudioStreamModule ?? NativeModulesProxy.ExpoAudioStream);
|
|
5
|
+
import { addAudioEventListener } from ".";
|
|
6
|
+
import ExpoAudioStreamModule from "./ExpoAudioStreamModule";
|
|
8
7
|
const log = debug("expo-audio-stream:useAudioRecording");
|
|
9
|
-
export function useAudioRecorder({ onAudioStream }) {
|
|
8
|
+
export function useAudioRecorder({ onAudioStream, }) {
|
|
10
9
|
const [isRecording, setIsRecording] = useState(false);
|
|
11
10
|
const [isPaused, setIsPaused] = useState(false);
|
|
12
11
|
const [duration, setDuration] = useState(0);
|
|
@@ -23,11 +22,19 @@ export function useAudioRecorder({ onAudioStream }) {
|
|
|
23
22
|
return () => null;
|
|
24
23
|
}, [isRecording, isPaused]);
|
|
25
24
|
useEffect(() => {
|
|
26
|
-
const subscribe = addAudioEventListener(async ({ fileUri, deltaSize, totalSize, from, streamUuid, encoded, mimeType, buffer }) => {
|
|
27
|
-
log(`Received audio event:`, {
|
|
25
|
+
const subscribe = addAudioEventListener(async ({ fileUri, deltaSize, totalSize, from, streamUuid, encoded, mimeType, buffer, }) => {
|
|
26
|
+
log(`Received audio event:`, {
|
|
27
|
+
fileUri,
|
|
28
|
+
deltaSize,
|
|
29
|
+
totalSize,
|
|
30
|
+
mimeType,
|
|
31
|
+
from,
|
|
32
|
+
streamUuid,
|
|
33
|
+
encodedLength: encoded?.length,
|
|
34
|
+
});
|
|
28
35
|
if (deltaSize > 0) {
|
|
29
36
|
// Coming from native ( ios / android ) otherwise buffer is set
|
|
30
|
-
if (Platform.OS !==
|
|
37
|
+
if (Platform.OS !== "web") {
|
|
31
38
|
// Read the audio file as a base64 string for comparison
|
|
32
39
|
try {
|
|
33
40
|
// convert encoded string to binary data
|
|
@@ -55,7 +62,7 @@ export function useAudioRecorder({ onAudioStream }) {
|
|
|
55
62
|
onAudioStream?.(audioBlob);
|
|
56
63
|
}
|
|
57
64
|
catch (error) {
|
|
58
|
-
console.error(
|
|
65
|
+
console.error("Error reading audio file:", error);
|
|
59
66
|
}
|
|
60
67
|
}
|
|
61
68
|
else if (buffer) {
|
|
@@ -77,7 +84,7 @@ export function useAudioRecorder({ onAudioStream }) {
|
|
|
77
84
|
return fileUrl;
|
|
78
85
|
}
|
|
79
86
|
catch (error) {
|
|
80
|
-
console.error(
|
|
87
|
+
console.error("Error starting recording:", error);
|
|
81
88
|
setIsRecording(false);
|
|
82
89
|
}
|
|
83
90
|
}, []);
|
|
@@ -94,7 +101,7 @@ export function useAudioRecorder({ onAudioStream }) {
|
|
|
94
101
|
setIsRecording(false);
|
|
95
102
|
}
|
|
96
103
|
catch (error) {
|
|
97
|
-
console.error(
|
|
104
|
+
console.error("Error pausing recording:", error);
|
|
98
105
|
}
|
|
99
106
|
}, []);
|
|
100
107
|
return {
|
|
@@ -104,7 +111,7 @@ export function useAudioRecorder({ onAudioStream }) {
|
|
|
104
111
|
isPaused,
|
|
105
112
|
isRecording,
|
|
106
113
|
duration,
|
|
107
|
-
size
|
|
114
|
+
size,
|
|
108
115
|
};
|
|
109
116
|
}
|
|
110
117
|
//# sourceMappingURL=useAudioRecording.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useAudioRecording.js","sourceRoot":"","sources":["../src/useAudioRecording.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"useAudioRecording.js","sourceRoot":"","sources":["../src/useAudioRecording.ts"],"names":[],"mappings":"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","sourcesContent":["import { decode as atob } from \"base-64\";\nimport debug from \"debug\";\nimport { Platform } from \"expo-modules-core\";\nimport { useCallback, useEffect, useState } from \"react\";\n\nimport { addAudioEventListener } from \".\";\nimport {\n AudioStreamResult,\n AudioStreamStatus,\n RecordingOptions,\n} from \"./ExpoAudioStream.types\";\nimport ExpoAudioStreamModule from \"./ExpoAudioStreamModule\";\n\nconst log = debug(\"expo-audio-stream:useAudioRecording\");\n\ninterface UseAudioRecorderState {\n startRecording: (_: RecordingOptions) => Promise<string | null>;\n stopRecording: () => Promise<AudioStreamResult | null>;\n pauseRecording: () => 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\nexport function useAudioRecorder({\n onAudioStream,\n}: {\n onAudioStream?: (buffer: Blob) => void;\n}): UseAudioRecorderState {\n const [isRecording, setIsRecording] = useState(false);\n const [isPaused, setIsPaused] = useState(false);\n const [duration, setDuration] = useState(0);\n const [size, setSize] = useState(0);\n\n useEffect(() => {\n if (isRecording || isPaused) {\n const interval = setInterval(() => {\n const status: AudioStreamStatus = ExpoAudioStreamModule.status();\n setDuration(status.duration);\n setSize(status.size);\n }, 1000);\n return () => clearInterval(interval);\n }\n\n return () => null;\n }, [isRecording, isPaused]);\n\n useEffect(() => {\n const subscribe = addAudioEventListener(\n async ({\n fileUri,\n deltaSize,\n totalSize,\n from,\n streamUuid,\n encoded,\n mimeType,\n buffer,\n }) => {\n log(`Received audio event:`, {\n fileUri,\n deltaSize,\n totalSize,\n mimeType,\n from,\n streamUuid,\n encodedLength: encoded?.length,\n });\n if (deltaSize > 0) {\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 try {\n // convert encoded string to binary data\n const binaryData = atob(encoded);\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: mimeType });\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: from,\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\n onAudioStream?.(audioBlob);\n } catch (error) {\n console.error(\"Error reading audio file:\", error);\n }\n } else if (buffer) {\n // Coming from web\n onAudioStream?.(buffer);\n }\n }\n },\n );\n return () => subscribe.remove();\n }, [isRecording, onAudioStream]);\n\n const startRecording = useCallback(\n async (recordingOptions: RecordingOptions) => {\n setIsRecording(true);\n setIsPaused(false);\n setSize(0);\n setDuration(0);\n try {\n log(`start recoding`, recordingOptions);\n const fileUrl =\n await ExpoAudioStreamModule.startRecording(recordingOptions);\n\n return fileUrl;\n } catch (error) {\n console.error(\"Error starting recording:\", error);\n setIsRecording(false);\n }\n },\n [],\n );\n\n const stopRecording = useCallback(async () => {\n setIsRecording(false);\n setIsPaused(false);\n const result: AudioStreamResult =\n await ExpoAudioStreamModule.stopRecording();\n return result;\n }, []);\n\n const pauseRecording = useCallback(async () => {\n try {\n await ExpoAudioStreamModule.stopRecording();\n setIsPaused(true);\n setIsRecording(false);\n } catch (error) {\n console.error(\"Error pausing recording:\", error);\n }\n }, []);\n\n return {\n startRecording,\n stopRecording,\n pauseRecording,\n isPaused,\n isRecording,\n duration,\n size,\n };\n}\n"]}
|
package/package.json
CHANGED
package/plugin/build/index.d.ts
CHANGED
package/plugin/build/index.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
const config_plugins_1 = require("@expo/config-plugins");
|
|
4
|
-
const pkg = require(
|
|
5
|
-
const MICROPHONE_USAGE =
|
|
4
|
+
const pkg = require("../../package.json");
|
|
5
|
+
const MICROPHONE_USAGE = "Allow $(PRODUCT_NAME) to access your microphone";
|
|
6
6
|
const withRecordingPermission = (config, { microphonePermission }) => {
|
|
7
7
|
config_plugins_1.IOSConfig.Permissions.createPermissionsPlugin({
|
|
8
8
|
NSMicrophoneUsageDescription: MICROPHONE_USAGE,
|
|
@@ -10,8 +10,8 @@ const withRecordingPermission = (config, { microphonePermission }) => {
|
|
|
10
10
|
NSMicrophoneUsageDescription: microphonePermission,
|
|
11
11
|
});
|
|
12
12
|
return config_plugins_1.AndroidConfig.Permissions.withPermissions(config, [
|
|
13
|
-
microphonePermission !== false &&
|
|
14
|
-
|
|
13
|
+
microphonePermission !== false && "android.permission.RECORD_AUDIO",
|
|
14
|
+
"android.permission.MODIFY_AUDIO_SETTINGS",
|
|
15
15
|
].filter(Boolean));
|
|
16
16
|
};
|
|
17
17
|
exports.default = (0, config_plugins_1.createRunOncePlugin)(withRecordingPermission, pkg.name, pkg.version);
|
package/plugin/src/index.ts
CHANGED
|
@@ -2,13 +2,15 @@ import {
|
|
|
2
2
|
AndroidConfig,
|
|
3
3
|
ConfigPlugin,
|
|
4
4
|
IOSConfig,
|
|
5
|
-
createRunOncePlugin
|
|
5
|
+
createRunOncePlugin,
|
|
6
6
|
} from "@expo/config-plugins";
|
|
7
7
|
|
|
8
|
-
const pkg = require(
|
|
9
|
-
const MICROPHONE_USAGE =
|
|
8
|
+
const pkg = require("../../package.json");
|
|
9
|
+
const MICROPHONE_USAGE = "Allow $(PRODUCT_NAME) to access your microphone";
|
|
10
10
|
|
|
11
|
-
const withRecordingPermission: ConfigPlugin<{
|
|
11
|
+
const withRecordingPermission: ConfigPlugin<{
|
|
12
|
+
microphonePermission: string | false | undefined;
|
|
13
|
+
}> = (config, { microphonePermission }) => {
|
|
12
14
|
IOSConfig.Permissions.createPermissionsPlugin({
|
|
13
15
|
NSMicrophoneUsageDescription: MICROPHONE_USAGE,
|
|
14
16
|
})(config, {
|
|
@@ -18,10 +20,14 @@ const withRecordingPermission: ConfigPlugin<{ microphonePermission: string | fal
|
|
|
18
20
|
return AndroidConfig.Permissions.withPermissions(
|
|
19
21
|
config,
|
|
20
22
|
[
|
|
21
|
-
microphonePermission !== false &&
|
|
22
|
-
|
|
23
|
-
].filter(Boolean) as string[]
|
|
23
|
+
microphonePermission !== false && "android.permission.RECORD_AUDIO",
|
|
24
|
+
"android.permission.MODIFY_AUDIO_SETTINGS",
|
|
25
|
+
].filter(Boolean) as string[],
|
|
24
26
|
);
|
|
25
27
|
};
|
|
26
28
|
|
|
27
|
-
export default createRunOncePlugin(
|
|
29
|
+
export default createRunOncePlugin(
|
|
30
|
+
withRecordingPermission,
|
|
31
|
+
pkg.name,
|
|
32
|
+
pkg.version,
|
|
33
|
+
);
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
export interface AudioEventPayload {
|
|
2
|
-
encoded?: string
|
|
3
|
-
buffer?: Blob
|
|
4
|
-
fileUri: string
|
|
5
|
-
from: number
|
|
6
|
-
deltaSize: number
|
|
7
|
-
totalSize: number
|
|
2
|
+
encoded?: string;
|
|
3
|
+
buffer?: Blob;
|
|
4
|
+
fileUri: string;
|
|
5
|
+
from: number;
|
|
6
|
+
deltaSize: number;
|
|
7
|
+
totalSize: number;
|
|
8
8
|
mimeType: string;
|
|
9
|
-
streamUuid: string
|
|
10
|
-
}
|
|
9
|
+
streamUuid: string;
|
|
10
|
+
}
|
|
11
11
|
|
|
12
12
|
export interface AudioStreamResult {
|
|
13
13
|
fileUri: string;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { requireNativeModule } from
|
|
1
|
+
import { requireNativeModule } from "expo-modules-core";
|
|
2
2
|
|
|
3
3
|
// It loads the native module object from the JSI or falls back to
|
|
4
4
|
// the bridge module (from NativeModulesProxy) if the remote debugger is on.
|
|
5
|
-
export default requireNativeModule(
|
|
5
|
+
export default requireNativeModule("ExpoAudioStream");
|
|
@@ -1,173 +1,178 @@
|
|
|
1
|
+
import debug from "debug";
|
|
1
2
|
import { EventEmitter } from "expo-modules-core";
|
|
2
|
-
import { AudioEventPayload, AudioStreamResult, RecordingOptions } from "./ExpoAudioStream.types";
|
|
3
|
-
import debug from 'debug';
|
|
4
3
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
isPaused: boolean;
|
|
11
|
-
recordingStartTime: number;
|
|
12
|
-
pausedTime: number;
|
|
13
|
-
currentDuration: number;
|
|
14
|
-
currentSize: number;
|
|
15
|
-
currentInterval: number;
|
|
16
|
-
lastEmittedSize: number;
|
|
17
|
-
streamUuid: string | null;
|
|
18
|
-
|
|
19
|
-
constructor() {
|
|
20
|
-
const mockNativeModule = {
|
|
21
|
-
addListener: (eventName: string) => {
|
|
22
|
-
// Not used on web
|
|
23
|
-
},
|
|
24
|
-
removeListeners: (count: number) => {
|
|
25
|
-
// Not used on web
|
|
26
|
-
}
|
|
27
|
-
};
|
|
28
|
-
super(mockNativeModule); // Pass the mock native module to the parent class
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
this.mediaRecorder = null;
|
|
32
|
-
this.audioChunks = [];
|
|
33
|
-
this.isRecording = false;
|
|
34
|
-
this.isPaused = false;
|
|
35
|
-
this.recordingStartTime = 0;
|
|
36
|
-
this.pausedTime = 0;
|
|
37
|
-
this.currentDuration = 0;
|
|
38
|
-
this.currentSize = 0;
|
|
39
|
-
this.currentInterval = 1000; // Default interval in ms
|
|
40
|
-
this.lastEmittedSize = 0;
|
|
41
|
-
this.streamUuid = null; // Initialize UUID on first recording start
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
// Utility to handle user media stream
|
|
45
|
-
async getMediaStream() {
|
|
46
|
-
try {
|
|
47
|
-
return await navigator.mediaDevices.getUserMedia({ audio: true });
|
|
48
|
-
} catch (error) {
|
|
49
|
-
console.error("Failed to get media stream:", error);
|
|
50
|
-
throw error;
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
// Start recording with options
|
|
55
|
-
async startRecording(options: RecordingOptions = {}) {
|
|
56
|
-
if (this.isRecording) {
|
|
57
|
-
throw new Error('Recording is already in progress');
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
const stream = await this.getMediaStream();
|
|
61
|
-
this.mediaRecorder = new MediaRecorder(stream);
|
|
62
|
-
this.setupRecordingListeners();
|
|
63
|
-
this.mediaRecorder.start(options.interval || this.currentInterval);
|
|
64
|
-
this.isRecording = true;
|
|
65
|
-
this.recordingStartTime = Date.now();
|
|
66
|
-
this.pausedTime = 0;
|
|
67
|
-
this.lastEmittedSize = 0;
|
|
68
|
-
this.streamUuid = this.generateUUID(); // Generate a UUID for the new recording session
|
|
69
|
-
const fileUri = `${this.streamUuid}.webm`;
|
|
70
|
-
return fileUri;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
// Setup listeners for the MediaRecorder
|
|
74
|
-
setupRecordingListeners() {
|
|
75
|
-
if (!this.mediaRecorder) {
|
|
76
|
-
throw new Error('No active media recorder');
|
|
77
|
-
}
|
|
78
|
-
this.mediaRecorder.ondataavailable = (event) => {
|
|
79
|
-
this.audioChunks.push(event.data);
|
|
80
|
-
this.currentSize += event.data.size; // Update the size of the recording
|
|
81
|
-
this.emitAudioEvent(event.data); // Emit the event with the correct payload
|
|
82
|
-
this.lastEmittedSize = this.currentSize;
|
|
83
|
-
};
|
|
84
|
-
|
|
85
|
-
this.mediaRecorder.onstop = () => {
|
|
86
|
-
this.isRecording = false;
|
|
87
|
-
log('Recording stopped', this.audioChunks);
|
|
88
|
-
};
|
|
89
|
-
|
|
90
|
-
this.mediaRecorder.onpause = () => {
|
|
91
|
-
this.isPaused = true;
|
|
92
|
-
};
|
|
93
|
-
|
|
94
|
-
this.mediaRecorder.onresume = () => {
|
|
95
|
-
this.isPaused = false;
|
|
96
|
-
this.recordingStartTime += (Date.now() - this.pausedTime); // Adjust start time after resuming
|
|
97
|
-
};
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
emitAudioEvent(data: Blob) {
|
|
101
|
-
const fileUri = `${this.streamUuid}.webm`;
|
|
102
|
-
const audioEventPayload: AudioEventPayload = {
|
|
103
|
-
fileUri: fileUri,
|
|
104
|
-
mimeType: 'audio/webm',
|
|
105
|
-
from: this.lastEmittedSize, // Since this might be continuously streaming, adjust accordingly
|
|
106
|
-
deltaSize: data.size,
|
|
107
|
-
totalSize: this.currentSize,
|
|
108
|
-
buffer: data,
|
|
109
|
-
streamUuid: this.streamUuid ?? '', // Generate or manage UUID for stream identification
|
|
110
|
-
};
|
|
111
|
-
|
|
112
|
-
this.emit('AudioData', audioEventPayload);
|
|
113
|
-
}
|
|
4
|
+
import {
|
|
5
|
+
AudioEventPayload,
|
|
6
|
+
AudioStreamResult,
|
|
7
|
+
RecordingOptions,
|
|
8
|
+
} from "./ExpoAudioStream.types";
|
|
114
9
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
10
|
+
const log = debug("expo-audio-stream:useAudioRecording");
|
|
11
|
+
class ExpoAudioStreamWeb extends EventEmitter {
|
|
12
|
+
mediaRecorder: MediaRecorder | null;
|
|
13
|
+
audioChunks: Blob[];
|
|
14
|
+
isRecording: boolean;
|
|
15
|
+
isPaused: boolean;
|
|
16
|
+
recordingStartTime: number;
|
|
17
|
+
pausedTime: number;
|
|
18
|
+
currentDuration: number;
|
|
19
|
+
currentSize: number;
|
|
20
|
+
currentInterval: number;
|
|
21
|
+
lastEmittedSize: number;
|
|
22
|
+
streamUuid: string | null;
|
|
23
|
+
|
|
24
|
+
constructor() {
|
|
25
|
+
const mockNativeModule = {
|
|
26
|
+
addListener: (eventName: string) => {
|
|
27
|
+
// Not used on web
|
|
28
|
+
},
|
|
29
|
+
removeListeners: (count: number) => {
|
|
30
|
+
// Not used on web
|
|
31
|
+
},
|
|
32
|
+
};
|
|
33
|
+
super(mockNativeModule); // Pass the mock native module to the parent class
|
|
34
|
+
|
|
35
|
+
this.mediaRecorder = null;
|
|
36
|
+
this.audioChunks = [];
|
|
37
|
+
this.isRecording = false;
|
|
38
|
+
this.isPaused = false;
|
|
39
|
+
this.recordingStartTime = 0;
|
|
40
|
+
this.pausedTime = 0;
|
|
41
|
+
this.currentDuration = 0;
|
|
42
|
+
this.currentSize = 0;
|
|
43
|
+
this.currentInterval = 1000; // Default interval in ms
|
|
44
|
+
this.lastEmittedSize = 0;
|
|
45
|
+
this.streamUuid = null; // Initialize UUID on first recording start
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Utility to handle user media stream
|
|
49
|
+
async getMediaStream() {
|
|
50
|
+
try {
|
|
51
|
+
return await navigator.mediaDevices.getUserMedia({ audio: true });
|
|
52
|
+
} catch (error) {
|
|
53
|
+
console.error("Failed to get media stream:", error);
|
|
54
|
+
throw error;
|
|
137
55
|
}
|
|
56
|
+
}
|
|
138
57
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
if (this.isRecording && !this.isPaused) {
|
|
146
|
-
this.mediaRecorder.pause();
|
|
147
|
-
this.pausedTime = Date.now();
|
|
148
|
-
} else {
|
|
149
|
-
throw new Error('Recording is not active or already paused');
|
|
150
|
-
}
|
|
58
|
+
// Start recording with options
|
|
59
|
+
async startRecording(options: RecordingOptions = {}) {
|
|
60
|
+
if (this.isRecording) {
|
|
61
|
+
throw new Error("Recording is already in progress");
|
|
151
62
|
}
|
|
152
63
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
64
|
+
const stream = await this.getMediaStream();
|
|
65
|
+
this.mediaRecorder = new MediaRecorder(stream);
|
|
66
|
+
this.setupRecordingListeners();
|
|
67
|
+
this.mediaRecorder.start(options.interval || this.currentInterval);
|
|
68
|
+
this.isRecording = true;
|
|
69
|
+
this.recordingStartTime = Date.now();
|
|
70
|
+
this.pausedTime = 0;
|
|
71
|
+
this.lastEmittedSize = 0;
|
|
72
|
+
this.streamUuid = this.generateUUID(); // Generate a UUID for the new recording session
|
|
73
|
+
const fileUri = `${this.streamUuid}.webm`;
|
|
74
|
+
return fileUri;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Setup listeners for the MediaRecorder
|
|
78
|
+
setupRecordingListeners() {
|
|
79
|
+
if (!this.mediaRecorder) {
|
|
80
|
+
throw new Error("No active media recorder");
|
|
162
81
|
}
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
82
|
+
this.mediaRecorder.ondataavailable = (event) => {
|
|
83
|
+
this.audioChunks.push(event.data);
|
|
84
|
+
this.currentSize += event.data.size; // Update the size of the recording
|
|
85
|
+
this.emitAudioEvent(event.data); // Emit the event with the correct payload
|
|
86
|
+
this.lastEmittedSize = this.currentSize;
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
this.mediaRecorder.onstop = () => {
|
|
90
|
+
this.isRecording = false;
|
|
91
|
+
log("Recording stopped", this.audioChunks);
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
this.mediaRecorder.onpause = () => {
|
|
95
|
+
this.isPaused = true;
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
this.mediaRecorder.onresume = () => {
|
|
99
|
+
this.isPaused = false;
|
|
100
|
+
this.recordingStartTime += Date.now() - this.pausedTime; // Adjust start time after resuming
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
emitAudioEvent(data: Blob) {
|
|
105
|
+
const fileUri = `${this.streamUuid}.webm`;
|
|
106
|
+
const audioEventPayload: AudioEventPayload = {
|
|
107
|
+
fileUri,
|
|
108
|
+
mimeType: "audio/webm",
|
|
109
|
+
from: this.lastEmittedSize, // Since this might be continuously streaming, adjust accordingly
|
|
110
|
+
deltaSize: data.size,
|
|
111
|
+
totalSize: this.currentSize,
|
|
112
|
+
buffer: data,
|
|
113
|
+
streamUuid: this.streamUuid ?? "", // Generate or manage UUID for stream identification
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
this.emit("AudioData", audioEventPayload);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Helper method to generate a UUID
|
|
120
|
+
generateUUID() {
|
|
121
|
+
// Implementation of UUID generation (use a library or custom method)
|
|
122
|
+
return "xxxx-xxxx-xxxx-xxxx".replace(/[x]/g, (c) => {
|
|
123
|
+
const r = (Math.random() * 16) | 0,
|
|
124
|
+
v = c === "x" ? r : (r & 0x3) | 0x8;
|
|
125
|
+
return v.toString(16);
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Stop recording
|
|
130
|
+
async stopRecording(): Promise<AudioStreamResult | null> {
|
|
131
|
+
this.mediaRecorder?.stop();
|
|
132
|
+
this.isRecording = false;
|
|
133
|
+
this.currentDuration = (Date.now() - this.recordingStartTime) / 1000;
|
|
134
|
+
const result: AudioStreamResult = {
|
|
135
|
+
fileUri: `${this.streamUuid}.webm`,
|
|
136
|
+
duration: this.currentDuration,
|
|
137
|
+
size: this.currentSize,
|
|
138
|
+
mimeType: "audio/webm",
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
return result;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Pause recording
|
|
145
|
+
async pauseRecording() {
|
|
146
|
+
if (!this.mediaRecorder) {
|
|
147
|
+
throw new Error("No active media recorder");
|
|
166
148
|
}
|
|
167
149
|
|
|
168
|
-
|
|
169
|
-
|
|
150
|
+
if (this.isRecording && !this.isPaused) {
|
|
151
|
+
this.mediaRecorder.pause();
|
|
152
|
+
this.pausedTime = Date.now();
|
|
153
|
+
} else {
|
|
154
|
+
throw new Error("Recording is not active or already paused");
|
|
170
155
|
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Get current status
|
|
159
|
+
status() {
|
|
160
|
+
return {
|
|
161
|
+
isRecording: this.isRecording,
|
|
162
|
+
isPaused: this.isPaused,
|
|
163
|
+
duration: Date.now() - this.recordingStartTime,
|
|
164
|
+
size: this.currentSize,
|
|
165
|
+
interval: this.currentInterval,
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
listAudioFiles() {
|
|
170
|
+
// Not applicable on web
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
clearAudioFiles() {
|
|
174
|
+
// Not applicable on web
|
|
175
|
+
}
|
|
171
176
|
}
|
|
172
177
|
|
|
173
|
-
export default new ExpoAudioStreamWeb();
|
|
178
|
+
export default new ExpoAudioStreamWeb();
|
package/src/index.ts
CHANGED
|
@@ -1,12 +1,18 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
EventEmitter,
|
|
3
|
+
NativeModulesProxy,
|
|
4
|
+
type Subscription,
|
|
5
|
+
} from "expo-modules-core";
|
|
2
6
|
|
|
3
7
|
// Import the native module. On web, it will be resolved to ExpoAudioStream.web.ts
|
|
4
8
|
// and on native platforms to ExpoAudioStream.ts
|
|
5
|
-
import
|
|
6
|
-
import
|
|
7
|
-
import { useAudioRecorder } from
|
|
9
|
+
import { AudioEventPayload } from "./ExpoAudioStream.types";
|
|
10
|
+
import ExpoAudioStreamModule from "./ExpoAudioStreamModule";
|
|
11
|
+
import { useAudioRecorder } from "./useAudioRecording";
|
|
8
12
|
|
|
9
|
-
const emitter = new EventEmitter(
|
|
13
|
+
const emitter = new EventEmitter(
|
|
14
|
+
ExpoAudioStreamModule ?? NativeModulesProxy.ExpoAudioStream,
|
|
15
|
+
);
|
|
10
16
|
|
|
11
17
|
export function listAudioFiles(): Promise<string[]> {
|
|
12
18
|
return ExpoAudioStreamModule.listAudioFiles();
|
|
@@ -16,8 +22,10 @@ export function clearAudioFiles(): Promise<void> {
|
|
|
16
22
|
return ExpoAudioStreamModule.clearAudioFiles();
|
|
17
23
|
}
|
|
18
24
|
|
|
19
|
-
export function addAudioEventListener(
|
|
20
|
-
|
|
25
|
+
export function addAudioEventListener(
|
|
26
|
+
listener: (event: AudioEventPayload) => void,
|
|
27
|
+
): Subscription {
|
|
28
|
+
return emitter.addListener<AudioEventPayload>("AudioData", listener);
|
|
21
29
|
}
|
|
22
30
|
|
|
23
31
|
export { AudioEventPayload, useAudioRecorder };
|
package/src/useAudioRecording.ts
CHANGED
|
@@ -1,134 +1,160 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
1
|
+
import { decode as atob } from "base-64";
|
|
2
|
+
import debug from "debug";
|
|
3
|
+
import { Platform } from "expo-modules-core";
|
|
3
4
|
import { useCallback, useEffect, useState } from "react";
|
|
4
|
-
import ExpoAudioStreamModule from './ExpoAudioStreamModule';
|
|
5
|
-
import { AudioEventPayload, AudioStreamResult, AudioStreamStatus, RecordingOptions } from "./ExpoAudioStream.types";
|
|
6
|
-
import { addAudioEventListener } from '.';
|
|
7
|
-
import * as FileSystem from 'expo-file-system';
|
|
8
|
-
import { decode as atob } from 'base-64';
|
|
9
|
-
import debug from 'debug';
|
|
10
5
|
|
|
11
|
-
|
|
6
|
+
import { addAudioEventListener } from ".";
|
|
7
|
+
import {
|
|
8
|
+
AudioStreamResult,
|
|
9
|
+
AudioStreamStatus,
|
|
10
|
+
RecordingOptions,
|
|
11
|
+
} from "./ExpoAudioStream.types";
|
|
12
|
+
import ExpoAudioStreamModule from "./ExpoAudioStreamModule";
|
|
12
13
|
|
|
13
|
-
const log =
|
|
14
|
+
const log = debug("expo-audio-stream:useAudioRecording");
|
|
14
15
|
|
|
15
16
|
interface UseAudioRecorderState {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
17
|
+
startRecording: (_: RecordingOptions) => Promise<string | null>;
|
|
18
|
+
stopRecording: () => Promise<AudioStreamResult | null>;
|
|
19
|
+
pauseRecording: () => void;
|
|
20
|
+
isRecording: boolean;
|
|
21
|
+
isPaused: boolean;
|
|
22
|
+
duration: number; // Duration of the recording
|
|
23
|
+
size: number; // Size in bytes of the recorded audio
|
|
23
24
|
}
|
|
24
25
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
const interval = setInterval(() => {
|
|
35
|
-
const status: AudioStreamStatus = ExpoAudioStreamModule.status()
|
|
36
|
-
setDuration(status.duration);
|
|
37
|
-
setSize(status.size);
|
|
38
|
-
}, 1000);
|
|
39
|
-
return () => clearInterval(interval);
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
return () => null;
|
|
43
|
-
}, [isRecording, isPaused])
|
|
44
|
-
|
|
26
|
+
export function useAudioRecorder({
|
|
27
|
+
onAudioStream,
|
|
28
|
+
}: {
|
|
29
|
+
onAudioStream?: (buffer: Blob) => void;
|
|
30
|
+
}): UseAudioRecorderState {
|
|
31
|
+
const [isRecording, setIsRecording] = useState(false);
|
|
32
|
+
const [isPaused, setIsPaused] = useState(false);
|
|
33
|
+
const [duration, setDuration] = useState(0);
|
|
34
|
+
const [size, setSize] = useState(0);
|
|
45
35
|
|
|
46
36
|
useEffect(() => {
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
content[i] = binaryData.charCodeAt(i);
|
|
59
|
-
}
|
|
60
|
-
const audioBlob = new Blob([content], { type: mimeType });
|
|
37
|
+
if (isRecording || isPaused) {
|
|
38
|
+
const interval = setInterval(() => {
|
|
39
|
+
const status: AudioStreamStatus = ExpoAudioStreamModule.status();
|
|
40
|
+
setDuration(status.duration);
|
|
41
|
+
setSize(status.size);
|
|
42
|
+
}, 1000);
|
|
43
|
+
return () => clearInterval(interval);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return () => null;
|
|
47
|
+
}, [isRecording, isPaused]);
|
|
61
48
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
49
|
+
useEffect(() => {
|
|
50
|
+
const subscribe = addAudioEventListener(
|
|
51
|
+
async ({
|
|
52
|
+
fileUri,
|
|
53
|
+
deltaSize,
|
|
54
|
+
totalSize,
|
|
55
|
+
from,
|
|
56
|
+
streamUuid,
|
|
57
|
+
encoded,
|
|
58
|
+
mimeType,
|
|
59
|
+
buffer,
|
|
60
|
+
}) => {
|
|
61
|
+
log(`Received audio event:`, {
|
|
62
|
+
fileUri,
|
|
63
|
+
deltaSize,
|
|
64
|
+
totalSize,
|
|
65
|
+
mimeType,
|
|
66
|
+
from,
|
|
67
|
+
streamUuid,
|
|
68
|
+
encodedLength: encoded?.length,
|
|
69
|
+
});
|
|
70
|
+
if (deltaSize > 0) {
|
|
71
|
+
// Coming from native ( ios / android ) otherwise buffer is set
|
|
72
|
+
if (Platform.OS !== "web") {
|
|
73
|
+
// Read the audio file as a base64 string for comparison
|
|
74
|
+
try {
|
|
75
|
+
// convert encoded string to binary data
|
|
76
|
+
const binaryData = atob(encoded);
|
|
77
|
+
const content = new Uint8Array(binaryData.length);
|
|
78
|
+
for (let i = 0; i < binaryData.length; i++) {
|
|
79
|
+
content[i] = binaryData.charCodeAt(i);
|
|
80
|
+
}
|
|
81
|
+
const audioBlob = new Blob([content], { type: mimeType });
|
|
82
|
+
|
|
83
|
+
// Below code is optional, used to compare encoded data to audio on file system
|
|
84
|
+
// Fetch the audio data from the fileUri
|
|
85
|
+
// const options = {
|
|
86
|
+
// encoding: FileSystem.EncodingType.Base64,
|
|
87
|
+
// position: from,
|
|
88
|
+
// length: deltaSize,
|
|
89
|
+
// };
|
|
90
|
+
// const base64Content = await FileSystem.readAsStringAsync(fileUri, options);
|
|
91
|
+
// const binaryData = atob(base64Content);
|
|
92
|
+
// const content = new Uint8Array(binaryData.length);
|
|
93
|
+
// for (let i = 0; i < binaryData.length; i++) {
|
|
94
|
+
// content[i] = binaryData.charCodeAt(i);
|
|
95
|
+
// }
|
|
96
|
+
// const audioBlob = new Blob([content], { type: 'application/octet-stream' }); // Create a Blob from the byte array
|
|
97
|
+
// console.debug(`Read audio file (len: ${content.length}) vs ${deltaSize}`)
|
|
98
|
+
|
|
99
|
+
onAudioStream?.(audioBlob);
|
|
100
|
+
} catch (error) {
|
|
101
|
+
console.error("Error reading audio file:", error);
|
|
85
102
|
}
|
|
103
|
+
} else if (buffer) {
|
|
104
|
+
// Coming from web
|
|
105
|
+
onAudioStream?.(buffer);
|
|
106
|
+
}
|
|
86
107
|
}
|
|
87
|
-
|
|
108
|
+
},
|
|
109
|
+
);
|
|
88
110
|
return () => subscribe.remove();
|
|
89
111
|
}, [isRecording, onAudioStream]);
|
|
90
112
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
}
|
|
106
|
-
}, []);
|
|
107
|
-
|
|
108
|
-
const stopRecording = useCallback(async () => {
|
|
113
|
+
const startRecording = useCallback(
|
|
114
|
+
async (recordingOptions: RecordingOptions) => {
|
|
115
|
+
setIsRecording(true);
|
|
116
|
+
setIsPaused(false);
|
|
117
|
+
setSize(0);
|
|
118
|
+
setDuration(0);
|
|
119
|
+
try {
|
|
120
|
+
log(`start recoding`, recordingOptions);
|
|
121
|
+
const fileUrl =
|
|
122
|
+
await ExpoAudioStreamModule.startRecording(recordingOptions);
|
|
123
|
+
|
|
124
|
+
return fileUrl;
|
|
125
|
+
} catch (error) {
|
|
126
|
+
console.error("Error starting recording:", error);
|
|
109
127
|
setIsRecording(false);
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
128
|
+
}
|
|
129
|
+
},
|
|
130
|
+
[],
|
|
131
|
+
);
|
|
132
|
+
|
|
133
|
+
const stopRecording = useCallback(async () => {
|
|
134
|
+
setIsRecording(false);
|
|
135
|
+
setIsPaused(false);
|
|
136
|
+
const result: AudioStreamResult =
|
|
137
|
+
await ExpoAudioStreamModule.stopRecording();
|
|
138
|
+
return result;
|
|
139
|
+
}, []);
|
|
140
|
+
|
|
141
|
+
const pauseRecording = useCallback(async () => {
|
|
142
|
+
try {
|
|
143
|
+
await ExpoAudioStreamModule.stopRecording();
|
|
144
|
+
setIsPaused(true);
|
|
145
|
+
setIsRecording(false);
|
|
146
|
+
} catch (error) {
|
|
147
|
+
console.error("Error pausing recording:", error);
|
|
148
|
+
}
|
|
149
|
+
}, []);
|
|
150
|
+
|
|
151
|
+
return {
|
|
152
|
+
startRecording,
|
|
153
|
+
stopRecording,
|
|
154
|
+
pauseRecording,
|
|
155
|
+
isPaused,
|
|
156
|
+
isRecording,
|
|
157
|
+
duration,
|
|
158
|
+
size,
|
|
159
|
+
};
|
|
160
|
+
}
|