audio-chunkify 1.0.0

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,326 @@
1
+ # audio-chunkify
2
+
3
+ **Record audio in the browser and automatically split it into chunks — by silence, by time, or both.**
4
+
5
+ Framework agnostic · Zero dependencies · TypeScript · ESM
6
+
7
+ ---
8
+
9
+ ## Why audio-chunkify?
10
+
11
+ Long audio recordings are a problem: large files are slow to upload, expensive to process, and painful to recover from if something goes wrong. `audio-chunkify` solves this by splitting recordings into smaller, manageable chunks as they happen — in real time, without gaps or clicks.
12
+
13
+ Each chunk lands in an `onChunkProcessed` callback as a `File` object, ready to upload the moment it's created.
14
+
15
+ ```
16
+ [──────────────── recording ────────────────────────────────]
17
+ [── chunk 0 ──][── chunk 1 ──][── chunk 2 ──][── chunk 3 ──]
18
+ ↓ ↓ ↓ ↓
19
+ upload() upload() upload() upload() ← final
20
+ ```
21
+
22
+ Splitting happens automatically: after a minimum duration, the recorder listens for a moment of silence and cuts there. If no silence comes, it cuts at a configurable maximum duration. You get clean boundaries and parallel uploads, without any manual work.
23
+
24
+ ---
25
+
26
+ ## Features
27
+
28
+ - ✂️ **Smart chunk splitting** — cuts at silence after a minimum duration, or forces a cut at maximum duration
29
+ - 🔇 **Real-time silence detection** — dB analysis via Web Audio API, configurable threshold
30
+ - 🔀 **Multi-stream mixing** — merge microphone + system audio (or any streams) into one recording
31
+ - ⏱️ **Time tracking** — elapsed recording time, pause-aware
32
+ - 📦 **TypeScript** — full type definitions included
33
+ - 🪶 **Zero dependencies**
34
+ - ⚙️ **Works with React, Vue, Svelte, Angular, Vanilla JS** — or any browser environment
35
+
36
+ ---
37
+
38
+ ## Installation
39
+
40
+ ```bash
41
+ npm install audio-chunkify
42
+ # or
43
+ pnpm add audio-chunkify
44
+ # or
45
+ yarn add audio-chunkify
46
+ ```
47
+
48
+ ---
49
+
50
+ ## Quick start
51
+
52
+ ```ts
53
+ import { AudioChunkify } from 'audio-chunkify'
54
+
55
+ const recorder = new AudioChunkify()
56
+ const stream = await navigator.mediaDevices.getUserMedia({ audio: true })
57
+
58
+ recorder.create(stream)
59
+
60
+ // Called automatically as each chunk is ready
61
+ recorder.onChunkProcessed(async ({ file, index, duration, isLastChunk }) => {
62
+ console.log(`Chunk ${index} — ${duration.toFixed(1)}s`)
63
+ await uploadToServer(file) // upload while recording continues
64
+
65
+ if (isLastChunk) {
66
+ console.log('Recording complete')
67
+ }
68
+ })
69
+
70
+ recorder.start()
71
+
72
+ // Later...
73
+ recorder.stop()
74
+
75
+ // Always release resources when done
76
+ recorder.destroy()
77
+ ```
78
+
79
+ ---
80
+
81
+ ## How chunking works
82
+
83
+ ```
84
+ minDuration ──────────────┐
85
+
86
+ [recording...] [silence detected?] ──yes──→ split here ✂️
87
+
88
+ no
89
+
90
+ maxDuration ───────────────┴──────────────→ force split ✂️
91
+ ```
92
+
93
+ | Parameter | Default | Description |
94
+ |---|---|---|
95
+ | `minDuration` | `30s` | Chunk won't split before this time |
96
+ | `maxDuration` | `300s` | Chunk always splits after this time |
97
+ | `minSilenceDuration` | `1s` | Silence must last this long to trigger a split |
98
+ | `silenceThreshold` | `-50 dB` | Audio below this level is considered silence |
99
+
100
+ All four values are configurable:
101
+
102
+ ```ts
103
+ recorder.create(stream, {
104
+ timeslice: 250,
105
+ silenceThreshold: -45,
106
+ chunkOptions: {
107
+ minDuration: 10,
108
+ maxDuration: 60,
109
+ minSilenceDuration: 1.5,
110
+ },
111
+ })
112
+ ```
113
+
114
+ ---
115
+
116
+ ## API
117
+
118
+ ### `new AudioChunkify()`
119
+
120
+ Creates a new instance. No arguments required.
121
+
122
+ ---
123
+
124
+ ### `.create(stream, options?)`
125
+
126
+ Initialises the recorder. Must be called before `start()`.
127
+
128
+ | Option | Type | Default | Description |
129
+ |---|---|---|---|
130
+ | `mimeType` | `string` | auto | Force a specific MIME type (e.g. `'audio/webm;codecs=opus'`). Auto-detected if omitted. |
131
+ | `timeslice` | `number` | — | Millisecond interval for internal `ondataavailable` events |
132
+ | `silenceThreshold` | `number` | `-50` | dB threshold for silence detection |
133
+ | `chunkOptions.minDuration` | `number` | `30` | Minimum chunk duration in seconds |
134
+ | `chunkOptions.maxDuration` | `number` | `300` | Maximum chunk duration in seconds |
135
+ | `chunkOptions.minSilenceDuration` | `number` | `1` | Seconds of silence required to trigger a split |
136
+
137
+ Returns the underlying `MediaRecorder` instance.
138
+
139
+ ---
140
+
141
+ ### `.start(timeslice?)` / `.pause()` / `.resume()` / `.stop()`
142
+
143
+ Control the recording lifecycle. Pause/resume correctly accounts for paused time when calculating chunk durations.
144
+
145
+ ---
146
+
147
+ ### `.addAudioStream(stream)` / `.removeAudioStream(stream)`
148
+
149
+ Dynamically add or remove streams from the mix at any time. The internal `AudioContext` graph is rebuilt transparently.
150
+
151
+ ---
152
+
153
+ ### `.destroy(options?)`
154
+
155
+ Stops recording, closes internal `AudioContext` instances, and clears all state. Always call this when done to avoid memory leaks.
156
+
157
+ | Option | Type | Default | Description |
158
+ |---|---|---|---|
159
+ | `stopStreams` | `boolean` | `true` | When `true`, calls `stop()` on every track of the streams you passed to `create()` / `addAudioStream()`. Set to `false` to keep those streams alive (e.g. reuse the same microphone for another recording). |
160
+
161
+ By default, `destroy()` releases the microphone and any other input tracks — same as today. If you own the `MediaStream` and want to keep it after teardown:
162
+
163
+ ```ts
164
+ recorder.stop()
165
+ recorder.destroy({ stopStreams: false })
166
+
167
+ // stream is still active — safe to pass to a new AudioChunkify instance
168
+ const next = new AudioChunkify()
169
+ next.create(stream)
170
+ ```
171
+
172
+ When unmounting a component or leaving a page, keep the default (`stopStreams: true`) so the browser releases the mic indicator.
173
+
174
+ ---
175
+
176
+ ### State & metadata
177
+
178
+ | Method | Returns | Description |
179
+ |---|---|---|
180
+ | `.getState()` | `'inactive' \| 'recording' \| 'paused'` | Current state |
181
+ | `.getMimeType()` | `string \| null` | Active MIME type |
182
+ | `.getStream()` | `MediaStream \| null` | The mixed audio stream |
183
+ | `.getMediaRecorder()` | `MediaRecorder \| null` | The underlying `MediaRecorder` |
184
+ | `.getRecordingTime()` | `number` | Elapsed seconds (excludes paused time) |
185
+ | `.getSilenceTime()` | `number` | Continuous silence in seconds |
186
+ | `.getSilenceThreshold()` | `number` | Current dB threshold |
187
+ | `.setSilenceThreshold(dB)` | `void` | Update the dB threshold (must be negative) |
188
+ | `.getCurrentChunkIndex()` | `number` | Zero-based index of the current chunk |
189
+ | `.getAudioChunks()` | `Blob[]` | Current internal chunk buffer |
190
+
191
+ ---
192
+
193
+ ### Events
194
+
195
+ All `on*` methods return `this` for chaining.
196
+
197
+ ```ts
198
+ recorder
199
+ .onStart(() => setUI('recording'))
200
+ .onPause(() => setUI('paused'))
201
+ .onResume(() => setUI('recording'))
202
+ .onStop(() => setUI('idle'))
203
+ .onTimeUpdate((seconds) => setTimer(seconds))
204
+ .onSilenceDetected(() => console.log('silence…'))
205
+ .onAudioDetected(() => console.log('audio resumed'))
206
+ .onError((e) => console.error(e))
207
+ .onChunkProcessed(async ({ file, index, duration, isLastChunk }) => {
208
+ await upload(file)
209
+ })
210
+ ```
211
+
212
+ #### `onChunkProcessed` payload
213
+
214
+ ```ts
215
+ interface ChunkProcessedPayload {
216
+ file: File // Named "chunk-{index}.{ext}" with correct MIME type
217
+ index: number // Zero-based chunk index within the session
218
+ duration: number // Duration of this chunk in seconds
219
+ isLastChunk: boolean // true only when triggered by stop()
220
+ }
221
+ ```
222
+
223
+ ---
224
+
225
+ ## Recipes
226
+
227
+ ### Microphone only
228
+
229
+ ```ts
230
+ import { AudioChunkify } from 'audio-chunkify'
231
+
232
+ const stream = await navigator.mediaDevices.getUserMedia({ audio: true })
233
+ const recorder = new AudioChunkify()
234
+
235
+ recorder.create(stream)
236
+ recorder.onChunkProcessed(async ({ file, isLastChunk }) => {
237
+ await uploadChunk(file)
238
+ })
239
+ recorder.start()
240
+ ```
241
+
242
+ ### Mix microphone + system audio
243
+
244
+ ```ts
245
+ const mic = await navigator.mediaDevices.getUserMedia({ audio: true })
246
+ const system = await navigator.mediaDevices.getDisplayMedia({ audio: true, video: false })
247
+
248
+ const recorder = new AudioChunkify()
249
+ recorder.create(mic)
250
+ recorder.addAudioStream(system)
251
+ recorder.start()
252
+ ```
253
+
254
+ ### Short chunks for real-time transcription
255
+
256
+ ```ts
257
+ recorder.create(stream, {
258
+ timeslice: 200,
259
+ silenceThreshold: -40,
260
+ chunkOptions: {
261
+ minDuration: 5,
262
+ maxDuration: 15,
263
+ minSilenceDuration: 0.5,
264
+ },
265
+ })
266
+
267
+ recorder.onChunkProcessed(async ({ file }) => {
268
+ const transcript = await transcribeAudio(file) // send to Whisper, etc.
269
+ appendToTranscript(transcript)
270
+ })
271
+ ```
272
+
273
+ ### React hook
274
+
275
+ ```tsx
276
+ import { useEffect, useRef, useState } from 'react'
277
+ import { AudioChunkify } from 'audio-chunkify'
278
+
279
+ export function useAudioChunkify() {
280
+ const recorder = useRef(new AudioChunkify())
281
+ const [state, setState] = useState<'idle' | 'recording' | 'paused'>('idle')
282
+ const [elapsed, setElapsed] = useState(0)
283
+
284
+ async function start() {
285
+ const stream = await navigator.mediaDevices.getUserMedia({ audio: true })
286
+
287
+ recorder.current
288
+ .create(stream, { timeslice: 250 })
289
+ .onStart(() => setState('recording'))
290
+ .onPause(() => setState('paused'))
291
+ .onResume(() => setState('recording'))
292
+ .onStop(() => { setState('idle'); setElapsed(0) })
293
+ .onTimeUpdate(setElapsed)
294
+ .onChunkProcessed(async ({ file, isLastChunk }) => {
295
+ await uploadChunk(file)
296
+ })
297
+
298
+ recorder.current.start()
299
+ }
300
+
301
+ useEffect(() => () => { recorder.current.destroy() }, [])
302
+
303
+ return {
304
+ state,
305
+ elapsed,
306
+ start,
307
+ pause: () => recorder.current.pause(),
308
+ resume: () => recorder.current.resume(),
309
+ stop: () => recorder.current.stop(),
310
+ }
311
+ }
312
+ ```
313
+
314
+ ---
315
+
316
+ ## Browser support
317
+
318
+ Requires the [MediaRecorder API](https://developer.mozilla.org/en-US/docs/Web/API/MediaRecorder) and [Web Audio API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Audio_API). Supported in all modern browsers (Chrome, Firefox, Safari 14.1+, Edge).
319
+
320
+ Not supported in Node.js or non-browser environments.
321
+
322
+ ---
323
+
324
+ ## License
325
+
326
+ MIT
@@ -0,0 +1,257 @@
1
+ /**
2
+ * Options passed to AudioRecorder.create()
3
+ */
4
+ interface RecorderCreateOptions {
5
+ /**
6
+ * Explicit MIME type to use (e.g. 'audio/webm;codecs=opus').
7
+ * If omitted the library auto-detects the best supported type.
8
+ */
9
+ mimeType?: string;
10
+ /**
11
+ * Millisecond interval for ondataavailable events.
12
+ * Passed directly to MediaRecorder.start(timeslice).
13
+ */
14
+ timeslice?: number;
15
+ /**
16
+ * Silence detection threshold in dB (negative value, e.g. -50).
17
+ * Audio below this level is considered silence.
18
+ * @default -50
19
+ */
20
+ silenceThreshold?: number;
21
+ /**
22
+ * Overrides for chunk-splitting timing constants.
23
+ */
24
+ chunkOptions?: Partial<ChunkOptions>;
25
+ }
26
+ /**
27
+ * Chunk splitting timing configuration.
28
+ */
29
+ interface ChunkOptions {
30
+ /** Maximum chunk duration in seconds before a forced split. @default 300 */
31
+ maxDuration: number;
32
+ /** Minimum chunk duration in seconds before silence-based split is allowed. @default 30 */
33
+ minDuration: number;
34
+ /** Minimum continuous silence (seconds) needed to trigger a split. @default 1 */
35
+ minSilenceDuration: number;
36
+ }
37
+ /**
38
+ * Payload delivered to the onChunkProcessed callback.
39
+ */
40
+ interface ChunkProcessedPayload {
41
+ /** File object for the audio chunk, named chunk-{index}.{ext} */
42
+ file: File;
43
+ /** Zero-based index of this chunk in the current recording session */
44
+ index: number;
45
+ /** Duration of this chunk in seconds */
46
+ duration: number;
47
+ /** true when this is the final chunk (recorder was stopped) */
48
+ isLastChunk: boolean;
49
+ }
50
+ /**
51
+ * Recorder state — mirrors the native MediaRecorder.state values.
52
+ */
53
+ type RecorderState = 'inactive' | 'recording' | 'paused';
54
+ /**
55
+ * Options passed to AudioChunkify.destroy().
56
+ */
57
+ interface DestroyOptions {
58
+ /**
59
+ * When `true` (default), calls `stop()` on every track of the streams
60
+ * passed to `create()` / `addAudioStream()`. Set to `false` when you
61
+ * plan to reuse those streams after tearing down the recorder
62
+ * (e.g. start a new recording with the same microphone).
63
+ */
64
+ stopStreams?: boolean;
65
+ }
66
+
67
+ /**
68
+ * AudioChunkify
69
+ *
70
+ * A framework-agnostic MediaRecorder wrapper that adds:
71
+ * - Automatic MIME type detection
72
+ * - Multi-stream mixing via AudioContext
73
+ * - Silence detection with configurable threshold
74
+ * - Automatic chunk splitting (time + silence based)
75
+ * - Recording time tracking
76
+ * - Pause/resume with correct duration accounting
77
+ */
78
+ declare class AudioChunkify {
79
+ private _mediaRecorder;
80
+ private _stream;
81
+ private _audioStreams;
82
+ private _mimeType;
83
+ private _timeslice;
84
+ private _onDataAvailableCallback;
85
+ private _onStartCallback;
86
+ private _onPauseCallback;
87
+ private _onResumeCallback;
88
+ private _onStopCallback;
89
+ private _onErrorCallback;
90
+ private _recordingTime;
91
+ private _intervalId;
92
+ private _onTimeUpdateCallback;
93
+ private _audioContext;
94
+ private _analyserNode;
95
+ private _sourceNode;
96
+ private _silenceTime;
97
+ private _silenceIntervalId;
98
+ private _silenceCheckIntervalId;
99
+ private _silenceThreshold;
100
+ private _isInSilence;
101
+ private _onSilenceDetectedCallback;
102
+ private _onAudioDetectedCallback;
103
+ private _mixedStreamContext;
104
+ private _mixedStreamDestination;
105
+ private _mixedStreamSourceNodes;
106
+ private _audioChunks;
107
+ private _currentChunkIndex;
108
+ private _processingChunk;
109
+ private _isChunkSplit;
110
+ private _lastChunkEnd;
111
+ private _silenceStartForChunking;
112
+ private _chunkSplitIntervalId;
113
+ private _onChunkProcessedCallback;
114
+ private _pauseStartTime;
115
+ private _totalPausedTime;
116
+ private _chunkOptions;
117
+ /**
118
+ * Initialises the recorder with the given stream and options.
119
+ *
120
+ * @example
121
+ * const recorder = new AudioChunkify()
122
+ * const stream = await navigator.mediaDevices.getUserMedia({ audio: true })
123
+ * recorder.create(stream, { timeslice: 250 })
124
+ */
125
+ create(stream: MediaStream, options?: RecorderCreateOptions): MediaRecorder;
126
+ /** Starts recording. Optionally overrides the timeslice set in create(). */
127
+ start(timeslice?: number): void;
128
+ /** Pauses an active recording. */
129
+ pause(): void;
130
+ /** Resumes a paused recording. */
131
+ resume(): void;
132
+ /** Stops the recording and finalises all pending chunks. */
133
+ stop(): void;
134
+ /**
135
+ * Dynamically mixes in an additional audio stream.
136
+ * The recorder is briefly restarted to rebuild the AudioContext graph.
137
+ */
138
+ addAudioStream(stream: MediaStream): void;
139
+ /**
140
+ * Removes a previously added audio stream from the mix.
141
+ * At least one stream must remain — call destroy() to tear everything down.
142
+ */
143
+ removeAudioStream(stream: MediaStream): void;
144
+ /** Current recorder state: 'inactive' | 'recording' | 'paused' */
145
+ getState(): RecorderState;
146
+ /** Active MIME type (resolved at create() time). */
147
+ getMimeType(): string | null;
148
+ /** The mixed MediaStream used internally by the recorder. */
149
+ getStream(): MediaStream | null;
150
+ /** The underlying MediaRecorder instance. */
151
+ getMediaRecorder(): MediaRecorder | null;
152
+ /** Elapsed recording time in seconds (excludes paused time). */
153
+ getRecordingTime(): number;
154
+ /** Resets the elapsed recording time counter to zero. */
155
+ resetRecordingTime(): void;
156
+ /** Current continuous silence duration in seconds. */
157
+ getSilenceTime(): number;
158
+ /** Current silence detection threshold in dB. */
159
+ getSilenceThreshold(): number;
160
+ /**
161
+ * Updates the silence detection threshold.
162
+ * @param threshold — Must be a negative number in dB (e.g. -50).
163
+ */
164
+ setSilenceThreshold(threshold: number): void;
165
+ /** Accumulated audio chunks since the last start() or split. */
166
+ getAudioChunks(): Blob[];
167
+ /** Zero-based index of the current chunk. */
168
+ getCurrentChunkIndex(): number;
169
+ /** Clears the internal chunk buffer. */
170
+ clearAudioChunks(): void;
171
+ onDataAvailable(callback: (event: BlobEvent) => void): this;
172
+ onStart(callback: () => void): this;
173
+ onPause(callback: () => void): this;
174
+ onResume(callback: () => void): this;
175
+ onStop(callback: () => void): this;
176
+ onError(callback: (event: Event) => void): this;
177
+ onTimeUpdate(callback: (seconds: number) => void): this;
178
+ onSilenceDetected(callback: () => void): this;
179
+ onAudioDetected(callback: () => void): this;
180
+ /**
181
+ * Called each time a chunk is finalised (either by splitting or on stop).
182
+ *
183
+ * @example
184
+ * recorder.onChunkProcessed(async ({ file, index, duration, isLastChunk }) => {
185
+ * await uploadChunk(file)
186
+ * if (isLastChunk) console.log('Recording complete')
187
+ * })
188
+ */
189
+ onChunkProcessed(callback: (payload: ChunkProcessedPayload) => Promise<void> | void): this;
190
+ /**
191
+ * Fully tears down the recorder: stops recording, closes AudioContexts
192
+ * and clears all internal state.
193
+ *
194
+ * By default also stops every track on the input streams. Pass
195
+ * `{ stopStreams: false }` to keep those streams alive for reuse.
196
+ */
197
+ destroy(options?: DestroyOptions): void;
198
+ private _assertReady;
199
+ private _assertCallback;
200
+ private _captureState;
201
+ private _restoreState;
202
+ private _now;
203
+ private _startTimeTracking;
204
+ private _stopTimeTracking;
205
+ private _pauseTimeTracking;
206
+ private _resumeTimeTracking;
207
+ private _setupAudioAnalysis;
208
+ private _cleanupAudioAnalysis;
209
+ /**
210
+ * Measures the current audio level in dBFS via time-domain RMS.
211
+ * Shared by silence events and chunk-splitting logic.
212
+ */
213
+ private _measureAudioLevelDb;
214
+ private _isCurrentlySilent;
215
+ private _checkSilence;
216
+ private _startSilenceChecking;
217
+ private _stopSilenceTracking;
218
+ private _shouldSplitChunk;
219
+ private _restartRecorderSegment;
220
+ private _startChunkSplitting;
221
+ private _stopChunkSplitting;
222
+ private _createMixedStream;
223
+ private _recreateMediaRecorder;
224
+ private _cleanupMixedStream;
225
+ private _setupEventHandlers;
226
+ private _processChunk;
227
+ }
228
+
229
+ /**
230
+ * Returns the first audio MIME type supported by the current browser,
231
+ * or null if MediaRecorder is unavailable.
232
+ */
233
+ declare function getCompatibleMimeType(): string | null;
234
+ /**
235
+ * Derives a file extension from a MIME type string.
236
+ * Falls back to 'webm' if the type is unrecognised.
237
+ */
238
+ declare function getFileExtensionFromMimeType(mimeType: string | null): string;
239
+
240
+ /**
241
+ * Default audio analysis and chunking constants.
242
+ * All values can be overridden via RecorderOptions.
243
+ */
244
+ declare const DEFAULT_AUDIO_CONSTANTS: {
245
+ /** FFT size for the AnalyserNode (must be a power of 2). Affects frequency resolution. */
246
+ readonly ANALYSIS_BUFFER_SIZE: 2048;
247
+ /** Silence threshold in dB used for chunk splitting (-Infinity to 0). */
248
+ readonly SILENCE_THRESHOLD: -50;
249
+ /** Maximum chunk duration in seconds before a forced split. */
250
+ readonly MAX_CHUNK_DURATION: 300;
251
+ /** Minimum chunk duration in seconds before a silence-based split is allowed. */
252
+ readonly MIN_CHUNK_DURATION: 30;
253
+ /** Minimum continuous silence duration in seconds required to trigger a split. */
254
+ readonly MIN_SILENCE_DURATION: 1;
255
+ };
256
+
257
+ export { AudioChunkify, type ChunkOptions, type ChunkProcessedPayload, DEFAULT_AUDIO_CONSTANTS, type DestroyOptions, type RecorderCreateOptions, type RecorderState, getCompatibleMimeType, getFileExtensionFromMimeType };