web-audio-recorder-ts 1.0.4 → 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 +217 -10
- package/dist/index.esm.js +217 -10
- package/dist/index.umd.js +217 -10
- 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;
|
|
@@ -643,15 +646,43 @@ class OggVorbisEncoderWrapper {
|
|
|
643
646
|
*/
|
|
644
647
|
constructor(sampleRate, numChannels, options = {}) {
|
|
645
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
|
+
}
|
|
646
658
|
this.sampleRate = sampleRate;
|
|
647
659
|
this.numChannels = numChannels;
|
|
648
|
-
|
|
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
|
+
}
|
|
649
673
|
// Verificar se OggVorbisEncoder está disponível
|
|
650
674
|
if (typeof OggVorbisEncoder === 'undefined') {
|
|
651
675
|
throw new Error('OggVorbisEncoder is not loaded. Make sure to load OggVorbisEncoder.min.js before using this encoder.');
|
|
652
676
|
}
|
|
653
|
-
|
|
654
|
-
|
|
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
|
+
}
|
|
655
686
|
}
|
|
656
687
|
/**
|
|
657
688
|
* Codifica buffers de áudio
|
|
@@ -665,7 +696,60 @@ class OggVorbisEncoderWrapper {
|
|
|
665
696
|
if (buffers.length !== this.numChannels) {
|
|
666
697
|
throw new Error(`Expected ${this.numChannels} channels, got ${buffers.length}`);
|
|
667
698
|
}
|
|
668
|
-
|
|
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
|
+
}
|
|
669
753
|
}
|
|
670
754
|
/**
|
|
671
755
|
* Finaliza o encoding e retorna o Blob OGG
|
|
@@ -677,7 +761,25 @@ class OggVorbisEncoderWrapper {
|
|
|
677
761
|
if (!this.encoder) {
|
|
678
762
|
throw new Error('Encoder is not initialized');
|
|
679
763
|
}
|
|
680
|
-
|
|
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
|
+
}
|
|
681
783
|
}
|
|
682
784
|
/**
|
|
683
785
|
* Cancela o encoding
|
|
@@ -687,6 +789,9 @@ class OggVorbisEncoderWrapper {
|
|
|
687
789
|
this.encoder.cancel();
|
|
688
790
|
this.encoder = null;
|
|
689
791
|
}
|
|
792
|
+
// Reset contadores
|
|
793
|
+
this.bufferCount = 0;
|
|
794
|
+
this.totalSamples = 0;
|
|
690
795
|
}
|
|
691
796
|
}
|
|
692
797
|
/**
|
|
@@ -841,15 +946,43 @@ class Mp3LameEncoderWrapper {
|
|
|
841
946
|
*/
|
|
842
947
|
constructor(sampleRate, numChannels, options = {}) {
|
|
843
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
|
+
}
|
|
844
958
|
this.sampleRate = sampleRate;
|
|
845
959
|
this.numChannels = numChannels;
|
|
846
|
-
|
|
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
|
+
}
|
|
847
973
|
// Verificar se Mp3LameEncoder está disponível
|
|
848
974
|
if (typeof Mp3LameEncoder === 'undefined') {
|
|
849
975
|
throw new Error('Mp3LameEncoder is not loaded. Make sure to load Mp3LameEncoder.min.js before using this encoder.');
|
|
850
976
|
}
|
|
851
|
-
|
|
852
|
-
|
|
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
|
+
}
|
|
853
986
|
}
|
|
854
987
|
/**
|
|
855
988
|
* Codifica buffers de áudio
|
|
@@ -863,7 +996,60 @@ class Mp3LameEncoderWrapper {
|
|
|
863
996
|
if (buffers.length !== this.numChannels) {
|
|
864
997
|
throw new Error(`Expected ${this.numChannels} channels, got ${buffers.length}`);
|
|
865
998
|
}
|
|
866
|
-
|
|
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
|
+
}
|
|
867
1053
|
}
|
|
868
1054
|
/**
|
|
869
1055
|
* Finaliza o encoding e retorna o Blob MP3
|
|
@@ -875,7 +1061,25 @@ class Mp3LameEncoderWrapper {
|
|
|
875
1061
|
if (!this.encoder) {
|
|
876
1062
|
throw new Error('Encoder is not initialized');
|
|
877
1063
|
}
|
|
878
|
-
|
|
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
|
+
}
|
|
879
1083
|
}
|
|
880
1084
|
/**
|
|
881
1085
|
* Cancela o encoding
|
|
@@ -885,6 +1089,9 @@ class Mp3LameEncoderWrapper {
|
|
|
885
1089
|
this.encoder.cancel();
|
|
886
1090
|
this.encoder = null;
|
|
887
1091
|
}
|
|
1092
|
+
// Reset contadores
|
|
1093
|
+
this.bufferCount = 0;
|
|
1094
|
+
this.totalSamples = 0;
|
|
888
1095
|
}
|
|
889
1096
|
}
|
|
890
1097
|
/**
|
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;
|
|
@@ -640,15 +643,43 @@ class OggVorbisEncoderWrapper {
|
|
|
640
643
|
*/
|
|
641
644
|
constructor(sampleRate, numChannels, options = {}) {
|
|
642
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
|
+
}
|
|
643
655
|
this.sampleRate = sampleRate;
|
|
644
656
|
this.numChannels = numChannels;
|
|
645
|
-
|
|
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
|
+
}
|
|
646
670
|
// Verificar se OggVorbisEncoder está disponível
|
|
647
671
|
if (typeof OggVorbisEncoder === 'undefined') {
|
|
648
672
|
throw new Error('OggVorbisEncoder is not loaded. Make sure to load OggVorbisEncoder.min.js before using this encoder.');
|
|
649
673
|
}
|
|
650
|
-
|
|
651
|
-
|
|
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
|
+
}
|
|
652
683
|
}
|
|
653
684
|
/**
|
|
654
685
|
* Codifica buffers de áudio
|
|
@@ -662,7 +693,60 @@ class OggVorbisEncoderWrapper {
|
|
|
662
693
|
if (buffers.length !== this.numChannels) {
|
|
663
694
|
throw new Error(`Expected ${this.numChannels} channels, got ${buffers.length}`);
|
|
664
695
|
}
|
|
665
|
-
|
|
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
|
+
}
|
|
666
750
|
}
|
|
667
751
|
/**
|
|
668
752
|
* Finaliza o encoding e retorna o Blob OGG
|
|
@@ -674,7 +758,25 @@ class OggVorbisEncoderWrapper {
|
|
|
674
758
|
if (!this.encoder) {
|
|
675
759
|
throw new Error('Encoder is not initialized');
|
|
676
760
|
}
|
|
677
|
-
|
|
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
|
+
}
|
|
678
780
|
}
|
|
679
781
|
/**
|
|
680
782
|
* Cancela o encoding
|
|
@@ -684,6 +786,9 @@ class OggVorbisEncoderWrapper {
|
|
|
684
786
|
this.encoder.cancel();
|
|
685
787
|
this.encoder = null;
|
|
686
788
|
}
|
|
789
|
+
// Reset contadores
|
|
790
|
+
this.bufferCount = 0;
|
|
791
|
+
this.totalSamples = 0;
|
|
687
792
|
}
|
|
688
793
|
}
|
|
689
794
|
/**
|
|
@@ -838,15 +943,43 @@ class Mp3LameEncoderWrapper {
|
|
|
838
943
|
*/
|
|
839
944
|
constructor(sampleRate, numChannels, options = {}) {
|
|
840
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
|
+
}
|
|
841
955
|
this.sampleRate = sampleRate;
|
|
842
956
|
this.numChannels = numChannels;
|
|
843
|
-
|
|
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
|
+
}
|
|
844
970
|
// Verificar se Mp3LameEncoder está disponível
|
|
845
971
|
if (typeof Mp3LameEncoder === 'undefined') {
|
|
846
972
|
throw new Error('Mp3LameEncoder is not loaded. Make sure to load Mp3LameEncoder.min.js before using this encoder.');
|
|
847
973
|
}
|
|
848
|
-
|
|
849
|
-
|
|
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
|
+
}
|
|
850
983
|
}
|
|
851
984
|
/**
|
|
852
985
|
* Codifica buffers de áudio
|
|
@@ -860,7 +993,60 @@ class Mp3LameEncoderWrapper {
|
|
|
860
993
|
if (buffers.length !== this.numChannels) {
|
|
861
994
|
throw new Error(`Expected ${this.numChannels} channels, got ${buffers.length}`);
|
|
862
995
|
}
|
|
863
|
-
|
|
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
|
+
}
|
|
864
1050
|
}
|
|
865
1051
|
/**
|
|
866
1052
|
* Finaliza o encoding e retorna o Blob MP3
|
|
@@ -872,7 +1058,25 @@ class Mp3LameEncoderWrapper {
|
|
|
872
1058
|
if (!this.encoder) {
|
|
873
1059
|
throw new Error('Encoder is not initialized');
|
|
874
1060
|
}
|
|
875
|
-
|
|
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
|
+
}
|
|
876
1080
|
}
|
|
877
1081
|
/**
|
|
878
1082
|
* Cancela o encoding
|
|
@@ -882,6 +1086,9 @@ class Mp3LameEncoderWrapper {
|
|
|
882
1086
|
this.encoder.cancel();
|
|
883
1087
|
this.encoder = null;
|
|
884
1088
|
}
|
|
1089
|
+
// Reset contadores
|
|
1090
|
+
this.bufferCount = 0;
|
|
1091
|
+
this.totalSamples = 0;
|
|
885
1092
|
}
|
|
886
1093
|
}
|
|
887
1094
|
/**
|
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;
|
|
@@ -647,15 +650,43 @@
|
|
|
647
650
|
*/
|
|
648
651
|
constructor(sampleRate, numChannels, options = {}) {
|
|
649
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
|
+
}
|
|
650
662
|
this.sampleRate = sampleRate;
|
|
651
663
|
this.numChannels = numChannels;
|
|
652
|
-
|
|
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
|
+
}
|
|
653
677
|
// Verificar se OggVorbisEncoder está disponível
|
|
654
678
|
if (typeof OggVorbisEncoder === 'undefined') {
|
|
655
679
|
throw new Error('OggVorbisEncoder is not loaded. Make sure to load OggVorbisEncoder.min.js before using this encoder.');
|
|
656
680
|
}
|
|
657
|
-
|
|
658
|
-
|
|
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
|
+
}
|
|
659
690
|
}
|
|
660
691
|
/**
|
|
661
692
|
* Codifica buffers de áudio
|
|
@@ -669,7 +700,60 @@
|
|
|
669
700
|
if (buffers.length !== this.numChannels) {
|
|
670
701
|
throw new Error(`Expected ${this.numChannels} channels, got ${buffers.length}`);
|
|
671
702
|
}
|
|
672
|
-
|
|
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
|
+
}
|
|
673
757
|
}
|
|
674
758
|
/**
|
|
675
759
|
* Finaliza o encoding e retorna o Blob OGG
|
|
@@ -681,7 +765,25 @@
|
|
|
681
765
|
if (!this.encoder) {
|
|
682
766
|
throw new Error('Encoder is not initialized');
|
|
683
767
|
}
|
|
684
|
-
|
|
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
|
+
}
|
|
685
787
|
}
|
|
686
788
|
/**
|
|
687
789
|
* Cancela o encoding
|
|
@@ -691,6 +793,9 @@
|
|
|
691
793
|
this.encoder.cancel();
|
|
692
794
|
this.encoder = null;
|
|
693
795
|
}
|
|
796
|
+
// Reset contadores
|
|
797
|
+
this.bufferCount = 0;
|
|
798
|
+
this.totalSamples = 0;
|
|
694
799
|
}
|
|
695
800
|
}
|
|
696
801
|
/**
|
|
@@ -845,15 +950,43 @@
|
|
|
845
950
|
*/
|
|
846
951
|
constructor(sampleRate, numChannels, options = {}) {
|
|
847
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
|
+
}
|
|
848
962
|
this.sampleRate = sampleRate;
|
|
849
963
|
this.numChannels = numChannels;
|
|
850
|
-
|
|
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
|
+
}
|
|
851
977
|
// Verificar se Mp3LameEncoder está disponível
|
|
852
978
|
if (typeof Mp3LameEncoder === 'undefined') {
|
|
853
979
|
throw new Error('Mp3LameEncoder is not loaded. Make sure to load Mp3LameEncoder.min.js before using this encoder.');
|
|
854
980
|
}
|
|
855
|
-
|
|
856
|
-
|
|
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
|
+
}
|
|
857
990
|
}
|
|
858
991
|
/**
|
|
859
992
|
* Codifica buffers de áudio
|
|
@@ -867,7 +1000,60 @@
|
|
|
867
1000
|
if (buffers.length !== this.numChannels) {
|
|
868
1001
|
throw new Error(`Expected ${this.numChannels} channels, got ${buffers.length}`);
|
|
869
1002
|
}
|
|
870
|
-
|
|
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
|
+
}
|
|
871
1057
|
}
|
|
872
1058
|
/**
|
|
873
1059
|
* Finaliza o encoding e retorna o Blob MP3
|
|
@@ -879,7 +1065,25 @@
|
|
|
879
1065
|
if (!this.encoder) {
|
|
880
1066
|
throw new Error('Encoder is not initialized');
|
|
881
1067
|
}
|
|
882
|
-
|
|
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
|
+
}
|
|
883
1087
|
}
|
|
884
1088
|
/**
|
|
885
1089
|
* Cancela o encoding
|
|
@@ -889,6 +1093,9 @@
|
|
|
889
1093
|
this.encoder.cancel();
|
|
890
1094
|
this.encoder = null;
|
|
891
1095
|
}
|
|
1096
|
+
// Reset contadores
|
|
1097
|
+
this.bufferCount = 0;
|
|
1098
|
+
this.totalSamples = 0;
|
|
892
1099
|
}
|
|
893
1100
|
}
|
|
894
1101
|
/**
|
package/package.json
CHANGED