@siteed/expo-audio-stream 1.0.1 → 1.0.3

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 (142) hide show
  1. package/.size-limit.json +6 -0
  2. package/README.md +6 -6
  3. package/android/build.gradle +5 -0
  4. package/android/src/main/java/net/siteed/audiostream/AudioAnalysisData.kt +120 -0
  5. package/android/src/main/java/net/siteed/audiostream/AudioFileHandler.kt +34 -4
  6. package/android/src/main/java/net/siteed/audiostream/AudioProcessor.kt +635 -0
  7. package/android/src/main/java/net/siteed/audiostream/AudioRecorderManager.kt +194 -79
  8. package/android/src/main/java/net/siteed/audiostream/Constants.kt +1 -0
  9. package/android/src/main/java/net/siteed/audiostream/ExpoAudioStreamModule.kt +48 -2
  10. package/android/src/main/java/net/siteed/audiostream/FFT.kt +44 -0
  11. package/android/src/main/java/net/siteed/audiostream/Features.kt +56 -0
  12. package/android/src/main/java/net/siteed/audiostream/RecordingConfig.kt +12 -0
  13. package/android/src/main/test/java/net/siteed/audiostream/AudioProcessorTest.kt +56 -0
  14. package/app.plugin.js +1 -1
  15. package/build/AudioAnalysis/AudioAnalysis.types.d.ts +76 -0
  16. package/build/AudioAnalysis/AudioAnalysis.types.d.ts.map +1 -0
  17. package/build/AudioAnalysis/AudioAnalysis.types.js +3 -0
  18. package/build/AudioAnalysis/AudioAnalysis.types.js.map +1 -0
  19. package/build/AudioAnalysis/extractAudioAnalysis.d.ts +4 -0
  20. package/build/AudioAnalysis/extractAudioAnalysis.d.ts.map +1 -0
  21. package/build/AudioAnalysis/extractAudioAnalysis.js +101 -0
  22. package/build/AudioAnalysis/extractAudioAnalysis.js.map +1 -0
  23. package/build/AudioAnalysis/extractWaveform.d.ts +8 -0
  24. package/build/AudioAnalysis/extractWaveform.d.ts.map +1 -0
  25. package/build/AudioAnalysis/extractWaveform.js +14 -0
  26. package/build/AudioAnalysis/extractWaveform.js.map +1 -0
  27. package/build/AudioRecorder.provider.d.ts +14 -1
  28. package/build/AudioRecorder.provider.d.ts.map +1 -1
  29. package/build/AudioRecorder.provider.js +18 -5
  30. package/build/AudioRecorder.provider.js.map +1 -1
  31. package/build/ExpoAudioStream.native.d.ts +3 -0
  32. package/build/ExpoAudioStream.native.d.ts.map +1 -0
  33. package/build/ExpoAudioStream.native.js +6 -0
  34. package/build/ExpoAudioStream.native.js.map +1 -0
  35. package/build/ExpoAudioStream.types.d.ts +35 -20
  36. package/build/ExpoAudioStream.types.d.ts.map +1 -1
  37. package/build/ExpoAudioStream.types.js.map +1 -1
  38. package/build/ExpoAudioStream.web.d.ts +42 -0
  39. package/build/ExpoAudioStream.web.d.ts.map +1 -0
  40. package/build/ExpoAudioStream.web.js +185 -0
  41. package/build/ExpoAudioStream.web.js.map +1 -0
  42. package/build/ExpoAudioStreamModule.d.ts +2 -2
  43. package/build/ExpoAudioStreamModule.d.ts.map +1 -1
  44. package/build/ExpoAudioStreamModule.js +16 -3
  45. package/build/ExpoAudioStreamModule.js.map +1 -1
  46. package/build/WebRecorder.web.d.ts +51 -0
  47. package/build/WebRecorder.web.d.ts.map +1 -0
  48. package/build/WebRecorder.web.js +288 -0
  49. package/build/WebRecorder.web.js.map +1 -0
  50. package/build/constants.d.ts +11 -0
  51. package/build/constants.d.ts.map +1 -0
  52. package/build/constants.js +14 -0
  53. package/build/constants.js.map +1 -0
  54. package/build/events.d.ts +6 -0
  55. package/build/events.d.ts.map +1 -0
  56. package/build/events.js +15 -0
  57. package/build/events.js.map +1 -0
  58. package/build/index.d.ts +8 -7
  59. package/build/index.d.ts.map +1 -1
  60. package/build/index.js +7 -14
  61. package/build/index.js.map +1 -1
  62. package/build/logger.d.ts +9 -0
  63. package/build/logger.d.ts.map +1 -0
  64. package/build/logger.js +17 -0
  65. package/build/logger.js.map +1 -0
  66. package/build/useAudioRecorder.d.ts +37 -0
  67. package/build/useAudioRecorder.d.ts.map +1 -0
  68. package/build/useAudioRecorder.js +271 -0
  69. package/build/useAudioRecorder.js.map +1 -0
  70. package/build/utils/convertPCMToFloat32.d.ts +11 -0
  71. package/build/utils/convertPCMToFloat32.d.ts.map +1 -0
  72. package/build/utils/convertPCMToFloat32.js +41 -0
  73. package/build/utils/convertPCMToFloat32.js.map +1 -0
  74. package/build/utils/encodingToBitDepth.d.ts +5 -0
  75. package/build/utils/encodingToBitDepth.d.ts.map +1 -0
  76. package/build/utils/encodingToBitDepth.js +13 -0
  77. package/build/utils/encodingToBitDepth.js.map +1 -0
  78. package/build/utils/getWavFileInfo.d.ts +25 -0
  79. package/build/utils/getWavFileInfo.d.ts.map +1 -0
  80. package/build/utils/getWavFileInfo.js +89 -0
  81. package/build/utils/getWavFileInfo.js.map +1 -0
  82. package/build/utils/writeWavHeader.d.ts +9 -0
  83. package/build/utils/writeWavHeader.d.ts.map +1 -0
  84. package/build/utils/writeWavHeader.js +41 -0
  85. package/build/utils/writeWavHeader.js.map +1 -0
  86. package/build/workers/InlineFeaturesExtractor.web.d.ts +2 -0
  87. package/build/workers/InlineFeaturesExtractor.web.d.ts.map +1 -0
  88. package/build/workers/InlineFeaturesExtractor.web.js +303 -0
  89. package/build/workers/InlineFeaturesExtractor.web.js.map +1 -0
  90. package/build/workers/inlineAudioWebWorker.web.d.ts +2 -0
  91. package/build/workers/inlineAudioWebWorker.web.d.ts.map +1 -0
  92. package/build/workers/inlineAudioWebWorker.web.js +243 -0
  93. package/build/workers/inlineAudioWebWorker.web.js.map +1 -0
  94. package/expo-module.config.json +13 -4
  95. package/ios/AudioAnalysisData.swift +39 -0
  96. package/ios/AudioProcessingHelpers.swift +59 -0
  97. package/ios/AudioProcessor.swift +317 -0
  98. package/ios/AudioStreamError.swift +7 -0
  99. package/ios/AudioStreamManager.swift +243 -54
  100. package/ios/AudioStreamManagerDelegate.swift +4 -0
  101. package/ios/DataPoint.swift +41 -0
  102. package/ios/ExpoAudioStreamModule.swift +198 -6
  103. package/ios/Features.swift +44 -0
  104. package/ios/RecordingResult.swift +19 -0
  105. package/ios/RecordingSettings.swift +13 -0
  106. package/ios/WaveformExtractor.swift +105 -0
  107. package/package.json +13 -12
  108. package/plugin/tsconfig.json +13 -8
  109. package/publish.sh +8 -0
  110. package/src/AudioAnalysis/AudioAnalysis.types.ts +85 -0
  111. package/src/AudioAnalysis/extractAudioAnalysis.ts +136 -0
  112. package/src/AudioAnalysis/extractWaveform.ts +25 -0
  113. package/src/AudioRecorder.provider.tsx +36 -8
  114. package/src/ExpoAudioStream.native.ts +6 -0
  115. package/src/ExpoAudioStream.types.ts +50 -25
  116. package/src/ExpoAudioStream.web.ts +229 -0
  117. package/src/ExpoAudioStreamModule.ts +22 -3
  118. package/src/WebRecorder.web.ts +416 -0
  119. package/src/constants.ts +18 -0
  120. package/src/events.ts +25 -0
  121. package/src/index.ts +14 -29
  122. package/src/logger.ts +26 -0
  123. package/src/useAudioRecorder.tsx +415 -0
  124. package/src/utils/convertPCMToFloat32.ts +48 -0
  125. package/src/utils/encodingToBitDepth.ts +18 -0
  126. package/src/utils/getWavFileInfo.ts +125 -0
  127. package/src/utils/writeWavHeader.ts +56 -0
  128. package/src/workers/InlineFeaturesExtractor.web.tsx +302 -0
  129. package/src/workers/inlineAudioWebWorker.web.tsx +242 -0
  130. package/build/ExpoAudioStreamModule.web.d.ts +0 -37
  131. package/build/ExpoAudioStreamModule.web.d.ts.map +0 -1
  132. package/build/ExpoAudioStreamModule.web.js +0 -156
  133. package/build/ExpoAudioStreamModule.web.js.map +0 -1
  134. package/build/useAudioRecording.d.ts +0 -23
  135. package/build/useAudioRecording.d.ts.map +0 -1
  136. package/build/useAudioRecording.js +0 -189
  137. package/build/useAudioRecording.js.map +0 -1
  138. package/docs/demo.gif +0 -0
  139. package/release-it.js +0 -18
  140. package/src/ExpoAudioStreamModule.web.ts +0 -181
  141. package/src/useAudioRecording.ts +0 -268
  142. 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"]}
@@ -1,23 +0,0 @@
1
- import { AudioStreamResult, RecordingConfig, StartAudioStreamResult } from "./ExpoAudioStream.types";
2
- export interface AudioDataEvent {
3
- data: string | Blob;
4
- position: number;
5
- fileUri: string;
6
- eventDataSize: number;
7
- totalSize: number;
8
- }
9
- export interface UseAudioRecorderProps {
10
- debug?: boolean;
11
- }
12
- export interface UseAudioRecorderState {
13
- startRecording: (_: RecordingConfig) => Promise<StartAudioStreamResult>;
14
- stopRecording: () => Promise<AudioStreamResult | null>;
15
- pauseRecording: () => void;
16
- resumeRecording: () => void;
17
- isRecording: boolean;
18
- isPaused: boolean;
19
- duration: number;
20
- size: number;
21
- }
22
- export declare function useAudioRecorder({ debug, }?: UseAudioRecorderProps): UseAudioRecorderState;
23
- //# sourceMappingURL=useAudioRecording.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"useAudioRecording.d.ts","sourceRoot":"","sources":["../src/useAudioRecording.ts"],"names":[],"mappings":"AAIA,OAAO,EAEL,iBAAiB,EAEjB,eAAe,EACf,sBAAsB,EACvB,MAAM,yBAAyB,CAAC;AAGjC,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,aAAa,EAAE,MAAM,CAAC;IACtB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,qBAAqB;IACpC,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AACD,MAAM,WAAW,qBAAqB;IACpC,cAAc,EAAE,CAAC,CAAC,EAAE,eAAe,KAAK,OAAO,CAAC,sBAAsB,CAAC,CAAC;IACxE,aAAa,EAAE,MAAM,OAAO,CAAC,iBAAiB,GAAG,IAAI,CAAC,CAAC;IACvD,cAAc,EAAE,MAAM,IAAI,CAAC;IAC3B,eAAe,EAAE,MAAM,IAAI,CAAC;IAC5B,WAAW,EAAE,OAAO,CAAC;IACrB,QAAQ,EAAE,OAAO,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;CACd;AA4CD,wBAAgB,gBAAgB,CAAC,EAC/B,KAAa,GACd,GAAE,qBAA0B,GAAG,qBAAqB,CA4LpD"}
@@ -1,189 +0,0 @@
1
- import { Platform } from "expo-modules-core";
2
- import { useCallback, useEffect, useReducer, useRef } from "react";
3
- import { addAudioEventListener } from ".";
4
- import ExpoAudioStreamModule from "./ExpoAudioStreamModule";
5
- function recorderReducer(state, action) {
6
- switch (action.type) {
7
- case "START":
8
- return {
9
- ...state,
10
- isRecording: true,
11
- isPaused: false,
12
- duration: 0,
13
- size: 0,
14
- };
15
- case "STOP":
16
- return { ...state, isRecording: false, isPaused: false };
17
- case "PAUSE":
18
- return { ...state, isPaused: true, isRecording: false };
19
- case "RESUME":
20
- return { ...state, isPaused: false, isRecording: true };
21
- case "UPDATE_STATUS":
22
- return {
23
- ...state,
24
- duration: action.payload.duration,
25
- size: action.payload.size,
26
- };
27
- default:
28
- return state;
29
- }
30
- }
31
- const TAG = "[ useAudioRecorder ] ";
32
- export function useAudioRecorder({ debug = false, } = {}) {
33
- const [state, dispatch] = useReducer(recorderReducer, {
34
- isRecording: false,
35
- isPaused: false,
36
- duration: 0,
37
- size: 0,
38
- });
39
- const onAudioStreamRef = useRef(null);
40
- const logDebug = useCallback((message, data) => {
41
- if (debug) {
42
- if (data) {
43
- console.log(`${TAG} ${message}`, data);
44
- }
45
- else {
46
- console.log(`${TAG} ${message}`);
47
- }
48
- }
49
- }, [debug]);
50
- const handleAudioEvent = useCallback(async (eventData) => {
51
- const { fileUri, deltaSize, totalSize, lastEmittedSize, position, streamUuid, encoded, mimeType, buffer, } = eventData;
52
- logDebug(`useAudioRecorder] Received audio event:`, {
53
- fileUri,
54
- deltaSize,
55
- totalSize,
56
- position,
57
- mimeType,
58
- lastEmittedSize,
59
- streamUuid,
60
- encodedLength: encoded?.length,
61
- });
62
- if (deltaSize === 0) {
63
- // Ignore packet with no data
64
- return;
65
- }
66
- try {
67
- // Coming from native ( ios / android ) otherwise buffer is set
68
- if (Platform.OS !== "web") {
69
- // Read the audio file as a base64 string for comparison
70
- if (!encoded) {
71
- console.error(`${TAG} Encoded audio data is missing`);
72
- throw new Error("Encoded audio data is missing");
73
- }
74
- onAudioStreamRef.current?.({
75
- data: encoded,
76
- position,
77
- fileUri,
78
- eventDataSize: deltaSize,
79
- totalSize,
80
- });
81
- }
82
- else if (buffer) {
83
- // Coming from web
84
- onAudioStreamRef.current?.({
85
- data: buffer,
86
- position,
87
- fileUri,
88
- eventDataSize: deltaSize,
89
- totalSize,
90
- });
91
- }
92
- }
93
- catch (error) {
94
- console.error(`${TAG} Error processing audio event:`, error);
95
- }
96
- }, [logDebug]);
97
- const checkStatus = useCallback(async () => {
98
- try {
99
- if (!state.isRecording) {
100
- logDebug(`${TAG} Not recording, exiting status check.`);
101
- return;
102
- }
103
- const status = ExpoAudioStreamModule.status();
104
- if (debug) {
105
- logDebug(`${TAG} Status:`, status);
106
- }
107
- if (!status.isRecording) {
108
- dispatch({ type: "STOP" });
109
- }
110
- else {
111
- dispatch({
112
- type: "UPDATE_STATUS",
113
- payload: { duration: status.duration, size: status.size },
114
- });
115
- }
116
- }
117
- catch (error) {
118
- console.error(`${TAG} Error getting status:`, error);
119
- }
120
- }, [state.isRecording, logDebug]);
121
- useEffect(() => {
122
- let interval;
123
- if (state.isRecording) {
124
- interval = setInterval(checkStatus, 1000);
125
- }
126
- return () => {
127
- if (interval) {
128
- clearInterval(interval);
129
- }
130
- };
131
- }, [checkStatus, state.isRecording]);
132
- useEffect(() => {
133
- logDebug(`${TAG} Registering audio event listener`);
134
- const subscribe = addAudioEventListener(handleAudioEvent);
135
- logDebug(`${TAG} Subscribed to audio event listener`, subscribe);
136
- return () => {
137
- logDebug(`${TAG} Removing audio event listener`);
138
- subscribe.remove();
139
- };
140
- }, [handleAudioEvent, logDebug]);
141
- const startRecording = useCallback(async (recordingOptions) => {
142
- if (debug) {
143
- logDebug(`${TAG} start recoding`, recordingOptions);
144
- }
145
- // remove onAudioStream from recordingOptions
146
- const { onAudioStream, ...options } = recordingOptions;
147
- if (typeof onAudioStream === "function") {
148
- onAudioStreamRef.current = onAudioStream;
149
- }
150
- else {
151
- console.warn(`${TAG} onAudioStream is not a function`, onAudioStream);
152
- onAudioStreamRef.current = null;
153
- }
154
- const startResult = await ExpoAudioStreamModule.startRecording(options);
155
- dispatch({ type: "START" });
156
- return startResult;
157
- }, [logDebug]);
158
- const stopRecording = useCallback(async () => {
159
- logDebug(`${TAG} stoping recording`);
160
- const stopResult = await ExpoAudioStreamModule.stopRecording();
161
- onAudioStreamRef.current = null;
162
- logDebug(`${TAG} recording stopped`, stopResult);
163
- dispatch({ type: "STOP" });
164
- return stopResult;
165
- }, [logDebug]);
166
- const pauseRecording = useCallback(async () => {
167
- logDebug(`${TAG} pause recording`);
168
- const pauseResult = await ExpoAudioStreamModule.pauseRecording();
169
- dispatch({ type: "PAUSE" });
170
- return pauseResult;
171
- }, [logDebug]);
172
- const resumeRecording = useCallback(async () => {
173
- logDebug(`${TAG} resume recording`);
174
- const resumeResult = await ExpoAudioStreamModule.resumeRecording();
175
- dispatch({ type: "RESUME" });
176
- return resumeResult;
177
- }, [logDebug]);
178
- return {
179
- startRecording,
180
- stopRecording,
181
- pauseRecording,
182
- resumeRecording,
183
- isPaused: state.isPaused,
184
- isRecording: state.isRecording,
185
- duration: state.duration,
186
- size: state.size,
187
- };
188
- }
189
- //# sourceMappingURL=useAudioRecording.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"useAudioRecording.js","sourceRoot":"","sources":["../src/useAudioRecording.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAC7C,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,OAAO,CAAC;AAEnE,OAAO,EAAE,qBAAqB,EAAE,MAAM,GAAG,CAAC;AAQ1C,OAAO,qBAAqB,MAAM,yBAAyB,CAAC;AAmC5D,SAAS,eAAe,CACtB,KAAoB,EACpB,MAAsB;IAEtB,QAAQ,MAAM,CAAC,IAAI,EAAE,CAAC;QACpB,KAAK,OAAO;YACV,OAAO;gBACL,GAAG,KAAK;gBACR,WAAW,EAAE,IAAI;gBACjB,QAAQ,EAAE,KAAK;gBACf,QAAQ,EAAE,CAAC;gBACX,IAAI,EAAE,CAAC;aACR,CAAC;QACJ,KAAK,MAAM;YACT,OAAO,EAAE,GAAG,KAAK,EAAE,WAAW,EAAE,KAAK,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC;QAC3D,KAAK,OAAO;YACV,OAAO,EAAE,GAAG,KAAK,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,EAAE,KAAK,EAAE,CAAC;QAC1D,KAAK,QAAQ;YACX,OAAO,EAAE,GAAG,KAAK,EAAE,QAAQ,EAAE,KAAK,EAAE,WAAW,EAAE,IAAI,EAAE,CAAC;QAC1D,KAAK,eAAe;YAClB,OAAO;gBACL,GAAG,KAAK;gBACR,QAAQ,EAAE,MAAM,CAAC,OAAO,CAAC,QAAQ;gBACjC,IAAI,EAAE,MAAM,CAAC,OAAO,CAAC,IAAI;aAC1B,CAAC;QACJ;YACE,OAAO,KAAK,CAAC;IACjB,CAAC;AACH,CAAC;AACD,MAAM,GAAG,GAAG,uBAAuB,CAAC;AAEpC,MAAM,UAAU,gBAAgB,CAAC,EAC/B,KAAK,GAAG,KAAK,MACY,EAAE;IAC3B,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,GAAG,UAAU,CAAC,eAAe,EAAE;QACpD,WAAW,EAAE,KAAK;QAClB,QAAQ,EAAE,KAAK;QACf,QAAQ,EAAE,CAAC;QACX,IAAI,EAAE,CAAC;KACR,CAAC,CAAC;IAEH,MAAM,gBAAgB,GAAG,MAAM,CAE7B,IAAI,CAAC,CAAC;IAER,MAAM,QAAQ,GAAG,WAAW,CAC1B,CAAC,OAAe,EAAE,IAAU,EAAE,EAAE;QAC9B,IAAI,KAAK,EAAE,CAAC;YACV,IAAI,IAAI,EAAE,CAAC;gBACT,OAAO,CAAC,GAAG,CAAC,GAAG,GAAG,IAAI,OAAO,EAAE,EAAE,IAAI,CAAC,CAAC;YACzC,CAAC;iBAAM,CAAC;gBACN,OAAO,CAAC,GAAG,CAAC,GAAG,GAAG,IAAI,OAAO,EAAE,CAAC,CAAC;YACnC,CAAC;QACH,CAAC;IACH,CAAC,EACD,CAAC,KAAK,CAAC,CACR,CAAC;IAEF,MAAM,gBAAgB,GAAG,WAAW,CAClC,KAAK,EAAE,SAA4B,EAAE,EAAE;QACrC,MAAM,EACJ,OAAO,EACP,SAAS,EACT,SAAS,EACT,eAAe,EACf,QAAQ,EACR,UAAU,EACV,OAAO,EACP,QAAQ,EACR,MAAM,GACP,GAAG,SAAS,CAAC;QACd,QAAQ,CAAC,yCAAyC,EAAE;YAClD,OAAO;YACP,SAAS;YACT,SAAS;YACT,QAAQ;YACR,QAAQ;YACR,eAAe;YACf,UAAU;YACV,aAAa,EAAE,OAAO,EAAE,MAAM;SAC/B,CAAC,CAAC;QACH,IAAI,SAAS,KAAK,CAAC,EAAE,CAAC;YACpB,6BAA6B;YAC7B,OAAO;QACT,CAAC;QACD,IAAI,CAAC;YACH,+DAA+D;YAC/D,IAAI,QAAQ,CAAC,EAAE,KAAK,KAAK,EAAE,CAAC;gBAC1B,wDAAwD;gBACxD,IAAI,CAAC,OAAO,EAAE,CAAC;oBACb,OAAO,CAAC,KAAK,CAAC,GAAG,GAAG,gCAAgC,CAAC,CAAC;oBACtD,MAAM,IAAI,KAAK,CAAC,+BAA+B,CAAC,CAAC;gBACnD,CAAC;gBACD,gBAAgB,CAAC,OAAO,EAAE,CAAC;oBACzB,IAAI,EAAE,OAAO;oBACb,QAAQ;oBACR,OAAO;oBACP,aAAa,EAAE,SAAS;oBACxB,SAAS;iBACV,CAAC,CAAC;YACL,CAAC;iBAAM,IAAI,MAAM,EAAE,CAAC;gBAClB,kBAAkB;gBAClB,gBAAgB,CAAC,OAAO,EAAE,CAAC;oBACzB,IAAI,EAAE,MAAM;oBACZ,QAAQ;oBACR,OAAO;oBACP,aAAa,EAAE,SAAS;oBACxB,SAAS;iBACV,CAAC,CAAC;YACL,CAAC;QACH,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,CAAC,KAAK,CAAC,GAAG,GAAG,gCAAgC,EAAE,KAAK,CAAC,CAAC;QAC/D,CAAC;IACH,CAAC,EACD,CAAC,QAAQ,CAAC,CACX,CAAC;IAEF,MAAM,WAAW,GAAG,WAAW,CAAC,KAAK,IAAI,EAAE;QACzC,IAAI,CAAC;YACH,IAAI,CAAC,KAAK,CAAC,WAAW,EAAE,CAAC;gBACvB,QAAQ,CAAC,GAAG,GAAG,uCAAuC,CAAC,CAAC;gBACxD,OAAO;YACT,CAAC;YAED,MAAM,MAAM,GAAsB,qBAAqB,CAAC,MAAM,EAAE,CAAC;YACjE,IAAI,KAAK,EAAE,CAAC;gBACV,QAAQ,CAAC,GAAG,GAAG,UAAU,EAAE,MAAM,CAAC,CAAC;YACrC,CAAC;YAED,IAAI,CAAC,MAAM,CAAC,WAAW,EAAE,CAAC;gBACxB,QAAQ,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,CAAC;YAC7B,CAAC;iBAAM,CAAC;gBACN,QAAQ,CAAC;oBACP,IAAI,EAAE,eAAe;oBACrB,OAAO,EAAE,EAAE,QAAQ,EAAE,MAAM,CAAC,QAAQ,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,EAAE;iBAC1D,CAAC,CAAC;YACL,CAAC;QACH,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,CAAC,KAAK,CAAC,GAAG,GAAG,wBAAwB,EAAE,KAAK,CAAC,CAAC;QACvD,CAAC;IACH,CAAC,EAAE,CAAC,KAAK,CAAC,WAAW,EAAE,QAAQ,CAAC,CAAC,CAAC;IAElC,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,QAAgB,CAAC;QACrB,IAAI,KAAK,CAAC,WAAW,EAAE,CAAC;YACtB,QAAQ,GAAG,WAAW,CAAC,WAAW,EAAE,IAAI,CAAC,CAAC;QAC5C,CAAC;QACD,OAAO,GAAG,EAAE;YACV,IAAI,QAAQ,EAAE,CAAC;gBACb,aAAa,CAAC,QAAQ,CAAC,CAAC;YAC1B,CAAC;QACH,CAAC,CAAC;IACJ,CAAC,EAAE,CAAC,WAAW,EAAE,KAAK,CAAC,WAAW,CAAC,CAAC,CAAC;IAErC,SAAS,CAAC,GAAG,EAAE;QACb,QAAQ,CAAC,GAAG,GAAG,mCAAmC,CAAC,CAAC;QACpD,MAAM,SAAS,GAAG,qBAAqB,CAAC,gBAAgB,CAAC,CAAC;QAC1D,QAAQ,CAAC,GAAG,GAAG,qCAAqC,EAAE,SAAS,CAAC,CAAC;QAEjE,OAAO,GAAG,EAAE;YACV,QAAQ,CAAC,GAAG,GAAG,gCAAgC,CAAC,CAAC;YACjD,SAAS,CAAC,MAAM,EAAE,CAAC;QACrB,CAAC,CAAC;IACJ,CAAC,EAAE,CAAC,gBAAgB,EAAE,QAAQ,CAAC,CAAC,CAAC;IAEjC,MAAM,cAAc,GAAG,WAAW,CAChC,KAAK,EAAE,gBAAiC,EAAE,EAAE;QAC1C,IAAI,KAAK,EAAE,CAAC;YACV,QAAQ,CAAC,GAAG,GAAG,iBAAiB,EAAE,gBAAgB,CAAC,CAAC;QACtD,CAAC;QACD,6CAA6C;QAC7C,MAAM,EAAE,aAAa,EAAE,GAAG,OAAO,EAAE,GAAG,gBAAgB,CAAC;QACvD,IAAI,OAAO,aAAa,KAAK,UAAU,EAAE,CAAC;YACxC,gBAAgB,CAAC,OAAO,GAAG,aAAa,CAAC;QAC3C,CAAC;aAAM,CAAC;YACN,OAAO,CAAC,IAAI,CAAC,GAAG,GAAG,kCAAkC,EAAE,aAAa,CAAC,CAAC;YACtE,gBAAgB,CAAC,OAAO,GAAG,IAAI,CAAC;QAClC,CAAC;QACD,MAAM,WAAW,GACf,MAAM,qBAAqB,CAAC,cAAc,CAAC,OAAO,CAAC,CAAC;QACtD,QAAQ,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,CAAC;QAE5B,OAAO,WAAW,CAAC;IACrB,CAAC,EACD,CAAC,QAAQ,CAAC,CACX,CAAC;IAEF,MAAM,aAAa,GAAG,WAAW,CAAC,KAAK,IAAI,EAAE;QAC3C,QAAQ,CAAC,GAAG,GAAG,oBAAoB,CAAC,CAAC;QACrC,MAAM,UAAU,GACd,MAAM,qBAAqB,CAAC,aAAa,EAAE,CAAC;QAC9C,gBAAgB,CAAC,OAAO,GAAG,IAAI,CAAC;QAChC,QAAQ,CAAC,GAAG,GAAG,oBAAoB,EAAE,UAAU,CAAC,CAAC;QACjD,QAAQ,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,CAAC;QAC3B,OAAO,UAAU,CAAC;IACpB,CAAC,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC;IAEf,MAAM,cAAc,GAAG,WAAW,CAAC,KAAK,IAAI,EAAE;QAC5C,QAAQ,CAAC,GAAG,GAAG,kBAAkB,CAAC,CAAC;QACnC,MAAM,WAAW,GAAG,MAAM,qBAAqB,CAAC,cAAc,EAAE,CAAC;QACjE,QAAQ,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,CAAC;QAC5B,OAAO,WAAW,CAAC;IACrB,CAAC,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC;IAEf,MAAM,eAAe,GAAG,WAAW,CAAC,KAAK,IAAI,EAAE;QAC7C,QAAQ,CAAC,GAAG,GAAG,mBAAmB,CAAC,CAAC;QACpC,MAAM,YAAY,GAAG,MAAM,qBAAqB,CAAC,eAAe,EAAE,CAAC;QACnE,QAAQ,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC,CAAC;QAC7B,OAAO,YAAY,CAAC;IACtB,CAAC,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC;IAEf,OAAO;QACL,cAAc;QACd,aAAa;QACb,cAAc;QACd,eAAe;QACf,QAAQ,EAAE,KAAK,CAAC,QAAQ;QACxB,WAAW,EAAE,KAAK,CAAC,WAAW;QAC9B,QAAQ,EAAE,KAAK,CAAC,QAAQ;QACxB,IAAI,EAAE,KAAK,CAAC,IAAI;KACjB,CAAC;AACJ,CAAC","sourcesContent":["import { Platform } from \"expo-modules-core\";\nimport { useCallback, useEffect, useReducer, useRef } from \"react\";\n\nimport { addAudioEventListener } from \".\";\nimport {\n AudioEventPayload,\n AudioStreamResult,\n AudioStreamStatus,\n RecordingConfig,\n StartAudioStreamResult,\n} from \"./ExpoAudioStream.types\";\nimport ExpoAudioStreamModule from \"./ExpoAudioStreamModule\";\n\nexport interface AudioDataEvent {\n data: string | Blob;\n position: number;\n fileUri: string;\n eventDataSize: number;\n totalSize: number;\n}\n\nexport interface UseAudioRecorderProps {\n debug?: boolean;\n}\nexport interface UseAudioRecorderState {\n startRecording: (_: RecordingConfig) => Promise<StartAudioStreamResult>;\n stopRecording: () => Promise<AudioStreamResult | null>;\n pauseRecording: () => void;\n resumeRecording: () => void;\n isRecording: boolean;\n isPaused: boolean;\n duration: number; // Duration of the recording\n size: number; // Size in bytes of the recorded audio\n}\n\ninterface RecorderState {\n isRecording: boolean;\n isPaused: boolean;\n duration: number;\n size: number;\n}\n\ntype RecorderAction =\n | { type: \"START\" | \"STOP\" | \"PAUSE\" | \"RESUME\" }\n | { type: \"UPDATE_STATUS\"; payload: { duration: number; size: number } };\n\nfunction recorderReducer(\n state: RecorderState,\n action: RecorderAction,\n): RecorderState {\n switch (action.type) {\n case \"START\":\n return {\n ...state,\n isRecording: true,\n isPaused: false,\n duration: 0,\n size: 0,\n };\n case \"STOP\":\n return { ...state, isRecording: false, isPaused: false };\n case \"PAUSE\":\n return { ...state, isPaused: true, isRecording: false };\n case \"RESUME\":\n return { ...state, isPaused: false, isRecording: true };\n case \"UPDATE_STATUS\":\n return {\n ...state,\n duration: action.payload.duration,\n size: action.payload.size,\n };\n default:\n return state;\n }\n}\nconst TAG = \"[ useAudioRecorder ] \";\n\nexport function useAudioRecorder({\n debug = false,\n}: UseAudioRecorderProps = {}): UseAudioRecorderState {\n const [state, dispatch] = useReducer(recorderReducer, {\n isRecording: false,\n isPaused: false,\n duration: 0,\n size: 0,\n });\n\n const onAudioStreamRef = useRef<\n ((_: AudioDataEvent) => Promise<void>) | null\n >(null);\n\n const logDebug = useCallback(\n (message: string, data?: any) => {\n if (debug) {\n if (data) {\n console.log(`${TAG} ${message}`, data);\n } else {\n console.log(`${TAG} ${message}`);\n }\n }\n },\n [debug],\n );\n\n const handleAudioEvent = useCallback(\n async (eventData: AudioEventPayload) => {\n const {\n fileUri,\n deltaSize,\n totalSize,\n lastEmittedSize,\n position,\n streamUuid,\n encoded,\n mimeType,\n buffer,\n } = eventData;\n logDebug(`useAudioRecorder] Received audio event:`, {\n fileUri,\n deltaSize,\n totalSize,\n position,\n mimeType,\n lastEmittedSize,\n streamUuid,\n encodedLength: encoded?.length,\n });\n if (deltaSize === 0) {\n // Ignore packet with no data\n return;\n }\n try {\n // Coming from native ( ios / android ) otherwise buffer is set\n if (Platform.OS !== \"web\") {\n // Read the audio file as a base64 string for comparison\n if (!encoded) {\n console.error(`${TAG} Encoded audio data is missing`);\n throw new Error(\"Encoded audio data is missing\");\n }\n onAudioStreamRef.current?.({\n data: encoded,\n position,\n fileUri,\n eventDataSize: deltaSize,\n totalSize,\n });\n } else if (buffer) {\n // Coming from web\n onAudioStreamRef.current?.({\n data: buffer,\n position,\n fileUri,\n eventDataSize: deltaSize,\n totalSize,\n });\n }\n } catch (error) {\n console.error(`${TAG} Error processing audio event:`, error);\n }\n },\n [logDebug],\n );\n\n const checkStatus = useCallback(async () => {\n try {\n if (!state.isRecording) {\n logDebug(`${TAG} Not recording, exiting status check.`);\n return;\n }\n\n const status: AudioStreamStatus = ExpoAudioStreamModule.status();\n if (debug) {\n logDebug(`${TAG} Status:`, status);\n }\n\n if (!status.isRecording) {\n dispatch({ type: \"STOP\" });\n } else {\n dispatch({\n type: \"UPDATE_STATUS\",\n payload: { duration: status.duration, size: status.size },\n });\n }\n } catch (error) {\n console.error(`${TAG} Error getting status:`, error);\n }\n }, [state.isRecording, logDebug]);\n\n useEffect(() => {\n let interval: number;\n if (state.isRecording) {\n interval = setInterval(checkStatus, 1000);\n }\n return () => {\n if (interval) {\n clearInterval(interval);\n }\n };\n }, [checkStatus, state.isRecording]);\n\n useEffect(() => {\n logDebug(`${TAG} Registering audio event listener`);\n const subscribe = addAudioEventListener(handleAudioEvent);\n logDebug(`${TAG} Subscribed to audio event listener`, subscribe);\n\n return () => {\n logDebug(`${TAG} Removing audio event listener`);\n subscribe.remove();\n };\n }, [handleAudioEvent, logDebug]);\n\n const startRecording = useCallback(\n async (recordingOptions: RecordingConfig) => {\n if (debug) {\n logDebug(`${TAG} start recoding`, recordingOptions);\n }\n // remove onAudioStream from recordingOptions\n const { onAudioStream, ...options } = recordingOptions;\n if (typeof onAudioStream === \"function\") {\n onAudioStreamRef.current = onAudioStream;\n } else {\n console.warn(`${TAG} onAudioStream is not a function`, onAudioStream);\n onAudioStreamRef.current = null;\n }\n const startResult: StartAudioStreamResult =\n await ExpoAudioStreamModule.startRecording(options);\n dispatch({ type: \"START\" });\n\n return startResult;\n },\n [logDebug],\n );\n\n const stopRecording = useCallback(async () => {\n logDebug(`${TAG} stoping recording`);\n const stopResult: AudioStreamResult =\n await ExpoAudioStreamModule.stopRecording();\n onAudioStreamRef.current = null;\n logDebug(`${TAG} recording stopped`, stopResult);\n dispatch({ type: \"STOP\" });\n return stopResult;\n }, [logDebug]);\n\n const pauseRecording = useCallback(async () => {\n logDebug(`${TAG} pause recording`);\n const pauseResult = await ExpoAudioStreamModule.pauseRecording();\n dispatch({ type: \"PAUSE\" });\n return pauseResult;\n }, [logDebug]);\n\n const resumeRecording = useCallback(async () => {\n logDebug(`${TAG} resume recording`);\n const resumeResult = await ExpoAudioStreamModule.resumeRecording();\n dispatch({ type: \"RESUME\" });\n return resumeResult;\n }, [logDebug]);\n\n return {\n startRecording,\n stopRecording,\n pauseRecording,\n resumeRecording,\n isPaused: state.isPaused,\n isRecording: state.isRecording,\n duration: state.duration,\n size: state.size,\n };\n}\n"]}
package/docs/demo.gif 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();