web-audio-recorder-ts 1.0.3 → 1.0.5
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/dist/encoders/Mp3LameEncoder.d.ts +2 -0
- package/dist/encoders/OggVorbisEncoder.d.ts +2 -0
- package/dist/index.cjs.js +260 -18
- package/dist/index.esm.js +260 -18
- package/dist/index.umd.js +260 -18
- package/package.json +1 -1
package/dist/index.cjs.js
CHANGED
|
@@ -147,6 +147,9 @@ class WebAudioRecorder {
|
|
|
147
147
|
if (!this.encoder) {
|
|
148
148
|
throw new Error('Encoder is not initialized');
|
|
149
149
|
}
|
|
150
|
+
// Aguardar um pouco para garantir que todos os callbacks de áudio foram processados
|
|
151
|
+
// Isso evita condições de corrida onde finish() é chamado antes de todos os buffers serem processados
|
|
152
|
+
await new Promise(resolve => setTimeout(resolve, 50));
|
|
150
153
|
const blob = this.encoder.finish(mimeType);
|
|
151
154
|
const url = URL.createObjectURL(blob);
|
|
152
155
|
const timecode = Date.now() - this.startTime;
|
|
@@ -549,33 +552,47 @@ function configureEncoderPaths(baseUrl) {
|
|
|
549
552
|
* Useful when auto-detection fails
|
|
550
553
|
*/
|
|
551
554
|
async function findEncoderPath(filename) {
|
|
555
|
+
// Obter base URL atual para construir caminhos relativos
|
|
556
|
+
const currentOrigin = typeof window !== 'undefined' ? window.location.origin : '';
|
|
557
|
+
const currentPath = typeof window !== 'undefined' ? window.location.pathname : '';
|
|
552
558
|
// Priorizar caminhos do node_modules primeiro
|
|
553
559
|
const possiblePaths = [
|
|
554
|
-
// Primeiro: tentar node_modules (prioridade máxima)
|
|
560
|
+
// Primeiro: tentar node_modules com caminhos absolutos (prioridade máxima)
|
|
555
561
|
`/node_modules/web-audio-recorder-ts/lib/${filename}`,
|
|
562
|
+
`${currentOrigin}/node_modules/web-audio-recorder-ts/lib/${filename}`,
|
|
563
|
+
// Caminhos relativos do node_modules
|
|
556
564
|
`./node_modules/web-audio-recorder-ts/lib/${filename}`,
|
|
557
565
|
`../node_modules/web-audio-recorder-ts/lib/${filename}`,
|
|
558
566
|
`../../node_modules/web-audio-recorder-ts/lib/${filename}`,
|
|
567
|
+
`../../../node_modules/web-audio-recorder-ts/lib/${filename}`,
|
|
568
|
+
// Com base no caminho atual
|
|
569
|
+
`${currentPath.replace(/\/[^/]*$/, '')}/node_modules/web-audio-recorder-ts/lib/${filename}`,
|
|
559
570
|
// From dist (se os arquivos foram copiados para dist/lib)
|
|
560
571
|
`/node_modules/web-audio-recorder-ts/dist/lib/${filename}`,
|
|
572
|
+
`${currentOrigin}/node_modules/web-audio-recorder-ts/dist/lib/${filename}`,
|
|
561
573
|
`./node_modules/web-audio-recorder-ts/dist/lib/${filename}`,
|
|
562
574
|
// Auto-detected path (pode apontar para node_modules)
|
|
563
575
|
getEncoderScriptUrl(filename),
|
|
564
576
|
// Para desenvolvimento/demo: try public folder (Vite serves public/ at root)
|
|
565
577
|
`/${filename}`,
|
|
578
|
+
`${currentOrigin}/${filename}`,
|
|
566
579
|
// Direct lib paths (for development or custom setups)
|
|
567
580
|
`/lib/${filename}`,
|
|
581
|
+
`${currentOrigin}/lib/${filename}`,
|
|
568
582
|
`./lib/${filename}`,
|
|
569
583
|
`../lib/${filename}`,
|
|
570
584
|
// CDN or absolute paths (if configured)
|
|
571
585
|
filename.startsWith('http') ? filename : null
|
|
572
586
|
].filter((path) => path !== null);
|
|
587
|
+
console.log(`[web-audio-recorder-ts] Searching for ${filename} in ${possiblePaths.length} possible paths...`);
|
|
573
588
|
// Try each path
|
|
574
|
-
for (
|
|
589
|
+
for (let i = 0; i < possiblePaths.length; i++) {
|
|
590
|
+
const path = possiblePaths[i];
|
|
575
591
|
try {
|
|
576
592
|
const testUrl = path.startsWith('http')
|
|
577
593
|
? path
|
|
578
594
|
: new URL(path, typeof window !== 'undefined' ? window.location.href : 'file://').href;
|
|
595
|
+
console.log(`[web-audio-recorder-ts] Trying path ${i + 1}/${possiblePaths.length}: ${path} -> ${testUrl}`);
|
|
579
596
|
// Usar GET para verificar se é JavaScript válido (não HTML)
|
|
580
597
|
const response = await fetch(testUrl, { method: 'GET', cache: 'no-cache' });
|
|
581
598
|
if (response.ok) {
|
|
@@ -584,21 +601,30 @@ async function findEncoderPath(filename) {
|
|
|
584
601
|
const trimmedText = text.trim();
|
|
585
602
|
// Se começar com '<', é HTML (404, etc) - pular este caminho
|
|
586
603
|
if (trimmedText.startsWith('<')) {
|
|
587
|
-
console.warn(`Path ${path} returned HTML instead of JavaScript, skipping...`);
|
|
604
|
+
console.warn(`[web-audio-recorder-ts] ❌ Path ${path} returned HTML instead of JavaScript (likely 404), skipping...`);
|
|
588
605
|
continue;
|
|
589
606
|
}
|
|
590
607
|
// Se parece JavaScript, retornar este caminho
|
|
591
608
|
if (trimmedText.includes('function') || trimmedText.includes('var') || trimmedText.includes('const') || trimmedText.includes('let') || trimmedText.length > 100) {
|
|
592
|
-
console.log(
|
|
609
|
+
console.log(`[web-audio-recorder-ts] ✅ Found encoder file at: ${path}`);
|
|
593
610
|
return path;
|
|
594
611
|
}
|
|
612
|
+
else {
|
|
613
|
+
console.warn(`[web-audio-recorder-ts] ⚠️ Path ${path} returned content but doesn't look like JavaScript`);
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
else {
|
|
617
|
+
console.warn(`[web-audio-recorder-ts] ❌ Path ${path} returned status ${response.status} ${response.statusText}`);
|
|
595
618
|
}
|
|
596
619
|
}
|
|
597
620
|
catch (e) {
|
|
621
|
+
console.warn(`[web-audio-recorder-ts] ❌ Error testing path ${path}:`, e);
|
|
598
622
|
// Continue to next path
|
|
599
623
|
continue;
|
|
600
624
|
}
|
|
601
625
|
}
|
|
626
|
+
console.error(`[web-audio-recorder-ts] ❌ Could not find ${filename} in any of the ${possiblePaths.length} paths tried`);
|
|
627
|
+
console.error(`[web-audio-recorder-ts] Tried paths:`, possiblePaths);
|
|
602
628
|
return null;
|
|
603
629
|
}
|
|
604
630
|
|
|
@@ -620,15 +646,43 @@ class OggVorbisEncoderWrapper {
|
|
|
620
646
|
*/
|
|
621
647
|
constructor(sampleRate, numChannels, options = {}) {
|
|
622
648
|
this.encoder = null;
|
|
649
|
+
this.bufferCount = 0;
|
|
650
|
+
this.totalSamples = 0;
|
|
651
|
+
// Validar parâmetros
|
|
652
|
+
if (!Number.isFinite(sampleRate) || sampleRate <= 0) {
|
|
653
|
+
throw new Error(`Invalid sampleRate: ${sampleRate}. Must be a positive number.`);
|
|
654
|
+
}
|
|
655
|
+
if (!Number.isInteger(numChannels) || numChannels < 1 || numChannels > 2) {
|
|
656
|
+
throw new Error(`Invalid numChannels: ${numChannels}. Must be 1 (mono) or 2 (stereo).`);
|
|
657
|
+
}
|
|
623
658
|
this.sampleRate = sampleRate;
|
|
624
659
|
this.numChannels = numChannels;
|
|
625
|
-
|
|
660
|
+
// Validar e limitar qualidade (-0.1 a 1.0 para Vorbis)
|
|
661
|
+
const rawQuality = options.quality ?? 0.5;
|
|
662
|
+
if (!Number.isFinite(rawQuality)) {
|
|
663
|
+
console.warn(`Invalid quality value: ${rawQuality}. Using default 0.5`);
|
|
664
|
+
this.quality = 0.5;
|
|
665
|
+
}
|
|
666
|
+
else {
|
|
667
|
+
// Clamp quality to valid range
|
|
668
|
+
this.quality = Math.max(-0.1, Math.min(1.0, rawQuality));
|
|
669
|
+
if (rawQuality !== this.quality) {
|
|
670
|
+
console.warn(`Quality value ${rawQuality} clamped to valid range: ${this.quality}`);
|
|
671
|
+
}
|
|
672
|
+
}
|
|
626
673
|
// Verificar se OggVorbisEncoder está disponível
|
|
627
674
|
if (typeof OggVorbisEncoder === 'undefined') {
|
|
628
675
|
throw new Error('OggVorbisEncoder is not loaded. Make sure to load OggVorbisEncoder.min.js before using this encoder.');
|
|
629
676
|
}
|
|
630
|
-
|
|
631
|
-
|
|
677
|
+
try {
|
|
678
|
+
// Criar instância do encoder
|
|
679
|
+
this.encoder = new OggVorbisEncoder(sampleRate, numChannels, this.quality);
|
|
680
|
+
}
|
|
681
|
+
catch (error) {
|
|
682
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
683
|
+
throw new Error(`Failed to initialize OGG encoder: ${errorMsg}. ` +
|
|
684
|
+
`Parameters: sampleRate=${sampleRate}, numChannels=${numChannels}, quality=${this.quality}`);
|
|
685
|
+
}
|
|
632
686
|
}
|
|
633
687
|
/**
|
|
634
688
|
* Codifica buffers de áudio
|
|
@@ -642,7 +696,60 @@ class OggVorbisEncoderWrapper {
|
|
|
642
696
|
if (buffers.length !== this.numChannels) {
|
|
643
697
|
throw new Error(`Expected ${this.numChannels} channels, got ${buffers.length}`);
|
|
644
698
|
}
|
|
645
|
-
|
|
699
|
+
// Validar que todos os buffers têm o mesmo tamanho
|
|
700
|
+
if (buffers.length > 0) {
|
|
701
|
+
const expectedLength = buffers[0].length;
|
|
702
|
+
for (let i = 1; i < buffers.length; i++) {
|
|
703
|
+
if (buffers[i].length !== expectedLength) {
|
|
704
|
+
throw new Error(`Channel ${i} has length ${buffers[i].length}, expected ${expectedLength}`);
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
// Validar que há dados para processar
|
|
708
|
+
if (expectedLength === 0) {
|
|
709
|
+
// Buffer vazio, não há nada para codificar
|
|
710
|
+
return;
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
else {
|
|
714
|
+
// Nenhum buffer fornecido
|
|
715
|
+
return;
|
|
716
|
+
}
|
|
717
|
+
// Criar cópias dos buffers e validar valores (NaN, Infinity)
|
|
718
|
+
const safeBuffers = buffers.map((buffer, channelIndex) => {
|
|
719
|
+
const safeBuffer = new Float32Array(buffer.length);
|
|
720
|
+
let hasInvalidValues = false;
|
|
721
|
+
for (let i = 0; i < buffer.length; i++) {
|
|
722
|
+
const value = buffer[i];
|
|
723
|
+
// Verificar NaN e Infinity
|
|
724
|
+
if (!Number.isFinite(value)) {
|
|
725
|
+
hasInvalidValues = true;
|
|
726
|
+
// Substituir valores inválidos por 0
|
|
727
|
+
safeBuffer[i] = 0;
|
|
728
|
+
}
|
|
729
|
+
else {
|
|
730
|
+
// Clamp valores para o range válido de áudio (-1.0 a 1.0)
|
|
731
|
+
safeBuffer[i] = Math.max(-1, Math.min(1.0, value));
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
if (hasInvalidValues) {
|
|
735
|
+
console.warn(`OGG Encoder: Found invalid values (NaN/Infinity) in channel ${channelIndex}. ` +
|
|
736
|
+
`Replaced with 0. Buffer length: ${buffer.length}`);
|
|
737
|
+
}
|
|
738
|
+
return safeBuffer;
|
|
739
|
+
});
|
|
740
|
+
try {
|
|
741
|
+
this.encoder.encode(safeBuffers);
|
|
742
|
+
// Contar buffers processados para garantir que há dados antes de finalizar
|
|
743
|
+
this.bufferCount++;
|
|
744
|
+
this.totalSamples += safeBuffers[0].length;
|
|
745
|
+
}
|
|
746
|
+
catch (error) {
|
|
747
|
+
// Melhorar mensagem de erro para incluir informações de debug
|
|
748
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
749
|
+
throw new Error(`OGG encoding error: ${errorMsg}. ` +
|
|
750
|
+
`Buffers: ${buffers.length} channels, lengths: ${buffers.map(b => b.length).join(', ')}, ` +
|
|
751
|
+
`Total buffers processed: ${this.bufferCount}, Total samples: ${this.totalSamples}`);
|
|
752
|
+
}
|
|
646
753
|
}
|
|
647
754
|
/**
|
|
648
755
|
* Finaliza o encoding e retorna o Blob OGG
|
|
@@ -654,7 +761,25 @@ class OggVorbisEncoderWrapper {
|
|
|
654
761
|
if (!this.encoder) {
|
|
655
762
|
throw new Error('Encoder is not initialized');
|
|
656
763
|
}
|
|
657
|
-
|
|
764
|
+
// Verificar se há dados processados
|
|
765
|
+
if (this.bufferCount === 0) {
|
|
766
|
+
console.warn('OGG Encoder: finish() called but no buffers were encoded. This may cause issues with the Emscripten encoder.');
|
|
767
|
+
// Ainda tentar finalizar, mas avisar
|
|
768
|
+
}
|
|
769
|
+
try {
|
|
770
|
+
const blob = this.encoder.finish(mimeType);
|
|
771
|
+
// Validar que o blob não está vazio
|
|
772
|
+
if (blob.size === 0) {
|
|
773
|
+
console.warn('OGG Encoder: finish() returned empty blob. This may indicate insufficient audio data was encoded.');
|
|
774
|
+
}
|
|
775
|
+
return blob;
|
|
776
|
+
}
|
|
777
|
+
catch (error) {
|
|
778
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
779
|
+
throw new Error(`OGG finish() error: ${errorMsg}. ` +
|
|
780
|
+
`Buffers processed: ${this.bufferCount}, Total samples: ${this.totalSamples}, ` +
|
|
781
|
+
`Sample rate: ${this.sampleRate}, Channels: ${this.numChannels}, Quality: ${this.quality}`);
|
|
782
|
+
}
|
|
658
783
|
}
|
|
659
784
|
/**
|
|
660
785
|
* Cancela o encoding
|
|
@@ -664,6 +789,9 @@ class OggVorbisEncoderWrapper {
|
|
|
664
789
|
this.encoder.cancel();
|
|
665
790
|
this.encoder = null;
|
|
666
791
|
}
|
|
792
|
+
// Reset contadores
|
|
793
|
+
this.bufferCount = 0;
|
|
794
|
+
this.totalSamples = 0;
|
|
667
795
|
}
|
|
668
796
|
}
|
|
669
797
|
/**
|
|
@@ -680,8 +808,14 @@ async function loadOggVorbisEncoder(scriptUrl) {
|
|
|
680
808
|
// Tentar encontrar o arquivo automaticamente
|
|
681
809
|
const foundPath = await findEncoderPath('OggVorbisEncoder.min.js');
|
|
682
810
|
if (!foundPath) {
|
|
683
|
-
|
|
684
|
-
'Please
|
|
811
|
+
const errorMsg = 'Could not find OggVorbisEncoder.min.js automatically.\n\n' +
|
|
812
|
+
'Please try one of the following:\n' +
|
|
813
|
+
'1. Provide the path manually: await loadOggVorbisEncoder("/path/to/OggVorbisEncoder.min.js")\n' +
|
|
814
|
+
'2. Copy files to public/: cp node_modules/web-audio-recorder-ts/lib/*.js public/\n' +
|
|
815
|
+
'3. Configure server to serve node_modules (see NUXT_USAGE.md)\n' +
|
|
816
|
+
'4. Check browser console for detailed path information';
|
|
817
|
+
console.error(errorMsg);
|
|
818
|
+
throw new Error(errorMsg);
|
|
685
819
|
}
|
|
686
820
|
scriptUrl = foundPath;
|
|
687
821
|
}
|
|
@@ -812,15 +946,43 @@ class Mp3LameEncoderWrapper {
|
|
|
812
946
|
*/
|
|
813
947
|
constructor(sampleRate, numChannels, options = {}) {
|
|
814
948
|
this.encoder = null;
|
|
949
|
+
this.bufferCount = 0;
|
|
950
|
+
this.totalSamples = 0;
|
|
951
|
+
// Validar parâmetros
|
|
952
|
+
if (!Number.isFinite(sampleRate) || sampleRate <= 0) {
|
|
953
|
+
throw new Error(`Invalid sampleRate: ${sampleRate}. Must be a positive number.`);
|
|
954
|
+
}
|
|
955
|
+
if (!Number.isInteger(numChannels) || numChannels < 1 || numChannels > 2) {
|
|
956
|
+
throw new Error(`Invalid numChannels: ${numChannels}. Must be 1 (mono) or 2 (stereo).`);
|
|
957
|
+
}
|
|
815
958
|
this.sampleRate = sampleRate;
|
|
816
959
|
this.numChannels = numChannels;
|
|
817
|
-
|
|
960
|
+
// Validar e limitar bitrate (32 a 320 kbps para MP3)
|
|
961
|
+
const rawBitrate = options.bitrate ?? 128;
|
|
962
|
+
if (!Number.isFinite(rawBitrate) || !Number.isInteger(rawBitrate)) {
|
|
963
|
+
console.warn(`Invalid bitrate value: ${rawBitrate}. Using default 128`);
|
|
964
|
+
this.bitrate = 128;
|
|
965
|
+
}
|
|
966
|
+
else {
|
|
967
|
+
// Clamp bitrate to valid range
|
|
968
|
+
this.bitrate = Math.max(32, Math.min(320, rawBitrate));
|
|
969
|
+
if (rawBitrate !== this.bitrate) {
|
|
970
|
+
console.warn(`Bitrate value ${rawBitrate} clamped to valid range: ${this.bitrate}`);
|
|
971
|
+
}
|
|
972
|
+
}
|
|
818
973
|
// Verificar se Mp3LameEncoder está disponível
|
|
819
974
|
if (typeof Mp3LameEncoder === 'undefined') {
|
|
820
975
|
throw new Error('Mp3LameEncoder is not loaded. Make sure to load Mp3LameEncoder.min.js before using this encoder.');
|
|
821
976
|
}
|
|
822
|
-
|
|
823
|
-
|
|
977
|
+
try {
|
|
978
|
+
// Criar instância do encoder
|
|
979
|
+
this.encoder = new Mp3LameEncoder(sampleRate, numChannels, this.bitrate);
|
|
980
|
+
}
|
|
981
|
+
catch (error) {
|
|
982
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
983
|
+
throw new Error(`Failed to initialize MP3 encoder: ${errorMsg}. ` +
|
|
984
|
+
`Parameters: sampleRate=${sampleRate}, numChannels=${numChannels}, bitrate=${this.bitrate}`);
|
|
985
|
+
}
|
|
824
986
|
}
|
|
825
987
|
/**
|
|
826
988
|
* Codifica buffers de áudio
|
|
@@ -834,7 +996,60 @@ class Mp3LameEncoderWrapper {
|
|
|
834
996
|
if (buffers.length !== this.numChannels) {
|
|
835
997
|
throw new Error(`Expected ${this.numChannels} channels, got ${buffers.length}`);
|
|
836
998
|
}
|
|
837
|
-
|
|
999
|
+
// Validar que todos os buffers têm o mesmo tamanho
|
|
1000
|
+
if (buffers.length > 0) {
|
|
1001
|
+
const expectedLength = buffers[0].length;
|
|
1002
|
+
for (let i = 1; i < buffers.length; i++) {
|
|
1003
|
+
if (buffers[i].length !== expectedLength) {
|
|
1004
|
+
throw new Error(`Channel ${i} has length ${buffers[i].length}, expected ${expectedLength}`);
|
|
1005
|
+
}
|
|
1006
|
+
}
|
|
1007
|
+
// Validar que há dados para processar
|
|
1008
|
+
if (expectedLength === 0) {
|
|
1009
|
+
// Buffer vazio, não há nada para codificar
|
|
1010
|
+
return;
|
|
1011
|
+
}
|
|
1012
|
+
}
|
|
1013
|
+
else {
|
|
1014
|
+
// Nenhum buffer fornecido
|
|
1015
|
+
return;
|
|
1016
|
+
}
|
|
1017
|
+
// Criar cópias dos buffers e validar valores (NaN, Infinity)
|
|
1018
|
+
const safeBuffers = buffers.map((buffer, channelIndex) => {
|
|
1019
|
+
const safeBuffer = new Float32Array(buffer.length);
|
|
1020
|
+
let hasInvalidValues = false;
|
|
1021
|
+
for (let i = 0; i < buffer.length; i++) {
|
|
1022
|
+
const value = buffer[i];
|
|
1023
|
+
// Verificar NaN e Infinity
|
|
1024
|
+
if (!Number.isFinite(value)) {
|
|
1025
|
+
hasInvalidValues = true;
|
|
1026
|
+
// Substituir valores inválidos por 0
|
|
1027
|
+
safeBuffer[i] = 0;
|
|
1028
|
+
}
|
|
1029
|
+
else {
|
|
1030
|
+
// Clamp valores para o range válido de áudio (-1.0 a 1.0)
|
|
1031
|
+
safeBuffer[i] = Math.max(-1, Math.min(1.0, value));
|
|
1032
|
+
}
|
|
1033
|
+
}
|
|
1034
|
+
if (hasInvalidValues) {
|
|
1035
|
+
console.warn(`MP3 Encoder: Found invalid values (NaN/Infinity) in channel ${channelIndex}. ` +
|
|
1036
|
+
`Replaced with 0. Buffer length: ${buffer.length}`);
|
|
1037
|
+
}
|
|
1038
|
+
return safeBuffer;
|
|
1039
|
+
});
|
|
1040
|
+
try {
|
|
1041
|
+
this.encoder.encode(safeBuffers);
|
|
1042
|
+
// Contar buffers processados para garantir que há dados antes de finalizar
|
|
1043
|
+
this.bufferCount++;
|
|
1044
|
+
this.totalSamples += safeBuffers[0].length;
|
|
1045
|
+
}
|
|
1046
|
+
catch (error) {
|
|
1047
|
+
// Melhorar mensagem de erro para incluir informações de debug
|
|
1048
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
1049
|
+
throw new Error(`MP3 encoding error: ${errorMsg}. ` +
|
|
1050
|
+
`Buffers: ${buffers.length} channels, lengths: ${buffers.map(b => b.length).join(', ')}, ` +
|
|
1051
|
+
`Total buffers processed: ${this.bufferCount}, Total samples: ${this.totalSamples}`);
|
|
1052
|
+
}
|
|
838
1053
|
}
|
|
839
1054
|
/**
|
|
840
1055
|
* Finaliza o encoding e retorna o Blob MP3
|
|
@@ -846,7 +1061,25 @@ class Mp3LameEncoderWrapper {
|
|
|
846
1061
|
if (!this.encoder) {
|
|
847
1062
|
throw new Error('Encoder is not initialized');
|
|
848
1063
|
}
|
|
849
|
-
|
|
1064
|
+
// Verificar se há dados processados
|
|
1065
|
+
if (this.bufferCount === 0) {
|
|
1066
|
+
console.warn('MP3 Encoder: finish() called but no buffers were encoded. This may cause issues with the Emscripten encoder.');
|
|
1067
|
+
// Ainda tentar finalizar, mas avisar
|
|
1068
|
+
}
|
|
1069
|
+
try {
|
|
1070
|
+
const blob = this.encoder.finish(mimeType);
|
|
1071
|
+
// Validar que o blob não está vazio
|
|
1072
|
+
if (blob.size === 0) {
|
|
1073
|
+
console.warn('MP3 Encoder: finish() returned empty blob. This may indicate insufficient audio data was encoded.');
|
|
1074
|
+
}
|
|
1075
|
+
return blob;
|
|
1076
|
+
}
|
|
1077
|
+
catch (error) {
|
|
1078
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
1079
|
+
throw new Error(`MP3 finish() error: ${errorMsg}. ` +
|
|
1080
|
+
`Buffers processed: ${this.bufferCount}, Total samples: ${this.totalSamples}, ` +
|
|
1081
|
+
`Sample rate: ${this.sampleRate}, Channels: ${this.numChannels}, Bitrate: ${this.bitrate}`);
|
|
1082
|
+
}
|
|
850
1083
|
}
|
|
851
1084
|
/**
|
|
852
1085
|
* Cancela o encoding
|
|
@@ -856,6 +1089,9 @@ class Mp3LameEncoderWrapper {
|
|
|
856
1089
|
this.encoder.cancel();
|
|
857
1090
|
this.encoder = null;
|
|
858
1091
|
}
|
|
1092
|
+
// Reset contadores
|
|
1093
|
+
this.bufferCount = 0;
|
|
1094
|
+
this.totalSamples = 0;
|
|
859
1095
|
}
|
|
860
1096
|
}
|
|
861
1097
|
/**
|
|
@@ -872,8 +1108,14 @@ async function loadMp3LameEncoder(scriptUrl) {
|
|
|
872
1108
|
// Tentar encontrar o arquivo automaticamente
|
|
873
1109
|
const foundPath = await findEncoderPath('Mp3LameEncoder.min.js');
|
|
874
1110
|
if (!foundPath) {
|
|
875
|
-
|
|
876
|
-
'Please
|
|
1111
|
+
const errorMsg = 'Could not find Mp3LameEncoder.min.js automatically.\n\n' +
|
|
1112
|
+
'Please try one of the following:\n' +
|
|
1113
|
+
'1. Provide the path manually: await loadMp3LameEncoder("/path/to/Mp3LameEncoder.min.js")\n' +
|
|
1114
|
+
'2. Copy files to public/: cp node_modules/web-audio-recorder-ts/lib/*.js public/\n' +
|
|
1115
|
+
'3. Configure server to serve node_modules (see NUXT_USAGE.md)\n' +
|
|
1116
|
+
'4. Check browser console for detailed path information';
|
|
1117
|
+
console.error(errorMsg);
|
|
1118
|
+
throw new Error(errorMsg);
|
|
877
1119
|
}
|
|
878
1120
|
scriptUrl = foundPath;
|
|
879
1121
|
}
|
package/dist/index.esm.js
CHANGED
|
@@ -144,6 +144,9 @@ class WebAudioRecorder {
|
|
|
144
144
|
if (!this.encoder) {
|
|
145
145
|
throw new Error('Encoder is not initialized');
|
|
146
146
|
}
|
|
147
|
+
// Aguardar um pouco para garantir que todos os callbacks de áudio foram processados
|
|
148
|
+
// Isso evita condições de corrida onde finish() é chamado antes de todos os buffers serem processados
|
|
149
|
+
await new Promise(resolve => setTimeout(resolve, 50));
|
|
147
150
|
const blob = this.encoder.finish(mimeType);
|
|
148
151
|
const url = URL.createObjectURL(blob);
|
|
149
152
|
const timecode = Date.now() - this.startTime;
|
|
@@ -546,33 +549,47 @@ function configureEncoderPaths(baseUrl) {
|
|
|
546
549
|
* Useful when auto-detection fails
|
|
547
550
|
*/
|
|
548
551
|
async function findEncoderPath(filename) {
|
|
552
|
+
// Obter base URL atual para construir caminhos relativos
|
|
553
|
+
const currentOrigin = typeof window !== 'undefined' ? window.location.origin : '';
|
|
554
|
+
const currentPath = typeof window !== 'undefined' ? window.location.pathname : '';
|
|
549
555
|
// Priorizar caminhos do node_modules primeiro
|
|
550
556
|
const possiblePaths = [
|
|
551
|
-
// Primeiro: tentar node_modules (prioridade máxima)
|
|
557
|
+
// Primeiro: tentar node_modules com caminhos absolutos (prioridade máxima)
|
|
552
558
|
`/node_modules/web-audio-recorder-ts/lib/${filename}`,
|
|
559
|
+
`${currentOrigin}/node_modules/web-audio-recorder-ts/lib/${filename}`,
|
|
560
|
+
// Caminhos relativos do node_modules
|
|
553
561
|
`./node_modules/web-audio-recorder-ts/lib/${filename}`,
|
|
554
562
|
`../node_modules/web-audio-recorder-ts/lib/${filename}`,
|
|
555
563
|
`../../node_modules/web-audio-recorder-ts/lib/${filename}`,
|
|
564
|
+
`../../../node_modules/web-audio-recorder-ts/lib/${filename}`,
|
|
565
|
+
// Com base no caminho atual
|
|
566
|
+
`${currentPath.replace(/\/[^/]*$/, '')}/node_modules/web-audio-recorder-ts/lib/${filename}`,
|
|
556
567
|
// From dist (se os arquivos foram copiados para dist/lib)
|
|
557
568
|
`/node_modules/web-audio-recorder-ts/dist/lib/${filename}`,
|
|
569
|
+
`${currentOrigin}/node_modules/web-audio-recorder-ts/dist/lib/${filename}`,
|
|
558
570
|
`./node_modules/web-audio-recorder-ts/dist/lib/${filename}`,
|
|
559
571
|
// Auto-detected path (pode apontar para node_modules)
|
|
560
572
|
getEncoderScriptUrl(filename),
|
|
561
573
|
// Para desenvolvimento/demo: try public folder (Vite serves public/ at root)
|
|
562
574
|
`/${filename}`,
|
|
575
|
+
`${currentOrigin}/${filename}`,
|
|
563
576
|
// Direct lib paths (for development or custom setups)
|
|
564
577
|
`/lib/${filename}`,
|
|
578
|
+
`${currentOrigin}/lib/${filename}`,
|
|
565
579
|
`./lib/${filename}`,
|
|
566
580
|
`../lib/${filename}`,
|
|
567
581
|
// CDN or absolute paths (if configured)
|
|
568
582
|
filename.startsWith('http') ? filename : null
|
|
569
583
|
].filter((path) => path !== null);
|
|
584
|
+
console.log(`[web-audio-recorder-ts] Searching for ${filename} in ${possiblePaths.length} possible paths...`);
|
|
570
585
|
// Try each path
|
|
571
|
-
for (
|
|
586
|
+
for (let i = 0; i < possiblePaths.length; i++) {
|
|
587
|
+
const path = possiblePaths[i];
|
|
572
588
|
try {
|
|
573
589
|
const testUrl = path.startsWith('http')
|
|
574
590
|
? path
|
|
575
591
|
: new URL(path, typeof window !== 'undefined' ? window.location.href : 'file://').href;
|
|
592
|
+
console.log(`[web-audio-recorder-ts] Trying path ${i + 1}/${possiblePaths.length}: ${path} -> ${testUrl}`);
|
|
576
593
|
// Usar GET para verificar se é JavaScript válido (não HTML)
|
|
577
594
|
const response = await fetch(testUrl, { method: 'GET', cache: 'no-cache' });
|
|
578
595
|
if (response.ok) {
|
|
@@ -581,21 +598,30 @@ async function findEncoderPath(filename) {
|
|
|
581
598
|
const trimmedText = text.trim();
|
|
582
599
|
// Se começar com '<', é HTML (404, etc) - pular este caminho
|
|
583
600
|
if (trimmedText.startsWith('<')) {
|
|
584
|
-
console.warn(`Path ${path} returned HTML instead of JavaScript, skipping...`);
|
|
601
|
+
console.warn(`[web-audio-recorder-ts] ❌ Path ${path} returned HTML instead of JavaScript (likely 404), skipping...`);
|
|
585
602
|
continue;
|
|
586
603
|
}
|
|
587
604
|
// Se parece JavaScript, retornar este caminho
|
|
588
605
|
if (trimmedText.includes('function') || trimmedText.includes('var') || trimmedText.includes('const') || trimmedText.includes('let') || trimmedText.length > 100) {
|
|
589
|
-
console.log(
|
|
606
|
+
console.log(`[web-audio-recorder-ts] ✅ Found encoder file at: ${path}`);
|
|
590
607
|
return path;
|
|
591
608
|
}
|
|
609
|
+
else {
|
|
610
|
+
console.warn(`[web-audio-recorder-ts] ⚠️ Path ${path} returned content but doesn't look like JavaScript`);
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
else {
|
|
614
|
+
console.warn(`[web-audio-recorder-ts] ❌ Path ${path} returned status ${response.status} ${response.statusText}`);
|
|
592
615
|
}
|
|
593
616
|
}
|
|
594
617
|
catch (e) {
|
|
618
|
+
console.warn(`[web-audio-recorder-ts] ❌ Error testing path ${path}:`, e);
|
|
595
619
|
// Continue to next path
|
|
596
620
|
continue;
|
|
597
621
|
}
|
|
598
622
|
}
|
|
623
|
+
console.error(`[web-audio-recorder-ts] ❌ Could not find ${filename} in any of the ${possiblePaths.length} paths tried`);
|
|
624
|
+
console.error(`[web-audio-recorder-ts] Tried paths:`, possiblePaths);
|
|
599
625
|
return null;
|
|
600
626
|
}
|
|
601
627
|
|
|
@@ -617,15 +643,43 @@ class OggVorbisEncoderWrapper {
|
|
|
617
643
|
*/
|
|
618
644
|
constructor(sampleRate, numChannels, options = {}) {
|
|
619
645
|
this.encoder = null;
|
|
646
|
+
this.bufferCount = 0;
|
|
647
|
+
this.totalSamples = 0;
|
|
648
|
+
// Validar parâmetros
|
|
649
|
+
if (!Number.isFinite(sampleRate) || sampleRate <= 0) {
|
|
650
|
+
throw new Error(`Invalid sampleRate: ${sampleRate}. Must be a positive number.`);
|
|
651
|
+
}
|
|
652
|
+
if (!Number.isInteger(numChannels) || numChannels < 1 || numChannels > 2) {
|
|
653
|
+
throw new Error(`Invalid numChannels: ${numChannels}. Must be 1 (mono) or 2 (stereo).`);
|
|
654
|
+
}
|
|
620
655
|
this.sampleRate = sampleRate;
|
|
621
656
|
this.numChannels = numChannels;
|
|
622
|
-
|
|
657
|
+
// Validar e limitar qualidade (-0.1 a 1.0 para Vorbis)
|
|
658
|
+
const rawQuality = options.quality ?? 0.5;
|
|
659
|
+
if (!Number.isFinite(rawQuality)) {
|
|
660
|
+
console.warn(`Invalid quality value: ${rawQuality}. Using default 0.5`);
|
|
661
|
+
this.quality = 0.5;
|
|
662
|
+
}
|
|
663
|
+
else {
|
|
664
|
+
// Clamp quality to valid range
|
|
665
|
+
this.quality = Math.max(-0.1, Math.min(1.0, rawQuality));
|
|
666
|
+
if (rawQuality !== this.quality) {
|
|
667
|
+
console.warn(`Quality value ${rawQuality} clamped to valid range: ${this.quality}`);
|
|
668
|
+
}
|
|
669
|
+
}
|
|
623
670
|
// Verificar se OggVorbisEncoder está disponível
|
|
624
671
|
if (typeof OggVorbisEncoder === 'undefined') {
|
|
625
672
|
throw new Error('OggVorbisEncoder is not loaded. Make sure to load OggVorbisEncoder.min.js before using this encoder.');
|
|
626
673
|
}
|
|
627
|
-
|
|
628
|
-
|
|
674
|
+
try {
|
|
675
|
+
// Criar instância do encoder
|
|
676
|
+
this.encoder = new OggVorbisEncoder(sampleRate, numChannels, this.quality);
|
|
677
|
+
}
|
|
678
|
+
catch (error) {
|
|
679
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
680
|
+
throw new Error(`Failed to initialize OGG encoder: ${errorMsg}. ` +
|
|
681
|
+
`Parameters: sampleRate=${sampleRate}, numChannels=${numChannels}, quality=${this.quality}`);
|
|
682
|
+
}
|
|
629
683
|
}
|
|
630
684
|
/**
|
|
631
685
|
* Codifica buffers de áudio
|
|
@@ -639,7 +693,60 @@ class OggVorbisEncoderWrapper {
|
|
|
639
693
|
if (buffers.length !== this.numChannels) {
|
|
640
694
|
throw new Error(`Expected ${this.numChannels} channels, got ${buffers.length}`);
|
|
641
695
|
}
|
|
642
|
-
|
|
696
|
+
// Validar que todos os buffers têm o mesmo tamanho
|
|
697
|
+
if (buffers.length > 0) {
|
|
698
|
+
const expectedLength = buffers[0].length;
|
|
699
|
+
for (let i = 1; i < buffers.length; i++) {
|
|
700
|
+
if (buffers[i].length !== expectedLength) {
|
|
701
|
+
throw new Error(`Channel ${i} has length ${buffers[i].length}, expected ${expectedLength}`);
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
// Validar que há dados para processar
|
|
705
|
+
if (expectedLength === 0) {
|
|
706
|
+
// Buffer vazio, não há nada para codificar
|
|
707
|
+
return;
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
else {
|
|
711
|
+
// Nenhum buffer fornecido
|
|
712
|
+
return;
|
|
713
|
+
}
|
|
714
|
+
// Criar cópias dos buffers e validar valores (NaN, Infinity)
|
|
715
|
+
const safeBuffers = buffers.map((buffer, channelIndex) => {
|
|
716
|
+
const safeBuffer = new Float32Array(buffer.length);
|
|
717
|
+
let hasInvalidValues = false;
|
|
718
|
+
for (let i = 0; i < buffer.length; i++) {
|
|
719
|
+
const value = buffer[i];
|
|
720
|
+
// Verificar NaN e Infinity
|
|
721
|
+
if (!Number.isFinite(value)) {
|
|
722
|
+
hasInvalidValues = true;
|
|
723
|
+
// Substituir valores inválidos por 0
|
|
724
|
+
safeBuffer[i] = 0;
|
|
725
|
+
}
|
|
726
|
+
else {
|
|
727
|
+
// Clamp valores para o range válido de áudio (-1.0 a 1.0)
|
|
728
|
+
safeBuffer[i] = Math.max(-1, Math.min(1.0, value));
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
if (hasInvalidValues) {
|
|
732
|
+
console.warn(`OGG Encoder: Found invalid values (NaN/Infinity) in channel ${channelIndex}. ` +
|
|
733
|
+
`Replaced with 0. Buffer length: ${buffer.length}`);
|
|
734
|
+
}
|
|
735
|
+
return safeBuffer;
|
|
736
|
+
});
|
|
737
|
+
try {
|
|
738
|
+
this.encoder.encode(safeBuffers);
|
|
739
|
+
// Contar buffers processados para garantir que há dados antes de finalizar
|
|
740
|
+
this.bufferCount++;
|
|
741
|
+
this.totalSamples += safeBuffers[0].length;
|
|
742
|
+
}
|
|
743
|
+
catch (error) {
|
|
744
|
+
// Melhorar mensagem de erro para incluir informações de debug
|
|
745
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
746
|
+
throw new Error(`OGG encoding error: ${errorMsg}. ` +
|
|
747
|
+
`Buffers: ${buffers.length} channels, lengths: ${buffers.map(b => b.length).join(', ')}, ` +
|
|
748
|
+
`Total buffers processed: ${this.bufferCount}, Total samples: ${this.totalSamples}`);
|
|
749
|
+
}
|
|
643
750
|
}
|
|
644
751
|
/**
|
|
645
752
|
* Finaliza o encoding e retorna o Blob OGG
|
|
@@ -651,7 +758,25 @@ class OggVorbisEncoderWrapper {
|
|
|
651
758
|
if (!this.encoder) {
|
|
652
759
|
throw new Error('Encoder is not initialized');
|
|
653
760
|
}
|
|
654
|
-
|
|
761
|
+
// Verificar se há dados processados
|
|
762
|
+
if (this.bufferCount === 0) {
|
|
763
|
+
console.warn('OGG Encoder: finish() called but no buffers were encoded. This may cause issues with the Emscripten encoder.');
|
|
764
|
+
// Ainda tentar finalizar, mas avisar
|
|
765
|
+
}
|
|
766
|
+
try {
|
|
767
|
+
const blob = this.encoder.finish(mimeType);
|
|
768
|
+
// Validar que o blob não está vazio
|
|
769
|
+
if (blob.size === 0) {
|
|
770
|
+
console.warn('OGG Encoder: finish() returned empty blob. This may indicate insufficient audio data was encoded.');
|
|
771
|
+
}
|
|
772
|
+
return blob;
|
|
773
|
+
}
|
|
774
|
+
catch (error) {
|
|
775
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
776
|
+
throw new Error(`OGG finish() error: ${errorMsg}. ` +
|
|
777
|
+
`Buffers processed: ${this.bufferCount}, Total samples: ${this.totalSamples}, ` +
|
|
778
|
+
`Sample rate: ${this.sampleRate}, Channels: ${this.numChannels}, Quality: ${this.quality}`);
|
|
779
|
+
}
|
|
655
780
|
}
|
|
656
781
|
/**
|
|
657
782
|
* Cancela o encoding
|
|
@@ -661,6 +786,9 @@ class OggVorbisEncoderWrapper {
|
|
|
661
786
|
this.encoder.cancel();
|
|
662
787
|
this.encoder = null;
|
|
663
788
|
}
|
|
789
|
+
// Reset contadores
|
|
790
|
+
this.bufferCount = 0;
|
|
791
|
+
this.totalSamples = 0;
|
|
664
792
|
}
|
|
665
793
|
}
|
|
666
794
|
/**
|
|
@@ -677,8 +805,14 @@ async function loadOggVorbisEncoder(scriptUrl) {
|
|
|
677
805
|
// Tentar encontrar o arquivo automaticamente
|
|
678
806
|
const foundPath = await findEncoderPath('OggVorbisEncoder.min.js');
|
|
679
807
|
if (!foundPath) {
|
|
680
|
-
|
|
681
|
-
'Please
|
|
808
|
+
const errorMsg = 'Could not find OggVorbisEncoder.min.js automatically.\n\n' +
|
|
809
|
+
'Please try one of the following:\n' +
|
|
810
|
+
'1. Provide the path manually: await loadOggVorbisEncoder("/path/to/OggVorbisEncoder.min.js")\n' +
|
|
811
|
+
'2. Copy files to public/: cp node_modules/web-audio-recorder-ts/lib/*.js public/\n' +
|
|
812
|
+
'3. Configure server to serve node_modules (see NUXT_USAGE.md)\n' +
|
|
813
|
+
'4. Check browser console for detailed path information';
|
|
814
|
+
console.error(errorMsg);
|
|
815
|
+
throw new Error(errorMsg);
|
|
682
816
|
}
|
|
683
817
|
scriptUrl = foundPath;
|
|
684
818
|
}
|
|
@@ -809,15 +943,43 @@ class Mp3LameEncoderWrapper {
|
|
|
809
943
|
*/
|
|
810
944
|
constructor(sampleRate, numChannels, options = {}) {
|
|
811
945
|
this.encoder = null;
|
|
946
|
+
this.bufferCount = 0;
|
|
947
|
+
this.totalSamples = 0;
|
|
948
|
+
// Validar parâmetros
|
|
949
|
+
if (!Number.isFinite(sampleRate) || sampleRate <= 0) {
|
|
950
|
+
throw new Error(`Invalid sampleRate: ${sampleRate}. Must be a positive number.`);
|
|
951
|
+
}
|
|
952
|
+
if (!Number.isInteger(numChannels) || numChannels < 1 || numChannels > 2) {
|
|
953
|
+
throw new Error(`Invalid numChannels: ${numChannels}. Must be 1 (mono) or 2 (stereo).`);
|
|
954
|
+
}
|
|
812
955
|
this.sampleRate = sampleRate;
|
|
813
956
|
this.numChannels = numChannels;
|
|
814
|
-
|
|
957
|
+
// Validar e limitar bitrate (32 a 320 kbps para MP3)
|
|
958
|
+
const rawBitrate = options.bitrate ?? 128;
|
|
959
|
+
if (!Number.isFinite(rawBitrate) || !Number.isInteger(rawBitrate)) {
|
|
960
|
+
console.warn(`Invalid bitrate value: ${rawBitrate}. Using default 128`);
|
|
961
|
+
this.bitrate = 128;
|
|
962
|
+
}
|
|
963
|
+
else {
|
|
964
|
+
// Clamp bitrate to valid range
|
|
965
|
+
this.bitrate = Math.max(32, Math.min(320, rawBitrate));
|
|
966
|
+
if (rawBitrate !== this.bitrate) {
|
|
967
|
+
console.warn(`Bitrate value ${rawBitrate} clamped to valid range: ${this.bitrate}`);
|
|
968
|
+
}
|
|
969
|
+
}
|
|
815
970
|
// Verificar se Mp3LameEncoder está disponível
|
|
816
971
|
if (typeof Mp3LameEncoder === 'undefined') {
|
|
817
972
|
throw new Error('Mp3LameEncoder is not loaded. Make sure to load Mp3LameEncoder.min.js before using this encoder.');
|
|
818
973
|
}
|
|
819
|
-
|
|
820
|
-
|
|
974
|
+
try {
|
|
975
|
+
// Criar instância do encoder
|
|
976
|
+
this.encoder = new Mp3LameEncoder(sampleRate, numChannels, this.bitrate);
|
|
977
|
+
}
|
|
978
|
+
catch (error) {
|
|
979
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
980
|
+
throw new Error(`Failed to initialize MP3 encoder: ${errorMsg}. ` +
|
|
981
|
+
`Parameters: sampleRate=${sampleRate}, numChannels=${numChannels}, bitrate=${this.bitrate}`);
|
|
982
|
+
}
|
|
821
983
|
}
|
|
822
984
|
/**
|
|
823
985
|
* Codifica buffers de áudio
|
|
@@ -831,7 +993,60 @@ class Mp3LameEncoderWrapper {
|
|
|
831
993
|
if (buffers.length !== this.numChannels) {
|
|
832
994
|
throw new Error(`Expected ${this.numChannels} channels, got ${buffers.length}`);
|
|
833
995
|
}
|
|
834
|
-
|
|
996
|
+
// Validar que todos os buffers têm o mesmo tamanho
|
|
997
|
+
if (buffers.length > 0) {
|
|
998
|
+
const expectedLength = buffers[0].length;
|
|
999
|
+
for (let i = 1; i < buffers.length; i++) {
|
|
1000
|
+
if (buffers[i].length !== expectedLength) {
|
|
1001
|
+
throw new Error(`Channel ${i} has length ${buffers[i].length}, expected ${expectedLength}`);
|
|
1002
|
+
}
|
|
1003
|
+
}
|
|
1004
|
+
// Validar que há dados para processar
|
|
1005
|
+
if (expectedLength === 0) {
|
|
1006
|
+
// Buffer vazio, não há nada para codificar
|
|
1007
|
+
return;
|
|
1008
|
+
}
|
|
1009
|
+
}
|
|
1010
|
+
else {
|
|
1011
|
+
// Nenhum buffer fornecido
|
|
1012
|
+
return;
|
|
1013
|
+
}
|
|
1014
|
+
// Criar cópias dos buffers e validar valores (NaN, Infinity)
|
|
1015
|
+
const safeBuffers = buffers.map((buffer, channelIndex) => {
|
|
1016
|
+
const safeBuffer = new Float32Array(buffer.length);
|
|
1017
|
+
let hasInvalidValues = false;
|
|
1018
|
+
for (let i = 0; i < buffer.length; i++) {
|
|
1019
|
+
const value = buffer[i];
|
|
1020
|
+
// Verificar NaN e Infinity
|
|
1021
|
+
if (!Number.isFinite(value)) {
|
|
1022
|
+
hasInvalidValues = true;
|
|
1023
|
+
// Substituir valores inválidos por 0
|
|
1024
|
+
safeBuffer[i] = 0;
|
|
1025
|
+
}
|
|
1026
|
+
else {
|
|
1027
|
+
// Clamp valores para o range válido de áudio (-1.0 a 1.0)
|
|
1028
|
+
safeBuffer[i] = Math.max(-1, Math.min(1.0, value));
|
|
1029
|
+
}
|
|
1030
|
+
}
|
|
1031
|
+
if (hasInvalidValues) {
|
|
1032
|
+
console.warn(`MP3 Encoder: Found invalid values (NaN/Infinity) in channel ${channelIndex}. ` +
|
|
1033
|
+
`Replaced with 0. Buffer length: ${buffer.length}`);
|
|
1034
|
+
}
|
|
1035
|
+
return safeBuffer;
|
|
1036
|
+
});
|
|
1037
|
+
try {
|
|
1038
|
+
this.encoder.encode(safeBuffers);
|
|
1039
|
+
// Contar buffers processados para garantir que há dados antes de finalizar
|
|
1040
|
+
this.bufferCount++;
|
|
1041
|
+
this.totalSamples += safeBuffers[0].length;
|
|
1042
|
+
}
|
|
1043
|
+
catch (error) {
|
|
1044
|
+
// Melhorar mensagem de erro para incluir informações de debug
|
|
1045
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
1046
|
+
throw new Error(`MP3 encoding error: ${errorMsg}. ` +
|
|
1047
|
+
`Buffers: ${buffers.length} channels, lengths: ${buffers.map(b => b.length).join(', ')}, ` +
|
|
1048
|
+
`Total buffers processed: ${this.bufferCount}, Total samples: ${this.totalSamples}`);
|
|
1049
|
+
}
|
|
835
1050
|
}
|
|
836
1051
|
/**
|
|
837
1052
|
* Finaliza o encoding e retorna o Blob MP3
|
|
@@ -843,7 +1058,25 @@ class Mp3LameEncoderWrapper {
|
|
|
843
1058
|
if (!this.encoder) {
|
|
844
1059
|
throw new Error('Encoder is not initialized');
|
|
845
1060
|
}
|
|
846
|
-
|
|
1061
|
+
// Verificar se há dados processados
|
|
1062
|
+
if (this.bufferCount === 0) {
|
|
1063
|
+
console.warn('MP3 Encoder: finish() called but no buffers were encoded. This may cause issues with the Emscripten encoder.');
|
|
1064
|
+
// Ainda tentar finalizar, mas avisar
|
|
1065
|
+
}
|
|
1066
|
+
try {
|
|
1067
|
+
const blob = this.encoder.finish(mimeType);
|
|
1068
|
+
// Validar que o blob não está vazio
|
|
1069
|
+
if (blob.size === 0) {
|
|
1070
|
+
console.warn('MP3 Encoder: finish() returned empty blob. This may indicate insufficient audio data was encoded.');
|
|
1071
|
+
}
|
|
1072
|
+
return blob;
|
|
1073
|
+
}
|
|
1074
|
+
catch (error) {
|
|
1075
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
1076
|
+
throw new Error(`MP3 finish() error: ${errorMsg}. ` +
|
|
1077
|
+
`Buffers processed: ${this.bufferCount}, Total samples: ${this.totalSamples}, ` +
|
|
1078
|
+
`Sample rate: ${this.sampleRate}, Channels: ${this.numChannels}, Bitrate: ${this.bitrate}`);
|
|
1079
|
+
}
|
|
847
1080
|
}
|
|
848
1081
|
/**
|
|
849
1082
|
* Cancela o encoding
|
|
@@ -853,6 +1086,9 @@ class Mp3LameEncoderWrapper {
|
|
|
853
1086
|
this.encoder.cancel();
|
|
854
1087
|
this.encoder = null;
|
|
855
1088
|
}
|
|
1089
|
+
// Reset contadores
|
|
1090
|
+
this.bufferCount = 0;
|
|
1091
|
+
this.totalSamples = 0;
|
|
856
1092
|
}
|
|
857
1093
|
}
|
|
858
1094
|
/**
|
|
@@ -869,8 +1105,14 @@ async function loadMp3LameEncoder(scriptUrl) {
|
|
|
869
1105
|
// Tentar encontrar o arquivo automaticamente
|
|
870
1106
|
const foundPath = await findEncoderPath('Mp3LameEncoder.min.js');
|
|
871
1107
|
if (!foundPath) {
|
|
872
|
-
|
|
873
|
-
'Please
|
|
1108
|
+
const errorMsg = 'Could not find Mp3LameEncoder.min.js automatically.\n\n' +
|
|
1109
|
+
'Please try one of the following:\n' +
|
|
1110
|
+
'1. Provide the path manually: await loadMp3LameEncoder("/path/to/Mp3LameEncoder.min.js")\n' +
|
|
1111
|
+
'2. Copy files to public/: cp node_modules/web-audio-recorder-ts/lib/*.js public/\n' +
|
|
1112
|
+
'3. Configure server to serve node_modules (see NUXT_USAGE.md)\n' +
|
|
1113
|
+
'4. Check browser console for detailed path information';
|
|
1114
|
+
console.error(errorMsg);
|
|
1115
|
+
throw new Error(errorMsg);
|
|
874
1116
|
}
|
|
875
1117
|
scriptUrl = foundPath;
|
|
876
1118
|
}
|
package/dist/index.umd.js
CHANGED
|
@@ -151,6 +151,9 @@
|
|
|
151
151
|
if (!this.encoder) {
|
|
152
152
|
throw new Error('Encoder is not initialized');
|
|
153
153
|
}
|
|
154
|
+
// Aguardar um pouco para garantir que todos os callbacks de áudio foram processados
|
|
155
|
+
// Isso evita condições de corrida onde finish() é chamado antes de todos os buffers serem processados
|
|
156
|
+
await new Promise(resolve => setTimeout(resolve, 50));
|
|
154
157
|
const blob = this.encoder.finish(mimeType);
|
|
155
158
|
const url = URL.createObjectURL(blob);
|
|
156
159
|
const timecode = Date.now() - this.startTime;
|
|
@@ -553,33 +556,47 @@
|
|
|
553
556
|
* Useful when auto-detection fails
|
|
554
557
|
*/
|
|
555
558
|
async function findEncoderPath(filename) {
|
|
559
|
+
// Obter base URL atual para construir caminhos relativos
|
|
560
|
+
const currentOrigin = typeof window !== 'undefined' ? window.location.origin : '';
|
|
561
|
+
const currentPath = typeof window !== 'undefined' ? window.location.pathname : '';
|
|
556
562
|
// Priorizar caminhos do node_modules primeiro
|
|
557
563
|
const possiblePaths = [
|
|
558
|
-
// Primeiro: tentar node_modules (prioridade máxima)
|
|
564
|
+
// Primeiro: tentar node_modules com caminhos absolutos (prioridade máxima)
|
|
559
565
|
`/node_modules/web-audio-recorder-ts/lib/${filename}`,
|
|
566
|
+
`${currentOrigin}/node_modules/web-audio-recorder-ts/lib/${filename}`,
|
|
567
|
+
// Caminhos relativos do node_modules
|
|
560
568
|
`./node_modules/web-audio-recorder-ts/lib/${filename}`,
|
|
561
569
|
`../node_modules/web-audio-recorder-ts/lib/${filename}`,
|
|
562
570
|
`../../node_modules/web-audio-recorder-ts/lib/${filename}`,
|
|
571
|
+
`../../../node_modules/web-audio-recorder-ts/lib/${filename}`,
|
|
572
|
+
// Com base no caminho atual
|
|
573
|
+
`${currentPath.replace(/\/[^/]*$/, '')}/node_modules/web-audio-recorder-ts/lib/${filename}`,
|
|
563
574
|
// From dist (se os arquivos foram copiados para dist/lib)
|
|
564
575
|
`/node_modules/web-audio-recorder-ts/dist/lib/${filename}`,
|
|
576
|
+
`${currentOrigin}/node_modules/web-audio-recorder-ts/dist/lib/${filename}`,
|
|
565
577
|
`./node_modules/web-audio-recorder-ts/dist/lib/${filename}`,
|
|
566
578
|
// Auto-detected path (pode apontar para node_modules)
|
|
567
579
|
getEncoderScriptUrl(filename),
|
|
568
580
|
// Para desenvolvimento/demo: try public folder (Vite serves public/ at root)
|
|
569
581
|
`/${filename}`,
|
|
582
|
+
`${currentOrigin}/${filename}`,
|
|
570
583
|
// Direct lib paths (for development or custom setups)
|
|
571
584
|
`/lib/${filename}`,
|
|
585
|
+
`${currentOrigin}/lib/${filename}`,
|
|
572
586
|
`./lib/${filename}`,
|
|
573
587
|
`../lib/${filename}`,
|
|
574
588
|
// CDN or absolute paths (if configured)
|
|
575
589
|
filename.startsWith('http') ? filename : null
|
|
576
590
|
].filter((path) => path !== null);
|
|
591
|
+
console.log(`[web-audio-recorder-ts] Searching for ${filename} in ${possiblePaths.length} possible paths...`);
|
|
577
592
|
// Try each path
|
|
578
|
-
for (
|
|
593
|
+
for (let i = 0; i < possiblePaths.length; i++) {
|
|
594
|
+
const path = possiblePaths[i];
|
|
579
595
|
try {
|
|
580
596
|
const testUrl = path.startsWith('http')
|
|
581
597
|
? path
|
|
582
598
|
: new URL(path, typeof window !== 'undefined' ? window.location.href : 'file://').href;
|
|
599
|
+
console.log(`[web-audio-recorder-ts] Trying path ${i + 1}/${possiblePaths.length}: ${path} -> ${testUrl}`);
|
|
583
600
|
// Usar GET para verificar se é JavaScript válido (não HTML)
|
|
584
601
|
const response = await fetch(testUrl, { method: 'GET', cache: 'no-cache' });
|
|
585
602
|
if (response.ok) {
|
|
@@ -588,21 +605,30 @@
|
|
|
588
605
|
const trimmedText = text.trim();
|
|
589
606
|
// Se começar com '<', é HTML (404, etc) - pular este caminho
|
|
590
607
|
if (trimmedText.startsWith('<')) {
|
|
591
|
-
console.warn(`Path ${path} returned HTML instead of JavaScript, skipping...`);
|
|
608
|
+
console.warn(`[web-audio-recorder-ts] ❌ Path ${path} returned HTML instead of JavaScript (likely 404), skipping...`);
|
|
592
609
|
continue;
|
|
593
610
|
}
|
|
594
611
|
// Se parece JavaScript, retornar este caminho
|
|
595
612
|
if (trimmedText.includes('function') || trimmedText.includes('var') || trimmedText.includes('const') || trimmedText.includes('let') || trimmedText.length > 100) {
|
|
596
|
-
console.log(
|
|
613
|
+
console.log(`[web-audio-recorder-ts] ✅ Found encoder file at: ${path}`);
|
|
597
614
|
return path;
|
|
598
615
|
}
|
|
616
|
+
else {
|
|
617
|
+
console.warn(`[web-audio-recorder-ts] ⚠️ Path ${path} returned content but doesn't look like JavaScript`);
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
else {
|
|
621
|
+
console.warn(`[web-audio-recorder-ts] ❌ Path ${path} returned status ${response.status} ${response.statusText}`);
|
|
599
622
|
}
|
|
600
623
|
}
|
|
601
624
|
catch (e) {
|
|
625
|
+
console.warn(`[web-audio-recorder-ts] ❌ Error testing path ${path}:`, e);
|
|
602
626
|
// Continue to next path
|
|
603
627
|
continue;
|
|
604
628
|
}
|
|
605
629
|
}
|
|
630
|
+
console.error(`[web-audio-recorder-ts] ❌ Could not find ${filename} in any of the ${possiblePaths.length} paths tried`);
|
|
631
|
+
console.error(`[web-audio-recorder-ts] Tried paths:`, possiblePaths);
|
|
606
632
|
return null;
|
|
607
633
|
}
|
|
608
634
|
|
|
@@ -624,15 +650,43 @@
|
|
|
624
650
|
*/
|
|
625
651
|
constructor(sampleRate, numChannels, options = {}) {
|
|
626
652
|
this.encoder = null;
|
|
653
|
+
this.bufferCount = 0;
|
|
654
|
+
this.totalSamples = 0;
|
|
655
|
+
// Validar parâmetros
|
|
656
|
+
if (!Number.isFinite(sampleRate) || sampleRate <= 0) {
|
|
657
|
+
throw new Error(`Invalid sampleRate: ${sampleRate}. Must be a positive number.`);
|
|
658
|
+
}
|
|
659
|
+
if (!Number.isInteger(numChannels) || numChannels < 1 || numChannels > 2) {
|
|
660
|
+
throw new Error(`Invalid numChannels: ${numChannels}. Must be 1 (mono) or 2 (stereo).`);
|
|
661
|
+
}
|
|
627
662
|
this.sampleRate = sampleRate;
|
|
628
663
|
this.numChannels = numChannels;
|
|
629
|
-
|
|
664
|
+
// Validar e limitar qualidade (-0.1 a 1.0 para Vorbis)
|
|
665
|
+
const rawQuality = options.quality ?? 0.5;
|
|
666
|
+
if (!Number.isFinite(rawQuality)) {
|
|
667
|
+
console.warn(`Invalid quality value: ${rawQuality}. Using default 0.5`);
|
|
668
|
+
this.quality = 0.5;
|
|
669
|
+
}
|
|
670
|
+
else {
|
|
671
|
+
// Clamp quality to valid range
|
|
672
|
+
this.quality = Math.max(-0.1, Math.min(1.0, rawQuality));
|
|
673
|
+
if (rawQuality !== this.quality) {
|
|
674
|
+
console.warn(`Quality value ${rawQuality} clamped to valid range: ${this.quality}`);
|
|
675
|
+
}
|
|
676
|
+
}
|
|
630
677
|
// Verificar se OggVorbisEncoder está disponível
|
|
631
678
|
if (typeof OggVorbisEncoder === 'undefined') {
|
|
632
679
|
throw new Error('OggVorbisEncoder is not loaded. Make sure to load OggVorbisEncoder.min.js before using this encoder.');
|
|
633
680
|
}
|
|
634
|
-
|
|
635
|
-
|
|
681
|
+
try {
|
|
682
|
+
// Criar instância do encoder
|
|
683
|
+
this.encoder = new OggVorbisEncoder(sampleRate, numChannels, this.quality);
|
|
684
|
+
}
|
|
685
|
+
catch (error) {
|
|
686
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
687
|
+
throw new Error(`Failed to initialize OGG encoder: ${errorMsg}. ` +
|
|
688
|
+
`Parameters: sampleRate=${sampleRate}, numChannels=${numChannels}, quality=${this.quality}`);
|
|
689
|
+
}
|
|
636
690
|
}
|
|
637
691
|
/**
|
|
638
692
|
* Codifica buffers de áudio
|
|
@@ -646,7 +700,60 @@
|
|
|
646
700
|
if (buffers.length !== this.numChannels) {
|
|
647
701
|
throw new Error(`Expected ${this.numChannels} channels, got ${buffers.length}`);
|
|
648
702
|
}
|
|
649
|
-
|
|
703
|
+
// Validar que todos os buffers têm o mesmo tamanho
|
|
704
|
+
if (buffers.length > 0) {
|
|
705
|
+
const expectedLength = buffers[0].length;
|
|
706
|
+
for (let i = 1; i < buffers.length; i++) {
|
|
707
|
+
if (buffers[i].length !== expectedLength) {
|
|
708
|
+
throw new Error(`Channel ${i} has length ${buffers[i].length}, expected ${expectedLength}`);
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
// Validar que há dados para processar
|
|
712
|
+
if (expectedLength === 0) {
|
|
713
|
+
// Buffer vazio, não há nada para codificar
|
|
714
|
+
return;
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
else {
|
|
718
|
+
// Nenhum buffer fornecido
|
|
719
|
+
return;
|
|
720
|
+
}
|
|
721
|
+
// Criar cópias dos buffers e validar valores (NaN, Infinity)
|
|
722
|
+
const safeBuffers = buffers.map((buffer, channelIndex) => {
|
|
723
|
+
const safeBuffer = new Float32Array(buffer.length);
|
|
724
|
+
let hasInvalidValues = false;
|
|
725
|
+
for (let i = 0; i < buffer.length; i++) {
|
|
726
|
+
const value = buffer[i];
|
|
727
|
+
// Verificar NaN e Infinity
|
|
728
|
+
if (!Number.isFinite(value)) {
|
|
729
|
+
hasInvalidValues = true;
|
|
730
|
+
// Substituir valores inválidos por 0
|
|
731
|
+
safeBuffer[i] = 0;
|
|
732
|
+
}
|
|
733
|
+
else {
|
|
734
|
+
// Clamp valores para o range válido de áudio (-1.0 a 1.0)
|
|
735
|
+
safeBuffer[i] = Math.max(-1, Math.min(1.0, value));
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
if (hasInvalidValues) {
|
|
739
|
+
console.warn(`OGG Encoder: Found invalid values (NaN/Infinity) in channel ${channelIndex}. ` +
|
|
740
|
+
`Replaced with 0. Buffer length: ${buffer.length}`);
|
|
741
|
+
}
|
|
742
|
+
return safeBuffer;
|
|
743
|
+
});
|
|
744
|
+
try {
|
|
745
|
+
this.encoder.encode(safeBuffers);
|
|
746
|
+
// Contar buffers processados para garantir que há dados antes de finalizar
|
|
747
|
+
this.bufferCount++;
|
|
748
|
+
this.totalSamples += safeBuffers[0].length;
|
|
749
|
+
}
|
|
750
|
+
catch (error) {
|
|
751
|
+
// Melhorar mensagem de erro para incluir informações de debug
|
|
752
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
753
|
+
throw new Error(`OGG encoding error: ${errorMsg}. ` +
|
|
754
|
+
`Buffers: ${buffers.length} channels, lengths: ${buffers.map(b => b.length).join(', ')}, ` +
|
|
755
|
+
`Total buffers processed: ${this.bufferCount}, Total samples: ${this.totalSamples}`);
|
|
756
|
+
}
|
|
650
757
|
}
|
|
651
758
|
/**
|
|
652
759
|
* Finaliza o encoding e retorna o Blob OGG
|
|
@@ -658,7 +765,25 @@
|
|
|
658
765
|
if (!this.encoder) {
|
|
659
766
|
throw new Error('Encoder is not initialized');
|
|
660
767
|
}
|
|
661
|
-
|
|
768
|
+
// Verificar se há dados processados
|
|
769
|
+
if (this.bufferCount === 0) {
|
|
770
|
+
console.warn('OGG Encoder: finish() called but no buffers were encoded. This may cause issues with the Emscripten encoder.');
|
|
771
|
+
// Ainda tentar finalizar, mas avisar
|
|
772
|
+
}
|
|
773
|
+
try {
|
|
774
|
+
const blob = this.encoder.finish(mimeType);
|
|
775
|
+
// Validar que o blob não está vazio
|
|
776
|
+
if (blob.size === 0) {
|
|
777
|
+
console.warn('OGG Encoder: finish() returned empty blob. This may indicate insufficient audio data was encoded.');
|
|
778
|
+
}
|
|
779
|
+
return blob;
|
|
780
|
+
}
|
|
781
|
+
catch (error) {
|
|
782
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
783
|
+
throw new Error(`OGG finish() error: ${errorMsg}. ` +
|
|
784
|
+
`Buffers processed: ${this.bufferCount}, Total samples: ${this.totalSamples}, ` +
|
|
785
|
+
`Sample rate: ${this.sampleRate}, Channels: ${this.numChannels}, Quality: ${this.quality}`);
|
|
786
|
+
}
|
|
662
787
|
}
|
|
663
788
|
/**
|
|
664
789
|
* Cancela o encoding
|
|
@@ -668,6 +793,9 @@
|
|
|
668
793
|
this.encoder.cancel();
|
|
669
794
|
this.encoder = null;
|
|
670
795
|
}
|
|
796
|
+
// Reset contadores
|
|
797
|
+
this.bufferCount = 0;
|
|
798
|
+
this.totalSamples = 0;
|
|
671
799
|
}
|
|
672
800
|
}
|
|
673
801
|
/**
|
|
@@ -684,8 +812,14 @@
|
|
|
684
812
|
// Tentar encontrar o arquivo automaticamente
|
|
685
813
|
const foundPath = await findEncoderPath('OggVorbisEncoder.min.js');
|
|
686
814
|
if (!foundPath) {
|
|
687
|
-
|
|
688
|
-
'Please
|
|
815
|
+
const errorMsg = 'Could not find OggVorbisEncoder.min.js automatically.\n\n' +
|
|
816
|
+
'Please try one of the following:\n' +
|
|
817
|
+
'1. Provide the path manually: await loadOggVorbisEncoder("/path/to/OggVorbisEncoder.min.js")\n' +
|
|
818
|
+
'2. Copy files to public/: cp node_modules/web-audio-recorder-ts/lib/*.js public/\n' +
|
|
819
|
+
'3. Configure server to serve node_modules (see NUXT_USAGE.md)\n' +
|
|
820
|
+
'4. Check browser console for detailed path information';
|
|
821
|
+
console.error(errorMsg);
|
|
822
|
+
throw new Error(errorMsg);
|
|
689
823
|
}
|
|
690
824
|
scriptUrl = foundPath;
|
|
691
825
|
}
|
|
@@ -816,15 +950,43 @@
|
|
|
816
950
|
*/
|
|
817
951
|
constructor(sampleRate, numChannels, options = {}) {
|
|
818
952
|
this.encoder = null;
|
|
953
|
+
this.bufferCount = 0;
|
|
954
|
+
this.totalSamples = 0;
|
|
955
|
+
// Validar parâmetros
|
|
956
|
+
if (!Number.isFinite(sampleRate) || sampleRate <= 0) {
|
|
957
|
+
throw new Error(`Invalid sampleRate: ${sampleRate}. Must be a positive number.`);
|
|
958
|
+
}
|
|
959
|
+
if (!Number.isInteger(numChannels) || numChannels < 1 || numChannels > 2) {
|
|
960
|
+
throw new Error(`Invalid numChannels: ${numChannels}. Must be 1 (mono) or 2 (stereo).`);
|
|
961
|
+
}
|
|
819
962
|
this.sampleRate = sampleRate;
|
|
820
963
|
this.numChannels = numChannels;
|
|
821
|
-
|
|
964
|
+
// Validar e limitar bitrate (32 a 320 kbps para MP3)
|
|
965
|
+
const rawBitrate = options.bitrate ?? 128;
|
|
966
|
+
if (!Number.isFinite(rawBitrate) || !Number.isInteger(rawBitrate)) {
|
|
967
|
+
console.warn(`Invalid bitrate value: ${rawBitrate}. Using default 128`);
|
|
968
|
+
this.bitrate = 128;
|
|
969
|
+
}
|
|
970
|
+
else {
|
|
971
|
+
// Clamp bitrate to valid range
|
|
972
|
+
this.bitrate = Math.max(32, Math.min(320, rawBitrate));
|
|
973
|
+
if (rawBitrate !== this.bitrate) {
|
|
974
|
+
console.warn(`Bitrate value ${rawBitrate} clamped to valid range: ${this.bitrate}`);
|
|
975
|
+
}
|
|
976
|
+
}
|
|
822
977
|
// Verificar se Mp3LameEncoder está disponível
|
|
823
978
|
if (typeof Mp3LameEncoder === 'undefined') {
|
|
824
979
|
throw new Error('Mp3LameEncoder is not loaded. Make sure to load Mp3LameEncoder.min.js before using this encoder.');
|
|
825
980
|
}
|
|
826
|
-
|
|
827
|
-
|
|
981
|
+
try {
|
|
982
|
+
// Criar instância do encoder
|
|
983
|
+
this.encoder = new Mp3LameEncoder(sampleRate, numChannels, this.bitrate);
|
|
984
|
+
}
|
|
985
|
+
catch (error) {
|
|
986
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
987
|
+
throw new Error(`Failed to initialize MP3 encoder: ${errorMsg}. ` +
|
|
988
|
+
`Parameters: sampleRate=${sampleRate}, numChannels=${numChannels}, bitrate=${this.bitrate}`);
|
|
989
|
+
}
|
|
828
990
|
}
|
|
829
991
|
/**
|
|
830
992
|
* Codifica buffers de áudio
|
|
@@ -838,7 +1000,60 @@
|
|
|
838
1000
|
if (buffers.length !== this.numChannels) {
|
|
839
1001
|
throw new Error(`Expected ${this.numChannels} channels, got ${buffers.length}`);
|
|
840
1002
|
}
|
|
841
|
-
|
|
1003
|
+
// Validar que todos os buffers têm o mesmo tamanho
|
|
1004
|
+
if (buffers.length > 0) {
|
|
1005
|
+
const expectedLength = buffers[0].length;
|
|
1006
|
+
for (let i = 1; i < buffers.length; i++) {
|
|
1007
|
+
if (buffers[i].length !== expectedLength) {
|
|
1008
|
+
throw new Error(`Channel ${i} has length ${buffers[i].length}, expected ${expectedLength}`);
|
|
1009
|
+
}
|
|
1010
|
+
}
|
|
1011
|
+
// Validar que há dados para processar
|
|
1012
|
+
if (expectedLength === 0) {
|
|
1013
|
+
// Buffer vazio, não há nada para codificar
|
|
1014
|
+
return;
|
|
1015
|
+
}
|
|
1016
|
+
}
|
|
1017
|
+
else {
|
|
1018
|
+
// Nenhum buffer fornecido
|
|
1019
|
+
return;
|
|
1020
|
+
}
|
|
1021
|
+
// Criar cópias dos buffers e validar valores (NaN, Infinity)
|
|
1022
|
+
const safeBuffers = buffers.map((buffer, channelIndex) => {
|
|
1023
|
+
const safeBuffer = new Float32Array(buffer.length);
|
|
1024
|
+
let hasInvalidValues = false;
|
|
1025
|
+
for (let i = 0; i < buffer.length; i++) {
|
|
1026
|
+
const value = buffer[i];
|
|
1027
|
+
// Verificar NaN e Infinity
|
|
1028
|
+
if (!Number.isFinite(value)) {
|
|
1029
|
+
hasInvalidValues = true;
|
|
1030
|
+
// Substituir valores inválidos por 0
|
|
1031
|
+
safeBuffer[i] = 0;
|
|
1032
|
+
}
|
|
1033
|
+
else {
|
|
1034
|
+
// Clamp valores para o range válido de áudio (-1.0 a 1.0)
|
|
1035
|
+
safeBuffer[i] = Math.max(-1, Math.min(1.0, value));
|
|
1036
|
+
}
|
|
1037
|
+
}
|
|
1038
|
+
if (hasInvalidValues) {
|
|
1039
|
+
console.warn(`MP3 Encoder: Found invalid values (NaN/Infinity) in channel ${channelIndex}. ` +
|
|
1040
|
+
`Replaced with 0. Buffer length: ${buffer.length}`);
|
|
1041
|
+
}
|
|
1042
|
+
return safeBuffer;
|
|
1043
|
+
});
|
|
1044
|
+
try {
|
|
1045
|
+
this.encoder.encode(safeBuffers);
|
|
1046
|
+
// Contar buffers processados para garantir que há dados antes de finalizar
|
|
1047
|
+
this.bufferCount++;
|
|
1048
|
+
this.totalSamples += safeBuffers[0].length;
|
|
1049
|
+
}
|
|
1050
|
+
catch (error) {
|
|
1051
|
+
// Melhorar mensagem de erro para incluir informações de debug
|
|
1052
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
1053
|
+
throw new Error(`MP3 encoding error: ${errorMsg}. ` +
|
|
1054
|
+
`Buffers: ${buffers.length} channels, lengths: ${buffers.map(b => b.length).join(', ')}, ` +
|
|
1055
|
+
`Total buffers processed: ${this.bufferCount}, Total samples: ${this.totalSamples}`);
|
|
1056
|
+
}
|
|
842
1057
|
}
|
|
843
1058
|
/**
|
|
844
1059
|
* Finaliza o encoding e retorna o Blob MP3
|
|
@@ -850,7 +1065,25 @@
|
|
|
850
1065
|
if (!this.encoder) {
|
|
851
1066
|
throw new Error('Encoder is not initialized');
|
|
852
1067
|
}
|
|
853
|
-
|
|
1068
|
+
// Verificar se há dados processados
|
|
1069
|
+
if (this.bufferCount === 0) {
|
|
1070
|
+
console.warn('MP3 Encoder: finish() called but no buffers were encoded. This may cause issues with the Emscripten encoder.');
|
|
1071
|
+
// Ainda tentar finalizar, mas avisar
|
|
1072
|
+
}
|
|
1073
|
+
try {
|
|
1074
|
+
const blob = this.encoder.finish(mimeType);
|
|
1075
|
+
// Validar que o blob não está vazio
|
|
1076
|
+
if (blob.size === 0) {
|
|
1077
|
+
console.warn('MP3 Encoder: finish() returned empty blob. This may indicate insufficient audio data was encoded.');
|
|
1078
|
+
}
|
|
1079
|
+
return blob;
|
|
1080
|
+
}
|
|
1081
|
+
catch (error) {
|
|
1082
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
1083
|
+
throw new Error(`MP3 finish() error: ${errorMsg}. ` +
|
|
1084
|
+
`Buffers processed: ${this.bufferCount}, Total samples: ${this.totalSamples}, ` +
|
|
1085
|
+
`Sample rate: ${this.sampleRate}, Channels: ${this.numChannels}, Bitrate: ${this.bitrate}`);
|
|
1086
|
+
}
|
|
854
1087
|
}
|
|
855
1088
|
/**
|
|
856
1089
|
* Cancela o encoding
|
|
@@ -860,6 +1093,9 @@
|
|
|
860
1093
|
this.encoder.cancel();
|
|
861
1094
|
this.encoder = null;
|
|
862
1095
|
}
|
|
1096
|
+
// Reset contadores
|
|
1097
|
+
this.bufferCount = 0;
|
|
1098
|
+
this.totalSamples = 0;
|
|
863
1099
|
}
|
|
864
1100
|
}
|
|
865
1101
|
/**
|
|
@@ -876,8 +1112,14 @@
|
|
|
876
1112
|
// Tentar encontrar o arquivo automaticamente
|
|
877
1113
|
const foundPath = await findEncoderPath('Mp3LameEncoder.min.js');
|
|
878
1114
|
if (!foundPath) {
|
|
879
|
-
|
|
880
|
-
'Please
|
|
1115
|
+
const errorMsg = 'Could not find Mp3LameEncoder.min.js automatically.\n\n' +
|
|
1116
|
+
'Please try one of the following:\n' +
|
|
1117
|
+
'1. Provide the path manually: await loadMp3LameEncoder("/path/to/Mp3LameEncoder.min.js")\n' +
|
|
1118
|
+
'2. Copy files to public/: cp node_modules/web-audio-recorder-ts/lib/*.js public/\n' +
|
|
1119
|
+
'3. Configure server to serve node_modules (see NUXT_USAGE.md)\n' +
|
|
1120
|
+
'4. Check browser console for detailed path information';
|
|
1121
|
+
console.error(errorMsg);
|
|
1122
|
+
throw new Error(errorMsg);
|
|
881
1123
|
}
|
|
882
1124
|
scriptUrl = foundPath;
|
|
883
1125
|
}
|
package/package.json
CHANGED