@siteed/expo-audio-studio 2.7.0 → 2.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (241) hide show
  1. package/CHANGELOG.md +12 -1
  2. package/app.plugin.cjs +15 -2
  3. package/build/cjs/AudioAnalysis/AudioAnalysis.types.js +4 -0
  4. package/build/cjs/AudioAnalysis/AudioAnalysis.types.js.map +1 -0
  5. package/build/cjs/AudioAnalysis/extractAudioAnalysis.js +205 -0
  6. package/build/cjs/AudioAnalysis/extractAudioAnalysis.js.map +1 -0
  7. package/build/cjs/AudioAnalysis/extractAudioData.js +12 -0
  8. package/build/cjs/AudioAnalysis/extractAudioData.js.map +1 -0
  9. package/build/cjs/AudioAnalysis/extractMelSpectrogram.js +89 -0
  10. package/build/cjs/AudioAnalysis/extractMelSpectrogram.js.map +1 -0
  11. package/build/cjs/AudioAnalysis/extractPreview.js +29 -0
  12. package/build/cjs/AudioAnalysis/extractPreview.js.map +1 -0
  13. package/build/cjs/AudioAnalysis/extractWaveform.js +18 -0
  14. package/build/cjs/AudioAnalysis/extractWaveform.js.map +1 -0
  15. package/build/cjs/AudioDeviceManager.js +500 -0
  16. package/build/cjs/AudioDeviceManager.js.map +1 -0
  17. package/build/cjs/AudioRecorder.provider.js +68 -0
  18. package/build/cjs/AudioRecorder.provider.js.map +1 -0
  19. package/build/cjs/ExpoAudioStream.native.js +8 -0
  20. package/build/cjs/ExpoAudioStream.native.js.map +1 -0
  21. package/build/cjs/ExpoAudioStream.types.js +11 -0
  22. package/build/cjs/ExpoAudioStream.types.js.map +1 -0
  23. package/build/cjs/ExpoAudioStream.web.js +705 -0
  24. package/build/cjs/ExpoAudioStream.web.js.map +1 -0
  25. package/build/cjs/ExpoAudioStreamModule.js +718 -0
  26. package/build/cjs/ExpoAudioStreamModule.js.map +1 -0
  27. package/build/cjs/WebRecorder.web.js +756 -0
  28. package/build/cjs/WebRecorder.web.js.map +1 -0
  29. package/build/cjs/constants.js +17 -0
  30. package/build/cjs/constants.js.map +1 -0
  31. package/build/cjs/events.js +30 -0
  32. package/build/cjs/events.js.map +1 -0
  33. package/build/cjs/hooks/useAudioDevices.js +155 -0
  34. package/build/cjs/hooks/useAudioDevices.js.map +1 -0
  35. package/build/cjs/index.js +50 -0
  36. package/build/cjs/index.js.map +1 -0
  37. package/build/cjs/trimAudio.js +75 -0
  38. package/build/cjs/trimAudio.js.map +1 -0
  39. package/build/cjs/useAudioRecorder.js +453 -0
  40. package/build/cjs/useAudioRecorder.js.map +1 -0
  41. package/build/cjs/utils/BlobFix.js +502 -0
  42. package/build/cjs/utils/BlobFix.js.map +1 -0
  43. package/build/cjs/utils/audioProcessing.js +137 -0
  44. package/build/cjs/utils/audioProcessing.js.map +1 -0
  45. package/build/cjs/utils/concatenateBuffers.js +25 -0
  46. package/build/cjs/utils/concatenateBuffers.js.map +1 -0
  47. package/build/cjs/utils/convertPCMToFloat32.js +124 -0
  48. package/build/cjs/utils/convertPCMToFloat32.js.map +1 -0
  49. package/build/cjs/utils/crc32.js +52 -0
  50. package/build/cjs/utils/crc32.js.map +1 -0
  51. package/build/cjs/utils/encodingToBitDepth.js +17 -0
  52. package/build/cjs/utils/encodingToBitDepth.js.map +1 -0
  53. package/build/cjs/utils/getWavFileInfo.js +96 -0
  54. package/build/cjs/utils/getWavFileInfo.js.map +1 -0
  55. package/build/cjs/utils/writeWavHeader.js +88 -0
  56. package/build/cjs/utils/writeWavHeader.js.map +1 -0
  57. package/build/cjs/workers/InlineFeaturesExtractor.web.js +853 -0
  58. package/build/cjs/workers/InlineFeaturesExtractor.web.js.map +1 -0
  59. package/build/cjs/workers/inlineAudioWebWorker.web.js +184 -0
  60. package/build/cjs/workers/inlineAudioWebWorker.web.js.map +1 -0
  61. package/build/esm/AudioAnalysis/AudioAnalysis.types.js.map +1 -0
  62. package/build/esm/AudioAnalysis/extractAudioAnalysis.js.map +1 -0
  63. package/build/esm/AudioAnalysis/extractAudioData.js.map +1 -0
  64. package/build/esm/AudioAnalysis/extractMelSpectrogram.js.map +1 -0
  65. package/build/esm/AudioAnalysis/extractPreview.js.map +1 -0
  66. package/build/esm/AudioAnalysis/extractWaveform.js.map +1 -0
  67. package/build/esm/AudioDeviceManager.js.map +1 -0
  68. package/build/esm/AudioRecorder.provider.js.map +1 -0
  69. package/build/esm/ExpoAudioStream.native.js.map +1 -0
  70. package/build/esm/ExpoAudioStream.types.js.map +1 -0
  71. package/build/esm/ExpoAudioStream.web.js.map +1 -0
  72. package/build/esm/ExpoAudioStreamModule.js.map +1 -0
  73. package/build/esm/WebRecorder.web.js.map +1 -0
  74. package/build/esm/constants.js.map +1 -0
  75. package/build/esm/events.js.map +1 -0
  76. package/build/esm/hooks/useAudioDevices.js.map +1 -0
  77. package/build/esm/index.js.map +1 -0
  78. package/build/esm/trimAudio.js.map +1 -0
  79. package/build/esm/useAudioRecorder.js.map +1 -0
  80. package/build/esm/utils/BlobFix.js.map +1 -0
  81. package/build/esm/utils/audioProcessing.js.map +1 -0
  82. package/build/esm/utils/concatenateBuffers.js.map +1 -0
  83. package/build/esm/utils/convertPCMToFloat32.js.map +1 -0
  84. package/build/esm/utils/crc32.js.map +1 -0
  85. package/build/esm/utils/encodingToBitDepth.js.map +1 -0
  86. package/build/esm/utils/getWavFileInfo.js.map +1 -0
  87. package/build/esm/utils/writeWavHeader.js.map +1 -0
  88. package/build/esm/workers/InlineFeaturesExtractor.web.js.map +1 -0
  89. package/build/esm/workers/inlineAudioWebWorker.web.js.map +1 -0
  90. package/build/types/AudioAnalysis/AudioAnalysis.types.d.ts.map +1 -0
  91. package/build/types/AudioAnalysis/extractAudioAnalysis.d.ts.map +1 -0
  92. package/build/types/AudioAnalysis/extractAudioData.d.ts.map +1 -0
  93. package/build/types/AudioAnalysis/extractMelSpectrogram.d.ts.map +1 -0
  94. package/build/types/AudioAnalysis/extractPreview.d.ts.map +1 -0
  95. package/build/types/AudioAnalysis/extractWaveform.d.ts.map +1 -0
  96. package/build/types/AudioDeviceManager.d.ts.map +1 -0
  97. package/build/types/AudioRecorder.provider.d.ts.map +1 -0
  98. package/build/types/ExpoAudioStream.native.d.ts.map +1 -0
  99. package/build/types/ExpoAudioStream.types.d.ts.map +1 -0
  100. package/build/types/ExpoAudioStream.web.d.ts.map +1 -0
  101. package/build/types/ExpoAudioStreamModule.d.ts.map +1 -0
  102. package/build/types/WebRecorder.web.d.ts.map +1 -0
  103. package/build/types/constants.d.ts.map +1 -0
  104. package/build/types/events.d.ts.map +1 -0
  105. package/build/types/hooks/useAudioDevices.d.ts.map +1 -0
  106. package/build/types/index.d.ts.map +1 -0
  107. package/build/types/trimAudio.d.ts.map +1 -0
  108. package/build/types/useAudioRecorder.d.ts.map +1 -0
  109. package/build/types/utils/BlobFix.d.ts.map +1 -0
  110. package/build/types/utils/audioProcessing.d.ts.map +1 -0
  111. package/build/types/utils/concatenateBuffers.d.ts.map +1 -0
  112. package/build/types/utils/convertPCMToFloat32.d.ts.map +1 -0
  113. package/build/types/utils/crc32.d.ts.map +1 -0
  114. package/build/types/utils/encodingToBitDepth.d.ts.map +1 -0
  115. package/build/types/utils/getWavFileInfo.d.ts.map +1 -0
  116. package/build/types/utils/writeWavHeader.d.ts.map +1 -0
  117. package/build/types/workers/InlineFeaturesExtractor.web.d.ts.map +1 -0
  118. package/build/types/workers/inlineAudioWebWorker.web.d.ts.map +1 -0
  119. package/ios/AudioNotificationManager.swift +42 -19
  120. package/ios/AudioProcessingHelpers.swift +5 -5
  121. package/ios/AudioProcessor.swift +44 -218
  122. package/ios/AudioStreamManager.swift +121 -61
  123. package/ios/DataPoint.swift +5 -5
  124. package/ios/ExpoAudioStreamModule.swift +2 -1
  125. package/package.json +25 -7
  126. package/build/AudioAnalysis/AudioAnalysis.types.d.ts.map +0 -1
  127. package/build/AudioAnalysis/AudioAnalysis.types.js.map +0 -1
  128. package/build/AudioAnalysis/extractAudioAnalysis.d.ts.map +0 -1
  129. package/build/AudioAnalysis/extractAudioAnalysis.js.map +0 -1
  130. package/build/AudioAnalysis/extractAudioData.d.ts.map +0 -1
  131. package/build/AudioAnalysis/extractAudioData.js.map +0 -1
  132. package/build/AudioAnalysis/extractMelSpectrogram.d.ts.map +0 -1
  133. package/build/AudioAnalysis/extractMelSpectrogram.js.map +0 -1
  134. package/build/AudioAnalysis/extractPreview.d.ts.map +0 -1
  135. package/build/AudioAnalysis/extractPreview.js.map +0 -1
  136. package/build/AudioAnalysis/extractWaveform.d.ts.map +0 -1
  137. package/build/AudioAnalysis/extractWaveform.js.map +0 -1
  138. package/build/AudioDeviceManager.d.ts.map +0 -1
  139. package/build/AudioDeviceManager.js.map +0 -1
  140. package/build/AudioRecorder.provider.d.ts.map +0 -1
  141. package/build/AudioRecorder.provider.js.map +0 -1
  142. package/build/ExpoAudioStream.native.d.ts.map +0 -1
  143. package/build/ExpoAudioStream.native.js.map +0 -1
  144. package/build/ExpoAudioStream.types.d.ts.map +0 -1
  145. package/build/ExpoAudioStream.types.js.map +0 -1
  146. package/build/ExpoAudioStream.web.d.ts.map +0 -1
  147. package/build/ExpoAudioStream.web.js.map +0 -1
  148. package/build/ExpoAudioStreamModule.d.ts.map +0 -1
  149. package/build/ExpoAudioStreamModule.js.map +0 -1
  150. package/build/WebRecorder.web.d.ts.map +0 -1
  151. package/build/WebRecorder.web.js.map +0 -1
  152. package/build/constants.d.ts.map +0 -1
  153. package/build/constants.js.map +0 -1
  154. package/build/events.d.ts.map +0 -1
  155. package/build/events.js.map +0 -1
  156. package/build/hooks/useAudioDevices.d.ts.map +0 -1
  157. package/build/hooks/useAudioDevices.js.map +0 -1
  158. package/build/index.d.ts.map +0 -1
  159. package/build/index.js.map +0 -1
  160. package/build/trimAudio.d.ts.map +0 -1
  161. package/build/trimAudio.js.map +0 -1
  162. package/build/useAudioRecorder.d.ts.map +0 -1
  163. package/build/useAudioRecorder.js.map +0 -1
  164. package/build/utils/BlobFix.d.ts.map +0 -1
  165. package/build/utils/BlobFix.js.map +0 -1
  166. package/build/utils/audioProcessing.d.ts.map +0 -1
  167. package/build/utils/audioProcessing.js.map +0 -1
  168. package/build/utils/concatenateBuffers.d.ts.map +0 -1
  169. package/build/utils/concatenateBuffers.js.map +0 -1
  170. package/build/utils/convertPCMToFloat32.d.ts.map +0 -1
  171. package/build/utils/convertPCMToFloat32.js.map +0 -1
  172. package/build/utils/crc32.d.ts.map +0 -1
  173. package/build/utils/crc32.js.map +0 -1
  174. package/build/utils/encodingToBitDepth.d.ts.map +0 -1
  175. package/build/utils/encodingToBitDepth.js.map +0 -1
  176. package/build/utils/getWavFileInfo.d.ts.map +0 -1
  177. package/build/utils/getWavFileInfo.js.map +0 -1
  178. package/build/utils/writeWavHeader.d.ts.map +0 -1
  179. package/build/utils/writeWavHeader.js.map +0 -1
  180. package/build/workers/InlineFeaturesExtractor.web.d.ts.map +0 -1
  181. package/build/workers/InlineFeaturesExtractor.web.js.map +0 -1
  182. package/build/workers/inlineAudioWebWorker.web.d.ts.map +0 -1
  183. package/build/workers/inlineAudioWebWorker.web.js.map +0 -1
  184. /package/build/{AudioAnalysis → esm/AudioAnalysis}/AudioAnalysis.types.js +0 -0
  185. /package/build/{AudioAnalysis → esm/AudioAnalysis}/extractAudioAnalysis.js +0 -0
  186. /package/build/{AudioAnalysis → esm/AudioAnalysis}/extractAudioData.js +0 -0
  187. /package/build/{AudioAnalysis → esm/AudioAnalysis}/extractMelSpectrogram.js +0 -0
  188. /package/build/{AudioAnalysis → esm/AudioAnalysis}/extractPreview.js +0 -0
  189. /package/build/{AudioAnalysis → esm/AudioAnalysis}/extractWaveform.js +0 -0
  190. /package/build/{AudioDeviceManager.js → esm/AudioDeviceManager.js} +0 -0
  191. /package/build/{AudioRecorder.provider.js → esm/AudioRecorder.provider.js} +0 -0
  192. /package/build/{ExpoAudioStream.native.js → esm/ExpoAudioStream.native.js} +0 -0
  193. /package/build/{ExpoAudioStream.types.js → esm/ExpoAudioStream.types.js} +0 -0
  194. /package/build/{ExpoAudioStream.web.js → esm/ExpoAudioStream.web.js} +0 -0
  195. /package/build/{ExpoAudioStreamModule.js → esm/ExpoAudioStreamModule.js} +0 -0
  196. /package/build/{WebRecorder.web.js → esm/WebRecorder.web.js} +0 -0
  197. /package/build/{constants.js → esm/constants.js} +0 -0
  198. /package/build/{events.js → esm/events.js} +0 -0
  199. /package/build/{hooks → esm/hooks}/useAudioDevices.js +0 -0
  200. /package/build/{index.js → esm/index.js} +0 -0
  201. /package/build/{trimAudio.js → esm/trimAudio.js} +0 -0
  202. /package/build/{useAudioRecorder.js → esm/useAudioRecorder.js} +0 -0
  203. /package/build/{utils → esm/utils}/BlobFix.js +0 -0
  204. /package/build/{utils → esm/utils}/audioProcessing.js +0 -0
  205. /package/build/{utils → esm/utils}/concatenateBuffers.js +0 -0
  206. /package/build/{utils → esm/utils}/convertPCMToFloat32.js +0 -0
  207. /package/build/{utils → esm/utils}/crc32.js +0 -0
  208. /package/build/{utils → esm/utils}/encodingToBitDepth.js +0 -0
  209. /package/build/{utils → esm/utils}/getWavFileInfo.js +0 -0
  210. /package/build/{utils → esm/utils}/writeWavHeader.js +0 -0
  211. /package/build/{workers → esm/workers}/InlineFeaturesExtractor.web.js +0 -0
  212. /package/build/{workers → esm/workers}/inlineAudioWebWorker.web.js +0 -0
  213. /package/build/{AudioAnalysis → types/AudioAnalysis}/AudioAnalysis.types.d.ts +0 -0
  214. /package/build/{AudioAnalysis → types/AudioAnalysis}/extractAudioAnalysis.d.ts +0 -0
  215. /package/build/{AudioAnalysis → types/AudioAnalysis}/extractAudioData.d.ts +0 -0
  216. /package/build/{AudioAnalysis → types/AudioAnalysis}/extractMelSpectrogram.d.ts +0 -0
  217. /package/build/{AudioAnalysis → types/AudioAnalysis}/extractPreview.d.ts +0 -0
  218. /package/build/{AudioAnalysis → types/AudioAnalysis}/extractWaveform.d.ts +0 -0
  219. /package/build/{AudioDeviceManager.d.ts → types/AudioDeviceManager.d.ts} +0 -0
  220. /package/build/{AudioRecorder.provider.d.ts → types/AudioRecorder.provider.d.ts} +0 -0
  221. /package/build/{ExpoAudioStream.native.d.ts → types/ExpoAudioStream.native.d.ts} +0 -0
  222. /package/build/{ExpoAudioStream.types.d.ts → types/ExpoAudioStream.types.d.ts} +0 -0
  223. /package/build/{ExpoAudioStream.web.d.ts → types/ExpoAudioStream.web.d.ts} +0 -0
  224. /package/build/{ExpoAudioStreamModule.d.ts → types/ExpoAudioStreamModule.d.ts} +0 -0
  225. /package/build/{WebRecorder.web.d.ts → types/WebRecorder.web.d.ts} +0 -0
  226. /package/build/{constants.d.ts → types/constants.d.ts} +0 -0
  227. /package/build/{events.d.ts → types/events.d.ts} +0 -0
  228. /package/build/{hooks → types/hooks}/useAudioDevices.d.ts +0 -0
  229. /package/build/{index.d.ts → types/index.d.ts} +0 -0
  230. /package/build/{trimAudio.d.ts → types/trimAudio.d.ts} +0 -0
  231. /package/build/{useAudioRecorder.d.ts → types/useAudioRecorder.d.ts} +0 -0
  232. /package/build/{utils → types/utils}/BlobFix.d.ts +0 -0
  233. /package/build/{utils → types/utils}/audioProcessing.d.ts +0 -0
  234. /package/build/{utils → types/utils}/concatenateBuffers.d.ts +0 -0
  235. /package/build/{utils → types/utils}/convertPCMToFloat32.d.ts +0 -0
  236. /package/build/{utils → types/utils}/crc32.d.ts +0 -0
  237. /package/build/{utils → types/utils}/encodingToBitDepth.d.ts +0 -0
  238. /package/build/{utils → types/utils}/getWavFileInfo.d.ts +0 -0
  239. /package/build/{utils → types/utils}/writeWavHeader.d.ts +0 -0
  240. /package/build/{workers → types/workers}/InlineFeaturesExtractor.web.d.ts +0 -0
  241. /package/build/{workers → types/workers}/inlineAudioWebWorker.web.d.ts +0 -0
@@ -0,0 +1,756 @@
1
+ "use strict";
2
+ // packages/expo-audio-stream/src/WebRecorder.web.ts
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.WebRecorder = void 0;
5
+ const encodingToBitDepth_1 = require("./utils/encodingToBitDepth");
6
+ const writeWavHeader_1 = require("./utils/writeWavHeader");
7
+ const InlineFeaturesExtractor_web_1 = require("./workers/InlineFeaturesExtractor.web");
8
+ const inlineAudioWebWorker_web_1 = require("./workers/inlineAudioWebWorker.web");
9
+ const DEFAULT_WEB_BITDEPTH = 32;
10
+ const DEFAULT_SEGMENT_DURATION_MS = 100;
11
+ const DEFAULT_WEB_INTERVAL = 500;
12
+ const DEFAULT_WEB_NUMBER_OF_CHANNELS = 1;
13
+ const TAG = 'WebRecorder';
14
+ class WebRecorder {
15
+ audioContext;
16
+ audioWorkletNode;
17
+ featureExtractorWorker;
18
+ source;
19
+ emitAudioEventCallback;
20
+ emitAudioAnalysisCallback;
21
+ config;
22
+ position = 0;
23
+ numberOfChannels; // Number of audio channels
24
+ bitDepth; // Bit depth of the audio
25
+ exportBitDepth; // Bit depth of the audio
26
+ audioAnalysisData; // Keep updating the full audio analysis data with latest events
27
+ logger;
28
+ compressedMediaRecorder = null;
29
+ compressedChunks = [];
30
+ compressedSize = 0;
31
+ pendingCompressedChunk = null;
32
+ dataPointIdCounter = 0; // Add this property to track the counter
33
+ deviceDisconnectionHandler = null;
34
+ mediaStream = null;
35
+ onInterruptionCallback;
36
+ _isDeviceDisconnected = false;
37
+ pcmData = null; // Store original PCM data
38
+ totalSampleCount = 0;
39
+ /**
40
+ * Flag to indicate whether this is the first audio chunk after a device switch
41
+ * Used to maintain proper duration counting
42
+ */
43
+ isFirstChunkAfterSwitch = false;
44
+ /**
45
+ * Gets whether the recording device has been disconnected
46
+ */
47
+ get isDeviceDisconnected() {
48
+ return this._isDeviceDisconnected;
49
+ }
50
+ /**
51
+ * Initializes a new WebRecorder instance for audio recording and processing
52
+ * @param audioContext - The AudioContext to use for recording
53
+ * @param source - The MediaStreamAudioSourceNode providing the audio input
54
+ * @param recordingConfig - Configuration options for the recording
55
+ * @param emitAudioEventCallback - Callback function for audio data events
56
+ * @param emitAudioAnalysisCallback - Callback function for audio analysis events
57
+ * @param onInterruption - Callback for recording interruptions
58
+ * @param logger - Optional logger for debugging information
59
+ */
60
+ constructor({ audioContext, source, recordingConfig, emitAudioEventCallback, emitAudioAnalysisCallback, onInterruption, logger, }) {
61
+ this.audioContext = audioContext;
62
+ this.source = source;
63
+ this.emitAudioEventCallback = emitAudioEventCallback;
64
+ this.emitAudioAnalysisCallback = emitAudioAnalysisCallback;
65
+ this.config = recordingConfig;
66
+ this.logger = logger;
67
+ const audioContextFormat = this.checkAudioContextFormat({
68
+ sampleRate: this.audioContext.sampleRate,
69
+ });
70
+ this.logger?.debug('Initialized WebRecorder with config:', {
71
+ sampleRate: audioContextFormat.sampleRate,
72
+ bitDepth: audioContextFormat.bitDepth,
73
+ numberOfChannels: audioContextFormat.numberOfChannels,
74
+ });
75
+ this.bitDepth = audioContextFormat.bitDepth;
76
+ this.numberOfChannels =
77
+ audioContextFormat.numberOfChannels ||
78
+ DEFAULT_WEB_NUMBER_OF_CHANNELS; // Default to 1 if not available
79
+ this.exportBitDepth =
80
+ (0, encodingToBitDepth_1.encodingToBitDepth)({
81
+ encoding: recordingConfig.encoding ?? 'pcm_32bit',
82
+ }) ||
83
+ audioContextFormat.bitDepth ||
84
+ DEFAULT_WEB_BITDEPTH;
85
+ this.audioAnalysisData = {
86
+ amplitudeRange: { min: 0, max: 0 },
87
+ rmsRange: { min: 0, max: 0 },
88
+ dataPoints: [],
89
+ durationMs: 0,
90
+ samples: 0,
91
+ bitDepth: this.bitDepth,
92
+ numberOfChannels: this.numberOfChannels,
93
+ sampleRate: this.config.sampleRate || this.audioContext.sampleRate,
94
+ segmentDurationMs: this.config.segmentDurationMs ?? DEFAULT_SEGMENT_DURATION_MS, // Default to 100ms segments
95
+ };
96
+ if (recordingConfig.enableProcessing) {
97
+ this.initFeatureExtractorWorker();
98
+ }
99
+ // Initialize compressed recording if enabled
100
+ if (recordingConfig.compression?.enabled) {
101
+ this.initializeCompressedRecorder();
102
+ }
103
+ this.mediaStream = source.mediaStream;
104
+ this.onInterruptionCallback = onInterruption;
105
+ // Setup device disconnection detection
106
+ this.setupDeviceDisconnectionDetection();
107
+ }
108
+ /**
109
+ * Initializes the audio worklet using an inline script
110
+ * Creates and connects the audio processing pipeline
111
+ */
112
+ async init() {
113
+ try {
114
+ // Create and use inline audio worklet
115
+ const blob = new Blob([inlineAudioWebWorker_web_1.InlineAudioWebWorker], {
116
+ type: 'application/javascript',
117
+ });
118
+ const url = URL.createObjectURL(blob);
119
+ await this.audioContext.audioWorklet.addModule(url);
120
+ this.audioWorkletNode = new AudioWorkletNode(this.audioContext, 'recorder-processor');
121
+ this.audioWorkletNode.port.onmessage = async (event) => {
122
+ const command = event.data.command;
123
+ if (command === 'debug') {
124
+ this.logger?.debug(`[AudioWorklet] ${event.data.message}`);
125
+ return;
126
+ }
127
+ if (command !== 'newData')
128
+ return;
129
+ const pcmBufferFloat = event.data.recordedData;
130
+ if (!pcmBufferFloat) {
131
+ this.logger?.warn('Received empty audio buffer', event);
132
+ return;
133
+ }
134
+ // Process data in smaller chunks and emit immediately
135
+ const sampleRate = event.data.sampleRate ?? this.audioContext.sampleRate;
136
+ // Use chunk size from config interval or default to 2 seconds
137
+ const intervalMs = this.config.interval ?? DEFAULT_WEB_INTERVAL;
138
+ const chunkSize = Math.floor(sampleRate * (intervalMs / 1000));
139
+ const duration = pcmBufferFloat.length / sampleRate;
140
+ // Use incoming position if provided by worklet, otherwise use our tracked position
141
+ const incomingPosition = typeof event.data.position === 'number'
142
+ ? event.data.position
143
+ : this.position;
144
+ // Calculate bytes per sample based on bit depth
145
+ const bytesPerSample = this.bitDepth / 8;
146
+ // Emit chunks without storing them
147
+ for (let i = 0; i < pcmBufferFloat.length; i += chunkSize) {
148
+ const chunk = pcmBufferFloat.slice(i, i + chunkSize);
149
+ const chunkPosition = incomingPosition + i / sampleRate;
150
+ // Calculate byte positions and samples
151
+ const startPosition = Math.floor(i * bytesPerSample);
152
+ const endPosition = Math.floor((i + chunk.length) * bytesPerSample);
153
+ const samples = chunk.length; // Number of samples in this chunk
154
+ // Process features if enabled
155
+ if (this.config.enableProcessing &&
156
+ this.featureExtractorWorker) {
157
+ this.featureExtractorWorker.postMessage({
158
+ command: 'process',
159
+ channelData: chunk,
160
+ sampleRate,
161
+ segmentDurationMs: this.config.segmentDurationMs ??
162
+ DEFAULT_SEGMENT_DURATION_MS, // Default to 100ms
163
+ bitDepth: this.bitDepth,
164
+ fullAudioDurationMs: chunkPosition * 1000,
165
+ numberOfChannels: this.numberOfChannels,
166
+ features: this.config.features,
167
+ intervalAnalysis: this.config.intervalAnalysis,
168
+ startPosition,
169
+ endPosition,
170
+ samples,
171
+ });
172
+ }
173
+ // Only store PCM data if web.storeUncompressedAudio is not explicitly false
174
+ const shouldStoreUncompressed = this.config.web?.storeUncompressedAudio !== false;
175
+ // Store PCM chunks when needed
176
+ if (shouldStoreUncompressed) {
177
+ // Store the original Float32Array data for later WAV creation
178
+ this.appendPcmData(chunk);
179
+ this.totalSampleCount += chunk.length;
180
+ }
181
+ // Emit chunk immediately
182
+ this.emitAudioEventCallback({
183
+ data: chunk,
184
+ position: chunkPosition,
185
+ compression: this.pendingCompressedChunk
186
+ ? {
187
+ data: this.pendingCompressedChunk,
188
+ size: this.pendingCompressedChunk.size,
189
+ totalSize: this.compressedSize,
190
+ mimeType: 'audio/webm',
191
+ format: 'opus',
192
+ bitrate: this.config.compression?.bitrate ??
193
+ 128000,
194
+ }
195
+ : undefined,
196
+ });
197
+ }
198
+ // Update our position based on the worklet's position if provided
199
+ this.position = incomingPosition + duration;
200
+ this.pendingCompressedChunk = null;
201
+ };
202
+ // Ensure we use all relevant settings from config
203
+ const recordSampleRate = this.audioContext.sampleRate;
204
+ const exportSampleRate = this.config.sampleRate ?? this.audioContext.sampleRate;
205
+ const channels = this.config.channels ?? this.numberOfChannels;
206
+ const interval = this.config.interval ?? DEFAULT_WEB_INTERVAL;
207
+ this.logger?.debug(`WebRecorder initialized with config:`, {
208
+ recordSampleRate,
209
+ exportSampleRate,
210
+ bitDepth: this.bitDepth,
211
+ exportBitDepth: this.exportBitDepth,
212
+ channels,
213
+ interval,
214
+ position: this.position,
215
+ deviceId: this.config.deviceId || 'default',
216
+ compression: this.config.compression
217
+ ? {
218
+ enabled: this.config.compression.enabled,
219
+ format: this.config.compression.format,
220
+ bitrate: this.config.compression.bitrate,
221
+ }
222
+ : 'disabled',
223
+ });
224
+ // Initialize the worklet with all settings from config
225
+ this.audioWorkletNode.port.postMessage({
226
+ command: 'init',
227
+ recordSampleRate,
228
+ exportSampleRate,
229
+ bitDepth: this.bitDepth,
230
+ exportBitDepth: this.exportBitDepth,
231
+ channels,
232
+ interval,
233
+ position: this.position, // Pass the current position to the processor
234
+ enableLogging: true,
235
+ });
236
+ // Connect the source to the AudioWorkletNode and start recording
237
+ this.source.connect(this.audioWorkletNode);
238
+ this.audioWorkletNode.connect(this.audioContext.destination);
239
+ }
240
+ catch (error) {
241
+ console.error(`[${TAG}] Failed to initialize WebRecorder`, error);
242
+ }
243
+ }
244
+ /**
245
+ * Append new PCM data to the existing buffer
246
+ * @param newData New Float32Array data to append
247
+ */
248
+ appendPcmData(newData) {
249
+ // Clone the incoming data to ensure it's not modified
250
+ const dataToAdd = new Float32Array(newData);
251
+ if (!this.pcmData) {
252
+ // First chunk - create a copy to avoid references to original data
253
+ this.pcmData = new Float32Array(dataToAdd);
254
+ return;
255
+ }
256
+ // Create a new buffer with increased size
257
+ const newBuffer = new Float32Array(this.pcmData.length + dataToAdd.length);
258
+ // Copy existing data
259
+ newBuffer.set(this.pcmData);
260
+ // Append new data
261
+ newBuffer.set(dataToAdd, this.pcmData.length);
262
+ // Replace existing buffer
263
+ this.pcmData = newBuffer;
264
+ }
265
+ /**
266
+ * Initializes the feature extractor worker for audio analysis
267
+ * Creates an inline worker from a blob for audio feature extraction
268
+ */
269
+ initFeatureExtractorWorker() {
270
+ try {
271
+ const blob = new Blob([InlineFeaturesExtractor_web_1.InlineFeaturesExtractor], {
272
+ type: 'application/javascript',
273
+ });
274
+ const url = URL.createObjectURL(blob);
275
+ this.featureExtractorWorker = new Worker(url);
276
+ this.featureExtractorWorker.onmessage =
277
+ this.handleFeatureExtractorMessage.bind(this);
278
+ this.featureExtractorWorker.onerror = (error) => {
279
+ console.error(`[${TAG}] Feature extractor worker error:`, error);
280
+ };
281
+ // Initialize worker with counter if needed
282
+ if (this.dataPointIdCounter > 0) {
283
+ this.featureExtractorWorker.postMessage({
284
+ command: 'resetCounter',
285
+ value: this.dataPointIdCounter,
286
+ });
287
+ this.logger?.debug(`Initialized worker with counter value ${this.dataPointIdCounter}`);
288
+ }
289
+ this.logger?.log('Feature extractor worker initialized successfully');
290
+ }
291
+ catch (error) {
292
+ console.error(`[${TAG}] Failed to initialize feature extractor worker`, error);
293
+ }
294
+ }
295
+ /**
296
+ * Processes audio analysis results from the feature extractor worker
297
+ * Updates the audio analysis data and emits events
298
+ * @param event - The event containing audio analysis results
299
+ */
300
+ handleFeatureExtractorMessage(event) {
301
+ if (event.data.command === 'features') {
302
+ const segmentResult = event.data.result;
303
+ // Track existing IDs to prevent duplicates
304
+ const existingIds = new Set(this.audioAnalysisData.dataPoints.map((dp) => dp.id));
305
+ // Filter out datapoints with duplicate IDs
306
+ const uniqueNewDataPoints = segmentResult.dataPoints.filter((dp) => {
307
+ return !existingIds.has(dp.id);
308
+ });
309
+ // Log filtered duplicates if any
310
+ if (uniqueNewDataPoints.length < segmentResult.dataPoints.length &&
311
+ this.logger?.warn) {
312
+ this.logger.warn(`Filtered ${segmentResult.dataPoints.length - uniqueNewDataPoints.length} duplicate datapoints`);
313
+ }
314
+ // Update counter based on the highest ID seen
315
+ if (uniqueNewDataPoints.length > 0) {
316
+ const lastDataPoint = uniqueNewDataPoints[uniqueNewDataPoints.length - 1];
317
+ if (lastDataPoint && typeof lastDataPoint.id === 'number') {
318
+ const nextIdValue = lastDataPoint.id + 1;
319
+ if (nextIdValue > this.dataPointIdCounter) {
320
+ this.dataPointIdCounter = nextIdValue;
321
+ this.logger?.debug(`Counter updated to ${this.dataPointIdCounter}`);
322
+ }
323
+ }
324
+ }
325
+ // Add unique data points to our analysis data
326
+ this.audioAnalysisData.dataPoints.push(...uniqueNewDataPoints);
327
+ this.audioAnalysisData.durationMs += segmentResult.durationMs;
328
+ this.audioAnalysisData.sampleRate = segmentResult.sampleRate;
329
+ // Merge amplitude ranges
330
+ if (segmentResult.amplitudeRange) {
331
+ if (!this.audioAnalysisData.amplitudeRange) {
332
+ this.audioAnalysisData.amplitudeRange = {
333
+ ...segmentResult.amplitudeRange,
334
+ };
335
+ }
336
+ else {
337
+ this.audioAnalysisData.amplitudeRange = {
338
+ min: Math.min(this.audioAnalysisData.amplitudeRange.min, segmentResult.amplitudeRange.min),
339
+ max: Math.max(this.audioAnalysisData.amplitudeRange.max, segmentResult.amplitudeRange.max),
340
+ };
341
+ }
342
+ }
343
+ // Merge RMS ranges
344
+ if (segmentResult.rmsRange) {
345
+ if (!this.audioAnalysisData.rmsRange) {
346
+ this.audioAnalysisData.rmsRange = {
347
+ ...segmentResult.rmsRange,
348
+ };
349
+ }
350
+ else {
351
+ this.audioAnalysisData.rmsRange = {
352
+ min: Math.min(this.audioAnalysisData.rmsRange.min, segmentResult.rmsRange.min),
353
+ max: Math.max(this.audioAnalysisData.rmsRange.max, segmentResult.rmsRange.max),
354
+ };
355
+ }
356
+ }
357
+ // Send filtered result to avoid duplicate IDs
358
+ const filteredSegmentResult = {
359
+ ...segmentResult,
360
+ dataPoints: uniqueNewDataPoints,
361
+ };
362
+ this.emitAudioAnalysisCallback(filteredSegmentResult);
363
+ }
364
+ }
365
+ /**
366
+ * Reset the data point counter to a specific value or zero
367
+ * @param startCounterFrom Optional value to start the counter from (for continuing from previous recordings)
368
+ */
369
+ resetDataPointCounter(startCounterFrom) {
370
+ // Set the counter with the passed value or 0
371
+ this.dataPointIdCounter =
372
+ startCounterFrom !== undefined ? startCounterFrom : 0;
373
+ this.logger?.debug(`Reset data point counter to ${this.dataPointIdCounter}`);
374
+ // Update worker counter if available
375
+ if (this.featureExtractorWorker) {
376
+ this.featureExtractorWorker.postMessage({
377
+ command: 'resetCounter',
378
+ value: this.dataPointIdCounter,
379
+ });
380
+ }
381
+ else {
382
+ this.logger?.warn('No feature extractor worker available to update counter');
383
+ }
384
+ }
385
+ /**
386
+ * Get the current data point counter value
387
+ * @returns The current value of the data point counter
388
+ */
389
+ getDataPointCounter() {
390
+ return this.dataPointIdCounter;
391
+ }
392
+ /**
393
+ * Prepares the recorder for continuity after device switch
394
+ * Sets up all necessary state to maintain proper recording continuity
395
+ */
396
+ prepareForDeviceSwitch() {
397
+ this.isFirstChunkAfterSwitch = true;
398
+ this.logger?.debug(`Prepared for device switch at position ${this.position}s`);
399
+ }
400
+ /**
401
+ * Starts the audio recording process
402
+ * Connects the audio nodes and begins capturing audio data
403
+ * @param preserveCounters If true, do not reset the counter (used for device switching)
404
+ */
405
+ start(preserveCounters = false) {
406
+ this.source.connect(this.audioWorkletNode);
407
+ this.audioWorkletNode.connect(this.audioContext.destination);
408
+ // Only reset the counter when not preserving state (e.g., for a fresh recording)
409
+ if (!preserveCounters) {
410
+ this.logger?.debug('Starting fresh recording, resetting counter to 0');
411
+ this.resetDataPointCounter(0); // Explicitly reset to 0 for new recordings
412
+ this.isFirstChunkAfterSwitch = false;
413
+ // Clear PCM data for new recording
414
+ this.pcmData = null;
415
+ this.totalSampleCount = 0;
416
+ }
417
+ else {
418
+ this.logger?.debug(`Preserving counter at ${this.dataPointIdCounter} during device switch`);
419
+ }
420
+ if (this.compressedMediaRecorder) {
421
+ this.compressedMediaRecorder.start(this.config.interval ?? 1000);
422
+ }
423
+ }
424
+ /**
425
+ * Creates a WAV file from the stored PCM data
426
+ */
427
+ createWavFromPcmData() {
428
+ try {
429
+ // Check if we have PCM data
430
+ if (!this.pcmData || this.pcmData.length === 0) {
431
+ this.logger?.warn('No PCM data available to create WAV file');
432
+ return null;
433
+ }
434
+ const sampleRate = this.config.sampleRate || this.audioContext.sampleRate;
435
+ const channels = this.numberOfChannels || 1;
436
+ // Convert float32 PCM data to 16-bit PCM for WAV
437
+ const bytesPerSample = 2; // 16-bit = 2 bytes
438
+ const dataLength = this.pcmData.length * bytesPerSample;
439
+ const buffer = new ArrayBuffer(dataLength);
440
+ const view = new DataView(buffer);
441
+ // Convert Float32Array (-1 to 1) to Int16Array (-32768 to 32767)
442
+ for (let i = 0; i < this.pcmData.length; i++) {
443
+ const sample = Math.max(-1, Math.min(1, this.pcmData[i]));
444
+ const int16Value = Math.round(sample * 32767);
445
+ view.setInt16(i * 2, int16Value, true);
446
+ }
447
+ // Use the existing writeWavHeader utility to add a WAV header
448
+ const wavBuffer = (0, writeWavHeader_1.writeWavHeader)({
449
+ buffer,
450
+ sampleRate,
451
+ numChannels: channels,
452
+ bitDepth: 16,
453
+ isFloat: false,
454
+ });
455
+ return new Blob([wavBuffer], { type: 'audio/wav' });
456
+ }
457
+ catch (error) {
458
+ this.logger?.error('Error creating WAV file from PCM data:', error);
459
+ return null;
460
+ }
461
+ }
462
+ /**
463
+ * Stops the audio recording process and returns the recorded data
464
+ * @returns Promise resolving to an object containing compressed and/or uncompressed blobs
465
+ */
466
+ async stop() {
467
+ try {
468
+ // Stop any compressed recording first
469
+ if (this.compressedMediaRecorder &&
470
+ this.compressedMediaRecorder.state !== 'inactive') {
471
+ this.compressedMediaRecorder.stop();
472
+ }
473
+ // Wait for any pending compressed chunks to be processed
474
+ if (this.compressedMediaRecorder) {
475
+ // Small delay to ensure all data is processed
476
+ await new Promise((resolve) => setTimeout(resolve, 100));
477
+ }
478
+ // Create uncompressed WAV file from the PCM data
479
+ let uncompressedBlob;
480
+ // Only create WAV if we have PCM data
481
+ if (this.pcmData && this.pcmData.length > 0) {
482
+ uncompressedBlob =
483
+ (await this.createWavFromPcmData()) || undefined;
484
+ }
485
+ // Return the compressed and/or uncompressed blobs if available
486
+ return {
487
+ compressedBlob: this.compressedChunks.length > 0
488
+ ? new Blob(this.compressedChunks, {
489
+ type: 'audio/webm;codecs=opus',
490
+ })
491
+ : undefined,
492
+ uncompressedBlob,
493
+ };
494
+ }
495
+ finally {
496
+ this.cleanup();
497
+ // Reset the chunks array
498
+ this.compressedChunks = [];
499
+ this.compressedSize = 0;
500
+ this.pendingCompressedChunk = null;
501
+ this.pcmData = null;
502
+ this.totalSampleCount = 0;
503
+ }
504
+ }
505
+ /**
506
+ * Cleans up resources when recording is stopped
507
+ * Closes audio context and disconnects nodes
508
+ */
509
+ cleanup() {
510
+ // Remove device disconnection handler
511
+ if (this.deviceDisconnectionHandler) {
512
+ this.deviceDisconnectionHandler();
513
+ this.deviceDisconnectionHandler = null;
514
+ }
515
+ // Check if AudioContext is already closed before attempting to close it
516
+ if (this.audioContext && this.audioContext.state !== 'closed') {
517
+ try {
518
+ this.audioContext.close();
519
+ }
520
+ catch (e) {
521
+ // Ignore closure errors - this happens if already closed
522
+ }
523
+ }
524
+ // Safely disconnect audioWorkletNode if it exists
525
+ if (this.audioWorkletNode) {
526
+ try {
527
+ this.audioWorkletNode.disconnect();
528
+ }
529
+ catch (e) {
530
+ // Ignore disconnection errors - node might be already disconnected
531
+ }
532
+ }
533
+ // Safely disconnect source if it exists
534
+ if (this.source) {
535
+ try {
536
+ this.source.disconnect();
537
+ }
538
+ catch (e) {
539
+ // Ignore disconnection errors - source might be already disconnected
540
+ }
541
+ }
542
+ // Always stop media stream tracks to release hardware resources
543
+ this.stopMediaStreamTracks();
544
+ // Mark as disconnected to prevent future errors
545
+ this._isDeviceDisconnected = true;
546
+ }
547
+ /**
548
+ * Pauses the audio recording process
549
+ * Disconnects audio nodes and pauses the media recorder
550
+ */
551
+ pause() {
552
+ try {
553
+ // Note: We're just pausing, not disconnecting the device
554
+ // Simply disconnect nodes temporarily without marking device as disconnected
555
+ this.source.disconnect(this.audioWorkletNode);
556
+ this.audioWorkletNode.disconnect(this.audioContext.destination);
557
+ this.audioWorkletNode.port.postMessage({ command: 'pause' });
558
+ if (this.compressedMediaRecorder?.state === 'recording') {
559
+ this.compressedMediaRecorder.pause();
560
+ }
561
+ this.logger?.debug('Recording paused successfully');
562
+ }
563
+ catch (error) {
564
+ this.logger?.error('Error in pause(): ', error);
565
+ // Already disconnected, just ignore and continue
566
+ }
567
+ }
568
+ /**
569
+ * Stops all media stream tracks to release hardware resources
570
+ * Ensures recording indicators (like microphone icon) are turned off
571
+ */
572
+ stopMediaStreamTracks() {
573
+ // Stop all audio tracks to stop the recording icon
574
+ if (this.mediaStream) {
575
+ const tracks = this.mediaStream.getTracks();
576
+ tracks.forEach((track) => track.stop());
577
+ }
578
+ else if (this.source?.mediaStream) {
579
+ const tracks = this.source.mediaStream.getTracks();
580
+ tracks.forEach((track) => track.stop());
581
+ }
582
+ }
583
+ /**
584
+ * Determines the audio format capabilities of the current audio context
585
+ * @param sampleRate - The sample rate to check
586
+ * @returns Object containing format information (sample rate, bit depth, channels)
587
+ */
588
+ checkAudioContextFormat({ sampleRate }) {
589
+ // Create a silent AudioBuffer
590
+ const frameCount = sampleRate * 1.0; // 1 second buffer
591
+ const audioBuffer = this.audioContext.createBuffer(1, frameCount, sampleRate);
592
+ // Check the format
593
+ const channelData = audioBuffer.getChannelData(0);
594
+ const bitDepth = channelData.BYTES_PER_ELEMENT * 8; // 4 bytes per element means 32-bit
595
+ return {
596
+ sampleRate: audioBuffer.sampleRate,
597
+ bitDepth,
598
+ numberOfChannels: audioBuffer.numberOfChannels,
599
+ };
600
+ }
601
+ /**
602
+ * Resumes a paused recording
603
+ * Reconnects audio nodes and resumes the media recorder
604
+ */
605
+ resume() {
606
+ // If device was disconnected, we can't resume
607
+ if (this._isDeviceDisconnected) {
608
+ this.logger?.warn('Cannot resume recording: device disconnected');
609
+ return;
610
+ }
611
+ try {
612
+ this.source.connect(this.audioWorkletNode);
613
+ this.audioWorkletNode.connect(this.audioContext.destination);
614
+ this.audioWorkletNode.port.postMessage({ command: 'resume' });
615
+ this.compressedMediaRecorder?.resume();
616
+ }
617
+ catch (error) {
618
+ this.logger?.error('Error in resume(): ', error);
619
+ }
620
+ }
621
+ /**
622
+ * Initializes the compressed media recorder if compression is enabled
623
+ * Sets up event handlers for compressed audio data
624
+ */
625
+ initializeCompressedRecorder() {
626
+ try {
627
+ const mimeType = 'audio/webm;codecs=opus';
628
+ if (!MediaRecorder.isTypeSupported(mimeType)) {
629
+ this.logger?.warn('Opus compression not supported in this browser');
630
+ return;
631
+ }
632
+ this.compressedMediaRecorder = new MediaRecorder(this.source.mediaStream, {
633
+ mimeType,
634
+ audioBitsPerSecond: this.config.compression?.bitrate ?? 128000,
635
+ });
636
+ this.compressedMediaRecorder.ondataavailable = (event) => {
637
+ if (event.data.size > 0) {
638
+ this.compressedChunks.push(event.data);
639
+ this.compressedSize += event.data.size;
640
+ this.pendingCompressedChunk = event.data;
641
+ }
642
+ };
643
+ }
644
+ catch (error) {
645
+ this.logger?.error('Failed to initialize compressed recorder:', error);
646
+ }
647
+ }
648
+ /**
649
+ * Processes features if enabled
650
+ */
651
+ processFeatures(chunk, sampleRate, chunkPosition, startPosition, endPosition, samples) {
652
+ if (this.config.enableProcessing && this.featureExtractorWorker) {
653
+ this.featureExtractorWorker.postMessage({
654
+ command: 'process',
655
+ channelData: chunk,
656
+ sampleRate,
657
+ segmentDurationMs: this.config.segmentDurationMs ??
658
+ DEFAULT_SEGMENT_DURATION_MS, // Default to 100ms
659
+ bitDepth: this.bitDepth,
660
+ fullAudioDurationMs: chunkPosition * 1000,
661
+ numberOfChannels: this.numberOfChannels,
662
+ features: this.config.features,
663
+ intervalAnalysis: this.config.intervalAnalysis,
664
+ startPosition,
665
+ endPosition,
666
+ samples,
667
+ });
668
+ }
669
+ }
670
+ /**
671
+ * Sets up detection for device disconnection events
672
+ */
673
+ setupDeviceDisconnectionDetection() {
674
+ if (!this.mediaStream)
675
+ return;
676
+ // Function to handle track ending (which happens on device disconnection)
677
+ const handleTrackEnded = () => {
678
+ this.logger?.warn('Audio track ended - device disconnected');
679
+ this._isDeviceDisconnected = true;
680
+ // Use the callback to notify parent component about device disconnection
681
+ if (this.onInterruptionCallback) {
682
+ this.onInterruptionCallback({
683
+ reason: 'deviceDisconnected',
684
+ isPaused: true,
685
+ timestamp: Date.now(),
686
+ });
687
+ this.logger?.debug('Notified about device disconnection');
688
+ }
689
+ // Ensure we disconnect nodes to prevent zombie recordings
690
+ if (this.audioWorkletNode) {
691
+ this.audioWorkletNode.port.postMessage({
692
+ command: 'deviceDisconnected',
693
+ });
694
+ try {
695
+ this.source.disconnect(this.audioWorkletNode);
696
+ this.audioWorkletNode.disconnect();
697
+ }
698
+ catch (e) {
699
+ // Ignore disconnection errors as the track might already be gone
700
+ }
701
+ }
702
+ };
703
+ // Add listeners to all audio tracks
704
+ const tracks = this.mediaStream.getAudioTracks();
705
+ tracks.forEach((track) => {
706
+ track.addEventListener('ended', handleTrackEnded);
707
+ });
708
+ // Store the handler for cleanup
709
+ this.deviceDisconnectionHandler = () => {
710
+ tracks.forEach((track) => {
711
+ track.removeEventListener('ended', handleTrackEnded);
712
+ });
713
+ };
714
+ }
715
+ /**
716
+ * Explicitly set the position for continuous recording across device switches
717
+ * @param position The position in seconds to continue from
718
+ */
719
+ setPosition(position) {
720
+ if (position >= 0) {
721
+ this.position = position;
722
+ this.logger?.debug(`Position explicitly set to ${position} seconds`);
723
+ }
724
+ else {
725
+ this.logger?.warn(`Invalid position value: ${position}, ignoring`);
726
+ }
727
+ }
728
+ /**
729
+ * Get the current position in seconds
730
+ * @returns The current position
731
+ */
732
+ getPosition() {
733
+ return this.position;
734
+ }
735
+ /**
736
+ * Gets the current compressed chunks
737
+ * @returns Array of current compressed audio chunks
738
+ */
739
+ getCompressedChunks() {
740
+ return [...this.compressedChunks];
741
+ }
742
+ /**
743
+ * Sets the compressed chunks from a previous recorder
744
+ * @param chunks Array of compressed chunks from a previous recorder
745
+ */
746
+ setCompressedChunks(chunks) {
747
+ if (chunks && chunks.length > 0) {
748
+ this.logger?.debug(`Adding ${chunks.length} compressed chunks from previous device`);
749
+ this.compressedChunks = [...chunks, ...this.compressedChunks];
750
+ // Update size
751
+ this.compressedSize = this.compressedChunks.reduce((size, chunk) => size + chunk.size, 0);
752
+ }
753
+ }
754
+ }
755
+ exports.WebRecorder = WebRecorder;
756
+ //# sourceMappingURL=WebRecorder.web.js.map