web-audio-recorder-ts 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.
Files changed (40) hide show
  1. package/LICENSE +62 -0
  2. package/README.md +261 -0
  3. package/dist/core/WebAudioRecorder.d.ts +85 -0
  4. package/dist/core/WebAudioRecorder.d.ts.map +1 -0
  5. package/dist/core/types.d.ts +109 -0
  6. package/dist/core/types.d.ts.map +1 -0
  7. package/dist/encoders/Mp3LameEncoder.d.ts +48 -0
  8. package/dist/encoders/Mp3LameEncoder.d.ts.map +1 -0
  9. package/dist/encoders/OggVorbisEncoder.d.ts +48 -0
  10. package/dist/encoders/OggVorbisEncoder.d.ts.map +1 -0
  11. package/dist/encoders/WavAudioEncoder.d.ts +47 -0
  12. package/dist/encoders/WavAudioEncoder.d.ts.map +1 -0
  13. package/dist/index.cjs.js +770 -0
  14. package/dist/index.cjs.js.map +1 -0
  15. package/dist/index.d.ts +17 -0
  16. package/dist/index.d.ts.map +1 -0
  17. package/dist/index.esm.js +760 -0
  18. package/dist/index.esm.js.map +1 -0
  19. package/dist/index.umd.js +776 -0
  20. package/dist/index.umd.js.map +1 -0
  21. package/dist/lib/Mp3LameEncoder.min.js +18 -0
  22. package/dist/lib/Mp3LameEncoder.min.js.mem +0 -0
  23. package/dist/lib/OggVorbisEncoder.min.js +17 -0
  24. package/dist/lib/OggVorbisEncoder.min.js.mem +0 -0
  25. package/dist/lib/WavAudioEncoder.min.js +1 -0
  26. package/dist/recorders/WebAudioRecorderMp3.d.ts +29 -0
  27. package/dist/recorders/WebAudioRecorderMp3.d.ts.map +1 -0
  28. package/dist/recorders/WebAudioRecorderOgg.d.ts +29 -0
  29. package/dist/recorders/WebAudioRecorderOgg.d.ts.map +1 -0
  30. package/dist/recorders/WebAudioRecorderWav.d.ts +20 -0
  31. package/dist/recorders/WebAudioRecorderWav.d.ts.map +1 -0
  32. package/lib/Mp3LameEncoder.min.js +18 -0
  33. package/lib/Mp3LameEncoder.min.js.mem +0 -0
  34. package/lib/OggVorbisEncoder.min.js +17 -0
  35. package/lib/OggVorbisEncoder.min.js.mem +0 -0
  36. package/lib/WavAudioEncoder.min.js +1 -0
  37. package/package.json +62 -0
  38. package/types/mp3-lame-encoder.d.ts +28 -0
  39. package/types/ogg-vorbis-encoder.d.ts +28 -0
  40. package/types/wav-audio-encoder.d.ts +17 -0
@@ -0,0 +1,770 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Tipos e interfaces principais para WebAudioRecorder
5
+ */
6
+ /**
7
+ * Formatos de áudio suportados
8
+ */
9
+ exports.AudioFormat = void 0;
10
+ (function (AudioFormat) {
11
+ AudioFormat["WAV"] = "wav";
12
+ AudioFormat["OGG"] = "ogg";
13
+ AudioFormat["MP3"] = "mp3";
14
+ })(exports.AudioFormat || (exports.AudioFormat = {}));
15
+ /**
16
+ * Status do recorder
17
+ */
18
+ exports.RecorderStatus = void 0;
19
+ (function (RecorderStatus) {
20
+ RecorderStatus["INACTIVE"] = "inactive";
21
+ RecorderStatus["RECORDING"] = "recording";
22
+ RecorderStatus["PAUSED"] = "paused";
23
+ RecorderStatus["PROCESSING"] = "processing";
24
+ RecorderStatus["COMPLETE"] = "complete";
25
+ RecorderStatus["ERROR"] = "error";
26
+ })(exports.RecorderStatus || (exports.RecorderStatus = {}));
27
+
28
+ /**
29
+ * Classe base WebAudioRecorder para gravação de áudio
30
+ *
31
+ * @module core/WebAudioRecorder
32
+ */
33
+ /**
34
+ * Classe principal para gravação de áudio usando Web Audio API
35
+ */
36
+ class WebAudioRecorder {
37
+ /**
38
+ * Cria uma instância do WebAudioRecorder
39
+ *
40
+ * @param audioContext - Contexto de áudio Web Audio API
41
+ * @param encoder - Encoder de áudio a ser usado
42
+ * @param options - Opções de configuração do recorder
43
+ */
44
+ constructor(audioContext, encoder, options = {}) {
45
+ this.audioContext = null;
46
+ this.sourceNode = null;
47
+ this.scriptProcessor = null;
48
+ this.encoder = null;
49
+ this.stream = null;
50
+ this.status = exports.RecorderStatus.INACTIVE;
51
+ this.sampleRate = 44100;
52
+ this.numChannels = 2;
53
+ this.bufferSize = 4096;
54
+ this.startTime = 0;
55
+ this.onDataAvailableCallback = null;
56
+ this.onCompleteCallback = null;
57
+ this.onErrorCallback = null;
58
+ this.audioContext = audioContext;
59
+ this.encoder = encoder;
60
+ this.sampleRate = options.sampleRate || this.audioContext.sampleRate;
61
+ this.numChannels = options.numChannels || 2;
62
+ this.bufferSize = options.bufferSize || 4096;
63
+ this.onDataAvailableCallback = options.onDataAvailable || null;
64
+ this.onCompleteCallback = options.onComplete || null;
65
+ this.onErrorCallback = options.onError || null;
66
+ }
67
+ /**
68
+ * Inicia a gravação de áudio
69
+ *
70
+ * @param stream - Stream de mídia a ser gravado
71
+ * @returns Promise que resolve quando a gravação inicia
72
+ */
73
+ async start(stream) {
74
+ if (this.status === exports.RecorderStatus.RECORDING) {
75
+ throw new Error('Recording is already in progress');
76
+ }
77
+ if (this.status === exports.RecorderStatus.PROCESSING) {
78
+ throw new Error('Previous recording is still being processed');
79
+ }
80
+ try {
81
+ this.stream = stream;
82
+ this.status = exports.RecorderStatus.RECORDING;
83
+ this.startTime = Date.now();
84
+ if (!this.audioContext) {
85
+ throw new Error('AudioContext is not initialized');
86
+ }
87
+ // Criar source node a partir do stream
88
+ this.sourceNode = this.audioContext.createMediaStreamSource(stream);
89
+ // Criar script processor para capturar dados de áudio
90
+ this.scriptProcessor = this.audioContext.createScriptProcessor(this.bufferSize, this.numChannels, this.numChannels);
91
+ // Conectar os nós
92
+ this.sourceNode.connect(this.scriptProcessor);
93
+ this.scriptProcessor.connect(this.audioContext.destination);
94
+ // Configurar callback para processar dados de áudio
95
+ this.scriptProcessor.onaudioprocess = (event) => {
96
+ if (this.status !== exports.RecorderStatus.RECORDING) {
97
+ return;
98
+ }
99
+ try {
100
+ const inputBuffer = event.inputBuffer;
101
+ const buffers = [];
102
+ // Extrair dados de cada canal
103
+ for (let channel = 0; channel < this.numChannels; channel++) {
104
+ const channelData = inputBuffer.getChannelData(channel);
105
+ buffers.push(new Float32Array(channelData));
106
+ }
107
+ // Codificar os dados
108
+ if (this.encoder) {
109
+ this.encoder.encode(buffers);
110
+ }
111
+ }
112
+ catch (error) {
113
+ this.handleError(error);
114
+ }
115
+ };
116
+ }
117
+ catch (error) {
118
+ this.status = exports.RecorderStatus.ERROR;
119
+ this.handleError(error);
120
+ throw error;
121
+ }
122
+ }
123
+ /**
124
+ * Para a gravação e finaliza o arquivo de áudio
125
+ *
126
+ * @param mimeType - Tipo MIME do arquivo (padrão: baseado no encoder)
127
+ * @returns Promise que resolve com o Blob do áudio gravado
128
+ */
129
+ async stop(mimeType) {
130
+ if (this.status !== exports.RecorderStatus.RECORDING) {
131
+ throw new Error('No recording in progress');
132
+ }
133
+ this.status = exports.RecorderStatus.PROCESSING;
134
+ try {
135
+ // Desconectar nós
136
+ if (this.scriptProcessor) {
137
+ this.scriptProcessor.disconnect();
138
+ this.scriptProcessor.onaudioprocess = null;
139
+ this.scriptProcessor = null;
140
+ }
141
+ if (this.sourceNode) {
142
+ this.sourceNode.disconnect();
143
+ this.sourceNode = null;
144
+ }
145
+ // Finalizar encoding
146
+ if (!this.encoder) {
147
+ throw new Error('Encoder is not initialized');
148
+ }
149
+ const blob = this.encoder.finish(mimeType);
150
+ const url = URL.createObjectURL(blob);
151
+ const timecode = Date.now() - this.startTime;
152
+ // Criar evento de conclusão
153
+ const completeEvent = {
154
+ blob,
155
+ url
156
+ };
157
+ this.status = exports.RecorderStatus.COMPLETE;
158
+ // Chamar callback de conclusão
159
+ if (this.onCompleteCallback) {
160
+ this.onCompleteCallback(completeEvent);
161
+ }
162
+ // Criar evento de dados disponíveis
163
+ const dataEvent = {
164
+ data: blob,
165
+ timecode
166
+ };
167
+ if (this.onDataAvailableCallback) {
168
+ this.onDataAvailableCallback(dataEvent);
169
+ }
170
+ return blob;
171
+ }
172
+ catch (error) {
173
+ this.status = exports.RecorderStatus.ERROR;
174
+ this.handleError(error);
175
+ throw error;
176
+ }
177
+ }
178
+ /**
179
+ * Cancela a gravação atual
180
+ */
181
+ cancel() {
182
+ if (this.status === exports.RecorderStatus.INACTIVE || this.status === exports.RecorderStatus.COMPLETE) {
183
+ return;
184
+ }
185
+ // Desconectar nós
186
+ if (this.scriptProcessor) {
187
+ this.scriptProcessor.disconnect();
188
+ this.scriptProcessor.onaudioprocess = null;
189
+ this.scriptProcessor = null;
190
+ }
191
+ if (this.sourceNode) {
192
+ this.sourceNode.disconnect();
193
+ this.sourceNode = null;
194
+ }
195
+ // Cancelar encoding
196
+ if (this.encoder) {
197
+ this.encoder.cancel();
198
+ }
199
+ // Limpar stream
200
+ if (this.stream) {
201
+ this.stream.getTracks().forEach(track => track.stop());
202
+ this.stream = null;
203
+ }
204
+ this.status = exports.RecorderStatus.INACTIVE;
205
+ }
206
+ /**
207
+ * Obtém o status atual do recorder
208
+ *
209
+ * @returns Status atual
210
+ */
211
+ getStatus() {
212
+ return this.status;
213
+ }
214
+ /**
215
+ * Define callback para quando dados estão disponíveis
216
+ *
217
+ * @param callback - Função callback
218
+ */
219
+ setOnDataAvailable(callback) {
220
+ this.onDataAvailableCallback = callback;
221
+ }
222
+ /**
223
+ * Define callback para quando gravação é completada
224
+ *
225
+ * @param callback - Função callback
226
+ */
227
+ setOnComplete(callback) {
228
+ this.onCompleteCallback = callback;
229
+ }
230
+ /**
231
+ * Define callback para quando ocorre erro
232
+ *
233
+ * @param callback - Função callback
234
+ */
235
+ setOnError(callback) {
236
+ this.onErrorCallback = callback;
237
+ }
238
+ /**
239
+ * Trata erros e chama callback de erro
240
+ *
241
+ * @param error - Erro ocorrido
242
+ */
243
+ handleError(error) {
244
+ const errorEvent = {
245
+ message: error.message,
246
+ error
247
+ };
248
+ if (this.onErrorCallback) {
249
+ this.onErrorCallback(errorEvent);
250
+ }
251
+ }
252
+ /**
253
+ * Limpa recursos e reseta o recorder
254
+ */
255
+ cleanup() {
256
+ this.cancel();
257
+ this.encoder = null;
258
+ this.audioContext = null;
259
+ this.status = exports.RecorderStatus.INACTIVE;
260
+ }
261
+ }
262
+
263
+ /**
264
+ * Encoder WAV para áudio
265
+ *
266
+ * @module encoders/WavAudioEncoder
267
+ */
268
+ /**
269
+ * Encoder WAV simples que cria arquivos WAV a partir de buffers de áudio
270
+ */
271
+ class WavAudioEncoder {
272
+ /**
273
+ * Cria uma instância do encoder WAV
274
+ *
275
+ * @param sampleRate - Taxa de amostragem em Hz
276
+ * @param numChannels - Número de canais (1 = mono, 2 = estéreo)
277
+ */
278
+ constructor(sampleRate, numChannels) {
279
+ this.buffers = [];
280
+ this.sampleRate = sampleRate;
281
+ this.numChannels = numChannels;
282
+ this.buffers = [];
283
+ }
284
+ /**
285
+ * Codifica buffers de áudio
286
+ *
287
+ * @param buffers - Array de buffers Float32Array, um por canal
288
+ */
289
+ encode(buffers) {
290
+ if (buffers.length !== this.numChannels) {
291
+ throw new Error(`Expected ${this.numChannels} channels, got ${buffers.length}`);
292
+ }
293
+ // Armazenar buffers para processamento posterior
294
+ this.buffers.push(buffers.map(buffer => new Float32Array(buffer)));
295
+ }
296
+ /**
297
+ * Finaliza o encoding e retorna o Blob WAV
298
+ *
299
+ * @param mimeType - Tipo MIME (padrão: 'audio/wav')
300
+ * @returns Blob contendo o arquivo WAV
301
+ */
302
+ finish(mimeType = 'audio/wav') {
303
+ if (this.buffers.length === 0) {
304
+ throw new Error('No audio data to encode');
305
+ }
306
+ // Calcular tamanho total somando todos os frames de todos os buffers
307
+ let totalFrames = 0;
308
+ for (let i = 0; i < this.buffers.length; i++) {
309
+ if (this.buffers[i].length > 0 && this.buffers[i][0].length > 0) {
310
+ totalFrames += this.buffers[i][0].length;
311
+ }
312
+ }
313
+ if (totalFrames === 0) {
314
+ throw new Error('No valid audio data to encode');
315
+ }
316
+ // Calcular tamanho dos dados (16-bit samples = 2 bytes por sample)
317
+ const dataSize = totalFrames * this.numChannels * 2;
318
+ const fileSize = 44 + dataSize; // 44 bytes de cabeçalho + dados
319
+ // Criar buffer para o arquivo WAV
320
+ const buffer = new ArrayBuffer(fileSize);
321
+ const view = new DataView(buffer);
322
+ // Escrever cabeçalho WAV
323
+ // RIFF header
324
+ this.writeString(view, 0, 'RIFF');
325
+ view.setUint32(4, fileSize - 8, true); // Tamanho do arquivo - 8 bytes do header RIFF
326
+ this.writeString(view, 8, 'WAVE');
327
+ // fmt chunk
328
+ this.writeString(view, 12, 'fmt ');
329
+ view.setUint32(16, 16, true); // fmt chunk size
330
+ view.setUint16(20, 1, true); // audio format (1 = PCM)
331
+ view.setUint16(22, this.numChannels, true);
332
+ view.setUint32(24, this.sampleRate, true);
333
+ view.setUint32(28, this.sampleRate * this.numChannels * 2, true); // byte rate
334
+ view.setUint16(32, this.numChannels * 2, true); // block align
335
+ view.setUint16(34, 16, true); // bits per sample
336
+ // data chunk
337
+ this.writeString(view, 36, 'data');
338
+ view.setUint32(40, dataSize, true);
339
+ // Escrever dados de áudio (intercalados para estéreo)
340
+ let offset = 44;
341
+ for (let i = 0; i < this.buffers.length; i++) {
342
+ const frameBuffers = this.buffers[i];
343
+ // Verificar se há dados válidos
344
+ if (frameBuffers.length === 0 || frameBuffers[0].length === 0) {
345
+ continue;
346
+ }
347
+ const frameLength = frameBuffers[0].length;
348
+ // Verificar se todos os canais têm o mesmo tamanho
349
+ for (let channel = 0; channel < this.numChannels; channel++) {
350
+ if (frameBuffers[channel].length !== frameLength) {
351
+ throw new Error(`Channel ${channel} has different length in frame ${i}`);
352
+ }
353
+ }
354
+ for (let j = 0; j < frameLength; j++) {
355
+ for (let channel = 0; channel < this.numChannels; channel++) {
356
+ // Verificar se o offset está dentro dos limites
357
+ if (offset + 2 > buffer.byteLength) {
358
+ throw new Error(`Offset ${offset + 2} exceeds buffer size ${buffer.byteLength}`);
359
+ }
360
+ // Converter float32 (-1.0 a 1.0) para int16 (-32768 a 32767)
361
+ const sample = Math.max(-1, Math.min(1, frameBuffers[channel][j]));
362
+ const int16Sample = sample < 0 ? sample * 0x8000 : sample * 0x7FFF;
363
+ view.setInt16(offset, int16Sample, true);
364
+ offset += 2;
365
+ }
366
+ }
367
+ }
368
+ // Verificar se escrevemos todos os dados
369
+ if (offset !== buffer.byteLength) {
370
+ console.warn(`Warning: Expected to write ${buffer.byteLength} bytes, but wrote ${offset} bytes`);
371
+ }
372
+ // Limpar buffers
373
+ this.buffers = [];
374
+ return new Blob([buffer], { type: mimeType });
375
+ }
376
+ /**
377
+ * Cancela o encoding e limpa os buffers
378
+ */
379
+ cancel() {
380
+ this.buffers = [];
381
+ }
382
+ /**
383
+ * Escreve string no DataView
384
+ *
385
+ * @param view - DataView
386
+ * @param offset - Offset inicial
387
+ * @param string - String a escrever
388
+ */
389
+ writeString(view, offset, string) {
390
+ for (let i = 0; i < string.length; i++) {
391
+ view.setUint8(offset + i, string.charCodeAt(i));
392
+ }
393
+ }
394
+ }
395
+
396
+ /**
397
+ * Wrapper TypeScript para OggVorbisEncoder (Emscripten)
398
+ *
399
+ * @module encoders/OggVorbisEncoder
400
+ */
401
+ /**
402
+ * Wrapper para o encoder OGG Vorbis compilado via Emscripten
403
+ */
404
+ class OggVorbisEncoderWrapper {
405
+ /**
406
+ * Cria uma instância do encoder OGG Vorbis
407
+ *
408
+ * @param sampleRate - Taxa de amostragem em Hz
409
+ * @param numChannels - Número de canais
410
+ * @param options - Opções do encoder OGG
411
+ */
412
+ constructor(sampleRate, numChannels, options = {}) {
413
+ this.encoder = null;
414
+ this.sampleRate = sampleRate;
415
+ this.numChannels = numChannels;
416
+ this.quality = options.quality ?? 0.5;
417
+ // Verificar se OggVorbisEncoder está disponível
418
+ if (typeof OggVorbisEncoder === 'undefined') {
419
+ throw new Error('OggVorbisEncoder is not loaded. Make sure to load OggVorbisEncoder.min.js before using this encoder.');
420
+ }
421
+ // Criar instância do encoder
422
+ this.encoder = new OggVorbisEncoder(sampleRate, numChannels, this.quality);
423
+ }
424
+ /**
425
+ * Codifica buffers de áudio
426
+ *
427
+ * @param buffers - Array de buffers Float32Array, um por canal
428
+ */
429
+ encode(buffers) {
430
+ if (!this.encoder) {
431
+ throw new Error('Encoder is not initialized');
432
+ }
433
+ if (buffers.length !== this.numChannels) {
434
+ throw new Error(`Expected ${this.numChannels} channels, got ${buffers.length}`);
435
+ }
436
+ this.encoder.encode(buffers);
437
+ }
438
+ /**
439
+ * Finaliza o encoding e retorna o Blob OGG
440
+ *
441
+ * @param mimeType - Tipo MIME (padrão: 'audio/ogg')
442
+ * @returns Blob contendo o arquivo OGG
443
+ */
444
+ finish(mimeType = 'audio/ogg') {
445
+ if (!this.encoder) {
446
+ throw new Error('Encoder is not initialized');
447
+ }
448
+ return this.encoder.finish(mimeType);
449
+ }
450
+ /**
451
+ * Cancela o encoding
452
+ */
453
+ cancel() {
454
+ if (this.encoder) {
455
+ this.encoder.cancel();
456
+ this.encoder = null;
457
+ }
458
+ }
459
+ }
460
+ /**
461
+ * Função helper para carregar o script OggVorbisEncoder
462
+ *
463
+ * @param scriptUrl - URL do script OggVorbisEncoder.min.js
464
+ * @returns Promise que resolve quando o script é carregado
465
+ */
466
+ function loadOggVorbisEncoder(scriptUrl) {
467
+ return new Promise((resolve, reject) => {
468
+ // Verificar se já está carregado
469
+ if (typeof window.OggVorbisEncoder !== 'undefined') {
470
+ resolve();
471
+ return;
472
+ }
473
+ // Verificar se o script já está sendo carregado
474
+ const existingScript = document.querySelector(`script[src="${scriptUrl}"]`);
475
+ if (existingScript) {
476
+ // Se já está carregado, verificar imediatamente
477
+ if (typeof window.OggVorbisEncoder !== 'undefined') {
478
+ resolve();
479
+ return;
480
+ }
481
+ // Aguardar o script existente carregar
482
+ existingScript.addEventListener('load', () => {
483
+ setTimeout(() => {
484
+ if (typeof window.OggVorbisEncoder !== 'undefined') {
485
+ resolve();
486
+ }
487
+ else {
488
+ reject(new Error('OggVorbisEncoder failed to load'));
489
+ }
490
+ }, 100);
491
+ });
492
+ existingScript.addEventListener('error', () => {
493
+ reject(new Error('Failed to load OggVorbisEncoder script'));
494
+ });
495
+ return;
496
+ }
497
+ // Primeiro, verificar se o arquivo existe fazendo uma requisição HEAD
498
+ fetch(scriptUrl, { method: 'HEAD', cache: 'no-cache' })
499
+ .then(response => {
500
+ if (!response.ok) {
501
+ throw new Error(`File not found: ${scriptUrl} (${response.status})`);
502
+ }
503
+ // Criar e carregar novo script
504
+ const script = document.createElement('script');
505
+ script.src = scriptUrl;
506
+ script.async = false; // Carregar de forma síncrona para garantir ordem
507
+ script.type = 'text/javascript';
508
+ script.onload = () => {
509
+ // Aguardar um pouco para garantir que o objeto global foi criado
510
+ setTimeout(() => {
511
+ if (typeof window.OggVorbisEncoder !== 'undefined') {
512
+ resolve();
513
+ }
514
+ else {
515
+ reject(new Error('OggVorbisEncoder object not found after script load. The script may not have exported the global correctly.'));
516
+ }
517
+ }, 200);
518
+ };
519
+ script.onerror = (event) => {
520
+ const error = new Error(`Failed to load OggVorbisEncoder script from ${scriptUrl}. Check browser console for CORS or network errors.`);
521
+ console.error('Script load error:', event);
522
+ console.error('Script URL:', scriptUrl);
523
+ reject(error);
524
+ };
525
+ document.head.appendChild(script);
526
+ })
527
+ .catch(error => {
528
+ reject(new Error(`Cannot access OggVorbisEncoder script at ${scriptUrl}: ${error.message}`));
529
+ });
530
+ });
531
+ }
532
+
533
+ /**
534
+ * Wrapper TypeScript para Mp3LameEncoder (Emscripten)
535
+ *
536
+ * @module encoders/Mp3LameEncoder
537
+ */
538
+ /**
539
+ * Wrapper para o encoder MP3 LAME compilado via Emscripten
540
+ */
541
+ class Mp3LameEncoderWrapper {
542
+ /**
543
+ * Cria uma instância do encoder MP3 LAME
544
+ *
545
+ * @param sampleRate - Taxa de amostragem em Hz
546
+ * @param numChannels - Número de canais
547
+ * @param options - Opções do encoder MP3
548
+ */
549
+ constructor(sampleRate, numChannels, options = {}) {
550
+ this.encoder = null;
551
+ this.sampleRate = sampleRate;
552
+ this.numChannels = numChannels;
553
+ this.bitrate = options.bitrate ?? 128;
554
+ // Verificar se Mp3LameEncoder está disponível
555
+ if (typeof Mp3LameEncoder === 'undefined') {
556
+ throw new Error('Mp3LameEncoder is not loaded. Make sure to load Mp3LameEncoder.min.js before using this encoder.');
557
+ }
558
+ // Criar instância do encoder
559
+ this.encoder = new Mp3LameEncoder(sampleRate, numChannels, this.bitrate);
560
+ }
561
+ /**
562
+ * Codifica buffers de áudio
563
+ *
564
+ * @param buffers - Array de buffers Float32Array, um por canal
565
+ */
566
+ encode(buffers) {
567
+ if (!this.encoder) {
568
+ throw new Error('Encoder is not initialized');
569
+ }
570
+ if (buffers.length !== this.numChannels) {
571
+ throw new Error(`Expected ${this.numChannels} channels, got ${buffers.length}`);
572
+ }
573
+ this.encoder.encode(buffers);
574
+ }
575
+ /**
576
+ * Finaliza o encoding e retorna o Blob MP3
577
+ *
578
+ * @param mimeType - Tipo MIME (padrão: 'audio/mpeg')
579
+ * @returns Blob contendo o arquivo MP3
580
+ */
581
+ finish(mimeType = 'audio/mpeg') {
582
+ if (!this.encoder) {
583
+ throw new Error('Encoder is not initialized');
584
+ }
585
+ return this.encoder.finish(mimeType);
586
+ }
587
+ /**
588
+ * Cancela o encoding
589
+ */
590
+ cancel() {
591
+ if (this.encoder) {
592
+ this.encoder.cancel();
593
+ this.encoder = null;
594
+ }
595
+ }
596
+ }
597
+ /**
598
+ * Função helper para carregar o script Mp3LameEncoder
599
+ *
600
+ * @param scriptUrl - URL do script Mp3LameEncoder.min.js
601
+ * @returns Promise que resolve quando o script é carregado
602
+ */
603
+ function loadMp3LameEncoder(scriptUrl) {
604
+ return new Promise((resolve, reject) => {
605
+ // Verificar se já está carregado
606
+ if (typeof window.Mp3LameEncoder !== 'undefined') {
607
+ resolve();
608
+ return;
609
+ }
610
+ // Verificar se o script já está sendo carregado
611
+ const existingScript = document.querySelector(`script[src="${scriptUrl}"]`);
612
+ if (existingScript) {
613
+ // Se já está carregado, verificar imediatamente
614
+ if (typeof window.Mp3LameEncoder !== 'undefined') {
615
+ resolve();
616
+ return;
617
+ }
618
+ // Aguardar o script existente carregar
619
+ existingScript.addEventListener('load', () => {
620
+ setTimeout(() => {
621
+ if (typeof window.Mp3LameEncoder !== 'undefined') {
622
+ resolve();
623
+ }
624
+ else {
625
+ reject(new Error('Mp3LameEncoder failed to load'));
626
+ }
627
+ }, 100);
628
+ });
629
+ existingScript.addEventListener('error', () => {
630
+ reject(new Error('Failed to load Mp3LameEncoder script'));
631
+ });
632
+ return;
633
+ }
634
+ // Primeiro, verificar se o arquivo existe fazendo uma requisição HEAD
635
+ fetch(scriptUrl, { method: 'HEAD', cache: 'no-cache' })
636
+ .then(response => {
637
+ if (!response.ok) {
638
+ throw new Error(`File not found: ${scriptUrl} (${response.status})`);
639
+ }
640
+ // Criar e carregar novo script
641
+ const script = document.createElement('script');
642
+ script.src = scriptUrl;
643
+ script.async = false; // Carregar de forma síncrona para garantir ordem
644
+ script.type = 'text/javascript';
645
+ script.onload = () => {
646
+ // Aguardar um pouco para garantir que o objeto global foi criado
647
+ setTimeout(() => {
648
+ if (typeof window.Mp3LameEncoder !== 'undefined') {
649
+ resolve();
650
+ }
651
+ else {
652
+ reject(new Error('Mp3LameEncoder object not found after script load. The script may not have exported the global correctly.'));
653
+ }
654
+ }, 200);
655
+ };
656
+ script.onerror = (event) => {
657
+ const error = new Error(`Failed to load Mp3LameEncoder script from ${scriptUrl}. Check browser console for CORS or network errors.`);
658
+ console.error('Script load error:', event);
659
+ console.error('Script URL:', scriptUrl);
660
+ reject(error);
661
+ };
662
+ document.head.appendChild(script);
663
+ })
664
+ .catch(error => {
665
+ reject(new Error(`Cannot access Mp3LameEncoder script at ${scriptUrl}: ${error.message}`));
666
+ });
667
+ });
668
+ }
669
+
670
+ /**
671
+ * Recorder WAV usando WebAudioRecorder
672
+ *
673
+ * @module recorders/WebAudioRecorderWav
674
+ */
675
+ /**
676
+ * Classe para gravação de áudio em formato WAV
677
+ */
678
+ class WebAudioRecorderWav extends WebAudioRecorder {
679
+ /**
680
+ * Cria uma instância do recorder WAV
681
+ *
682
+ * @param audioContext - Contexto de áudio Web Audio API
683
+ * @param options - Opções de configuração do recorder
684
+ */
685
+ constructor(audioContext, options = {}) {
686
+ const sampleRate = options.sampleRate || audioContext.sampleRate;
687
+ const numChannels = options.numChannels || 2;
688
+ const encoder = new WavAudioEncoder(sampleRate, numChannels);
689
+ super(audioContext, encoder, options);
690
+ }
691
+ }
692
+
693
+ /**
694
+ * Recorder OGG Vorbis usando WebAudioRecorder
695
+ *
696
+ * @module recorders/WebAudioRecorderOgg
697
+ */
698
+ /**
699
+ * Classe para gravação de áudio em formato OGG Vorbis
700
+ */
701
+ class WebAudioRecorderOgg extends WebAudioRecorder {
702
+ /**
703
+ * Cria uma instância do recorder OGG Vorbis
704
+ *
705
+ * @param audioContext - Contexto de áudio Web Audio API
706
+ * @param options - Opções de configuração do recorder
707
+ * @param oggOptions - Opções específicas do encoder OGG
708
+ */
709
+ constructor(audioContext, options = {}, oggOptions = {}) {
710
+ const sampleRate = options.sampleRate || audioContext.sampleRate;
711
+ const numChannels = options.numChannels || 2;
712
+ const encoder = new OggVorbisEncoderWrapper(sampleRate, numChannels, oggOptions);
713
+ super(audioContext, encoder, options);
714
+ this.oggOptions = oggOptions;
715
+ }
716
+ /**
717
+ * Carrega o script OggVorbisEncoder antes de usar
718
+ *
719
+ * @param scriptUrl - URL do script OggVorbisEncoder.min.js
720
+ * @returns Promise que resolve quando o script é carregado
721
+ */
722
+ static async loadEncoder(scriptUrl) {
723
+ return loadOggVorbisEncoder(scriptUrl);
724
+ }
725
+ }
726
+
727
+ /**
728
+ * Recorder MP3 usando WebAudioRecorder
729
+ *
730
+ * @module recorders/WebAudioRecorderMp3
731
+ */
732
+ /**
733
+ * Classe para gravação de áudio em formato MP3
734
+ */
735
+ class WebAudioRecorderMp3 extends WebAudioRecorder {
736
+ /**
737
+ * Cria uma instância do recorder MP3
738
+ *
739
+ * @param audioContext - Contexto de áudio Web Audio API
740
+ * @param options - Opções de configuração do recorder
741
+ * @param mp3Options - Opções específicas do encoder MP3
742
+ */
743
+ constructor(audioContext, options = {}, mp3Options = {}) {
744
+ const sampleRate = options.sampleRate || audioContext.sampleRate;
745
+ const numChannels = options.numChannels || 2;
746
+ const encoder = new Mp3LameEncoderWrapper(sampleRate, numChannels, mp3Options);
747
+ super(audioContext, encoder, options);
748
+ this.mp3Options = mp3Options;
749
+ }
750
+ /**
751
+ * Carrega o script Mp3LameEncoder antes de usar
752
+ *
753
+ * @param scriptUrl - URL do script Mp3LameEncoder.min.js
754
+ * @returns Promise que resolve quando o script é carregado
755
+ */
756
+ static async loadEncoder(scriptUrl) {
757
+ return loadMp3LameEncoder(scriptUrl);
758
+ }
759
+ }
760
+
761
+ exports.Mp3LameEncoderWrapper = Mp3LameEncoderWrapper;
762
+ exports.OggVorbisEncoderWrapper = OggVorbisEncoderWrapper;
763
+ exports.WavAudioEncoder = WavAudioEncoder;
764
+ exports.WebAudioRecorder = WebAudioRecorder;
765
+ exports.WebAudioRecorderMp3 = WebAudioRecorderMp3;
766
+ exports.WebAudioRecorderOgg = WebAudioRecorderOgg;
767
+ exports.WebAudioRecorderWav = WebAudioRecorderWav;
768
+ exports.loadMp3LameEncoder = loadMp3LameEncoder;
769
+ exports.loadOggVorbisEncoder = loadOggVorbisEncoder;
770
+ //# sourceMappingURL=index.cjs.js.map