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