@siteed/expo-audio-stream 0.2.0 → 0.2.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.
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
- - remove the dependency on expo-av
158
- - add multiple format of audio stream (wav, mp3, opus)
153
+ - add multiple format for native audio stream (wav, mp3, opus)
159
154
 
@@ -1,3 +1,2 @@
1
- ;
2
1
  export {};
3
2
  //# sourceMappingURL=ExpoAudioStream.types.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"ExpoAudioStream.types.js","sourceRoot":"","sources":["../src/ExpoAudioStream.types.ts"],"names":[],"mappings":"AASC,CAAC","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
+ {"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 'expo-modules-core';
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('ExpoAudioStream');
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 '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
+ {"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":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACjD,OAAO,EAAqB,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAIjG,cAAM,kBAAmB,SAAQ,YAAY;IACzC,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;;IA4BpB,cAAc;IAUd,cAAc,CAAC,OAAO,GAAE,gBAAqB;IAmBnD,uBAAuB;IA0BvB,cAAc,CAAC,IAAI,EAAE,IAAI;IAgBxB,YAAY;IASP,aAAa,IAAI,OAAO,CAAC,iBAAiB,GAAG,IAAI,CAAC;IAelD,cAAc;IAcpB,MAAM;;;;;;;IAUN,cAAc;IAId,eAAe;CAGlB;;AAED,wBAAwC"}
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('Recording is already in progress');
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('No active media recorder');
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('Recording stopped', this.audioChunks);
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 += (Date.now() - this.pausedTime); // Adjust start time after resuming
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: fileUri,
92
- mimeType: 'audio/webm',
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 ?? '', // Generate or manage UUID for stream identification
97
+ streamUuid: this.streamUuid ?? "", // Generate or manage UUID for stream identification
98
98
  };
99
- this.emit('AudioData', audioEventPayload);
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 'xxxx-xxxx-xxxx-xxxx'.replace(/[x]/g, (c) => {
105
- const r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8);
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: 'audio/webm',
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('No active media recorder');
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('Recording is not active or already paused');
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":"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","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":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAQjD,MAAM,GAAG,GAAG,KAAK,CAAC,qCAAqC,CAAC,CAAC;AACzD,MAAM,kBAAmB,SAAQ,YAAY;IAC3C,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;QACE,MAAM,gBAAgB,GAAG;YACvB,WAAW,EAAE,CAAC,SAAiB,EAAE,EAAE;gBACjC,kBAAkB;YACpB,CAAC;YACD,eAAe,EAAE,CAAC,KAAa,EAAE,EAAE;gBACjC,kBAAkB;YACpB,CAAC;SACF,CAAC;QACF,KAAK,CAAC,gBAAgB,CAAC,CAAC,CAAC,kDAAkD;QAE3E,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;IACrE,CAAC;IAED,sCAAsC;IACtC,KAAK,CAAC,cAAc;QAClB,IAAI,CAAC;YACH,OAAO,MAAM,SAAS,CAAC,YAAY,CAAC,YAAY,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;QACpE,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,CAAC,KAAK,CAAC,6BAA6B,EAAE,KAAK,CAAC,CAAC;YACpD,MAAM,KAAK,CAAC;QACd,CAAC;IACH,CAAC;IAED,+BAA+B;IAC/B,KAAK,CAAC,cAAc,CAAC,UAA4B,EAAE;QACjD,IAAI,IAAI,CAAC,WAAW,EAAE,CAAC;YACrB,MAAM,IAAI,KAAK,CAAC,kCAAkC,CAAC,CAAC;QACtD,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;IACjB,CAAC;IAED,wCAAwC;IACxC,uBAAuB;QACrB,IAAI,CAAC,IAAI,CAAC,aAAa,EAAE,CAAC;YACxB,MAAM,IAAI,KAAK,CAAC,0BAA0B,CAAC,CAAC;QAC9C,CAAC;QACD,IAAI,CAAC,aAAa,CAAC,eAAe,GAAG,CAAC,KAAK,EAAE,EAAE;YAC7C,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,CAAC,0CAA0C;YAC3E,IAAI,CAAC,eAAe,GAAG,IAAI,CAAC,WAAW,CAAC;QAC1C,CAAC,CAAC;QAEF,IAAI,CAAC,aAAa,CAAC,MAAM,GAAG,GAAG,EAAE;YAC/B,IAAI,CAAC,WAAW,GAAG,KAAK,CAAC;YACzB,GAAG,CAAC,mBAAmB,EAAE,IAAI,CAAC,WAAW,CAAC,CAAC;QAC7C,CAAC,CAAC;QAEF,IAAI,CAAC,aAAa,CAAC,OAAO,GAAG,GAAG,EAAE;YAChC,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC;QACvB,CAAC,CAAC;QAEF,IAAI,CAAC,aAAa,CAAC,QAAQ,GAAG,GAAG,EAAE;YACjC,IAAI,CAAC,QAAQ,GAAG,KAAK,CAAC;YACtB,IAAI,CAAC,kBAAkB,IAAI,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,UAAU,CAAC,CAAC,mCAAmC;QAC9F,CAAC,CAAC;IACJ,CAAC;IAED,cAAc,CAAC,IAAU;QACvB,MAAM,OAAO,GAAG,GAAG,IAAI,CAAC,UAAU,OAAO,CAAC;QAC1C,MAAM,iBAAiB,GAAsB;YAC3C,OAAO;YACP,QAAQ,EAAE,YAAY;YACtB,IAAI,EAAE,IAAI,CAAC,eAAe,EAAE,iEAAiE;YAC7F,SAAS,EAAE,IAAI,CAAC,IAAI;YACpB,SAAS,EAAE,IAAI,CAAC,WAAW;YAC3B,MAAM,EAAE,IAAI;YACZ,UAAU,EAAE,IAAI,CAAC,UAAU,IAAI,EAAE,EAAE,oDAAoD;SACxF,CAAC;QAEF,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,iBAAiB,CAAC,CAAC;IAC5C,CAAC;IAED,mCAAmC;IACnC,YAAY;QACV,qEAAqE;QACrE,OAAO,qBAAqB,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC,EAAE,EAAE;YACjD,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,EAAE,CAAC,GAAG,CAAC,EAChC,CAAC,GAAG,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC,GAAG,GAAG,CAAC;YACtC,OAAO,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;QACxB,CAAC,CAAC,CAAC;IACL,CAAC;IAED,iBAAiB;IACjB,KAAK,CAAC,aAAa;QACjB,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;YAChC,OAAO,EAAE,GAAG,IAAI,CAAC,UAAU,OAAO;YAClC,QAAQ,EAAE,IAAI,CAAC,eAAe;YAC9B,IAAI,EAAE,IAAI,CAAC,WAAW;YACtB,QAAQ,EAAE,YAAY;SACvB,CAAC;QAEF,OAAO,MAAM,CAAC;IAChB,CAAC;IAED,kBAAkB;IAClB,KAAK,CAAC,cAAc;QAClB,IAAI,CAAC,IAAI,CAAC,aAAa,EAAE,CAAC;YACxB,MAAM,IAAI,KAAK,CAAC,0BAA0B,CAAC,CAAC;QAC9C,CAAC;QAED,IAAI,IAAI,CAAC,WAAW,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC;YACvC,IAAI,CAAC,aAAa,CAAC,KAAK,EAAE,CAAC;YAC3B,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QAC/B,CAAC;aAAM,CAAC;YACN,MAAM,IAAI,KAAK,CAAC,2CAA2C,CAAC,CAAC;QAC/D,CAAC;IACH,CAAC;IAED,qBAAqB;IACrB,MAAM;QACJ,OAAO;YACL,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;SAC/B,CAAC;IACJ,CAAC;IAED,cAAc;QACZ,wBAAwB;IAC1B,CAAC;IAED,eAAe;QACb,wBAAwB;IAC1B,CAAC;CACF;AAED,eAAe,IAAI,kBAAkB,EAAE,CAAC","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 'expo-modules-core';
2
- import { AudioEventPayload } from './ExpoAudioStream.types';
3
- import { useAudioRecorder } from './useAudioRecording';
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;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAoC,KAAK,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAKxF,OAAO,EAAE,iBAAiB,EAAoB,MAAM,yBAAyB,CAAC;AAC9E,OAAO,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAIvD,wBAAgB,cAAc,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC,CAElD;AAED,wBAAgB,eAAe,IAAI,OAAO,CAAC,IAAI,CAAC,CAE/C;AAED,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,CAAC,KAAK,EAAE,iBAAiB,KAAK,IAAI,GAAG,YAAY,CAEhG;AAED,OAAO,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,CAAC"}
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, EventEmitter } 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 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('AudioData', listener);
12
+ return emitter.addListener("AudioData", listener);
15
13
  }
16
14
  export { useAudioRecorder };
17
15
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,YAAY,EAAqB,MAAM,mBAAmB,CAAC;AAExF,kFAAkF;AAClF,gDAAgD;AAChD,OAAO,qBAAqB,MAAM,yBAAyB,CAAC;AAE5D,OAAO,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAEvD,MAAM,OAAO,GAAG,IAAI,YAAY,CAAC,qBAAqB,IAAI,kBAAkB,CAAC,eAAe,CAAC,CAAC;AAE9F,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,CAAC,QAA4C;IAChF,OAAO,OAAO,CAAC,WAAW,CAAoB,WAAW,EAAE,QAAQ,CAAC,CAAC;AACvE,CAAC;AAED,OAAO,EAAqB,gBAAgB,EAAE,CAAC","sourcesContent":["import { NativeModulesProxy, EventEmitter, type Subscription } 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 ExpoAudioStreamModule from './ExpoAudioStreamModule';\nimport { AudioEventPayload, RecordingOptions } from './ExpoAudioStream.types';\nimport { useAudioRecorder } from './useAudioRecording';\n\nconst emitter = new EventEmitter(ExpoAudioStreamModule ?? NativeModulesProxy.ExpoAudioStream);\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(listener: (event: AudioEventPayload) => void): Subscription {\n return emitter.addListener<AudioEventPayload>('AudioData', listener);\n}\n\nexport { AudioEventPayload, useAudioRecorder };\n"]}
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":"AAIA,OAAO,EAAqB,iBAAiB,EAAqB,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAUpH,UAAU,qBAAqB;IAC3B,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;CAChB;AAGD,wBAAgB,gBAAgB,CAAC,EAAC,aAAa,EAAC,EAAE;IAAC,aAAa,CAAC,EAAE,CAAC,MAAM,EAAE,IAAI,KAAK,IAAI,CAAA;CAAC,GAAG,qBAAqB,CA4GjH"}
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 { NativeModulesProxy, EventEmitter, Platform } from 'expo-modules-core';
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 ExpoAudioStreamModule from './ExpoAudioStreamModule';
4
- import { addAudioEventListener } from '.';
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:`, { fileUri, deltaSize, totalSize, mimeType, from, streamUuid, encodedLength: encoded?.length });
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 !== 'web') {
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('Error reading audio file:', 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('Error starting recording:', 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('Error pausing recording:', 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,kBAAkB,EAAE,YAAY,EAAqB,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAElG,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AACzD,OAAO,qBAAqB,MAAM,yBAAyB,CAAC;AAE5D,OAAO,EAAE,qBAAqB,EAAE,MAAM,GAAG,CAAC;AAE1C,OAAO,EAAE,MAAM,IAAI,IAAI,EAAE,MAAM,SAAS,CAAC;AACzC,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,MAAM,OAAO,GAAG,IAAI,YAAY,CAAC,qBAAqB,IAAI,kBAAkB,CAAC,eAAe,CAAC,CAAC;AAE9F,MAAM,GAAG,GAAI,KAAK,CAAC,qCAAqC,CAAC,CAAC;AAa1D,MAAM,UAAU,gBAAgB,CAAC,EAAC,aAAa,EAA2C;IACtF,MAAM,CAAC,WAAW,EAAE,cAAc,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC;IACtD,MAAM,CAAC,QAAQ,EAAE,WAAW,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC;IAChD,MAAM,CAAC,QAAQ,EAAE,WAAW,CAAC,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAC;IAC5C,MAAM,CAAC,IAAI,EAAE,OAAO,CAAC,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAC;IAEpC,SAAS,CAAE,GAAG,EAAE;QACZ,IAAG,WAAW,IAAI,QAAQ,EAAE,CAAC;YACzB,MAAM,QAAQ,GAAG,WAAW,CAAC,GAAG,EAAE;gBAC9B,MAAM,MAAM,GAAsB,qBAAqB,CAAC,MAAM,EAAE,CAAA;gBAChE,WAAW,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;gBAC7B,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;YACzB,CAAC,EAAE,IAAI,CAAC,CAAC;YACT,OAAO,GAAG,EAAE,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAC;QACzC,CAAC;QAED,OAAO,GAAG,EAAE,CAAC,IAAI,CAAC;IACtB,CAAC,EAAE,CAAC,WAAW,EAAE,QAAQ,CAAC,CAAC,CAAA;IAG7B,SAAS,CAAC,GAAG,EAAE;QACb,MAAM,SAAS,GAAG,qBAAqB,CAAC,KAAK,EAAE,EAAC,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,IAAI,EAAE,UAAU,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAC,EAAE,EAAE;YAC3H,GAAG,CAAC,uBAAuB,EAAE,EAAC,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,QAAQ,EAAE,IAAI,EAAE,UAAU,EAAE,aAAa,EAAE,OAAO,EAAE,MAAM,EAAC,CAAC,CAAA;YACzH,IAAG,SAAS,GAAG,CAAC,EAAE,CAAC;gBACf,+DAA+D;gBAC7D,IAAG,QAAQ,CAAC,EAAE,KAAK,KAAK,EAAE,CAAC;oBACzB,wDAAwD;oBACxD,IAAI,CAAC;wBACD,wCAAwC;wBACxC,MAAM,UAAU,GAAG,IAAI,CAAC,OAAO,CAAC,CAAC;wBACjC,MAAM,OAAO,GAAG,IAAI,UAAU,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC;wBAClD,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,UAAU,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;4BACzC,OAAO,CAAC,CAAC,CAAC,GAAG,UAAU,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC;wBAC1C,CAAC;wBACD,MAAM,SAAS,GAAG,IAAI,IAAI,CAAC,CAAC,OAAO,CAAC,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC,CAAC;wBAE1D,+EAA+E;wBAC/E,wCAAwC;wBACxC,oBAAoB;wBACpB,gDAAgD;wBAChD,sBAAsB;wBACtB,yBAAyB;wBACzB,KAAK;wBACL,8EAA8E;wBAC9E,0CAA0C;wBAC1C,qDAAqD;wBACrD,gDAAgD;wBAChD,yCAAyC;wBACzC,IAAI;wBACJ,oHAAoH;wBACpH,4EAA4E;wBAE5E,aAAa,EAAE,CAAC,SAAS,CAAC,CAAC;oBAC/B,CAAC;oBAAC,OAAO,KAAK,EAAE,CAAC;wBACb,OAAO,CAAC,KAAK,CAAC,2BAA2B,EAAE,KAAK,CAAC,CAAC;oBACtD,CAAC;gBACL,CAAC;qBAAM,IAAG,MAAM,EAAE,CAAC;oBACf,kBAAkB;oBAClB,aAAa,EAAE,CAAC,MAAM,CAAC,CAAC;gBAC5B,CAAC;YACL,CAAC;QACL,CAAC,CAAC,CAAC;QACH,OAAO,GAAG,EAAE,CAAC,SAAS,CAAC,MAAM,EAAE,CAAC;IAClC,CAAC,EAAE,CAAC,WAAW,EAAE,aAAa,CAAC,CAAC,CAAC;IAG/B,MAAM,cAAc,GAAG,WAAW,CAAC,KAAK,EAAE,gBAAkC,EAAE,EAAE;QAC5E,cAAc,CAAC,IAAI,CAAC,CAAC;QACrB,WAAW,CAAC,KAAK,CAAC,CAAC;QACnB,OAAO,CAAC,CAAC,CAAC,CAAC;QACX,WAAW,CAAC,CAAC,CAAC,CAAC;QACf,IAAI,CAAC;YACD,GAAG,CAAC,gBAAgB,EAAE,gBAAgB,CAAC,CAAA;YACvC,MAAM,OAAO,GAAG,MAAM,qBAAqB,CAAC,cAAc,CAAC,gBAAgB,CAAC,CAAC;YAE7E,OAAO,OAAO,CAAC;QACnB,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACb,OAAO,CAAC,KAAK,CAAC,2BAA2B,EAAE,KAAK,CAAC,CAAC;YAClD,cAAc,CAAC,KAAK,CAAC,CAAC;QAC1B,CAAC;IACL,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,MAAM,aAAa,GAAG,WAAW,CAAC,KAAK,IAAI,EAAE;QACzC,cAAc,CAAC,KAAK,CAAC,CAAC;QACtB,WAAW,CAAC,KAAK,CAAC,CAAC;QACnB,MAAM,MAAM,GAAsB,MAAM,qBAAqB,CAAC,aAAa,EAAE,CAAC;QAC9E,OAAO,MAAM,CAAC;IAClB,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,MAAM,cAAc,GAAG,WAAW,CAAC,KAAK,IAAI,EAAE;QAC1C,IAAI,CAAC;YACD,MAAM,qBAAqB,CAAC,aAAa,EAAE,CAAA;YAC3C,WAAW,CAAC,IAAI,CAAC,CAAC;YAClB,cAAc,CAAC,KAAK,CAAC,CAAC;QAC1B,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACb,OAAO,CAAC,KAAK,CAAC,0BAA0B,EAAE,KAAK,CAAC,CAAC;QACrD,CAAC;IACL,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,OAAO;QACH,cAAc;QACd,aAAa;QACb,cAAc;QACd,QAAQ;QACR,WAAW;QACX,QAAQ;QACR,IAAI;KACP,CAAC;AACN,CAAC","sourcesContent":["import { NativeModulesProxy, EventEmitter, type Subscription, Platform } from 'expo-modules-core';\n\nimport { useCallback, useEffect, useState } from \"react\";\nimport ExpoAudioStreamModule from './ExpoAudioStreamModule';\nimport { AudioEventPayload, AudioStreamResult, AudioStreamStatus, RecordingOptions } from \"./ExpoAudioStream.types\";\nimport { addAudioEventListener } from '.';\nimport * as FileSystem from 'expo-file-system';\nimport { decode as atob } from 'base-64';\nimport debug from 'debug';\n\nconst emitter = new EventEmitter(ExpoAudioStreamModule ?? NativeModulesProxy.ExpoAudioStream);\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\n\nexport function useAudioRecorder({onAudioStream}: {onAudioStream?: (buffer: Blob) => void}): 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\n useEffect(() => {\n const subscribe = addAudioEventListener(async ({fileUri, deltaSize, totalSize, from, streamUuid, encoded, mimeType, buffer}) => {\n log(`Received audio event:`, {fileUri, deltaSize, totalSize, mimeType, from, streamUuid, encodedLength: encoded?.length})\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 return () => subscribe.remove();\n }, [isRecording, onAudioStream]);\n\n\n const startRecording = useCallback(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 = 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 const stopRecording = useCallback(async () => {\n setIsRecording(false);\n setIsPaused(false);\n const result: AudioStreamResult = 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}"]}
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@siteed/expo-audio-stream",
3
- "version": "0.2.0",
3
+ "version": "0.2.2",
4
4
  "description": "stream audio crossplatform",
5
5
  "license": "MIT",
6
6
  "main": "build/index.js",
@@ -1,5 +1,5 @@
1
1
  import { ConfigPlugin } from "@expo/config-plugins";
2
- declare const _default: ConfigPlugin<{
3
- microphonePermission: string | false;
2
+ declare const withRecordingPermission: ConfigPlugin<{
3
+ microphonePermission: string;
4
4
  }>;
5
- export default _default;
5
+ export default withRecordingPermission;
@@ -1,17 +1,17 @@
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('../../package.json');
5
- const MICROPHONE_USAGE = 'Allow $(PRODUCT_NAME) to access your microphone';
4
+ const MICROPHONE_USAGE = "Allow $(PRODUCT_NAME) to access your microphone";
6
5
  const withRecordingPermission = (config, { microphonePermission }) => {
7
- config_plugins_1.IOSConfig.Permissions.createPermissionsPlugin({
8
- NSMicrophoneUsageDescription: MICROPHONE_USAGE,
9
- })(config, {
10
- NSMicrophoneUsageDescription: microphonePermission,
6
+ config = (0, config_plugins_1.withInfoPlist)(config, (config) => {
7
+ config.modResults["NSMicrophoneUsageDescription"] = MICROPHONE_USAGE;
8
+ return config;
11
9
  });
12
- return config_plugins_1.AndroidConfig.Permissions.withPermissions(config, [
13
- microphonePermission !== false && 'android.permission.RECORD_AUDIO',
14
- 'android.permission.MODIFY_AUDIO_SETTINGS',
15
- ].filter(Boolean));
10
+ config = (0, config_plugins_1.withAndroidManifest)(config, (config) => {
11
+ const mainApplication = config_plugins_1.AndroidConfig.Manifest.getMainApplicationOrThrow(config.modResults);
12
+ config_plugins_1.AndroidConfig.Manifest.addMetaDataItemToMainApplication(mainApplication, "android.permission.RECORD_AUDIO", MICROPHONE_USAGE);
13
+ return config;
14
+ });
15
+ return config;
16
16
  };
17
- exports.default = (0, config_plugins_1.createRunOncePlugin)(withRecordingPermission, pkg.name, pkg.version);
17
+ exports.default = withRecordingPermission;
@@ -1,27 +1,34 @@
1
1
  import {
2
2
  AndroidConfig,
3
3
  ConfigPlugin,
4
- IOSConfig,
5
- createRunOncePlugin
4
+ withAndroidManifest,
5
+ withInfoPlist,
6
6
  } from "@expo/config-plugins";
7
7
 
8
- const pkg = require('../../package.json');
9
- const MICROPHONE_USAGE = 'Allow $(PRODUCT_NAME) to access your microphone';
8
+ const MICROPHONE_USAGE = "Allow $(PRODUCT_NAME) to access your microphone";
10
9
 
11
- const withRecordingPermission: ConfigPlugin<{ microphonePermission: string | false }> = (config, { microphonePermission }) => {
12
- IOSConfig.Permissions.createPermissionsPlugin({
13
- NSMicrophoneUsageDescription: MICROPHONE_USAGE,
14
- })(config, {
15
- NSMicrophoneUsageDescription: microphonePermission,
10
+ const withRecordingPermission: ConfigPlugin<{
11
+ microphonePermission: string;
12
+ }> = (config, { microphonePermission }) => {
13
+ config = withInfoPlist(config, (config) => {
14
+ config.modResults["NSMicrophoneUsageDescription"] = MICROPHONE_USAGE;
15
+ return config;
16
16
  });
17
17
 
18
- return AndroidConfig.Permissions.withPermissions(
19
- config,
20
- [
21
- microphonePermission !== false && 'android.permission.RECORD_AUDIO',
22
- 'android.permission.MODIFY_AUDIO_SETTINGS',
23
- ].filter(Boolean) as string[]
24
- );
18
+ config = withAndroidManifest(config, (config) => {
19
+ const mainApplication = AndroidConfig.Manifest.getMainApplicationOrThrow(
20
+ config.modResults,
21
+ );
22
+
23
+ AndroidConfig.Manifest.addMetaDataItemToMainApplication(
24
+ mainApplication,
25
+ "android.permission.RECORD_AUDIO",
26
+ MICROPHONE_USAGE,
27
+ );
28
+ return config;
29
+ });
30
+
31
+ return config;
25
32
  };
26
33
 
27
- export default createRunOncePlugin(withRecordingPermission, pkg.name, pkg.version);
34
+ export default withRecordingPermission;
@@ -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 'expo-modules-core';
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('ExpoAudioStream');
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
- const log = debug("expo-audio-stream:useAudioRecording");
6
- class ExpoAudioStreamWeb extends EventEmitter {
7
- mediaRecorder: MediaRecorder | null;
8
- audioChunks: Blob[];
9
- isRecording: boolean;
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
- // Helper method to generate a UUID
116
- generateUUID() {
117
- // Implementation of UUID generation (use a library or custom method)
118
- return 'xxxx-xxxx-xxxx-xxxx'.replace(/[x]/g, (c) => {
119
- const r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8);
120
- return v.toString(16);
121
- });
122
- }
123
-
124
- // Stop recording
125
- async stopRecording(): Promise<AudioStreamResult | null> {
126
- this.mediaRecorder?.stop();
127
- this.isRecording = false;
128
- this.currentDuration = (Date.now() - this.recordingStartTime) / 1000;
129
- const result: AudioStreamResult = {
130
- fileUri: `${this.streamUuid}.webm`,
131
- duration: this.currentDuration,
132
- size: this.currentSize,
133
- mimeType: 'audio/webm',
134
- }
135
-
136
- return result;
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
- // Pause recording
140
- async pauseRecording() {
141
- if (!this.mediaRecorder) {
142
- throw new Error('No active media recorder');
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
- // Get current status
154
- status() {
155
- return {
156
- isRecording: this.isRecording,
157
- isPaused: this.isPaused,
158
- duration: Date.now() - this.recordingStartTime,
159
- size: this.currentSize,
160
- interval: this.currentInterval,
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
- listAudioFiles() {
165
- // Not applicable on web
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
- clearAudioFiles() {
169
- // Not applicable on web
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 { NativeModulesProxy, EventEmitter, type Subscription } from 'expo-modules-core';
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 ExpoAudioStreamModule from './ExpoAudioStreamModule';
6
- import { AudioEventPayload, RecordingOptions } from './ExpoAudioStream.types';
7
- import { useAudioRecorder } from './useAudioRecording';
9
+ import { AudioEventPayload } from "./ExpoAudioStream.types";
10
+ import ExpoAudioStreamModule from "./ExpoAudioStreamModule";
11
+ import { useAudioRecorder } from "./useAudioRecording";
8
12
 
9
- const emitter = new EventEmitter(ExpoAudioStreamModule ?? NativeModulesProxy.ExpoAudioStream);
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(listener: (event: AudioEventPayload) => void): Subscription {
20
- return emitter.addListener<AudioEventPayload>('AudioData', listener);
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 };
@@ -1,134 +1,160 @@
1
- import { NativeModulesProxy, EventEmitter, type Subscription, Platform } from 'expo-modules-core';
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
- const emitter = new EventEmitter(ExpoAudioStreamModule ?? NativeModulesProxy.ExpoAudioStream);
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 = debug("expo-audio-stream:useAudioRecording");
14
+ const log = debug("expo-audio-stream:useAudioRecording");
14
15
 
15
16
  interface UseAudioRecorderState {
16
- startRecording: (_: RecordingOptions) => Promise<string | null>;
17
- stopRecording: () => Promise<AudioStreamResult | null>;
18
- pauseRecording: () => void;
19
- isRecording: boolean;
20
- isPaused: boolean;
21
- duration: number; // Duration of the recording
22
- size: number; // Size in bytes of the recorded audio
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
- export function useAudioRecorder({onAudioStream}: {onAudioStream?: (buffer: Blob) => void}): UseAudioRecorderState {
27
- const [isRecording, setIsRecording] = useState(false);
28
- const [isPaused, setIsPaused] = useState(false);
29
- const [duration, setDuration] = useState(0);
30
- const [size, setSize] = useState(0);
31
-
32
- useEffect( () => {
33
- if(isRecording || isPaused) {
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
- const subscribe = addAudioEventListener(async ({fileUri, deltaSize, totalSize, from, streamUuid, encoded, mimeType, buffer}) => {
48
- log(`Received audio event:`, {fileUri, deltaSize, totalSize, mimeType, from, streamUuid, encodedLength: encoded?.length})
49
- if(deltaSize > 0) {
50
- // Coming from native ( ios / android ) otherwise buffer is set
51
- if(Platform.OS !== 'web') {
52
- // Read the audio file as a base64 string for comparison
53
- try {
54
- // convert encoded string to binary data
55
- const binaryData = atob(encoded);
56
- const content = new Uint8Array(binaryData.length);
57
- for (let i = 0; i < binaryData.length; i++) {
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
- // Below code is optional, used to compare encoded data to audio on file system
63
- // Fetch the audio data from the fileUri
64
- // const options = {
65
- // encoding: FileSystem.EncodingType.Base64,
66
- // position: from,
67
- // length: deltaSize,
68
- // };
69
- // const base64Content = await FileSystem.readAsStringAsync(fileUri, options);
70
- // const binaryData = atob(base64Content);
71
- // const content = new Uint8Array(binaryData.length);
72
- // for (let i = 0; i < binaryData.length; i++) {
73
- // content[i] = binaryData.charCodeAt(i);
74
- // }
75
- // const audioBlob = new Blob([content], { type: 'application/octet-stream' }); // Create a Blob from the byte array
76
- // console.debug(`Read audio file (len: ${content.length}) vs ${deltaSize}`)
77
-
78
- onAudioStream?.(audioBlob);
79
- } catch (error) {
80
- console.error('Error reading audio file:', error);
81
- }
82
- } else if(buffer) {
83
- // Coming from web
84
- onAudioStream?.(buffer);
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
- const startRecording = useCallback(async (recordingOptions: RecordingOptions) => {
93
- setIsRecording(true);
94
- setIsPaused(false);
95
- setSize(0);
96
- setDuration(0);
97
- try {
98
- log(`start recoding`, recordingOptions)
99
- const fileUrl = await ExpoAudioStreamModule.startRecording(recordingOptions);
100
-
101
- return fileUrl;
102
- } catch (error) {
103
- console.error('Error starting recording:', error);
104
- setIsRecording(false);
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
- setIsPaused(false);
111
- const result: AudioStreamResult = await ExpoAudioStreamModule.stopRecording();
112
- return result;
113
- }, []);
114
-
115
- const pauseRecording = useCallback(async () => {
116
- try {
117
- await ExpoAudioStreamModule.stopRecording()
118
- setIsPaused(true);
119
- setIsRecording(false);
120
- } catch (error) {
121
- console.error('Error pausing recording:', error);
122
- }
123
- }, []);
124
-
125
- return {
126
- startRecording,
127
- stopRecording,
128
- pauseRecording,
129
- isPaused,
130
- isRecording,
131
- duration,
132
- size
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
+ }