@siteed/expo-audio-stream 1.7.1 → 1.7.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/CHANGELOG.md +16 -1
- package/build/ExpoAudioStream.web.js +9 -10
- package/build/ExpoAudioStream.web.js.map +1 -1
- package/build/WebRecorder.web.d.ts.map +1 -1
- package/build/WebRecorder.web.js +12 -9
- package/build/WebRecorder.web.js.map +1 -1
- package/package.json +1 -1
- package/src/ExpoAudioStream.web.ts +11 -11
- package/src/WebRecorder.web.ts +14 -9
package/CHANGELOG.md
CHANGED
|
@@ -8,9 +8,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
10
|
|
|
11
|
+
## [1.7.2] - 2025-01-07
|
|
12
|
+
- fix(audio-stream): correct WAV header handling in web audio recording ([9ba7de5](https://github.com/deeeed/expo-audio-stream/commit/9ba7de5b96ca4cc937dea261c80d3fda9c99e8f4))
|
|
13
|
+
|
|
11
14
|
## [1.7.1] - 2025-01-07
|
|
12
15
|
- update notification to avoid triggering new alerts (#71) ([32dcfc5](https://github.com/deeeed/expo-audio-stream/commit/32dcfc55daf3236babefc17016f329c177d466fd))
|
|
13
16
|
|
|
17
|
+
|
|
14
18
|
## [1.7.0] - 2025-01-05
|
|
15
19
|
- feat(playground): enhance app configuration and build setup for production deployment (#58) ([929d443](https://github.com/deeeed/expo-audio-stream/commit/929d443145378b1430d215db5c00b13758420e2b))
|
|
16
20
|
- chore(expo-audio-stream): release @siteed/expo-audio-stream@1.6.1 ([084e8ad](https://github.com/deeeed/expo-audio-stream/commit/084e8adb91da7874c9e608b55d9c7b2ffd7a8327))
|
|
@@ -20,6 +24,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
20
24
|
- total size doesnt reset on new recording android (#64) ([f7da57b](https://github.com/deeeed/expo-audio-stream/commit/f7da57ba9d6f25870c130c54a049ba4cfad1c444))
|
|
21
25
|
|
|
22
26
|
|
|
27
|
+
|
|
23
28
|
## [1.6.1] - 2024-12-11
|
|
24
29
|
- chore(expo-audio-stream): remove git commit step from publish script ([4a772ce](https://github.com/deeeed/expo-audio-stream/commit/4a772ce93bb7405d9b8e981f46bdf8941a71ecfe))
|
|
25
30
|
- chore: more publishing automation ([3693021](https://github.com/deeeed/expo-audio-stream/commit/369302107f9dca9dddd8ae68e6214481a39976ac))
|
|
@@ -33,24 +38,28 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
33
38
|
|
|
34
39
|
|
|
35
40
|
|
|
41
|
+
|
|
36
42
|
## [1.5.0] - 2024-12-10
|
|
37
43
|
- UNPUBLISHED because of a bug in the build system
|
|
38
44
|
|
|
39
45
|
|
|
40
46
|
|
|
41
47
|
|
|
48
|
+
|
|
42
49
|
## [1.4.0] - 2024-12-05
|
|
43
50
|
- chore: remove unusded dependencies ([ad81dd5](https://github.com/deeeed/expo-audio-stream/commit/ad81dd560c93dd1d04995a323a4ae72d4de20f3e))
|
|
44
51
|
|
|
45
52
|
|
|
46
53
|
|
|
47
54
|
|
|
55
|
+
|
|
48
56
|
## [1.3.1] - 2024-12-05
|
|
49
57
|
- feat(web): implement throttling and optimize event processing (#49) ([da28765](https://github.com/deeeed/expo-audio-stream/commit/da2876524c2c9d6e0a980fde40a0197b929d8a7f))
|
|
50
58
|
|
|
51
59
|
|
|
52
60
|
|
|
53
61
|
|
|
62
|
+
|
|
54
63
|
## [1.3.0] - 2024-11-28
|
|
55
64
|
### Added
|
|
56
65
|
- refactor(permissions): standardize permission status response structure across platforms (#44) ([7c9c800](https://github.com/deeeed/expo-audio-stream/commit/7c9c800d83b7cea3516643371484d5e1f3b99e4c))
|
|
@@ -62,6 +71,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
62
71
|
|
|
63
72
|
|
|
64
73
|
|
|
74
|
+
|
|
65
75
|
## [1.2.5] - 2024-11-12
|
|
66
76
|
### Added
|
|
67
77
|
- docs(license): add MIT license to all packages (6 files changed)
|
|
@@ -70,6 +80,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
70
80
|
|
|
71
81
|
|
|
72
82
|
|
|
83
|
+
|
|
73
84
|
## [1.2.4] - 2024-11-05
|
|
74
85
|
### Changed
|
|
75
86
|
- Android minimum audio interval set to 10ms.
|
|
@@ -81,6 +92,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
81
92
|
|
|
82
93
|
|
|
83
94
|
|
|
95
|
+
|
|
84
96
|
## [1.2.0] - 2024-10-24
|
|
85
97
|
### Added
|
|
86
98
|
- Feature: Keep device awake during recording with `keepAwake` option
|
|
@@ -92,6 +104,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
92
104
|
|
|
93
105
|
|
|
94
106
|
|
|
107
|
+
|
|
95
108
|
## [1.1.17] - 2024-10-21
|
|
96
109
|
### Added
|
|
97
110
|
- Support bluetooth headset on ios
|
|
@@ -100,6 +113,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
100
113
|
|
|
101
114
|
|
|
102
115
|
|
|
116
|
+
|
|
103
117
|
## [1.0.0] - 2024-04-01
|
|
104
118
|
### Added
|
|
105
119
|
- Initial release of @siteed/expo-audio-stream.
|
|
@@ -110,7 +124,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
110
124
|
- Feature: Audio features extraction during recording.
|
|
111
125
|
- Feature: Consistent WAV PCM recording format across all platforms.
|
|
112
126
|
|
|
113
|
-
[unreleased]: https://github.com/deeeed/expo-audio-stream/compare/@siteed/expo-audio-stream@1.7.
|
|
127
|
+
[unreleased]: https://github.com/deeeed/expo-audio-stream/compare/@siteed/expo-audio-stream@1.7.2...HEAD
|
|
128
|
+
[1.7.2]: https://github.com/deeeed/expo-audio-stream/compare/@siteed/expo-audio-stream@1.7.1...@siteed/expo-audio-stream@1.7.2
|
|
114
129
|
[1.7.1]: https://github.com/deeeed/expo-audio-stream/compare/@siteed/expo-audio-stream@1.7.0...@siteed/expo-audio-stream@1.7.1
|
|
115
130
|
[1.7.0]: https://github.com/deeeed/expo-audio-stream/compare/@siteed/expo-audio-stream@1.6.1...@siteed/expo-audio-stream@1.7.0
|
|
116
131
|
[1.6.1]: https://github.com/deeeed/expo-audio-stream/compare/@siteed/expo-audio-stream@1.6.0...@siteed/expo-audio-stream@1.6.1
|
|
@@ -136,29 +136,28 @@ export class ExpoAudioStreamWeb extends LegacyEventEmitter {
|
|
|
136
136
|
throw new Error('Recorder is not initialized');
|
|
137
137
|
}
|
|
138
138
|
const fullPcmBufferArray = await this.customRecorder.stop();
|
|
139
|
-
// concat all audio chunks
|
|
140
139
|
this.logger?.debug(`Stopped recording`, fullPcmBufferArray);
|
|
141
140
|
this.isRecording = false;
|
|
142
141
|
this.isPaused = false;
|
|
143
142
|
this.currentDurationMs = Date.now() - this.recordingStartTime;
|
|
144
|
-
//
|
|
145
|
-
const
|
|
146
|
-
buffer:
|
|
143
|
+
// Create WAV header and combine with PCM data in one step
|
|
144
|
+
const wavBuffer = writeWavHeader({
|
|
145
|
+
buffer: fullPcmBufferArray.buffer,
|
|
147
146
|
sampleRate: this.recordingConfig?.sampleRate ?? 44100,
|
|
148
147
|
numChannels: this.recordingConfig?.channels ?? 1,
|
|
149
148
|
bitDepth: this.bitDepth,
|
|
150
|
-
};
|
|
151
|
-
|
|
152
|
-
const
|
|
153
|
-
// Create blob
|
|
154
|
-
const blob = new Blob([
|
|
149
|
+
});
|
|
150
|
+
// Create a cloneable copy
|
|
151
|
+
const cloneableBuffer = wavBuffer.slice(0);
|
|
152
|
+
// Create blob with complete WAV data
|
|
153
|
+
const blob = new Blob([cloneableBuffer], {
|
|
155
154
|
type: `audio/${this.extension}`,
|
|
156
155
|
});
|
|
157
156
|
const fileUri = URL.createObjectURL(blob);
|
|
158
157
|
const result = {
|
|
159
158
|
fileUri,
|
|
160
159
|
filename: `${this.streamUuid}.${this.extension}`,
|
|
161
|
-
wavPCMData:
|
|
160
|
+
wavPCMData: new Float32Array(cloneableBuffer),
|
|
162
161
|
bitDepth: this.bitDepth,
|
|
163
162
|
channels: this.recordingConfig?.channels ?? 1,
|
|
164
163
|
sampleRate: this.recordingConfig?.sampleRate ?? 44100,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ExpoAudioStream.web.js","sourceRoot":"","sources":["../src/ExpoAudioStream.web.ts"],"names":[],"mappings":"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","sourcesContent":["// src/ExpoAudioStreamModule.web.ts\nimport { LegacyEventEmitter } from 'expo-modules-core'\n\nimport { AudioAnalysis } from './AudioAnalysis/AudioAnalysis.types'\nimport {\n AudioRecording,\n AudioStreamStatus,\n BitDepth,\n ConsoleLike,\n RecordingConfig,\n StartRecordingResult,\n} from './ExpoAudioStream.types'\nimport { WebRecorder } from './WebRecorder.web'\nimport { AudioEventPayload } from './events'\nimport { encodingToBitDepth } from './utils/encodingToBitDepth'\nimport { WavHeaderOptions, writeWavHeader } from './utils/writeWavHeader'\n\nexport interface EmitAudioEventProps {\n data: Float32Array\n position: number\n}\nexport type EmitAudioEventFunction = (_: EmitAudioEventProps) => void\nexport type EmitAudioAnalysisFunction = (_: AudioAnalysis) => void\n\nexport interface ExpoAudioStreamWebProps {\n logger?: ConsoleLike\n audioWorkletUrl: string\n featuresExtratorUrl: string\n}\n\nexport class ExpoAudioStreamWeb extends LegacyEventEmitter {\n customRecorder: WebRecorder | null\n audioChunks: Float32Array[]\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 extension: 'webm' | 'wav' = 'wav' // Default extension is 'webm'\n recordingConfig?: RecordingConfig\n bitDepth: BitDepth // Bit depth of the audio\n audioWorkletUrl: string\n featuresExtratorUrl: string\n logger?: ConsoleLike\n\n constructor({\n audioWorkletUrl,\n featuresExtratorUrl,\n logger,\n }: ExpoAudioStreamWebProps) {\n const mockNativeModule = {\n addListener: () => {\n // Not used on web\n },\n removeListeners: () => {\n // Not used on web\n },\n }\n super(mockNativeModule) // Pass the mock native module to the parent class\n\n this.logger = logger\n this.customRecorder = 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.bitDepth = 32 // Default\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 this.audioWorkletUrl = audioWorkletUrl\n this.featuresExtratorUrl = featuresExtratorUrl\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(recordingConfig: RecordingConfig = {}) {\n if (this.isRecording) {\n throw new Error('Recording is already in progress')\n }\n\n this.bitDepth = encodingToBitDepth({\n encoding: recordingConfig.encoding ?? 'pcm_32bit',\n })\n\n const audioContext = new (window.AudioContext ||\n // eslint-disable-next-line @typescript-eslint/ban-ts-comment\n // @ts-ignore - Allow webkitAudioContext for Safari\n window.webkitAudioContext)()\n const stream = await this.getMediaStream()\n\n const source = audioContext.createMediaStreamSource(stream)\n\n this.customRecorder = new WebRecorder({\n audioContext,\n source,\n recordingConfig,\n audioWorkletUrl: this.audioWorkletUrl,\n emitAudioEventCallback: ({\n data,\n position,\n }: EmitAudioEventProps) => {\n this.audioChunks.push(new Float32Array(data))\n this.currentSize += data.byteLength\n this.emitAudioEvent({ data, position })\n this.lastEmittedTime = Date.now()\n this.lastEmittedSize = this.currentSize\n },\n emitAudioAnalysisCallback: (audioAnalysisData: AudioAnalysis) => {\n this.logger?.log(`Emitted AudioAnalysis:`, audioAnalysisData)\n this.emit('AudioAnalysis', audioAnalysisData)\n },\n })\n await this.customRecorder.init()\n this.customRecorder.start()\n\n // // Set a timer to stop recording after 5 seconds\n // setTimeout(() => {\n // logger.log(\"AUTO Stopping recording\");\n // this.customRecorder?.stopAndPlay();\n // this.isRecording = false;\n // }, 3000);\n\n this.isRecording = true\n this.recordingConfig = recordingConfig\n this.recordingStartTime = Date.now()\n this.pausedTime = 0\n this.isPaused = false\n this.lastEmittedSize = 0\n this.lastEmittedTime = 0\n this.streamUuid = Date.now().toString()\n const fileUri = `${this.streamUuid}.${this.extension}`\n const streamConfig: StartRecordingResult = {\n fileUri,\n mimeType: `audio/${this.extension}`,\n bitDepth: this.bitDepth,\n channels: recordingConfig.channels ?? 1,\n sampleRate: recordingConfig.sampleRate ?? 44100,\n }\n return streamConfig\n }\n\n emitAudioEvent({ data, position }: EmitAudioEventProps) {\n const fileUri = `${this.streamUuid}.${this.extension}`\n const audioEventPayload: AudioEventPayload = {\n fileUri,\n mimeType: `audio/${this.extension}`,\n lastEmittedSize: this.lastEmittedSize, // Since this might be continuously streaming, adjust accordingly\n deltaSize: data.byteLength,\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 // Stop recording\n async stopRecording(): Promise<AudioRecording> {\n if (!this.customRecorder) {\n throw new Error('Recorder is not initialized')\n }\n\n const fullPcmBufferArray = await this.customRecorder.stop()\n\n // concat all audio chunks\n this.logger?.debug(`Stopped recording`, fullPcmBufferArray)\n this.isRecording = false\n this.isPaused = false\n this.currentDurationMs = Date.now() - this.recordingStartTime\n\n // Rewrite wav header with correct data size\n const wavConfig: WavHeaderOptions = {\n buffer: new ArrayBuffer(fullPcmBufferArray.buffer.byteLength),\n sampleRate: this.recordingConfig?.sampleRate ?? 44100,\n numChannels: this.recordingConfig?.channels ?? 1,\n bitDepth: this.bitDepth,\n }\n this.logger?.debug(`Writing wav header`, wavConfig)\n const wavBuffer = writeWavHeader(wavConfig).slice(0)\n\n // Create blob fileUri from audio chunks\n const blob = new Blob([wavBuffer], {\n type: `audio/${this.extension}`,\n })\n const fileUri = URL.createObjectURL(blob)\n\n const result: AudioRecording = {\n fileUri,\n filename: `${this.streamUuid}.${this.extension}`,\n wavPCMData: fullPcmBufferArray,\n bitDepth: this.bitDepth,\n channels: this.recordingConfig?.channels ?? 1,\n sampleRate: this.recordingConfig?.sampleRate ?? 44100,\n durationMs: this.currentDurationMs,\n size: this.currentSize,\n mimeType: `audio/${this.extension}`,\n }\n\n return result\n }\n\n // Pause recording\n async pauseRecording() {\n if (!this.isRecording || this.isPaused) {\n throw new Error('Recording is not active or already paused')\n }\n\n if (this.customRecorder) {\n this.customRecorder.pause()\n }\n this.isPaused = true\n this.pausedTime = Date.now()\n }\n\n // Resume recording\n async resumeRecording() {\n if (!this.isPaused) {\n throw new Error('Recording is not paused')\n }\n\n if (this.customRecorder) {\n this.customRecorder.resume()\n }\n this.isPaused = false\n this.recordingStartTime += Date.now() - this.pausedTime\n }\n\n // Get current status\n status() {\n const status: AudioStreamStatus = {\n isRecording: this.isRecording,\n isPaused: this.isPaused,\n durationMs: Date.now() - this.recordingStartTime,\n size: this.currentSize,\n interval: this.currentInterval,\n mimeType: `audio/${this.extension}`,\n }\n return status\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"ExpoAudioStream.web.js","sourceRoot":"","sources":["../src/ExpoAudioStream.web.ts"],"names":[],"mappings":"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","sourcesContent":["// src/ExpoAudioStreamModule.web.ts\nimport { LegacyEventEmitter } from 'expo-modules-core'\n\nimport { AudioAnalysis } from './AudioAnalysis/AudioAnalysis.types'\nimport {\n AudioRecording,\n AudioStreamStatus,\n BitDepth,\n ConsoleLike,\n RecordingConfig,\n StartRecordingResult,\n} from './ExpoAudioStream.types'\nimport { WebRecorder } from './WebRecorder.web'\nimport { AudioEventPayload } from './events'\nimport { encodingToBitDepth } from './utils/encodingToBitDepth'\nimport { writeWavHeader } from './utils/writeWavHeader'\n\nexport interface EmitAudioEventProps {\n data: Float32Array\n position: number\n}\nexport type EmitAudioEventFunction = (_: EmitAudioEventProps) => void\nexport type EmitAudioAnalysisFunction = (_: AudioAnalysis) => void\n\nexport interface ExpoAudioStreamWebProps {\n logger?: ConsoleLike\n audioWorkletUrl: string\n featuresExtratorUrl: string\n}\n\nexport class ExpoAudioStreamWeb extends LegacyEventEmitter {\n customRecorder: WebRecorder | null\n audioChunks: Float32Array[]\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 extension: 'webm' | 'wav' = 'wav' // Default extension is 'webm'\n recordingConfig?: RecordingConfig\n bitDepth: BitDepth // Bit depth of the audio\n audioWorkletUrl: string\n featuresExtratorUrl: string\n logger?: ConsoleLike\n\n constructor({\n audioWorkletUrl,\n featuresExtratorUrl,\n logger,\n }: ExpoAudioStreamWebProps) {\n const mockNativeModule = {\n addListener: () => {\n // Not used on web\n },\n removeListeners: () => {\n // Not used on web\n },\n }\n super(mockNativeModule) // Pass the mock native module to the parent class\n\n this.logger = logger\n this.customRecorder = 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.bitDepth = 32 // Default\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 this.audioWorkletUrl = audioWorkletUrl\n this.featuresExtratorUrl = featuresExtratorUrl\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(recordingConfig: RecordingConfig = {}) {\n if (this.isRecording) {\n throw new Error('Recording is already in progress')\n }\n\n this.bitDepth = encodingToBitDepth({\n encoding: recordingConfig.encoding ?? 'pcm_32bit',\n })\n\n const audioContext = new (window.AudioContext ||\n // eslint-disable-next-line @typescript-eslint/ban-ts-comment\n // @ts-ignore - Allow webkitAudioContext for Safari\n window.webkitAudioContext)()\n const stream = await this.getMediaStream()\n\n const source = audioContext.createMediaStreamSource(stream)\n\n this.customRecorder = new WebRecorder({\n audioContext,\n source,\n recordingConfig,\n audioWorkletUrl: this.audioWorkletUrl,\n emitAudioEventCallback: ({\n data,\n position,\n }: EmitAudioEventProps) => {\n this.audioChunks.push(new Float32Array(data))\n this.currentSize += data.byteLength\n this.emitAudioEvent({ data, position })\n this.lastEmittedTime = Date.now()\n this.lastEmittedSize = this.currentSize\n },\n emitAudioAnalysisCallback: (audioAnalysisData: AudioAnalysis) => {\n this.logger?.log(`Emitted AudioAnalysis:`, audioAnalysisData)\n this.emit('AudioAnalysis', audioAnalysisData)\n },\n })\n await this.customRecorder.init()\n this.customRecorder.start()\n\n // // Set a timer to stop recording after 5 seconds\n // setTimeout(() => {\n // logger.log(\"AUTO Stopping recording\");\n // this.customRecorder?.stopAndPlay();\n // this.isRecording = false;\n // }, 3000);\n\n this.isRecording = true\n this.recordingConfig = recordingConfig\n this.recordingStartTime = Date.now()\n this.pausedTime = 0\n this.isPaused = false\n this.lastEmittedSize = 0\n this.lastEmittedTime = 0\n this.streamUuid = Date.now().toString()\n const fileUri = `${this.streamUuid}.${this.extension}`\n const streamConfig: StartRecordingResult = {\n fileUri,\n mimeType: `audio/${this.extension}`,\n bitDepth: this.bitDepth,\n channels: recordingConfig.channels ?? 1,\n sampleRate: recordingConfig.sampleRate ?? 44100,\n }\n return streamConfig\n }\n\n emitAudioEvent({ data, position }: EmitAudioEventProps) {\n const fileUri = `${this.streamUuid}.${this.extension}`\n const audioEventPayload: AudioEventPayload = {\n fileUri,\n mimeType: `audio/${this.extension}`,\n lastEmittedSize: this.lastEmittedSize, // Since this might be continuously streaming, adjust accordingly\n deltaSize: data.byteLength,\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 // Stop recording\n async stopRecording(): Promise<AudioRecording> {\n if (!this.customRecorder) {\n throw new Error('Recorder is not initialized')\n }\n\n const fullPcmBufferArray = await this.customRecorder.stop()\n\n this.logger?.debug(`Stopped recording`, fullPcmBufferArray)\n this.isRecording = false\n this.isPaused = false\n this.currentDurationMs = Date.now() - this.recordingStartTime\n\n // Create WAV header and combine with PCM data in one step\n const wavBuffer = writeWavHeader({\n buffer: fullPcmBufferArray.buffer,\n sampleRate: this.recordingConfig?.sampleRate ?? 44100,\n numChannels: this.recordingConfig?.channels ?? 1,\n bitDepth: this.bitDepth,\n })\n\n // Create a cloneable copy\n const cloneableBuffer = wavBuffer.slice(0)\n\n // Create blob with complete WAV data\n const blob = new Blob([cloneableBuffer], {\n type: `audio/${this.extension}`,\n })\n const fileUri = URL.createObjectURL(blob)\n\n const result: AudioRecording = {\n fileUri,\n filename: `${this.streamUuid}.${this.extension}`,\n wavPCMData: new Float32Array(cloneableBuffer),\n bitDepth: this.bitDepth,\n channels: this.recordingConfig?.channels ?? 1,\n sampleRate: this.recordingConfig?.sampleRate ?? 44100,\n durationMs: this.currentDurationMs,\n size: this.currentSize,\n mimeType: `audio/${this.extension}`,\n }\n\n return result\n }\n\n // Pause recording\n async pauseRecording() {\n if (!this.isRecording || this.isPaused) {\n throw new Error('Recording is not active or already paused')\n }\n\n if (this.customRecorder) {\n this.customRecorder.pause()\n }\n this.isPaused = true\n this.pausedTime = Date.now()\n }\n\n // Resume recording\n async resumeRecording() {\n if (!this.isPaused) {\n throw new Error('Recording is not paused')\n }\n\n if (this.customRecorder) {\n this.customRecorder.resume()\n }\n this.isPaused = false\n this.recordingStartTime += Date.now() - this.pausedTime\n }\n\n // Get current status\n status() {\n const status: AudioStreamStatus = {\n isRecording: this.isRecording,\n isPaused: this.isPaused,\n durationMs: Date.now() - this.recordingStartTime,\n size: this.currentSize,\n interval: this.currentInterval,\n mimeType: `audio/${this.extension}`,\n }\n return status\n }\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"WebRecorder.web.d.ts","sourceRoot":"","sources":["../src/WebRecorder.web.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,aAAa,EAAE,MAAM,qCAAqC,CAAA;AACnE,OAAO,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAA;AACtE,OAAO,EACH,yBAAyB,EACzB,sBAAsB,EACzB,MAAM,uBAAuB,CAAA;AAe9B,UAAU,kBAAkB;IACxB,IAAI,EAAE;QACF,OAAO,EAAE,MAAM,CAAA;QACf,MAAM,EAAE,aAAa,CAAA;KACxB,CAAA;CACJ;AAUD,qBAAa,WAAW;IACpB,OAAO,CAAC,YAAY,CAAc;IAClC,OAAO,CAAC,gBAAgB,CAAmB;IAC3C,OAAO,CAAC,sBAAsB,CAAC,CAAQ;IACvC,OAAO,CAAC,MAAM,CAA4B;IAC1C,OAAO,CAAC,eAAe,CAAQ;IAC/B,OAAO,CAAC,sBAAsB,CAAwB;IACtD,OAAO,CAAC,yBAAyB,CAA2B;IAC5D,OAAO,CAAC,MAAM,CAAiB;IAC/B,OAAO,CAAC,QAAQ,CAAQ;IACxB,OAAO,CAAC,gBAAgB,CAAQ;IAChC,OAAO,CAAC,QAAQ,CAAQ;IACxB,OAAO,CAAC,cAAc,CAAQ;IAC9B,OAAO,CAAC,WAAW,CAAc;IACjC,OAAO,CAAC,eAAe,CAAQ;IAC/B,OAAO,CAAC,iBAAiB,CAAe;IACxC,OAAO,CAAC,WAAW,CAAY;IAC/B,OAAO,CAAC,MAAM,CAAC,CAAa;gBAEhB,EACR,YAAY,EACZ,MAAM,EACN,eAAe,EACf,eAAe,EACf,sBAAsB,EACtB,yBAAyB,EACzB,MAAM,GACT,EAAE;QACC,YAAY,EAAE,YAAY,CAAA;QAC1B,MAAM,EAAE,0BAA0B,CAAA;QAClC,eAAe,EAAE,eAAe,CAAA;QAChC,eAAe,EAAE,MAAM,CAAA;QACvB,sBAAsB,EAAE,sBAAsB,CAAA;QAC9C,yBAAyB,EAAE,yBAAyB,CAAA;QACpD,MAAM,CAAC,EAAE,WAAW,CAAA;KACvB;IAqDK,IAAI;IAyHV,0BAA0B,CAAC,mBAAmB,CAAC,EAAE,MAAM;IA0BvD,kBAAkB;IAqBlB,iBAAiB,CAAC,KAAK,EAAE,UAAU;IAInC,6BAA6B,CAAC,KAAK,EAAE,kBAAkB;IAgCvD,KAAK;IAML,IAAI,IAAI,OAAO,CAAC,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"WebRecorder.web.d.ts","sourceRoot":"","sources":["../src/WebRecorder.web.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,aAAa,EAAE,MAAM,qCAAqC,CAAA;AACnE,OAAO,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAA;AACtE,OAAO,EACH,yBAAyB,EACzB,sBAAsB,EACzB,MAAM,uBAAuB,CAAA;AAe9B,UAAU,kBAAkB;IACxB,IAAI,EAAE;QACF,OAAO,EAAE,MAAM,CAAA;QACf,MAAM,EAAE,aAAa,CAAA;KACxB,CAAA;CACJ;AAUD,qBAAa,WAAW;IACpB,OAAO,CAAC,YAAY,CAAc;IAClC,OAAO,CAAC,gBAAgB,CAAmB;IAC3C,OAAO,CAAC,sBAAsB,CAAC,CAAQ;IACvC,OAAO,CAAC,MAAM,CAA4B;IAC1C,OAAO,CAAC,eAAe,CAAQ;IAC/B,OAAO,CAAC,sBAAsB,CAAwB;IACtD,OAAO,CAAC,yBAAyB,CAA2B;IAC5D,OAAO,CAAC,MAAM,CAAiB;IAC/B,OAAO,CAAC,QAAQ,CAAQ;IACxB,OAAO,CAAC,gBAAgB,CAAQ;IAChC,OAAO,CAAC,QAAQ,CAAQ;IACxB,OAAO,CAAC,cAAc,CAAQ;IAC9B,OAAO,CAAC,WAAW,CAAc;IACjC,OAAO,CAAC,eAAe,CAAQ;IAC/B,OAAO,CAAC,iBAAiB,CAAe;IACxC,OAAO,CAAC,WAAW,CAAY;IAC/B,OAAO,CAAC,MAAM,CAAC,CAAa;gBAEhB,EACR,YAAY,EACZ,MAAM,EACN,eAAe,EACf,eAAe,EACf,sBAAsB,EACtB,yBAAyB,EACzB,MAAM,GACT,EAAE;QACC,YAAY,EAAE,YAAY,CAAA;QAC1B,MAAM,EAAE,0BAA0B,CAAA;QAClC,eAAe,EAAE,eAAe,CAAA;QAChC,eAAe,EAAE,MAAM,CAAA;QACvB,sBAAsB,EAAE,sBAAsB,CAAA;QAC9C,yBAAyB,EAAE,yBAAyB,CAAA;QACpD,MAAM,CAAC,EAAE,WAAW,CAAA;KACvB;IAqDK,IAAI;IAyHV,0BAA0B,CAAC,mBAAmB,CAAC,EAAE,MAAM;IA0BvD,kBAAkB;IAqBlB,iBAAiB,CAAC,KAAK,EAAE,UAAU;IAInC,6BAA6B,CAAC,KAAK,EAAE,kBAAkB;IAgCvD,KAAK;IAML,IAAI,IAAI,OAAO,CAAC,YAAY,CAAC;IA6E7B,KAAK;IAML,qBAAqB;IAMf,gBAAgB,CAAC,EACnB,YAAY,GACf,EAAE;QACC,YAAY,EAAE,WAAW,CAAA;QACzB,QAAQ,CAAC,EAAE,MAAM,CAAA;KACpB;IAiCD,OAAO,CAAC,uBAAuB;IAoB/B,MAAM;CAKT"}
|
package/build/WebRecorder.web.js
CHANGED
|
@@ -261,15 +261,9 @@ export class WebRecorder {
|
|
|
261
261
|
this.logger?.debug(`recordedData.length=${rawPCMDataFull.byteLength} vs transmittedData.length=${this.audioBufferSize}`);
|
|
262
262
|
// Remove the event listener after receiving the final data
|
|
263
263
|
this.audioWorkletNode.port.removeEventListener('message', onMessage);
|
|
264
|
-
// Add wav header to the raw PCM data
|
|
265
|
-
const wavHeaderBuffer = writeWavHeader({
|
|
266
|
-
buffer: rawPCMDataFull.buffer,
|
|
267
|
-
sampleRate: this.audioContext.sampleRate,
|
|
268
|
-
numChannels: this.numberOfChannels,
|
|
269
|
-
bitDepth: this.exportBitDepth,
|
|
270
|
-
});
|
|
271
264
|
const convertedPCM = await convertPCMToFloat32({
|
|
272
|
-
buffer:
|
|
265
|
+
buffer: rawPCMDataFull.buffer,
|
|
266
|
+
skipWavHeader: true,
|
|
273
267
|
bitDepth: this.exportBitDepth,
|
|
274
268
|
});
|
|
275
269
|
resolve(convertedPCM.pcmValues); // Resolve the promise with the collected buffers
|
|
@@ -297,7 +291,14 @@ export class WebRecorder {
|
|
|
297
291
|
}
|
|
298
292
|
async playRecordedData({ recordedData, }) {
|
|
299
293
|
try {
|
|
300
|
-
|
|
294
|
+
// Create a WAV blob with proper headers
|
|
295
|
+
const wavHeaderBuffer = writeWavHeader({
|
|
296
|
+
buffer: recordedData,
|
|
297
|
+
sampleRate: this.audioContext.sampleRate,
|
|
298
|
+
numChannels: this.numberOfChannels,
|
|
299
|
+
bitDepth: this.exportBitDepth,
|
|
300
|
+
});
|
|
301
|
+
const blob = new Blob([wavHeaderBuffer], { type: 'audio/wav' });
|
|
301
302
|
const url = URL.createObjectURL(blob);
|
|
302
303
|
const response = await fetch(url);
|
|
303
304
|
const arrayBuffer = await response.arrayBuffer();
|
|
@@ -309,6 +310,8 @@ export class WebRecorder {
|
|
|
309
310
|
bufferSource.connect(this.audioContext.destination);
|
|
310
311
|
bufferSource.start();
|
|
311
312
|
this.logger?.debug('Playing recorded data', recordedData);
|
|
313
|
+
// Clean up
|
|
314
|
+
URL.revokeObjectURL(url);
|
|
312
315
|
}
|
|
313
316
|
catch (error) {
|
|
314
317
|
console.error(`[${TAG}] Failed to play recorded data:`, error);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"WebRecorder.web.js","sourceRoot":"","sources":["../src/WebRecorder.web.ts"],"names":[],"mappings":"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","sourcesContent":["// src/WebRecorder.ts\n\nimport { AudioAnalysis } from './AudioAnalysis/AudioAnalysis.types'\nimport { ConsoleLike, RecordingConfig } from './ExpoAudioStream.types'\nimport {\n EmitAudioAnalysisFunction,\n EmitAudioEventFunction,\n} from './ExpoAudioStream.web'\nimport { convertPCMToFloat32 } from './utils/convertPCMToFloat32'\nimport { encodingToBitDepth } from './utils/encodingToBitDepth'\nimport { writeWavHeader } from './utils/writeWavHeader'\nimport { InlineFeaturesExtractor } from './workers/InlineFeaturesExtractor.web'\nimport { InlineAudioWebWorker } from './workers/inlineAudioWebWorker.web'\n\ninterface AudioWorkletEvent {\n data: {\n command: string\n recordedData?: Float32Array\n sampleRate?: number\n }\n}\n\ninterface AudioFeaturesEvent {\n data: {\n command: string\n result: AudioAnalysis\n }\n}\n\nconst DEFAULT_WEB_BITDEPTH = 32\nconst DEFAULT_WEB_POINTS_PER_SECOND = 10\nconst DEFAULT_WEB_INTERVAL = 500\nconst DEFAULT_WEB_NUMBER_OF_CHANNELS = 1\nconst DEFAULT_ALGORITHM = 'rms'\n\nconst TAG = 'WebRecorder'\n\nexport class WebRecorder {\n private audioContext: AudioContext\n private audioWorkletNode!: AudioWorkletNode\n private featureExtractorWorker?: Worker\n private source: MediaStreamAudioSourceNode\n private audioWorkletUrl: string\n private emitAudioEventCallback: EmitAudioEventFunction\n private emitAudioAnalysisCallback: EmitAudioAnalysisFunction\n private config: RecordingConfig\n private position: number // Track the cumulative position\n private numberOfChannels: number // Number of audio channels\n private bitDepth: number // Bit depth of the audio\n private exportBitDepth: number // Bit depth of the audio\n private audioBuffer: Float32Array // Single buffer to store the audio data\n private audioBufferSize: number // Keep track of the buffer size\n private audioAnalysisData: AudioAnalysis // Keep updating the full audio analysis data with latest events\n private packetCount: number = 0\n private logger?: ConsoleLike\n\n constructor({\n audioContext,\n source,\n recordingConfig,\n audioWorkletUrl,\n emitAudioEventCallback,\n emitAudioAnalysisCallback,\n logger,\n }: {\n audioContext: AudioContext\n source: MediaStreamAudioSourceNode\n recordingConfig: RecordingConfig\n audioWorkletUrl: string\n emitAudioEventCallback: EmitAudioEventFunction\n emitAudioAnalysisCallback: EmitAudioAnalysisFunction\n logger?: ConsoleLike\n }) {\n this.audioContext = audioContext\n this.source = source\n this.audioWorkletUrl = audioWorkletUrl\n this.emitAudioEventCallback = emitAudioEventCallback\n this.emitAudioAnalysisCallback = emitAudioAnalysisCallback\n this.config = recordingConfig\n this.position = 0\n this.logger = logger\n\n const audioContextFormat = this.checkAudioContextFormat({\n sampleRate: this.audioContext.sampleRate,\n })\n this.logger?.debug('Initialized WebRecorder with config:', {\n sampleRate: audioContextFormat.sampleRate,\n bitDepth: audioContextFormat.bitDepth,\n numberOfChannels: audioContextFormat.numberOfChannels,\n })\n\n this.bitDepth = audioContextFormat.bitDepth\n this.numberOfChannels =\n audioContextFormat.numberOfChannels ||\n DEFAULT_WEB_NUMBER_OF_CHANNELS // Default to 1 if not available\n this.exportBitDepth =\n encodingToBitDepth({\n encoding: recordingConfig.encoding ?? 'pcm_32bit',\n }) ||\n audioContextFormat.bitDepth ||\n DEFAULT_WEB_BITDEPTH\n\n // Initialize the audio buffer separately\n this.audioBuffer = new Float32Array(0)\n this.audioBufferSize = 0\n\n this.audioAnalysisData = {\n amplitudeRange: { min: 0, max: 0 },\n dataPoints: [],\n durationMs: 0,\n samples: 0,\n amplitudeAlgorithm: recordingConfig.algorithm || DEFAULT_ALGORITHM,\n bitDepth: this.bitDepth,\n numberOfChannels: this.numberOfChannels,\n sampleRate: this.config.sampleRate || this.audioContext.sampleRate,\n pointsPerSecond:\n this.config.pointsPerSecond || DEFAULT_WEB_POINTS_PER_SECOND,\n speakerChanges: [],\n }\n\n if (recordingConfig.enableProcessing) {\n this.initFeatureExtractorWorker()\n }\n }\n\n async init() {\n try {\n if (!this.audioWorkletUrl) {\n const blob = new Blob([InlineAudioWebWorker], {\n type: 'application/javascript',\n })\n const url = URL.createObjectURL(blob)\n await this.audioContext.audioWorklet.addModule(url)\n } else {\n await this.audioContext.audioWorklet.addModule(\n this.audioWorkletUrl\n )\n }\n this.audioWorkletNode = new AudioWorkletNode(\n this.audioContext,\n 'recorder-processor'\n )\n\n this.audioWorkletNode.port.onmessage = async (\n event: AudioWorkletEvent\n ) => {\n const command = event.data.command\n if (command !== 'newData') {\n return\n }\n const pcmBufferFloat = event.data.recordedData\n\n if (!pcmBufferFloat) {\n this.logger?.warn('Received empty audio buffer', event)\n return\n }\n\n // Handle the audio blob (e.g., send it to the server or process it further)\n this.logger?.debug(\n `Received audio blob from processor len:${pcmBufferFloat?.length}`,\n event\n )\n // Concatenate the incoming Float32Array to the existing buffer\n const newBuffer = new Float32Array(\n this.audioBufferSize + pcmBufferFloat.length\n )\n newBuffer.set(this.audioBuffer, 0)\n newBuffer.set(pcmBufferFloat, this.audioBufferSize)\n this.audioBuffer = newBuffer\n this.audioBufferSize += pcmBufferFloat.length\n\n const sampleRate =\n event.data.sampleRate ?? this.audioContext.sampleRate\n const duration = pcmBufferFloat.length / sampleRate // Calculate duration of the current buffer\n\n let data: Float32Array\n if (this.packetCount === 0) {\n // Initialize WAV header\n const wavHeaderBuffer = writeWavHeader({\n sampleRate: this.audioContext.sampleRate,\n numChannels: this.numberOfChannels,\n bitDepth: this.exportBitDepth,\n })\n\n // For the first packet, combine WAV header with audio data\n const headerFloatArray = new Float32Array(wavHeaderBuffer)\n data = new Float32Array(\n headerFloatArray.length + this.audioBuffer.length\n )\n data.set(headerFloatArray, 0)\n data.set(this.audioBuffer, headerFloatArray.length)\n } else {\n // For subsequent packets, just send the new audio data\n data = pcmBufferFloat\n }\n\n // Track the number of packets\n this.packetCount += 1\n\n this.emitAudioEventCallback({\n data,\n position: this.position,\n })\n this.position += duration // Update position\n\n this.featureExtractorWorker?.postMessage(\n {\n command: 'process',\n channelData: pcmBufferFloat,\n sampleRate,\n pointsPerSecond:\n this.config.pointsPerSecond ||\n DEFAULT_WEB_POINTS_PER_SECOND,\n algorithm: this.config.algorithm || 'rms',\n bitDepth: this.bitDepth,\n fullAudioDurationMs: this.position * 1000,\n numberOfChannels: this.numberOfChannels,\n features: this.config.features,\n },\n []\n )\n }\n\n this.logger?.debug(\n `WebRecorder initialized -- recordSampleRate=${this.audioContext.sampleRate}`,\n this.config\n )\n this.audioWorkletNode.port.postMessage({\n command: 'init',\n recordSampleRate: this.audioContext.sampleRate, // Pass the original sample rate\n exportSampleRate:\n this.config.sampleRate ?? this.audioContext.sampleRate,\n bitDepth: this.bitDepth,\n exportBitDepth: this.exportBitDepth,\n channels: this.numberOfChannels,\n interval: this.config.interval ?? DEFAULT_WEB_INTERVAL,\n })\n\n // Connect the source to the AudioWorkletNode and start recording\n this.source.connect(this.audioWorkletNode)\n this.audioWorkletNode.connect(this.audioContext.destination)\n } catch (error) {\n console.error(`[${TAG}] Failed to initialize WebRecorder`, error)\n }\n }\n\n initFeatureExtractorWorker(featuresExtratorUrl?: string) {\n try {\n if (featuresExtratorUrl) {\n // Initialize the feature extractor worker\n //TODO: create audio feature extractor from a Blob instead of url since we cannot include the url directly in the library\n // We keep the url during dev and use the blob in production.\n this.featureExtractorWorker = new Worker(\n new URL(featuresExtratorUrl, window.location.href)\n )\n this.featureExtractorWorker.onmessage =\n this.handleFeatureExtractorMessage.bind(this)\n this.featureExtractorWorker.onerror =\n this.handleWorkerError.bind(this)\n } else {\n // Fallback to the inline worker if the URL is not provided\n this.initFallbackWorker()\n }\n } catch (error) {\n console.error(\n `[${TAG}] Failed to initialize feature extractor worker`,\n error\n )\n this.initFallbackWorker()\n }\n }\n\n initFallbackWorker() {\n try {\n const blob = new Blob([InlineFeaturesExtractor], {\n type: 'application/javascript',\n })\n const url = URL.createObjectURL(blob)\n this.featureExtractorWorker = new Worker(url)\n this.featureExtractorWorker.onmessage =\n this.handleFeatureExtractorMessage.bind(this)\n this.featureExtractorWorker.onerror = (error) => {\n console.error(`[${TAG}] Default Inline worker failed`, error)\n }\n this.logger?.log('Inline worker initialized successfully')\n } catch (error) {\n console.error(\n `[${TAG}] Failed to initialize Inline Feature Extractor worker`,\n error\n )\n }\n }\n\n handleWorkerError(error: ErrorEvent) {\n console.error(`[${TAG}] Feature extractor worker error:`, error)\n }\n\n handleFeatureExtractorMessage(event: AudioFeaturesEvent) {\n if (event.data.command === 'features') {\n const segmentResult = event.data.result\n\n // Merge the segment result with the full audio analysis data\n this.audioAnalysisData.dataPoints.push(...segmentResult.dataPoints)\n this.audioAnalysisData.speakerChanges?.push(\n ...(segmentResult.speakerChanges ?? [])\n )\n this.audioAnalysisData.durationMs = segmentResult.durationMs\n if (segmentResult.amplitudeRange) {\n this.audioAnalysisData.amplitudeRange = {\n min: Math.min(\n this.audioAnalysisData.amplitudeRange.min,\n segmentResult.amplitudeRange.min\n ),\n max: Math.max(\n this.audioAnalysisData.amplitudeRange.max,\n segmentResult.amplitudeRange.max\n ),\n }\n }\n // Handle the extracted features (e.g., emit an event or log them)\n this.logger?.debug('features event segmentResult', segmentResult)\n this.logger?.debug(\n `features event audioAnalysisData duration=${this.audioAnalysisData.durationMs}`,\n this.audioAnalysisData\n )\n this.emitAudioAnalysisCallback(segmentResult)\n }\n }\n\n start() {\n this.source.connect(this.audioWorkletNode)\n this.audioWorkletNode.connect(this.audioContext.destination)\n this.packetCount = 0\n }\n\n stop(): Promise<Float32Array> {\n return new Promise((resolve, reject) => {\n try {\n if (this.audioWorkletNode) {\n // this.source.disconnect(this.audioWorkletNode);\n // this.audioWorkletNode.disconnect(this.audioContext.destination);\n this.audioWorkletNode.port.postMessage({ command: 'stop' })\n\n // Set a timeout to reject the promise if no message is received within 5 seconds\n const timeout = setTimeout(() => {\n this.audioWorkletNode.port.removeEventListener(\n 'message',\n onMessage\n )\n reject(\n new Error(\n \"Timeout error, audioWorkletNode didn't complete.\"\n )\n )\n }, 5000)\n\n // Listen for the recordedData message to confirm stopping\n const onMessage = async (event: AudioWorkletEvent) => {\n const command = event.data.command\n if (command === 'recordedData') {\n clearTimeout(timeout) // Clear the timeout\n\n const rawPCMDataFull =\n event.data.recordedData?.slice(0)\n\n if (!rawPCMDataFull) {\n reject(new Error('Failed to get recorded data'))\n return\n }\n\n // Compute duration of the recorded data\n const duration =\n rawPCMDataFull.byteLength /\n (this.audioContext.sampleRate *\n (this.exportBitDepth /\n this.numberOfChannels))\n this.logger?.debug(\n `Received recorded data -- Duration: ${duration} vs ${rawPCMDataFull.byteLength / this.audioContext.sampleRate} seconds`\n )\n this.logger?.debug(\n `recordedData.length=${rawPCMDataFull.byteLength} vs transmittedData.length=${this.audioBufferSize}`\n )\n\n // Remove the event listener after receiving the final data\n this.audioWorkletNode.port.removeEventListener(\n 'message',\n onMessage\n )\n\n // Add wav header to the raw PCM data\n const wavHeaderBuffer = writeWavHeader({\n buffer: rawPCMDataFull.buffer,\n sampleRate: this.audioContext.sampleRate,\n numChannels: this.numberOfChannels,\n bitDepth: this.exportBitDepth,\n })\n const convertedPCM = await convertPCMToFloat32({\n buffer: wavHeaderBuffer,\n bitDepth: this.exportBitDepth,\n })\n\n resolve(convertedPCM.pcmValues) // Resolve the promise with the collected buffers\n }\n }\n this.audioWorkletNode.port.addEventListener(\n 'message',\n onMessage\n )\n }\n\n // Stop all media stream tracks to stop the browser recording\n this.stopMediaStreamTracks()\n } catch (error) {\n reject(error)\n }\n })\n }\n\n pause() {\n this.source.disconnect(this.audioWorkletNode) // Disconnect the source from the AudioWorkletNode\n this.audioWorkletNode.disconnect(this.audioContext.destination) // Disconnect the AudioWorkletNode from the destination\n this.audioWorkletNode.port.postMessage({ command: 'pause' })\n }\n\n stopMediaStreamTracks() {\n // Stop all audio tracks to stop the recording icon\n const tracks = this.source.mediaStream.getTracks()\n tracks.forEach((track) => track.stop())\n }\n\n async playRecordedData({\n recordedData,\n }: {\n recordedData: ArrayBuffer\n mimeType?: string\n }) {\n try {\n const blob = new Blob([recordedData])\n const url = URL.createObjectURL(blob)\n const response = await fetch(url)\n const arrayBuffer = await response.arrayBuffer()\n\n // Decode the audio data\n const audioBuffer =\n await this.audioContext.decodeAudioData(arrayBuffer)\n\n // Create a buffer source node and play the audio\n const bufferSource = this.audioContext.createBufferSource()\n bufferSource.buffer = audioBuffer\n bufferSource.connect(this.audioContext.destination)\n bufferSource.start()\n this.logger?.debug('Playing recorded data', recordedData)\n } catch (error) {\n console.error(`[${TAG}] Failed to play recorded data:`, error)\n }\n }\n\n private checkAudioContextFormat({ sampleRate }: { sampleRate: number }) {\n // Create a silent AudioBuffer\n const frameCount = sampleRate * 1.0 // 1 second buffer\n const audioBuffer = this.audioContext.createBuffer(\n 1,\n frameCount,\n sampleRate\n )\n\n // Check the format\n const channelData = audioBuffer.getChannelData(0)\n const bitDepth = channelData.BYTES_PER_ELEMENT * 8 // 4 bytes per element means 32-bit\n\n return {\n sampleRate: audioBuffer.sampleRate,\n bitDepth,\n numberOfChannels: audioBuffer.numberOfChannels,\n }\n }\n\n resume() {\n this.source.connect(this.audioWorkletNode)\n this.audioWorkletNode.connect(this.audioContext.destination)\n this.audioWorkletNode.port.postMessage({ command: 'resume' })\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"WebRecorder.web.js","sourceRoot":"","sources":["../src/WebRecorder.web.ts"],"names":[],"mappings":"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","sourcesContent":["// src/WebRecorder.ts\n\nimport { AudioAnalysis } from './AudioAnalysis/AudioAnalysis.types'\nimport { ConsoleLike, RecordingConfig } from './ExpoAudioStream.types'\nimport {\n EmitAudioAnalysisFunction,\n EmitAudioEventFunction,\n} from './ExpoAudioStream.web'\nimport { convertPCMToFloat32 } from './utils/convertPCMToFloat32'\nimport { encodingToBitDepth } from './utils/encodingToBitDepth'\nimport { writeWavHeader } from './utils/writeWavHeader'\nimport { InlineFeaturesExtractor } from './workers/InlineFeaturesExtractor.web'\nimport { InlineAudioWebWorker } from './workers/inlineAudioWebWorker.web'\n\ninterface AudioWorkletEvent {\n data: {\n command: string\n recordedData?: Float32Array\n sampleRate?: number\n }\n}\n\ninterface AudioFeaturesEvent {\n data: {\n command: string\n result: AudioAnalysis\n }\n}\n\nconst DEFAULT_WEB_BITDEPTH = 32\nconst DEFAULT_WEB_POINTS_PER_SECOND = 10\nconst DEFAULT_WEB_INTERVAL = 500\nconst DEFAULT_WEB_NUMBER_OF_CHANNELS = 1\nconst DEFAULT_ALGORITHM = 'rms'\n\nconst TAG = 'WebRecorder'\n\nexport class WebRecorder {\n private audioContext: AudioContext\n private audioWorkletNode!: AudioWorkletNode\n private featureExtractorWorker?: Worker\n private source: MediaStreamAudioSourceNode\n private audioWorkletUrl: string\n private emitAudioEventCallback: EmitAudioEventFunction\n private emitAudioAnalysisCallback: EmitAudioAnalysisFunction\n private config: RecordingConfig\n private position: number // Track the cumulative position\n private numberOfChannels: number // Number of audio channels\n private bitDepth: number // Bit depth of the audio\n private exportBitDepth: number // Bit depth of the audio\n private audioBuffer: Float32Array // Single buffer to store the audio data\n private audioBufferSize: number // Keep track of the buffer size\n private audioAnalysisData: AudioAnalysis // Keep updating the full audio analysis data with latest events\n private packetCount: number = 0\n private logger?: ConsoleLike\n\n constructor({\n audioContext,\n source,\n recordingConfig,\n audioWorkletUrl,\n emitAudioEventCallback,\n emitAudioAnalysisCallback,\n logger,\n }: {\n audioContext: AudioContext\n source: MediaStreamAudioSourceNode\n recordingConfig: RecordingConfig\n audioWorkletUrl: string\n emitAudioEventCallback: EmitAudioEventFunction\n emitAudioAnalysisCallback: EmitAudioAnalysisFunction\n logger?: ConsoleLike\n }) {\n this.audioContext = audioContext\n this.source = source\n this.audioWorkletUrl = audioWorkletUrl\n this.emitAudioEventCallback = emitAudioEventCallback\n this.emitAudioAnalysisCallback = emitAudioAnalysisCallback\n this.config = recordingConfig\n this.position = 0\n this.logger = logger\n\n const audioContextFormat = this.checkAudioContextFormat({\n sampleRate: this.audioContext.sampleRate,\n })\n this.logger?.debug('Initialized WebRecorder with config:', {\n sampleRate: audioContextFormat.sampleRate,\n bitDepth: audioContextFormat.bitDepth,\n numberOfChannels: audioContextFormat.numberOfChannels,\n })\n\n this.bitDepth = audioContextFormat.bitDepth\n this.numberOfChannels =\n audioContextFormat.numberOfChannels ||\n DEFAULT_WEB_NUMBER_OF_CHANNELS // Default to 1 if not available\n this.exportBitDepth =\n encodingToBitDepth({\n encoding: recordingConfig.encoding ?? 'pcm_32bit',\n }) ||\n audioContextFormat.bitDepth ||\n DEFAULT_WEB_BITDEPTH\n\n // Initialize the audio buffer separately\n this.audioBuffer = new Float32Array(0)\n this.audioBufferSize = 0\n\n this.audioAnalysisData = {\n amplitudeRange: { min: 0, max: 0 },\n dataPoints: [],\n durationMs: 0,\n samples: 0,\n amplitudeAlgorithm: recordingConfig.algorithm || DEFAULT_ALGORITHM,\n bitDepth: this.bitDepth,\n numberOfChannels: this.numberOfChannels,\n sampleRate: this.config.sampleRate || this.audioContext.sampleRate,\n pointsPerSecond:\n this.config.pointsPerSecond || DEFAULT_WEB_POINTS_PER_SECOND,\n speakerChanges: [],\n }\n\n if (recordingConfig.enableProcessing) {\n this.initFeatureExtractorWorker()\n }\n }\n\n async init() {\n try {\n if (!this.audioWorkletUrl) {\n const blob = new Blob([InlineAudioWebWorker], {\n type: 'application/javascript',\n })\n const url = URL.createObjectURL(blob)\n await this.audioContext.audioWorklet.addModule(url)\n } else {\n await this.audioContext.audioWorklet.addModule(\n this.audioWorkletUrl\n )\n }\n this.audioWorkletNode = new AudioWorkletNode(\n this.audioContext,\n 'recorder-processor'\n )\n\n this.audioWorkletNode.port.onmessage = async (\n event: AudioWorkletEvent\n ) => {\n const command = event.data.command\n if (command !== 'newData') {\n return\n }\n const pcmBufferFloat = event.data.recordedData\n\n if (!pcmBufferFloat) {\n this.logger?.warn('Received empty audio buffer', event)\n return\n }\n\n // Handle the audio blob (e.g., send it to the server or process it further)\n this.logger?.debug(\n `Received audio blob from processor len:${pcmBufferFloat?.length}`,\n event\n )\n // Concatenate the incoming Float32Array to the existing buffer\n const newBuffer = new Float32Array(\n this.audioBufferSize + pcmBufferFloat.length\n )\n newBuffer.set(this.audioBuffer, 0)\n newBuffer.set(pcmBufferFloat, this.audioBufferSize)\n this.audioBuffer = newBuffer\n this.audioBufferSize += pcmBufferFloat.length\n\n const sampleRate =\n event.data.sampleRate ?? this.audioContext.sampleRate\n const duration = pcmBufferFloat.length / sampleRate // Calculate duration of the current buffer\n\n let data: Float32Array\n if (this.packetCount === 0) {\n // Initialize WAV header\n const wavHeaderBuffer = writeWavHeader({\n sampleRate: this.audioContext.sampleRate,\n numChannels: this.numberOfChannels,\n bitDepth: this.exportBitDepth,\n })\n\n // For the first packet, combine WAV header with audio data\n const headerFloatArray = new Float32Array(wavHeaderBuffer)\n data = new Float32Array(\n headerFloatArray.length + this.audioBuffer.length\n )\n data.set(headerFloatArray, 0)\n data.set(this.audioBuffer, headerFloatArray.length)\n } else {\n // For subsequent packets, just send the new audio data\n data = pcmBufferFloat\n }\n\n // Track the number of packets\n this.packetCount += 1\n\n this.emitAudioEventCallback({\n data,\n position: this.position,\n })\n this.position += duration // Update position\n\n this.featureExtractorWorker?.postMessage(\n {\n command: 'process',\n channelData: pcmBufferFloat,\n sampleRate,\n pointsPerSecond:\n this.config.pointsPerSecond ||\n DEFAULT_WEB_POINTS_PER_SECOND,\n algorithm: this.config.algorithm || 'rms',\n bitDepth: this.bitDepth,\n fullAudioDurationMs: this.position * 1000,\n numberOfChannels: this.numberOfChannels,\n features: this.config.features,\n },\n []\n )\n }\n\n this.logger?.debug(\n `WebRecorder initialized -- recordSampleRate=${this.audioContext.sampleRate}`,\n this.config\n )\n this.audioWorkletNode.port.postMessage({\n command: 'init',\n recordSampleRate: this.audioContext.sampleRate, // Pass the original sample rate\n exportSampleRate:\n this.config.sampleRate ?? this.audioContext.sampleRate,\n bitDepth: this.bitDepth,\n exportBitDepth: this.exportBitDepth,\n channels: this.numberOfChannels,\n interval: this.config.interval ?? DEFAULT_WEB_INTERVAL,\n })\n\n // Connect the source to the AudioWorkletNode and start recording\n this.source.connect(this.audioWorkletNode)\n this.audioWorkletNode.connect(this.audioContext.destination)\n } catch (error) {\n console.error(`[${TAG}] Failed to initialize WebRecorder`, error)\n }\n }\n\n initFeatureExtractorWorker(featuresExtratorUrl?: string) {\n try {\n if (featuresExtratorUrl) {\n // Initialize the feature extractor worker\n //TODO: create audio feature extractor from a Blob instead of url since we cannot include the url directly in the library\n // We keep the url during dev and use the blob in production.\n this.featureExtractorWorker = new Worker(\n new URL(featuresExtratorUrl, window.location.href)\n )\n this.featureExtractorWorker.onmessage =\n this.handleFeatureExtractorMessage.bind(this)\n this.featureExtractorWorker.onerror =\n this.handleWorkerError.bind(this)\n } else {\n // Fallback to the inline worker if the URL is not provided\n this.initFallbackWorker()\n }\n } catch (error) {\n console.error(\n `[${TAG}] Failed to initialize feature extractor worker`,\n error\n )\n this.initFallbackWorker()\n }\n }\n\n initFallbackWorker() {\n try {\n const blob = new Blob([InlineFeaturesExtractor], {\n type: 'application/javascript',\n })\n const url = URL.createObjectURL(blob)\n this.featureExtractorWorker = new Worker(url)\n this.featureExtractorWorker.onmessage =\n this.handleFeatureExtractorMessage.bind(this)\n this.featureExtractorWorker.onerror = (error) => {\n console.error(`[${TAG}] Default Inline worker failed`, error)\n }\n this.logger?.log('Inline worker initialized successfully')\n } catch (error) {\n console.error(\n `[${TAG}] Failed to initialize Inline Feature Extractor worker`,\n error\n )\n }\n }\n\n handleWorkerError(error: ErrorEvent) {\n console.error(`[${TAG}] Feature extractor worker error:`, error)\n }\n\n handleFeatureExtractorMessage(event: AudioFeaturesEvent) {\n if (event.data.command === 'features') {\n const segmentResult = event.data.result\n\n // Merge the segment result with the full audio analysis data\n this.audioAnalysisData.dataPoints.push(...segmentResult.dataPoints)\n this.audioAnalysisData.speakerChanges?.push(\n ...(segmentResult.speakerChanges ?? [])\n )\n this.audioAnalysisData.durationMs = segmentResult.durationMs\n if (segmentResult.amplitudeRange) {\n this.audioAnalysisData.amplitudeRange = {\n min: Math.min(\n this.audioAnalysisData.amplitudeRange.min,\n segmentResult.amplitudeRange.min\n ),\n max: Math.max(\n this.audioAnalysisData.amplitudeRange.max,\n segmentResult.amplitudeRange.max\n ),\n }\n }\n // Handle the extracted features (e.g., emit an event or log them)\n this.logger?.debug('features event segmentResult', segmentResult)\n this.logger?.debug(\n `features event audioAnalysisData duration=${this.audioAnalysisData.durationMs}`,\n this.audioAnalysisData\n )\n this.emitAudioAnalysisCallback(segmentResult)\n }\n }\n\n start() {\n this.source.connect(this.audioWorkletNode)\n this.audioWorkletNode.connect(this.audioContext.destination)\n this.packetCount = 0\n }\n\n stop(): Promise<Float32Array> {\n return new Promise((resolve, reject) => {\n try {\n if (this.audioWorkletNode) {\n // this.source.disconnect(this.audioWorkletNode);\n // this.audioWorkletNode.disconnect(this.audioContext.destination);\n this.audioWorkletNode.port.postMessage({ command: 'stop' })\n\n // Set a timeout to reject the promise if no message is received within 5 seconds\n const timeout = setTimeout(() => {\n this.audioWorkletNode.port.removeEventListener(\n 'message',\n onMessage\n )\n reject(\n new Error(\n \"Timeout error, audioWorkletNode didn't complete.\"\n )\n )\n }, 5000)\n\n // Listen for the recordedData message to confirm stopping\n const onMessage = async (event: AudioWorkletEvent) => {\n const command = event.data.command\n if (command === 'recordedData') {\n clearTimeout(timeout) // Clear the timeout\n\n const rawPCMDataFull =\n event.data.recordedData?.slice(0)\n\n if (!rawPCMDataFull) {\n reject(new Error('Failed to get recorded data'))\n return\n }\n\n // Compute duration of the recorded data\n const duration =\n rawPCMDataFull.byteLength /\n (this.audioContext.sampleRate *\n (this.exportBitDepth /\n this.numberOfChannels))\n this.logger?.debug(\n `Received recorded data -- Duration: ${duration} vs ${rawPCMDataFull.byteLength / this.audioContext.sampleRate} seconds`\n )\n this.logger?.debug(\n `recordedData.length=${rawPCMDataFull.byteLength} vs transmittedData.length=${this.audioBufferSize}`\n )\n\n // Remove the event listener after receiving the final data\n this.audioWorkletNode.port.removeEventListener(\n 'message',\n onMessage\n )\n\n const convertedPCM = await convertPCMToFloat32({\n buffer: rawPCMDataFull.buffer,\n skipWavHeader: true,\n bitDepth: this.exportBitDepth,\n })\n\n resolve(convertedPCM.pcmValues) // Resolve the promise with the collected buffers\n }\n }\n this.audioWorkletNode.port.addEventListener(\n 'message',\n onMessage\n )\n }\n\n // Stop all media stream tracks to stop the browser recording\n this.stopMediaStreamTracks()\n } catch (error) {\n reject(error)\n }\n })\n }\n\n pause() {\n this.source.disconnect(this.audioWorkletNode) // Disconnect the source from the AudioWorkletNode\n this.audioWorkletNode.disconnect(this.audioContext.destination) // Disconnect the AudioWorkletNode from the destination\n this.audioWorkletNode.port.postMessage({ command: 'pause' })\n }\n\n stopMediaStreamTracks() {\n // Stop all audio tracks to stop the recording icon\n const tracks = this.source.mediaStream.getTracks()\n tracks.forEach((track) => track.stop())\n }\n\n async playRecordedData({\n recordedData,\n }: {\n recordedData: ArrayBuffer\n mimeType?: string\n }) {\n try {\n // Create a WAV blob with proper headers\n const wavHeaderBuffer = writeWavHeader({\n buffer: recordedData,\n sampleRate: this.audioContext.sampleRate,\n numChannels: this.numberOfChannels,\n bitDepth: this.exportBitDepth,\n })\n\n const blob = new Blob([wavHeaderBuffer], { type: 'audio/wav' })\n const url = URL.createObjectURL(blob)\n const response = await fetch(url)\n const arrayBuffer = await response.arrayBuffer()\n\n // Decode the audio data\n const audioBuffer =\n await this.audioContext.decodeAudioData(arrayBuffer)\n\n // Create a buffer source node and play the audio\n const bufferSource = this.audioContext.createBufferSource()\n bufferSource.buffer = audioBuffer\n bufferSource.connect(this.audioContext.destination)\n bufferSource.start()\n this.logger?.debug('Playing recorded data', recordedData)\n\n // Clean up\n URL.revokeObjectURL(url)\n } catch (error) {\n console.error(`[${TAG}] Failed to play recorded data:`, error)\n }\n }\n\n private checkAudioContextFormat({ sampleRate }: { sampleRate: number }) {\n // Create a silent AudioBuffer\n const frameCount = sampleRate * 1.0 // 1 second buffer\n const audioBuffer = this.audioContext.createBuffer(\n 1,\n frameCount,\n sampleRate\n )\n\n // Check the format\n const channelData = audioBuffer.getChannelData(0)\n const bitDepth = channelData.BYTES_PER_ELEMENT * 8 // 4 bytes per element means 32-bit\n\n return {\n sampleRate: audioBuffer.sampleRate,\n bitDepth,\n numberOfChannels: audioBuffer.numberOfChannels,\n }\n }\n\n resume() {\n this.source.connect(this.audioWorkletNode)\n this.audioWorkletNode.connect(this.audioContext.destination)\n this.audioWorkletNode.port.postMessage({ command: 'resume' })\n }\n}\n"]}
|
package/package.json
CHANGED
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
import { WebRecorder } from './WebRecorder.web'
|
|
14
14
|
import { AudioEventPayload } from './events'
|
|
15
15
|
import { encodingToBitDepth } from './utils/encodingToBitDepth'
|
|
16
|
-
import {
|
|
16
|
+
import { writeWavHeader } from './utils/writeWavHeader'
|
|
17
17
|
|
|
18
18
|
export interface EmitAudioEventProps {
|
|
19
19
|
data: Float32Array
|
|
@@ -182,24 +182,24 @@ export class ExpoAudioStreamWeb extends LegacyEventEmitter {
|
|
|
182
182
|
|
|
183
183
|
const fullPcmBufferArray = await this.customRecorder.stop()
|
|
184
184
|
|
|
185
|
-
// concat all audio chunks
|
|
186
185
|
this.logger?.debug(`Stopped recording`, fullPcmBufferArray)
|
|
187
186
|
this.isRecording = false
|
|
188
187
|
this.isPaused = false
|
|
189
188
|
this.currentDurationMs = Date.now() - this.recordingStartTime
|
|
190
189
|
|
|
191
|
-
//
|
|
192
|
-
const
|
|
193
|
-
buffer:
|
|
190
|
+
// Create WAV header and combine with PCM data in one step
|
|
191
|
+
const wavBuffer = writeWavHeader({
|
|
192
|
+
buffer: fullPcmBufferArray.buffer,
|
|
194
193
|
sampleRate: this.recordingConfig?.sampleRate ?? 44100,
|
|
195
194
|
numChannels: this.recordingConfig?.channels ?? 1,
|
|
196
195
|
bitDepth: this.bitDepth,
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
|
|
196
|
+
})
|
|
197
|
+
|
|
198
|
+
// Create a cloneable copy
|
|
199
|
+
const cloneableBuffer = wavBuffer.slice(0)
|
|
200
200
|
|
|
201
|
-
// Create blob
|
|
202
|
-
const blob = new Blob([
|
|
201
|
+
// Create blob with complete WAV data
|
|
202
|
+
const blob = new Blob([cloneableBuffer], {
|
|
203
203
|
type: `audio/${this.extension}`,
|
|
204
204
|
})
|
|
205
205
|
const fileUri = URL.createObjectURL(blob)
|
|
@@ -207,7 +207,7 @@ export class ExpoAudioStreamWeb extends LegacyEventEmitter {
|
|
|
207
207
|
const result: AudioRecording = {
|
|
208
208
|
fileUri,
|
|
209
209
|
filename: `${this.streamUuid}.${this.extension}`,
|
|
210
|
-
wavPCMData:
|
|
210
|
+
wavPCMData: new Float32Array(cloneableBuffer),
|
|
211
211
|
bitDepth: this.bitDepth,
|
|
212
212
|
channels: this.recordingConfig?.channels ?? 1,
|
|
213
213
|
sampleRate: this.recordingConfig?.sampleRate ?? 44100,
|
package/src/WebRecorder.web.ts
CHANGED
|
@@ -387,15 +387,9 @@ export class WebRecorder {
|
|
|
387
387
|
onMessage
|
|
388
388
|
)
|
|
389
389
|
|
|
390
|
-
// Add wav header to the raw PCM data
|
|
391
|
-
const wavHeaderBuffer = writeWavHeader({
|
|
392
|
-
buffer: rawPCMDataFull.buffer,
|
|
393
|
-
sampleRate: this.audioContext.sampleRate,
|
|
394
|
-
numChannels: this.numberOfChannels,
|
|
395
|
-
bitDepth: this.exportBitDepth,
|
|
396
|
-
})
|
|
397
390
|
const convertedPCM = await convertPCMToFloat32({
|
|
398
|
-
buffer:
|
|
391
|
+
buffer: rawPCMDataFull.buffer,
|
|
392
|
+
skipWavHeader: true,
|
|
399
393
|
bitDepth: this.exportBitDepth,
|
|
400
394
|
})
|
|
401
395
|
|
|
@@ -435,7 +429,15 @@ export class WebRecorder {
|
|
|
435
429
|
mimeType?: string
|
|
436
430
|
}) {
|
|
437
431
|
try {
|
|
438
|
-
|
|
432
|
+
// Create a WAV blob with proper headers
|
|
433
|
+
const wavHeaderBuffer = writeWavHeader({
|
|
434
|
+
buffer: recordedData,
|
|
435
|
+
sampleRate: this.audioContext.sampleRate,
|
|
436
|
+
numChannels: this.numberOfChannels,
|
|
437
|
+
bitDepth: this.exportBitDepth,
|
|
438
|
+
})
|
|
439
|
+
|
|
440
|
+
const blob = new Blob([wavHeaderBuffer], { type: 'audio/wav' })
|
|
439
441
|
const url = URL.createObjectURL(blob)
|
|
440
442
|
const response = await fetch(url)
|
|
441
443
|
const arrayBuffer = await response.arrayBuffer()
|
|
@@ -450,6 +452,9 @@ export class WebRecorder {
|
|
|
450
452
|
bufferSource.connect(this.audioContext.destination)
|
|
451
453
|
bufferSource.start()
|
|
452
454
|
this.logger?.debug('Playing recorded data', recordedData)
|
|
455
|
+
|
|
456
|
+
// Clean up
|
|
457
|
+
URL.revokeObjectURL(url)
|
|
453
458
|
} catch (error) {
|
|
454
459
|
console.error(`[${TAG}] Failed to play recorded data:`, error)
|
|
455
460
|
}
|