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