@siteed/expo-audio-stream 1.0.1 → 1.0.2

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.
Files changed (85) hide show
  1. package/README.md +6 -6
  2. package/android/build.gradle +5 -0
  3. package/android/src/main/java/net/siteed/audiostream/AudioAnalysisData.kt +120 -0
  4. package/android/src/main/java/net/siteed/audiostream/AudioFileHandler.kt +34 -4
  5. package/android/src/main/java/net/siteed/audiostream/AudioProcessor.kt +635 -0
  6. package/android/src/main/java/net/siteed/audiostream/AudioRecorderManager.kt +194 -79
  7. package/android/src/main/java/net/siteed/audiostream/Constants.kt +1 -0
  8. package/android/src/main/java/net/siteed/audiostream/ExpoAudioStreamModule.kt +48 -2
  9. package/android/src/main/java/net/siteed/audiostream/FFT.kt +44 -0
  10. package/android/src/main/java/net/siteed/audiostream/Features.kt +56 -0
  11. package/android/src/main/java/net/siteed/audiostream/RecordingConfig.kt +12 -0
  12. package/android/src/main/test/java/net/siteed/audiostream/AudioProcessorTest.kt +56 -0
  13. package/app.plugin.js +1 -1
  14. package/build/AudioRecorder.provider.js +1 -1
  15. package/build/AudioRecorder.provider.js.map +1 -1
  16. package/build/ExpoAudioStream.native.d.ts +3 -0
  17. package/build/ExpoAudioStream.native.d.ts.map +1 -0
  18. package/build/ExpoAudioStream.native.js +6 -0
  19. package/build/ExpoAudioStream.native.js.map +1 -0
  20. package/build/ExpoAudioStream.types.d.ts +79 -6
  21. package/build/ExpoAudioStream.types.d.ts.map +1 -1
  22. package/build/ExpoAudioStream.types.js.map +1 -1
  23. package/build/ExpoAudioStream.web.d.ts +41 -0
  24. package/build/ExpoAudioStream.web.d.ts.map +1 -0
  25. package/build/ExpoAudioStream.web.js +184 -0
  26. package/build/ExpoAudioStream.web.js.map +1 -0
  27. package/build/ExpoAudioStreamModule.d.ts +2 -2
  28. package/build/ExpoAudioStreamModule.d.ts.map +1 -1
  29. package/build/ExpoAudioStreamModule.js +12 -3
  30. package/build/ExpoAudioStreamModule.js.map +1 -1
  31. package/build/WebRecorder.d.ts +47 -0
  32. package/build/WebRecorder.d.ts.map +1 -0
  33. package/build/WebRecorder.js +243 -0
  34. package/build/WebRecorder.js.map +1 -0
  35. package/build/index.d.ts +14 -5
  36. package/build/index.d.ts.map +1 -1
  37. package/build/index.js +106 -7
  38. package/build/index.js.map +1 -1
  39. package/build/inlineAudioWebWorker.d.ts +3 -0
  40. package/build/inlineAudioWebWorker.d.ts.map +1 -0
  41. package/build/inlineAudioWebWorker.js +340 -0
  42. package/build/inlineAudioWebWorker.js.map +1 -0
  43. package/build/useAudioRecording.d.ts +24 -9
  44. package/build/useAudioRecording.d.ts.map +1 -1
  45. package/build/useAudioRecording.js +107 -29
  46. package/build/useAudioRecording.js.map +1 -1
  47. package/build/utils.d.ts +31 -0
  48. package/build/utils.d.ts.map +1 -0
  49. package/build/utils.js +143 -0
  50. package/build/utils.js.map +1 -0
  51. package/expo-module.config.json +13 -4
  52. package/ios/AudioAnalysisData.swift +39 -0
  53. package/ios/AudioProcessingHelpers.swift +59 -0
  54. package/ios/AudioProcessor.swift +317 -0
  55. package/ios/AudioStreamError.swift +7 -0
  56. package/ios/AudioStreamManager.swift +204 -52
  57. package/ios/AudioStreamManagerDelegate.swift +4 -0
  58. package/ios/DataPoint.swift +41 -0
  59. package/ios/ExpoAudioStreamModule.swift +188 -6
  60. package/ios/Features.swift +44 -0
  61. package/ios/RecordingResult.swift +19 -0
  62. package/ios/RecordingSettings.swift +13 -0
  63. package/ios/WaveformExtractor.swift +105 -0
  64. package/package.json +9 -9
  65. package/plugin/tsconfig.json +13 -8
  66. package/publish.sh +8 -0
  67. package/src/AudioRecorder.provider.tsx +1 -1
  68. package/src/ExpoAudioStream.native.ts +6 -0
  69. package/src/ExpoAudioStream.types.ts +97 -11
  70. package/src/ExpoAudioStream.web.ts +228 -0
  71. package/src/ExpoAudioStreamModule.ts +17 -3
  72. package/src/WebRecorder.ts +364 -0
  73. package/src/index.ts +166 -20
  74. package/src/inlineAudioWebWorker.tsx +340 -0
  75. package/src/useAudioRecording.tsx +410 -0
  76. package/src/utils.ts +189 -0
  77. package/build/ExpoAudioStreamModule.web.d.ts +0 -37
  78. package/build/ExpoAudioStreamModule.web.d.ts.map +0 -1
  79. package/build/ExpoAudioStreamModule.web.js +0 -156
  80. package/build/ExpoAudioStreamModule.web.js.map +0 -1
  81. package/docs/demo.gif +0 -0
  82. package/release-it.js +0 -18
  83. package/src/ExpoAudioStreamModule.web.ts +0 -181
  84. package/src/useAudioRecording.ts +0 -268
  85. package/yarn-error.log +0 -7793
@@ -1,156 +0,0 @@
1
- import debug from "debug";
2
- import { EventEmitter } from "expo-modules-core";
3
- const log = debug("expo-audio-stream:useAudioRecording");
4
- class ExpoAudioStreamWeb extends EventEmitter {
5
- mediaRecorder;
6
- audioChunks;
7
- isRecording;
8
- isPaused;
9
- recordingStartTime;
10
- pausedTime;
11
- currentDurationMs;
12
- currentSize;
13
- currentInterval;
14
- lastEmittedSize;
15
- lastEmittedTime;
16
- streamUuid;
17
- constructor() {
18
- const mockNativeModule = {
19
- addListener: (eventName) => {
20
- // Not used on web
21
- },
22
- removeListeners: (count) => {
23
- // Not used on web
24
- },
25
- };
26
- super(mockNativeModule); // Pass the mock native module to the parent class
27
- this.mediaRecorder = null;
28
- this.audioChunks = [];
29
- this.isRecording = false;
30
- this.isPaused = false;
31
- this.recordingStartTime = 0;
32
- this.pausedTime = 0;
33
- this.currentDurationMs = 0;
34
- this.currentSize = 0;
35
- this.currentInterval = 1000; // Default interval in ms
36
- this.lastEmittedSize = 0;
37
- this.lastEmittedTime = 0;
38
- this.streamUuid = null; // Initialize UUID on first recording start
39
- }
40
- // Utility to handle user media stream
41
- async getMediaStream() {
42
- try {
43
- return await navigator.mediaDevices.getUserMedia({ audio: true });
44
- }
45
- catch (error) {
46
- console.error("Failed to get media stream:", error);
47
- throw error;
48
- }
49
- }
50
- // Start recording with options
51
- async startRecording(options = {}) {
52
- if (this.isRecording) {
53
- throw new Error("Recording is already in progress");
54
- }
55
- const stream = await this.getMediaStream();
56
- this.mediaRecorder = new MediaRecorder(stream);
57
- this.setupRecordingListeners();
58
- this.mediaRecorder.start(options.interval || this.currentInterval);
59
- this.isRecording = true;
60
- this.recordingStartTime = Date.now();
61
- this.pausedTime = 0;
62
- this.lastEmittedSize = 0;
63
- this.lastEmittedTime = 0;
64
- this.streamUuid = this.generateUUID(); // Generate a UUID for the new recording session
65
- const fileUri = `${this.streamUuid}.webm`;
66
- const streamConfig = {
67
- fileUri,
68
- mimeType: "audio/webm",
69
- };
70
- return streamConfig;
71
- }
72
- // Setup listeners for the MediaRecorder
73
- setupRecordingListeners() {
74
- if (!this.mediaRecorder) {
75
- throw new Error("No active media recorder");
76
- }
77
- this.mediaRecorder.ondataavailable = (event) => {
78
- this.audioChunks.push(event.data);
79
- this.currentSize += event.data.size; // Update the size of the recording
80
- this.emitAudioEvent({ data: event.data, position: this.lastEmittedTime });
81
- this.lastEmittedTime = event.timeStamp;
82
- this.lastEmittedSize = this.currentSize;
83
- };
84
- this.mediaRecorder.onstop = () => {
85
- this.isRecording = false;
86
- log("Recording stopped", this.audioChunks);
87
- };
88
- this.mediaRecorder.onpause = () => {
89
- this.isPaused = true;
90
- };
91
- this.mediaRecorder.onresume = () => {
92
- this.isPaused = false;
93
- this.recordingStartTime += Date.now() - this.pausedTime; // Adjust start time after resuming
94
- };
95
- }
96
- emitAudioEvent({ data, position }) {
97
- const fileUri = `${this.streamUuid}.webm`;
98
- const audioEventPayload = {
99
- fileUri,
100
- mimeType: "audio/webm",
101
- lastEmittedSize: this.lastEmittedSize, // Since this might be continuously streaming, adjust accordingly
102
- deltaSize: data.size,
103
- position,
104
- totalSize: this.currentSize,
105
- buffer: data,
106
- streamUuid: this.streamUuid ?? "", // Generate or manage UUID for stream identification
107
- };
108
- this.emit("AudioData", audioEventPayload);
109
- }
110
- // Helper method to generate a UUID
111
- generateUUID() {
112
- // Implementation of UUID generation (use a library or custom method)
113
- return "xxxx-xxxx-xxxx-xxxx".replace(/[x]/g, (c) => {
114
- const r = (Math.random() * 16) | 0, v = c === "x" ? r : (r & 0x3) | 0x8;
115
- return v.toString(16);
116
- });
117
- }
118
- // Stop recording
119
- async stopRecording() {
120
- this.mediaRecorder?.stop();
121
- this.isRecording = false;
122
- this.currentDurationMs = Date.now() - this.recordingStartTime;
123
- const result = {
124
- fileUri: `${this.streamUuid}.webm`,
125
- duration: this.currentDurationMs,
126
- size: this.currentSize,
127
- mimeType: "audio/webm",
128
- };
129
- return result;
130
- }
131
- // Pause recording
132
- async pauseRecording() {
133
- if (!this.mediaRecorder) {
134
- throw new Error("No active media recorder");
135
- }
136
- if (this.isRecording && !this.isPaused) {
137
- this.mediaRecorder.pause();
138
- this.pausedTime = Date.now();
139
- }
140
- else {
141
- throw new Error("Recording is not active or already paused");
142
- }
143
- }
144
- // Get current status
145
- status() {
146
- return {
147
- isRecording: this.isRecording,
148
- isPaused: this.isPaused,
149
- duration: Date.now() - this.recordingStartTime,
150
- size: this.currentSize,
151
- interval: this.currentInterval,
152
- };
153
- }
154
- }
155
- export default new ExpoAudioStreamWeb();
156
- //# sourceMappingURL=ExpoAudioStreamModule.web.js.map
@@ -1 +0,0 @@
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/docs/demo.gif DELETED
Binary file
package/release-it.js DELETED
@@ -1,18 +0,0 @@
1
- module.exports = {
2
- git: {
3
- "commitMessage": "chore: release ${version}",
4
- "tagName": "v${version}",
5
- "requireCleanWorkingDir": false
6
- },
7
- npm: {
8
- "publish": true
9
- },
10
- github: {
11
- "release": true
12
- },
13
- plugins: {
14
- "@release-it/conventional-changelog": {
15
- "preset": "angular",
16
- }
17
- }
18
- }
@@ -1,181 +0,0 @@
1
- import debug from "debug";
2
- import { EventEmitter } from "expo-modules-core";
3
-
4
- import {
5
- AudioEventPayload,
6
- AudioStreamResult,
7
- RecordingConfig,
8
- StartAudioStreamResult,
9
- } from "./ExpoAudioStream.types";
10
-
11
- const log = debug("expo-audio-stream:useAudioRecording");
12
- class ExpoAudioStreamWeb extends EventEmitter {
13
- mediaRecorder: MediaRecorder | null;
14
- audioChunks: Blob[];
15
- isRecording: boolean;
16
- isPaused: boolean;
17
- recordingStartTime: number;
18
- pausedTime: number;
19
- currentDurationMs: number;
20
- currentSize: number;
21
- currentInterval: number;
22
- lastEmittedSize: number;
23
- lastEmittedTime: number;
24
- streamUuid: string | null;
25
-
26
- constructor() {
27
- const mockNativeModule = {
28
- addListener: (eventName: string) => {
29
- // Not used on web
30
- },
31
- removeListeners: (count: number) => {
32
- // Not used on web
33
- },
34
- };
35
- super(mockNativeModule); // Pass the mock native module to the parent class
36
-
37
- this.mediaRecorder = null;
38
- this.audioChunks = [];
39
- this.isRecording = false;
40
- this.isPaused = false;
41
- this.recordingStartTime = 0;
42
- this.pausedTime = 0;
43
- this.currentDurationMs = 0;
44
- this.currentSize = 0;
45
- this.currentInterval = 1000; // Default interval in ms
46
- this.lastEmittedSize = 0;
47
- this.lastEmittedTime = 0;
48
- this.streamUuid = null; // Initialize UUID on first recording start
49
- }
50
-
51
- // Utility to handle user media stream
52
- async getMediaStream() {
53
- try {
54
- return await navigator.mediaDevices.getUserMedia({ audio: true });
55
- } catch (error) {
56
- console.error("Failed to get media stream:", error);
57
- throw error;
58
- }
59
- }
60
-
61
- // Start recording with options
62
- async startRecording(options: RecordingConfig = {}) {
63
- if (this.isRecording) {
64
- throw new Error("Recording is already in progress");
65
- }
66
-
67
- const stream = await this.getMediaStream();
68
- this.mediaRecorder = new MediaRecorder(stream);
69
- this.setupRecordingListeners();
70
- this.mediaRecorder.start(options.interval || this.currentInterval);
71
- this.isRecording = true;
72
- this.recordingStartTime = Date.now();
73
- this.pausedTime = 0;
74
- this.lastEmittedSize = 0;
75
- this.lastEmittedTime = 0;
76
- this.streamUuid = this.generateUUID(); // Generate a UUID for the new recording session
77
- const fileUri = `${this.streamUuid}.webm`;
78
- const streamConfig: StartAudioStreamResult = {
79
- fileUri,
80
- mimeType: "audio/webm",
81
- };
82
- return streamConfig;
83
- }
84
-
85
- // Setup listeners for the MediaRecorder
86
- setupRecordingListeners() {
87
- if (!this.mediaRecorder) {
88
- throw new Error("No active media recorder");
89
- }
90
- this.mediaRecorder.ondataavailable = (event) => {
91
- this.audioChunks.push(event.data);
92
- this.currentSize += event.data.size; // Update the size of the recording
93
-
94
- this.emitAudioEvent({ data: event.data, position: this.lastEmittedTime });
95
- this.lastEmittedTime = event.timeStamp;
96
- this.lastEmittedSize = this.currentSize;
97
- };
98
-
99
- this.mediaRecorder.onstop = () => {
100
- this.isRecording = false;
101
- log("Recording stopped", this.audioChunks);
102
- };
103
-
104
- this.mediaRecorder.onpause = () => {
105
- this.isPaused = true;
106
- };
107
-
108
- this.mediaRecorder.onresume = () => {
109
- this.isPaused = false;
110
- this.recordingStartTime += Date.now() - this.pausedTime; // Adjust start time after resuming
111
- };
112
- }
113
-
114
- emitAudioEvent({ data, position }: { data: Blob; position: number }) {
115
- const fileUri = `${this.streamUuid}.webm`;
116
- const audioEventPayload: AudioEventPayload = {
117
- fileUri,
118
- mimeType: "audio/webm",
119
- lastEmittedSize: this.lastEmittedSize, // Since this might be continuously streaming, adjust accordingly
120
- deltaSize: data.size,
121
- position,
122
- totalSize: this.currentSize,
123
- buffer: data,
124
- streamUuid: this.streamUuid ?? "", // Generate or manage UUID for stream identification
125
- };
126
-
127
- this.emit("AudioData", audioEventPayload);
128
- }
129
-
130
- // Helper method to generate a UUID
131
- generateUUID() {
132
- // Implementation of UUID generation (use a library or custom method)
133
- return "xxxx-xxxx-xxxx-xxxx".replace(/[x]/g, (c) => {
134
- const r = (Math.random() * 16) | 0,
135
- v = c === "x" ? r : (r & 0x3) | 0x8;
136
- return v.toString(16);
137
- });
138
- }
139
-
140
- // Stop recording
141
- async stopRecording(): Promise<AudioStreamResult | null> {
142
- this.mediaRecorder?.stop();
143
- this.isRecording = false;
144
- this.currentDurationMs = Date.now() - this.recordingStartTime;
145
- const result: AudioStreamResult = {
146
- fileUri: `${this.streamUuid}.webm`,
147
- duration: this.currentDurationMs,
148
- size: this.currentSize,
149
- mimeType: "audio/webm",
150
- };
151
-
152
- return result;
153
- }
154
-
155
- // Pause recording
156
- async pauseRecording() {
157
- if (!this.mediaRecorder) {
158
- throw new Error("No active media recorder");
159
- }
160
-
161
- if (this.isRecording && !this.isPaused) {
162
- this.mediaRecorder.pause();
163
- this.pausedTime = Date.now();
164
- } else {
165
- throw new Error("Recording is not active or already paused");
166
- }
167
- }
168
-
169
- // Get current status
170
- status() {
171
- return {
172
- isRecording: this.isRecording,
173
- isPaused: this.isPaused,
174
- duration: Date.now() - this.recordingStartTime,
175
- size: this.currentSize,
176
- interval: this.currentInterval,
177
- };
178
- }
179
- }
180
-
181
- export default new ExpoAudioStreamWeb();
@@ -1,268 +0,0 @@
1
- import { Platform } from "expo-modules-core";
2
- import { useCallback, useEffect, useReducer, useRef } from "react";
3
-
4
- import { addAudioEventListener } from ".";
5
- import {
6
- AudioEventPayload,
7
- AudioStreamResult,
8
- AudioStreamStatus,
9
- RecordingConfig,
10
- StartAudioStreamResult,
11
- } from "./ExpoAudioStream.types";
12
- import ExpoAudioStreamModule from "./ExpoAudioStreamModule";
13
-
14
- export interface AudioDataEvent {
15
- data: string | Blob;
16
- position: number;
17
- fileUri: string;
18
- eventDataSize: number;
19
- totalSize: number;
20
- }
21
-
22
- export interface UseAudioRecorderProps {
23
- debug?: boolean;
24
- }
25
- export interface UseAudioRecorderState {
26
- startRecording: (_: RecordingConfig) => Promise<StartAudioStreamResult>;
27
- stopRecording: () => Promise<AudioStreamResult | null>;
28
- pauseRecording: () => void;
29
- resumeRecording: () => void;
30
- isRecording: boolean;
31
- isPaused: boolean;
32
- duration: number; // Duration of the recording
33
- size: number; // Size in bytes of the recorded audio
34
- }
35
-
36
- interface RecorderState {
37
- isRecording: boolean;
38
- isPaused: boolean;
39
- duration: number;
40
- size: number;
41
- }
42
-
43
- type RecorderAction =
44
- | { type: "START" | "STOP" | "PAUSE" | "RESUME" }
45
- | { type: "UPDATE_STATUS"; payload: { duration: number; size: number } };
46
-
47
- function recorderReducer(
48
- state: RecorderState,
49
- action: RecorderAction,
50
- ): RecorderState {
51
- switch (action.type) {
52
- case "START":
53
- return {
54
- ...state,
55
- isRecording: true,
56
- isPaused: false,
57
- duration: 0,
58
- size: 0,
59
- };
60
- case "STOP":
61
- return { ...state, isRecording: false, isPaused: false };
62
- case "PAUSE":
63
- return { ...state, isPaused: true, isRecording: false };
64
- case "RESUME":
65
- return { ...state, isPaused: false, isRecording: true };
66
- case "UPDATE_STATUS":
67
- return {
68
- ...state,
69
- duration: action.payload.duration,
70
- size: action.payload.size,
71
- };
72
- default:
73
- return state;
74
- }
75
- }
76
- const TAG = "[ useAudioRecorder ] ";
77
-
78
- export function useAudioRecorder({
79
- debug = false,
80
- }: UseAudioRecorderProps = {}): UseAudioRecorderState {
81
- const [state, dispatch] = useReducer(recorderReducer, {
82
- isRecording: false,
83
- isPaused: false,
84
- duration: 0,
85
- size: 0,
86
- });
87
-
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
- }
100
- }
101
- },
102
- [debug],
103
- );
104
-
105
- const handleAudioEvent = useCallback(
106
- async (eventData: AudioEventPayload) => {
107
- const {
108
- fileUri,
109
- deltaSize,
110
- totalSize,
111
- lastEmittedSize,
112
- position,
113
- streamUuid,
114
- encoded,
115
- mimeType,
116
- buffer,
117
- } = eventData;
118
- logDebug(`useAudioRecorder] Received audio event:`, {
119
- fileUri,
120
- deltaSize,
121
- totalSize,
122
- position,
123
- mimeType,
124
- lastEmittedSize,
125
- streamUuid,
126
- encodedLength: encoded?.length,
127
- });
128
- if (deltaSize === 0) {
129
- // Ignore packet with no data
130
- return;
131
- }
132
- try {
133
- // Coming from native ( ios / android ) otherwise buffer is set
134
- if (Platform.OS !== "web") {
135
- // Read the audio file as a base64 string for comparison
136
- if (!encoded) {
137
- console.error(`${TAG} Encoded audio data is missing`);
138
- throw new Error("Encoded audio data is missing");
139
- }
140
- onAudioStreamRef.current?.({
141
- data: encoded,
142
- position,
143
- fileUri,
144
- eventDataSize: deltaSize,
145
- totalSize,
146
- });
147
- } else if (buffer) {
148
- // Coming from web
149
- onAudioStreamRef.current?.({
150
- data: buffer,
151
- position,
152
- fileUri,
153
- eventDataSize: deltaSize,
154
- totalSize,
155
- });
156
- }
157
- } catch (error) {
158
- console.error(`${TAG} Error processing audio event:`, error);
159
- }
160
- },
161
- [logDebug],
162
- );
163
-
164
- const checkStatus = useCallback(async () => {
165
- try {
166
- if (!state.isRecording) {
167
- logDebug(`${TAG} Not recording, exiting status check.`);
168
- return;
169
- }
170
-
171
- const status: AudioStreamStatus = ExpoAudioStreamModule.status();
172
- if (debug) {
173
- logDebug(`${TAG} Status:`, status);
174
- }
175
-
176
- if (!status.isRecording) {
177
- dispatch({ type: "STOP" });
178
- } else {
179
- dispatch({
180
- type: "UPDATE_STATUS",
181
- payload: { duration: status.duration, size: status.size },
182
- });
183
- }
184
- } catch (error) {
185
- console.error(`${TAG} Error getting status:`, error);
186
- }
187
- }, [state.isRecording, logDebug]);
188
-
189
- useEffect(() => {
190
- let interval: number;
191
- if (state.isRecording) {
192
- interval = setInterval(checkStatus, 1000);
193
- }
194
- return () => {
195
- if (interval) {
196
- clearInterval(interval);
197
- }
198
- };
199
- }, [checkStatus, state.isRecording]);
200
-
201
- useEffect(() => {
202
- logDebug(`${TAG} Registering audio event listener`);
203
- const subscribe = addAudioEventListener(handleAudioEvent);
204
- logDebug(`${TAG} Subscribed to audio event listener`, subscribe);
205
-
206
- return () => {
207
- logDebug(`${TAG} Removing audio event listener`);
208
- subscribe.remove();
209
- };
210
- }, [handleAudioEvent, logDebug]);
211
-
212
- const startRecording = useCallback(
213
- async (recordingOptions: RecordingConfig) => {
214
- if (debug) {
215
- logDebug(`${TAG} start recoding`, recordingOptions);
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
- }
225
- const startResult: StartAudioStreamResult =
226
- await ExpoAudioStreamModule.startRecording(options);
227
- dispatch({ type: "START" });
228
-
229
- return startResult;
230
- },
231
- [logDebug],
232
- );
233
-
234
- const stopRecording = useCallback(async () => {
235
- logDebug(`${TAG} stoping recording`);
236
- const stopResult: AudioStreamResult =
237
- await ExpoAudioStreamModule.stopRecording();
238
- onAudioStreamRef.current = null;
239
- logDebug(`${TAG} recording stopped`, stopResult);
240
- dispatch({ type: "STOP" });
241
- return stopResult;
242
- }, [logDebug]);
243
-
244
- const pauseRecording = useCallback(async () => {
245
- logDebug(`${TAG} pause recording`);
246
- const pauseResult = await ExpoAudioStreamModule.pauseRecording();
247
- dispatch({ type: "PAUSE" });
248
- return pauseResult;
249
- }, [logDebug]);
250
-
251
- const resumeRecording = useCallback(async () => {
252
- logDebug(`${TAG} resume recording`);
253
- const resumeResult = await ExpoAudioStreamModule.resumeRecording();
254
- dispatch({ type: "RESUME" });
255
- return resumeResult;
256
- }, [logDebug]);
257
-
258
- return {
259
- startRecording,
260
- stopRecording,
261
- pauseRecording,
262
- resumeRecording,
263
- isPaused: state.isPaused,
264
- isRecording: state.isRecording,
265
- duration: state.duration,
266
- size: state.size,
267
- };
268
- }