web-audio-recorder-ts 1.0.4 → 1.0.6
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 +242 -13
- package/dist/index.esm.js +242 -13
- package/dist/index.umd.js +242 -13
- package/package.json +1 -1
package/dist/index.cjs.js
CHANGED
|
@@ -99,14 +99,36 @@ class WebAudioRecorder {
|
|
|
99
99
|
}
|
|
100
100
|
try {
|
|
101
101
|
const inputBuffer = event.inputBuffer;
|
|
102
|
+
const actualChannels = inputBuffer.numberOfChannels;
|
|
102
103
|
const buffers = [];
|
|
103
|
-
// Extrair dados de cada canal
|
|
104
|
-
for (let channel = 0; channel <
|
|
104
|
+
// Extrair dados de cada canal disponível
|
|
105
|
+
for (let channel = 0; channel < actualChannels; channel++) {
|
|
105
106
|
const channelData = inputBuffer.getChannelData(channel);
|
|
106
107
|
buffers.push(new Float32Array(channelData));
|
|
107
108
|
}
|
|
109
|
+
// Se o número de canais do buffer não corresponde ao esperado, ajustar
|
|
110
|
+
if (actualChannels !== this.numChannels) {
|
|
111
|
+
if (actualChannels === 1 && this.numChannels === 2) {
|
|
112
|
+
// Mono -> Estéreo: duplicar o canal
|
|
113
|
+
buffers.push(new Float32Array(buffers[0]));
|
|
114
|
+
}
|
|
115
|
+
else if (actualChannels === 2 && this.numChannels === 1) {
|
|
116
|
+
// Estéreo -> Mono: usar apenas o primeiro canal
|
|
117
|
+
buffers.splice(1);
|
|
118
|
+
}
|
|
119
|
+
else {
|
|
120
|
+
// Outros casos: usar apenas os canais disponíveis até o limite esperado
|
|
121
|
+
while (buffers.length < this.numChannels && buffers.length > 0) {
|
|
122
|
+
// Duplicar o último canal se necessário
|
|
123
|
+
buffers.push(new Float32Array(buffers[buffers.length - 1]));
|
|
124
|
+
}
|
|
125
|
+
if (buffers.length > this.numChannels) {
|
|
126
|
+
buffers.splice(this.numChannels);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
108
130
|
// Codificar os dados
|
|
109
|
-
if (this.encoder) {
|
|
131
|
+
if (this.encoder && buffers.length > 0) {
|
|
110
132
|
this.encoder.encode(buffers);
|
|
111
133
|
}
|
|
112
134
|
}
|
|
@@ -147,6 +169,9 @@ class WebAudioRecorder {
|
|
|
147
169
|
if (!this.encoder) {
|
|
148
170
|
throw new Error('Encoder is not initialized');
|
|
149
171
|
}
|
|
172
|
+
// Aguardar um pouco para garantir que todos os callbacks de áudio foram processados
|
|
173
|
+
// Isso evita condições de corrida onde finish() é chamado antes de todos os buffers serem processados
|
|
174
|
+
await new Promise(resolve => setTimeout(resolve, 50));
|
|
150
175
|
const blob = this.encoder.finish(mimeType);
|
|
151
176
|
const url = URL.createObjectURL(blob);
|
|
152
177
|
const timecode = Date.now() - this.startTime;
|
|
@@ -643,15 +668,43 @@ class OggVorbisEncoderWrapper {
|
|
|
643
668
|
*/
|
|
644
669
|
constructor(sampleRate, numChannels, options = {}) {
|
|
645
670
|
this.encoder = null;
|
|
671
|
+
this.bufferCount = 0;
|
|
672
|
+
this.totalSamples = 0;
|
|
673
|
+
// Validar parâmetros
|
|
674
|
+
if (!Number.isFinite(sampleRate) || sampleRate <= 0) {
|
|
675
|
+
throw new Error(`Invalid sampleRate: ${sampleRate}. Must be a positive number.`);
|
|
676
|
+
}
|
|
677
|
+
if (!Number.isInteger(numChannels) || numChannels < 1 || numChannels > 2) {
|
|
678
|
+
throw new Error(`Invalid numChannels: ${numChannels}. Must be 1 (mono) or 2 (stereo).`);
|
|
679
|
+
}
|
|
646
680
|
this.sampleRate = sampleRate;
|
|
647
681
|
this.numChannels = numChannels;
|
|
648
|
-
|
|
682
|
+
// Validar e limitar qualidade (-0.1 a 1.0 para Vorbis)
|
|
683
|
+
const rawQuality = options.quality ?? 0.5;
|
|
684
|
+
if (!Number.isFinite(rawQuality)) {
|
|
685
|
+
console.warn(`Invalid quality value: ${rawQuality}. Using default 0.5`);
|
|
686
|
+
this.quality = 0.5;
|
|
687
|
+
}
|
|
688
|
+
else {
|
|
689
|
+
// Clamp quality to valid range
|
|
690
|
+
this.quality = Math.max(-0.1, Math.min(1.0, rawQuality));
|
|
691
|
+
if (rawQuality !== this.quality) {
|
|
692
|
+
console.warn(`Quality value ${rawQuality} clamped to valid range: ${this.quality}`);
|
|
693
|
+
}
|
|
694
|
+
}
|
|
649
695
|
// Verificar se OggVorbisEncoder está disponível
|
|
650
696
|
if (typeof OggVorbisEncoder === 'undefined') {
|
|
651
697
|
throw new Error('OggVorbisEncoder is not loaded. Make sure to load OggVorbisEncoder.min.js before using this encoder.');
|
|
652
698
|
}
|
|
653
|
-
|
|
654
|
-
|
|
699
|
+
try {
|
|
700
|
+
// Criar instância do encoder
|
|
701
|
+
this.encoder = new OggVorbisEncoder(sampleRate, numChannels, this.quality);
|
|
702
|
+
}
|
|
703
|
+
catch (error) {
|
|
704
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
705
|
+
throw new Error(`Failed to initialize OGG encoder: ${errorMsg}. ` +
|
|
706
|
+
`Parameters: sampleRate=${sampleRate}, numChannels=${numChannels}, quality=${this.quality}`);
|
|
707
|
+
}
|
|
655
708
|
}
|
|
656
709
|
/**
|
|
657
710
|
* Codifica buffers de áudio
|
|
@@ -665,7 +718,60 @@ class OggVorbisEncoderWrapper {
|
|
|
665
718
|
if (buffers.length !== this.numChannels) {
|
|
666
719
|
throw new Error(`Expected ${this.numChannels} channels, got ${buffers.length}`);
|
|
667
720
|
}
|
|
668
|
-
|
|
721
|
+
// Validar que todos os buffers têm o mesmo tamanho
|
|
722
|
+
if (buffers.length > 0) {
|
|
723
|
+
const expectedLength = buffers[0].length;
|
|
724
|
+
for (let i = 1; i < buffers.length; i++) {
|
|
725
|
+
if (buffers[i].length !== expectedLength) {
|
|
726
|
+
throw new Error(`Channel ${i} has length ${buffers[i].length}, expected ${expectedLength}`);
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
// Validar que há dados para processar
|
|
730
|
+
if (expectedLength === 0) {
|
|
731
|
+
// Buffer vazio, não há nada para codificar
|
|
732
|
+
return;
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
else {
|
|
736
|
+
// Nenhum buffer fornecido
|
|
737
|
+
return;
|
|
738
|
+
}
|
|
739
|
+
// Criar cópias dos buffers e validar valores (NaN, Infinity)
|
|
740
|
+
const safeBuffers = buffers.map((buffer, channelIndex) => {
|
|
741
|
+
const safeBuffer = new Float32Array(buffer.length);
|
|
742
|
+
let hasInvalidValues = false;
|
|
743
|
+
for (let i = 0; i < buffer.length; i++) {
|
|
744
|
+
const value = buffer[i];
|
|
745
|
+
// Verificar NaN e Infinity
|
|
746
|
+
if (!Number.isFinite(value)) {
|
|
747
|
+
hasInvalidValues = true;
|
|
748
|
+
// Substituir valores inválidos por 0
|
|
749
|
+
safeBuffer[i] = 0;
|
|
750
|
+
}
|
|
751
|
+
else {
|
|
752
|
+
// Clamp valores para o range válido de áudio (-1.0 a 1.0)
|
|
753
|
+
safeBuffer[i] = Math.max(-1, Math.min(1.0, value));
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
if (hasInvalidValues) {
|
|
757
|
+
console.warn(`OGG Encoder: Found invalid values (NaN/Infinity) in channel ${channelIndex}. ` +
|
|
758
|
+
`Replaced with 0. Buffer length: ${buffer.length}`);
|
|
759
|
+
}
|
|
760
|
+
return safeBuffer;
|
|
761
|
+
});
|
|
762
|
+
try {
|
|
763
|
+
this.encoder.encode(safeBuffers);
|
|
764
|
+
// Contar buffers processados para garantir que há dados antes de finalizar
|
|
765
|
+
this.bufferCount++;
|
|
766
|
+
this.totalSamples += safeBuffers[0].length;
|
|
767
|
+
}
|
|
768
|
+
catch (error) {
|
|
769
|
+
// Melhorar mensagem de erro para incluir informações de debug
|
|
770
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
771
|
+
throw new Error(`OGG encoding error: ${errorMsg}. ` +
|
|
772
|
+
`Buffers: ${buffers.length} channels, lengths: ${buffers.map(b => b.length).join(', ')}, ` +
|
|
773
|
+
`Total buffers processed: ${this.bufferCount}, Total samples: ${this.totalSamples}`);
|
|
774
|
+
}
|
|
669
775
|
}
|
|
670
776
|
/**
|
|
671
777
|
* Finaliza o encoding e retorna o Blob OGG
|
|
@@ -677,7 +783,25 @@ class OggVorbisEncoderWrapper {
|
|
|
677
783
|
if (!this.encoder) {
|
|
678
784
|
throw new Error('Encoder is not initialized');
|
|
679
785
|
}
|
|
680
|
-
|
|
786
|
+
// Verificar se há dados processados
|
|
787
|
+
if (this.bufferCount === 0) {
|
|
788
|
+
console.warn('OGG Encoder: finish() called but no buffers were encoded. This may cause issues with the Emscripten encoder.');
|
|
789
|
+
// Ainda tentar finalizar, mas avisar
|
|
790
|
+
}
|
|
791
|
+
try {
|
|
792
|
+
const blob = this.encoder.finish(mimeType);
|
|
793
|
+
// Validar que o blob não está vazio
|
|
794
|
+
if (blob.size === 0) {
|
|
795
|
+
console.warn('OGG Encoder: finish() returned empty blob. This may indicate insufficient audio data was encoded.');
|
|
796
|
+
}
|
|
797
|
+
return blob;
|
|
798
|
+
}
|
|
799
|
+
catch (error) {
|
|
800
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
801
|
+
throw new Error(`OGG finish() error: ${errorMsg}. ` +
|
|
802
|
+
`Buffers processed: ${this.bufferCount}, Total samples: ${this.totalSamples}, ` +
|
|
803
|
+
`Sample rate: ${this.sampleRate}, Channels: ${this.numChannels}, Quality: ${this.quality}`);
|
|
804
|
+
}
|
|
681
805
|
}
|
|
682
806
|
/**
|
|
683
807
|
* Cancela o encoding
|
|
@@ -687,6 +811,9 @@ class OggVorbisEncoderWrapper {
|
|
|
687
811
|
this.encoder.cancel();
|
|
688
812
|
this.encoder = null;
|
|
689
813
|
}
|
|
814
|
+
// Reset contadores
|
|
815
|
+
this.bufferCount = 0;
|
|
816
|
+
this.totalSamples = 0;
|
|
690
817
|
}
|
|
691
818
|
}
|
|
692
819
|
/**
|
|
@@ -841,15 +968,43 @@ class Mp3LameEncoderWrapper {
|
|
|
841
968
|
*/
|
|
842
969
|
constructor(sampleRate, numChannels, options = {}) {
|
|
843
970
|
this.encoder = null;
|
|
971
|
+
this.bufferCount = 0;
|
|
972
|
+
this.totalSamples = 0;
|
|
973
|
+
// Validar parâmetros
|
|
974
|
+
if (!Number.isFinite(sampleRate) || sampleRate <= 0) {
|
|
975
|
+
throw new Error(`Invalid sampleRate: ${sampleRate}. Must be a positive number.`);
|
|
976
|
+
}
|
|
977
|
+
if (!Number.isInteger(numChannels) || numChannels < 1 || numChannels > 2) {
|
|
978
|
+
throw new Error(`Invalid numChannels: ${numChannels}. Must be 1 (mono) or 2 (stereo).`);
|
|
979
|
+
}
|
|
844
980
|
this.sampleRate = sampleRate;
|
|
845
981
|
this.numChannels = numChannels;
|
|
846
|
-
|
|
982
|
+
// Validar e limitar bitrate (32 a 320 kbps para MP3)
|
|
983
|
+
const rawBitrate = options.bitrate ?? 128;
|
|
984
|
+
if (!Number.isFinite(rawBitrate) || !Number.isInteger(rawBitrate)) {
|
|
985
|
+
console.warn(`Invalid bitrate value: ${rawBitrate}. Using default 128`);
|
|
986
|
+
this.bitrate = 128;
|
|
987
|
+
}
|
|
988
|
+
else {
|
|
989
|
+
// Clamp bitrate to valid range
|
|
990
|
+
this.bitrate = Math.max(32, Math.min(320, rawBitrate));
|
|
991
|
+
if (rawBitrate !== this.bitrate) {
|
|
992
|
+
console.warn(`Bitrate value ${rawBitrate} clamped to valid range: ${this.bitrate}`);
|
|
993
|
+
}
|
|
994
|
+
}
|
|
847
995
|
// Verificar se Mp3LameEncoder está disponível
|
|
848
996
|
if (typeof Mp3LameEncoder === 'undefined') {
|
|
849
997
|
throw new Error('Mp3LameEncoder is not loaded. Make sure to load Mp3LameEncoder.min.js before using this encoder.');
|
|
850
998
|
}
|
|
851
|
-
|
|
852
|
-
|
|
999
|
+
try {
|
|
1000
|
+
// Criar instância do encoder
|
|
1001
|
+
this.encoder = new Mp3LameEncoder(sampleRate, numChannels, this.bitrate);
|
|
1002
|
+
}
|
|
1003
|
+
catch (error) {
|
|
1004
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
1005
|
+
throw new Error(`Failed to initialize MP3 encoder: ${errorMsg}. ` +
|
|
1006
|
+
`Parameters: sampleRate=${sampleRate}, numChannels=${numChannels}, bitrate=${this.bitrate}`);
|
|
1007
|
+
}
|
|
853
1008
|
}
|
|
854
1009
|
/**
|
|
855
1010
|
* Codifica buffers de áudio
|
|
@@ -863,7 +1018,60 @@ class Mp3LameEncoderWrapper {
|
|
|
863
1018
|
if (buffers.length !== this.numChannels) {
|
|
864
1019
|
throw new Error(`Expected ${this.numChannels} channels, got ${buffers.length}`);
|
|
865
1020
|
}
|
|
866
|
-
|
|
1021
|
+
// Validar que todos os buffers têm o mesmo tamanho
|
|
1022
|
+
if (buffers.length > 0) {
|
|
1023
|
+
const expectedLength = buffers[0].length;
|
|
1024
|
+
for (let i = 1; i < buffers.length; i++) {
|
|
1025
|
+
if (buffers[i].length !== expectedLength) {
|
|
1026
|
+
throw new Error(`Channel ${i} has length ${buffers[i].length}, expected ${expectedLength}`);
|
|
1027
|
+
}
|
|
1028
|
+
}
|
|
1029
|
+
// Validar que há dados para processar
|
|
1030
|
+
if (expectedLength === 0) {
|
|
1031
|
+
// Buffer vazio, não há nada para codificar
|
|
1032
|
+
return;
|
|
1033
|
+
}
|
|
1034
|
+
}
|
|
1035
|
+
else {
|
|
1036
|
+
// Nenhum buffer fornecido
|
|
1037
|
+
return;
|
|
1038
|
+
}
|
|
1039
|
+
// Criar cópias dos buffers e validar valores (NaN, Infinity)
|
|
1040
|
+
const safeBuffers = buffers.map((buffer, channelIndex) => {
|
|
1041
|
+
const safeBuffer = new Float32Array(buffer.length);
|
|
1042
|
+
let hasInvalidValues = false;
|
|
1043
|
+
for (let i = 0; i < buffer.length; i++) {
|
|
1044
|
+
const value = buffer[i];
|
|
1045
|
+
// Verificar NaN e Infinity
|
|
1046
|
+
if (!Number.isFinite(value)) {
|
|
1047
|
+
hasInvalidValues = true;
|
|
1048
|
+
// Substituir valores inválidos por 0
|
|
1049
|
+
safeBuffer[i] = 0;
|
|
1050
|
+
}
|
|
1051
|
+
else {
|
|
1052
|
+
// Clamp valores para o range válido de áudio (-1.0 a 1.0)
|
|
1053
|
+
safeBuffer[i] = Math.max(-1, Math.min(1.0, value));
|
|
1054
|
+
}
|
|
1055
|
+
}
|
|
1056
|
+
if (hasInvalidValues) {
|
|
1057
|
+
console.warn(`MP3 Encoder: Found invalid values (NaN/Infinity) in channel ${channelIndex}. ` +
|
|
1058
|
+
`Replaced with 0. Buffer length: ${buffer.length}`);
|
|
1059
|
+
}
|
|
1060
|
+
return safeBuffer;
|
|
1061
|
+
});
|
|
1062
|
+
try {
|
|
1063
|
+
this.encoder.encode(safeBuffers);
|
|
1064
|
+
// Contar buffers processados para garantir que há dados antes de finalizar
|
|
1065
|
+
this.bufferCount++;
|
|
1066
|
+
this.totalSamples += safeBuffers[0].length;
|
|
1067
|
+
}
|
|
1068
|
+
catch (error) {
|
|
1069
|
+
// Melhorar mensagem de erro para incluir informações de debug
|
|
1070
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
1071
|
+
throw new Error(`MP3 encoding error: ${errorMsg}. ` +
|
|
1072
|
+
`Buffers: ${buffers.length} channels, lengths: ${buffers.map(b => b.length).join(', ')}, ` +
|
|
1073
|
+
`Total buffers processed: ${this.bufferCount}, Total samples: ${this.totalSamples}`);
|
|
1074
|
+
}
|
|
867
1075
|
}
|
|
868
1076
|
/**
|
|
869
1077
|
* Finaliza o encoding e retorna o Blob MP3
|
|
@@ -875,7 +1083,25 @@ class Mp3LameEncoderWrapper {
|
|
|
875
1083
|
if (!this.encoder) {
|
|
876
1084
|
throw new Error('Encoder is not initialized');
|
|
877
1085
|
}
|
|
878
|
-
|
|
1086
|
+
// Verificar se há dados processados
|
|
1087
|
+
if (this.bufferCount === 0) {
|
|
1088
|
+
console.warn('MP3 Encoder: finish() called but no buffers were encoded. This may cause issues with the Emscripten encoder.');
|
|
1089
|
+
// Ainda tentar finalizar, mas avisar
|
|
1090
|
+
}
|
|
1091
|
+
try {
|
|
1092
|
+
const blob = this.encoder.finish(mimeType);
|
|
1093
|
+
// Validar que o blob não está vazio
|
|
1094
|
+
if (blob.size === 0) {
|
|
1095
|
+
console.warn('MP3 Encoder: finish() returned empty blob. This may indicate insufficient audio data was encoded.');
|
|
1096
|
+
}
|
|
1097
|
+
return blob;
|
|
1098
|
+
}
|
|
1099
|
+
catch (error) {
|
|
1100
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
1101
|
+
throw new Error(`MP3 finish() error: ${errorMsg}. ` +
|
|
1102
|
+
`Buffers processed: ${this.bufferCount}, Total samples: ${this.totalSamples}, ` +
|
|
1103
|
+
`Sample rate: ${this.sampleRate}, Channels: ${this.numChannels}, Bitrate: ${this.bitrate}`);
|
|
1104
|
+
}
|
|
879
1105
|
}
|
|
880
1106
|
/**
|
|
881
1107
|
* Cancela o encoding
|
|
@@ -885,6 +1111,9 @@ class Mp3LameEncoderWrapper {
|
|
|
885
1111
|
this.encoder.cancel();
|
|
886
1112
|
this.encoder = null;
|
|
887
1113
|
}
|
|
1114
|
+
// Reset contadores
|
|
1115
|
+
this.bufferCount = 0;
|
|
1116
|
+
this.totalSamples = 0;
|
|
888
1117
|
}
|
|
889
1118
|
}
|
|
890
1119
|
/**
|
package/dist/index.esm.js
CHANGED
|
@@ -96,14 +96,36 @@ class WebAudioRecorder {
|
|
|
96
96
|
}
|
|
97
97
|
try {
|
|
98
98
|
const inputBuffer = event.inputBuffer;
|
|
99
|
+
const actualChannels = inputBuffer.numberOfChannels;
|
|
99
100
|
const buffers = [];
|
|
100
|
-
// Extrair dados de cada canal
|
|
101
|
-
for (let channel = 0; channel <
|
|
101
|
+
// Extrair dados de cada canal disponível
|
|
102
|
+
for (let channel = 0; channel < actualChannels; channel++) {
|
|
102
103
|
const channelData = inputBuffer.getChannelData(channel);
|
|
103
104
|
buffers.push(new Float32Array(channelData));
|
|
104
105
|
}
|
|
106
|
+
// Se o número de canais do buffer não corresponde ao esperado, ajustar
|
|
107
|
+
if (actualChannels !== this.numChannels) {
|
|
108
|
+
if (actualChannels === 1 && this.numChannels === 2) {
|
|
109
|
+
// Mono -> Estéreo: duplicar o canal
|
|
110
|
+
buffers.push(new Float32Array(buffers[0]));
|
|
111
|
+
}
|
|
112
|
+
else if (actualChannels === 2 && this.numChannels === 1) {
|
|
113
|
+
// Estéreo -> Mono: usar apenas o primeiro canal
|
|
114
|
+
buffers.splice(1);
|
|
115
|
+
}
|
|
116
|
+
else {
|
|
117
|
+
// Outros casos: usar apenas os canais disponíveis até o limite esperado
|
|
118
|
+
while (buffers.length < this.numChannels && buffers.length > 0) {
|
|
119
|
+
// Duplicar o último canal se necessário
|
|
120
|
+
buffers.push(new Float32Array(buffers[buffers.length - 1]));
|
|
121
|
+
}
|
|
122
|
+
if (buffers.length > this.numChannels) {
|
|
123
|
+
buffers.splice(this.numChannels);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
105
127
|
// Codificar os dados
|
|
106
|
-
if (this.encoder) {
|
|
128
|
+
if (this.encoder && buffers.length > 0) {
|
|
107
129
|
this.encoder.encode(buffers);
|
|
108
130
|
}
|
|
109
131
|
}
|
|
@@ -144,6 +166,9 @@ class WebAudioRecorder {
|
|
|
144
166
|
if (!this.encoder) {
|
|
145
167
|
throw new Error('Encoder is not initialized');
|
|
146
168
|
}
|
|
169
|
+
// Aguardar um pouco para garantir que todos os callbacks de áudio foram processados
|
|
170
|
+
// Isso evita condições de corrida onde finish() é chamado antes de todos os buffers serem processados
|
|
171
|
+
await new Promise(resolve => setTimeout(resolve, 50));
|
|
147
172
|
const blob = this.encoder.finish(mimeType);
|
|
148
173
|
const url = URL.createObjectURL(blob);
|
|
149
174
|
const timecode = Date.now() - this.startTime;
|
|
@@ -640,15 +665,43 @@ class OggVorbisEncoderWrapper {
|
|
|
640
665
|
*/
|
|
641
666
|
constructor(sampleRate, numChannels, options = {}) {
|
|
642
667
|
this.encoder = null;
|
|
668
|
+
this.bufferCount = 0;
|
|
669
|
+
this.totalSamples = 0;
|
|
670
|
+
// Validar parâmetros
|
|
671
|
+
if (!Number.isFinite(sampleRate) || sampleRate <= 0) {
|
|
672
|
+
throw new Error(`Invalid sampleRate: ${sampleRate}. Must be a positive number.`);
|
|
673
|
+
}
|
|
674
|
+
if (!Number.isInteger(numChannels) || numChannels < 1 || numChannels > 2) {
|
|
675
|
+
throw new Error(`Invalid numChannels: ${numChannels}. Must be 1 (mono) or 2 (stereo).`);
|
|
676
|
+
}
|
|
643
677
|
this.sampleRate = sampleRate;
|
|
644
678
|
this.numChannels = numChannels;
|
|
645
|
-
|
|
679
|
+
// Validar e limitar qualidade (-0.1 a 1.0 para Vorbis)
|
|
680
|
+
const rawQuality = options.quality ?? 0.5;
|
|
681
|
+
if (!Number.isFinite(rawQuality)) {
|
|
682
|
+
console.warn(`Invalid quality value: ${rawQuality}. Using default 0.5`);
|
|
683
|
+
this.quality = 0.5;
|
|
684
|
+
}
|
|
685
|
+
else {
|
|
686
|
+
// Clamp quality to valid range
|
|
687
|
+
this.quality = Math.max(-0.1, Math.min(1.0, rawQuality));
|
|
688
|
+
if (rawQuality !== this.quality) {
|
|
689
|
+
console.warn(`Quality value ${rawQuality} clamped to valid range: ${this.quality}`);
|
|
690
|
+
}
|
|
691
|
+
}
|
|
646
692
|
// Verificar se OggVorbisEncoder está disponível
|
|
647
693
|
if (typeof OggVorbisEncoder === 'undefined') {
|
|
648
694
|
throw new Error('OggVorbisEncoder is not loaded. Make sure to load OggVorbisEncoder.min.js before using this encoder.');
|
|
649
695
|
}
|
|
650
|
-
|
|
651
|
-
|
|
696
|
+
try {
|
|
697
|
+
// Criar instância do encoder
|
|
698
|
+
this.encoder = new OggVorbisEncoder(sampleRate, numChannels, this.quality);
|
|
699
|
+
}
|
|
700
|
+
catch (error) {
|
|
701
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
702
|
+
throw new Error(`Failed to initialize OGG encoder: ${errorMsg}. ` +
|
|
703
|
+
`Parameters: sampleRate=${sampleRate}, numChannels=${numChannels}, quality=${this.quality}`);
|
|
704
|
+
}
|
|
652
705
|
}
|
|
653
706
|
/**
|
|
654
707
|
* Codifica buffers de áudio
|
|
@@ -662,7 +715,60 @@ class OggVorbisEncoderWrapper {
|
|
|
662
715
|
if (buffers.length !== this.numChannels) {
|
|
663
716
|
throw new Error(`Expected ${this.numChannels} channels, got ${buffers.length}`);
|
|
664
717
|
}
|
|
665
|
-
|
|
718
|
+
// Validar que todos os buffers têm o mesmo tamanho
|
|
719
|
+
if (buffers.length > 0) {
|
|
720
|
+
const expectedLength = buffers[0].length;
|
|
721
|
+
for (let i = 1; i < buffers.length; i++) {
|
|
722
|
+
if (buffers[i].length !== expectedLength) {
|
|
723
|
+
throw new Error(`Channel ${i} has length ${buffers[i].length}, expected ${expectedLength}`);
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
// Validar que há dados para processar
|
|
727
|
+
if (expectedLength === 0) {
|
|
728
|
+
// Buffer vazio, não há nada para codificar
|
|
729
|
+
return;
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
else {
|
|
733
|
+
// Nenhum buffer fornecido
|
|
734
|
+
return;
|
|
735
|
+
}
|
|
736
|
+
// Criar cópias dos buffers e validar valores (NaN, Infinity)
|
|
737
|
+
const safeBuffers = buffers.map((buffer, channelIndex) => {
|
|
738
|
+
const safeBuffer = new Float32Array(buffer.length);
|
|
739
|
+
let hasInvalidValues = false;
|
|
740
|
+
for (let i = 0; i < buffer.length; i++) {
|
|
741
|
+
const value = buffer[i];
|
|
742
|
+
// Verificar NaN e Infinity
|
|
743
|
+
if (!Number.isFinite(value)) {
|
|
744
|
+
hasInvalidValues = true;
|
|
745
|
+
// Substituir valores inválidos por 0
|
|
746
|
+
safeBuffer[i] = 0;
|
|
747
|
+
}
|
|
748
|
+
else {
|
|
749
|
+
// Clamp valores para o range válido de áudio (-1.0 a 1.0)
|
|
750
|
+
safeBuffer[i] = Math.max(-1, Math.min(1.0, value));
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
if (hasInvalidValues) {
|
|
754
|
+
console.warn(`OGG Encoder: Found invalid values (NaN/Infinity) in channel ${channelIndex}. ` +
|
|
755
|
+
`Replaced with 0. Buffer length: ${buffer.length}`);
|
|
756
|
+
}
|
|
757
|
+
return safeBuffer;
|
|
758
|
+
});
|
|
759
|
+
try {
|
|
760
|
+
this.encoder.encode(safeBuffers);
|
|
761
|
+
// Contar buffers processados para garantir que há dados antes de finalizar
|
|
762
|
+
this.bufferCount++;
|
|
763
|
+
this.totalSamples += safeBuffers[0].length;
|
|
764
|
+
}
|
|
765
|
+
catch (error) {
|
|
766
|
+
// Melhorar mensagem de erro para incluir informações de debug
|
|
767
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
768
|
+
throw new Error(`OGG encoding error: ${errorMsg}. ` +
|
|
769
|
+
`Buffers: ${buffers.length} channels, lengths: ${buffers.map(b => b.length).join(', ')}, ` +
|
|
770
|
+
`Total buffers processed: ${this.bufferCount}, Total samples: ${this.totalSamples}`);
|
|
771
|
+
}
|
|
666
772
|
}
|
|
667
773
|
/**
|
|
668
774
|
* Finaliza o encoding e retorna o Blob OGG
|
|
@@ -674,7 +780,25 @@ class OggVorbisEncoderWrapper {
|
|
|
674
780
|
if (!this.encoder) {
|
|
675
781
|
throw new Error('Encoder is not initialized');
|
|
676
782
|
}
|
|
677
|
-
|
|
783
|
+
// Verificar se há dados processados
|
|
784
|
+
if (this.bufferCount === 0) {
|
|
785
|
+
console.warn('OGG Encoder: finish() called but no buffers were encoded. This may cause issues with the Emscripten encoder.');
|
|
786
|
+
// Ainda tentar finalizar, mas avisar
|
|
787
|
+
}
|
|
788
|
+
try {
|
|
789
|
+
const blob = this.encoder.finish(mimeType);
|
|
790
|
+
// Validar que o blob não está vazio
|
|
791
|
+
if (blob.size === 0) {
|
|
792
|
+
console.warn('OGG Encoder: finish() returned empty blob. This may indicate insufficient audio data was encoded.');
|
|
793
|
+
}
|
|
794
|
+
return blob;
|
|
795
|
+
}
|
|
796
|
+
catch (error) {
|
|
797
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
798
|
+
throw new Error(`OGG finish() error: ${errorMsg}. ` +
|
|
799
|
+
`Buffers processed: ${this.bufferCount}, Total samples: ${this.totalSamples}, ` +
|
|
800
|
+
`Sample rate: ${this.sampleRate}, Channels: ${this.numChannels}, Quality: ${this.quality}`);
|
|
801
|
+
}
|
|
678
802
|
}
|
|
679
803
|
/**
|
|
680
804
|
* Cancela o encoding
|
|
@@ -684,6 +808,9 @@ class OggVorbisEncoderWrapper {
|
|
|
684
808
|
this.encoder.cancel();
|
|
685
809
|
this.encoder = null;
|
|
686
810
|
}
|
|
811
|
+
// Reset contadores
|
|
812
|
+
this.bufferCount = 0;
|
|
813
|
+
this.totalSamples = 0;
|
|
687
814
|
}
|
|
688
815
|
}
|
|
689
816
|
/**
|
|
@@ -838,15 +965,43 @@ class Mp3LameEncoderWrapper {
|
|
|
838
965
|
*/
|
|
839
966
|
constructor(sampleRate, numChannels, options = {}) {
|
|
840
967
|
this.encoder = null;
|
|
968
|
+
this.bufferCount = 0;
|
|
969
|
+
this.totalSamples = 0;
|
|
970
|
+
// Validar parâmetros
|
|
971
|
+
if (!Number.isFinite(sampleRate) || sampleRate <= 0) {
|
|
972
|
+
throw new Error(`Invalid sampleRate: ${sampleRate}. Must be a positive number.`);
|
|
973
|
+
}
|
|
974
|
+
if (!Number.isInteger(numChannels) || numChannels < 1 || numChannels > 2) {
|
|
975
|
+
throw new Error(`Invalid numChannels: ${numChannels}. Must be 1 (mono) or 2 (stereo).`);
|
|
976
|
+
}
|
|
841
977
|
this.sampleRate = sampleRate;
|
|
842
978
|
this.numChannels = numChannels;
|
|
843
|
-
|
|
979
|
+
// Validar e limitar bitrate (32 a 320 kbps para MP3)
|
|
980
|
+
const rawBitrate = options.bitrate ?? 128;
|
|
981
|
+
if (!Number.isFinite(rawBitrate) || !Number.isInteger(rawBitrate)) {
|
|
982
|
+
console.warn(`Invalid bitrate value: ${rawBitrate}. Using default 128`);
|
|
983
|
+
this.bitrate = 128;
|
|
984
|
+
}
|
|
985
|
+
else {
|
|
986
|
+
// Clamp bitrate to valid range
|
|
987
|
+
this.bitrate = Math.max(32, Math.min(320, rawBitrate));
|
|
988
|
+
if (rawBitrate !== this.bitrate) {
|
|
989
|
+
console.warn(`Bitrate value ${rawBitrate} clamped to valid range: ${this.bitrate}`);
|
|
990
|
+
}
|
|
991
|
+
}
|
|
844
992
|
// Verificar se Mp3LameEncoder está disponível
|
|
845
993
|
if (typeof Mp3LameEncoder === 'undefined') {
|
|
846
994
|
throw new Error('Mp3LameEncoder is not loaded. Make sure to load Mp3LameEncoder.min.js before using this encoder.');
|
|
847
995
|
}
|
|
848
|
-
|
|
849
|
-
|
|
996
|
+
try {
|
|
997
|
+
// Criar instância do encoder
|
|
998
|
+
this.encoder = new Mp3LameEncoder(sampleRate, numChannels, this.bitrate);
|
|
999
|
+
}
|
|
1000
|
+
catch (error) {
|
|
1001
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
1002
|
+
throw new Error(`Failed to initialize MP3 encoder: ${errorMsg}. ` +
|
|
1003
|
+
`Parameters: sampleRate=${sampleRate}, numChannels=${numChannels}, bitrate=${this.bitrate}`);
|
|
1004
|
+
}
|
|
850
1005
|
}
|
|
851
1006
|
/**
|
|
852
1007
|
* Codifica buffers de áudio
|
|
@@ -860,7 +1015,60 @@ class Mp3LameEncoderWrapper {
|
|
|
860
1015
|
if (buffers.length !== this.numChannels) {
|
|
861
1016
|
throw new Error(`Expected ${this.numChannels} channels, got ${buffers.length}`);
|
|
862
1017
|
}
|
|
863
|
-
|
|
1018
|
+
// Validar que todos os buffers têm o mesmo tamanho
|
|
1019
|
+
if (buffers.length > 0) {
|
|
1020
|
+
const expectedLength = buffers[0].length;
|
|
1021
|
+
for (let i = 1; i < buffers.length; i++) {
|
|
1022
|
+
if (buffers[i].length !== expectedLength) {
|
|
1023
|
+
throw new Error(`Channel ${i} has length ${buffers[i].length}, expected ${expectedLength}`);
|
|
1024
|
+
}
|
|
1025
|
+
}
|
|
1026
|
+
// Validar que há dados para processar
|
|
1027
|
+
if (expectedLength === 0) {
|
|
1028
|
+
// Buffer vazio, não há nada para codificar
|
|
1029
|
+
return;
|
|
1030
|
+
}
|
|
1031
|
+
}
|
|
1032
|
+
else {
|
|
1033
|
+
// Nenhum buffer fornecido
|
|
1034
|
+
return;
|
|
1035
|
+
}
|
|
1036
|
+
// Criar cópias dos buffers e validar valores (NaN, Infinity)
|
|
1037
|
+
const safeBuffers = buffers.map((buffer, channelIndex) => {
|
|
1038
|
+
const safeBuffer = new Float32Array(buffer.length);
|
|
1039
|
+
let hasInvalidValues = false;
|
|
1040
|
+
for (let i = 0; i < buffer.length; i++) {
|
|
1041
|
+
const value = buffer[i];
|
|
1042
|
+
// Verificar NaN e Infinity
|
|
1043
|
+
if (!Number.isFinite(value)) {
|
|
1044
|
+
hasInvalidValues = true;
|
|
1045
|
+
// Substituir valores inválidos por 0
|
|
1046
|
+
safeBuffer[i] = 0;
|
|
1047
|
+
}
|
|
1048
|
+
else {
|
|
1049
|
+
// Clamp valores para o range válido de áudio (-1.0 a 1.0)
|
|
1050
|
+
safeBuffer[i] = Math.max(-1, Math.min(1.0, value));
|
|
1051
|
+
}
|
|
1052
|
+
}
|
|
1053
|
+
if (hasInvalidValues) {
|
|
1054
|
+
console.warn(`MP3 Encoder: Found invalid values (NaN/Infinity) in channel ${channelIndex}. ` +
|
|
1055
|
+
`Replaced with 0. Buffer length: ${buffer.length}`);
|
|
1056
|
+
}
|
|
1057
|
+
return safeBuffer;
|
|
1058
|
+
});
|
|
1059
|
+
try {
|
|
1060
|
+
this.encoder.encode(safeBuffers);
|
|
1061
|
+
// Contar buffers processados para garantir que há dados antes de finalizar
|
|
1062
|
+
this.bufferCount++;
|
|
1063
|
+
this.totalSamples += safeBuffers[0].length;
|
|
1064
|
+
}
|
|
1065
|
+
catch (error) {
|
|
1066
|
+
// Melhorar mensagem de erro para incluir informações de debug
|
|
1067
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
1068
|
+
throw new Error(`MP3 encoding error: ${errorMsg}. ` +
|
|
1069
|
+
`Buffers: ${buffers.length} channels, lengths: ${buffers.map(b => b.length).join(', ')}, ` +
|
|
1070
|
+
`Total buffers processed: ${this.bufferCount}, Total samples: ${this.totalSamples}`);
|
|
1071
|
+
}
|
|
864
1072
|
}
|
|
865
1073
|
/**
|
|
866
1074
|
* Finaliza o encoding e retorna o Blob MP3
|
|
@@ -872,7 +1080,25 @@ class Mp3LameEncoderWrapper {
|
|
|
872
1080
|
if (!this.encoder) {
|
|
873
1081
|
throw new Error('Encoder is not initialized');
|
|
874
1082
|
}
|
|
875
|
-
|
|
1083
|
+
// Verificar se há dados processados
|
|
1084
|
+
if (this.bufferCount === 0) {
|
|
1085
|
+
console.warn('MP3 Encoder: finish() called but no buffers were encoded. This may cause issues with the Emscripten encoder.');
|
|
1086
|
+
// Ainda tentar finalizar, mas avisar
|
|
1087
|
+
}
|
|
1088
|
+
try {
|
|
1089
|
+
const blob = this.encoder.finish(mimeType);
|
|
1090
|
+
// Validar que o blob não está vazio
|
|
1091
|
+
if (blob.size === 0) {
|
|
1092
|
+
console.warn('MP3 Encoder: finish() returned empty blob. This may indicate insufficient audio data was encoded.');
|
|
1093
|
+
}
|
|
1094
|
+
return blob;
|
|
1095
|
+
}
|
|
1096
|
+
catch (error) {
|
|
1097
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
1098
|
+
throw new Error(`MP3 finish() error: ${errorMsg}. ` +
|
|
1099
|
+
`Buffers processed: ${this.bufferCount}, Total samples: ${this.totalSamples}, ` +
|
|
1100
|
+
`Sample rate: ${this.sampleRate}, Channels: ${this.numChannels}, Bitrate: ${this.bitrate}`);
|
|
1101
|
+
}
|
|
876
1102
|
}
|
|
877
1103
|
/**
|
|
878
1104
|
* Cancela o encoding
|
|
@@ -882,6 +1108,9 @@ class Mp3LameEncoderWrapper {
|
|
|
882
1108
|
this.encoder.cancel();
|
|
883
1109
|
this.encoder = null;
|
|
884
1110
|
}
|
|
1111
|
+
// Reset contadores
|
|
1112
|
+
this.bufferCount = 0;
|
|
1113
|
+
this.totalSamples = 0;
|
|
885
1114
|
}
|
|
886
1115
|
}
|
|
887
1116
|
/**
|
package/dist/index.umd.js
CHANGED
|
@@ -103,14 +103,36 @@
|
|
|
103
103
|
}
|
|
104
104
|
try {
|
|
105
105
|
const inputBuffer = event.inputBuffer;
|
|
106
|
+
const actualChannels = inputBuffer.numberOfChannels;
|
|
106
107
|
const buffers = [];
|
|
107
|
-
// Extrair dados de cada canal
|
|
108
|
-
for (let channel = 0; channel <
|
|
108
|
+
// Extrair dados de cada canal disponível
|
|
109
|
+
for (let channel = 0; channel < actualChannels; channel++) {
|
|
109
110
|
const channelData = inputBuffer.getChannelData(channel);
|
|
110
111
|
buffers.push(new Float32Array(channelData));
|
|
111
112
|
}
|
|
113
|
+
// Se o número de canais do buffer não corresponde ao esperado, ajustar
|
|
114
|
+
if (actualChannels !== this.numChannels) {
|
|
115
|
+
if (actualChannels === 1 && this.numChannels === 2) {
|
|
116
|
+
// Mono -> Estéreo: duplicar o canal
|
|
117
|
+
buffers.push(new Float32Array(buffers[0]));
|
|
118
|
+
}
|
|
119
|
+
else if (actualChannels === 2 && this.numChannels === 1) {
|
|
120
|
+
// Estéreo -> Mono: usar apenas o primeiro canal
|
|
121
|
+
buffers.splice(1);
|
|
122
|
+
}
|
|
123
|
+
else {
|
|
124
|
+
// Outros casos: usar apenas os canais disponíveis até o limite esperado
|
|
125
|
+
while (buffers.length < this.numChannels && buffers.length > 0) {
|
|
126
|
+
// Duplicar o último canal se necessário
|
|
127
|
+
buffers.push(new Float32Array(buffers[buffers.length - 1]));
|
|
128
|
+
}
|
|
129
|
+
if (buffers.length > this.numChannels) {
|
|
130
|
+
buffers.splice(this.numChannels);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
112
134
|
// Codificar os dados
|
|
113
|
-
if (this.encoder) {
|
|
135
|
+
if (this.encoder && buffers.length > 0) {
|
|
114
136
|
this.encoder.encode(buffers);
|
|
115
137
|
}
|
|
116
138
|
}
|
|
@@ -151,6 +173,9 @@
|
|
|
151
173
|
if (!this.encoder) {
|
|
152
174
|
throw new Error('Encoder is not initialized');
|
|
153
175
|
}
|
|
176
|
+
// Aguardar um pouco para garantir que todos os callbacks de áudio foram processados
|
|
177
|
+
// Isso evita condições de corrida onde finish() é chamado antes de todos os buffers serem processados
|
|
178
|
+
await new Promise(resolve => setTimeout(resolve, 50));
|
|
154
179
|
const blob = this.encoder.finish(mimeType);
|
|
155
180
|
const url = URL.createObjectURL(blob);
|
|
156
181
|
const timecode = Date.now() - this.startTime;
|
|
@@ -647,15 +672,43 @@
|
|
|
647
672
|
*/
|
|
648
673
|
constructor(sampleRate, numChannels, options = {}) {
|
|
649
674
|
this.encoder = null;
|
|
675
|
+
this.bufferCount = 0;
|
|
676
|
+
this.totalSamples = 0;
|
|
677
|
+
// Validar parâmetros
|
|
678
|
+
if (!Number.isFinite(sampleRate) || sampleRate <= 0) {
|
|
679
|
+
throw new Error(`Invalid sampleRate: ${sampleRate}. Must be a positive number.`);
|
|
680
|
+
}
|
|
681
|
+
if (!Number.isInteger(numChannels) || numChannels < 1 || numChannels > 2) {
|
|
682
|
+
throw new Error(`Invalid numChannels: ${numChannels}. Must be 1 (mono) or 2 (stereo).`);
|
|
683
|
+
}
|
|
650
684
|
this.sampleRate = sampleRate;
|
|
651
685
|
this.numChannels = numChannels;
|
|
652
|
-
|
|
686
|
+
// Validar e limitar qualidade (-0.1 a 1.0 para Vorbis)
|
|
687
|
+
const rawQuality = options.quality ?? 0.5;
|
|
688
|
+
if (!Number.isFinite(rawQuality)) {
|
|
689
|
+
console.warn(`Invalid quality value: ${rawQuality}. Using default 0.5`);
|
|
690
|
+
this.quality = 0.5;
|
|
691
|
+
}
|
|
692
|
+
else {
|
|
693
|
+
// Clamp quality to valid range
|
|
694
|
+
this.quality = Math.max(-0.1, Math.min(1.0, rawQuality));
|
|
695
|
+
if (rawQuality !== this.quality) {
|
|
696
|
+
console.warn(`Quality value ${rawQuality} clamped to valid range: ${this.quality}`);
|
|
697
|
+
}
|
|
698
|
+
}
|
|
653
699
|
// Verificar se OggVorbisEncoder está disponível
|
|
654
700
|
if (typeof OggVorbisEncoder === 'undefined') {
|
|
655
701
|
throw new Error('OggVorbisEncoder is not loaded. Make sure to load OggVorbisEncoder.min.js before using this encoder.');
|
|
656
702
|
}
|
|
657
|
-
|
|
658
|
-
|
|
703
|
+
try {
|
|
704
|
+
// Criar instância do encoder
|
|
705
|
+
this.encoder = new OggVorbisEncoder(sampleRate, numChannels, this.quality);
|
|
706
|
+
}
|
|
707
|
+
catch (error) {
|
|
708
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
709
|
+
throw new Error(`Failed to initialize OGG encoder: ${errorMsg}. ` +
|
|
710
|
+
`Parameters: sampleRate=${sampleRate}, numChannels=${numChannels}, quality=${this.quality}`);
|
|
711
|
+
}
|
|
659
712
|
}
|
|
660
713
|
/**
|
|
661
714
|
* Codifica buffers de áudio
|
|
@@ -669,7 +722,60 @@
|
|
|
669
722
|
if (buffers.length !== this.numChannels) {
|
|
670
723
|
throw new Error(`Expected ${this.numChannels} channels, got ${buffers.length}`);
|
|
671
724
|
}
|
|
672
|
-
|
|
725
|
+
// Validar que todos os buffers têm o mesmo tamanho
|
|
726
|
+
if (buffers.length > 0) {
|
|
727
|
+
const expectedLength = buffers[0].length;
|
|
728
|
+
for (let i = 1; i < buffers.length; i++) {
|
|
729
|
+
if (buffers[i].length !== expectedLength) {
|
|
730
|
+
throw new Error(`Channel ${i} has length ${buffers[i].length}, expected ${expectedLength}`);
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
// Validar que há dados para processar
|
|
734
|
+
if (expectedLength === 0) {
|
|
735
|
+
// Buffer vazio, não há nada para codificar
|
|
736
|
+
return;
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
else {
|
|
740
|
+
// Nenhum buffer fornecido
|
|
741
|
+
return;
|
|
742
|
+
}
|
|
743
|
+
// Criar cópias dos buffers e validar valores (NaN, Infinity)
|
|
744
|
+
const safeBuffers = buffers.map((buffer, channelIndex) => {
|
|
745
|
+
const safeBuffer = new Float32Array(buffer.length);
|
|
746
|
+
let hasInvalidValues = false;
|
|
747
|
+
for (let i = 0; i < buffer.length; i++) {
|
|
748
|
+
const value = buffer[i];
|
|
749
|
+
// Verificar NaN e Infinity
|
|
750
|
+
if (!Number.isFinite(value)) {
|
|
751
|
+
hasInvalidValues = true;
|
|
752
|
+
// Substituir valores inválidos por 0
|
|
753
|
+
safeBuffer[i] = 0;
|
|
754
|
+
}
|
|
755
|
+
else {
|
|
756
|
+
// Clamp valores para o range válido de áudio (-1.0 a 1.0)
|
|
757
|
+
safeBuffer[i] = Math.max(-1, Math.min(1.0, value));
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
if (hasInvalidValues) {
|
|
761
|
+
console.warn(`OGG Encoder: Found invalid values (NaN/Infinity) in channel ${channelIndex}. ` +
|
|
762
|
+
`Replaced with 0. Buffer length: ${buffer.length}`);
|
|
763
|
+
}
|
|
764
|
+
return safeBuffer;
|
|
765
|
+
});
|
|
766
|
+
try {
|
|
767
|
+
this.encoder.encode(safeBuffers);
|
|
768
|
+
// Contar buffers processados para garantir que há dados antes de finalizar
|
|
769
|
+
this.bufferCount++;
|
|
770
|
+
this.totalSamples += safeBuffers[0].length;
|
|
771
|
+
}
|
|
772
|
+
catch (error) {
|
|
773
|
+
// Melhorar mensagem de erro para incluir informações de debug
|
|
774
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
775
|
+
throw new Error(`OGG encoding error: ${errorMsg}. ` +
|
|
776
|
+
`Buffers: ${buffers.length} channels, lengths: ${buffers.map(b => b.length).join(', ')}, ` +
|
|
777
|
+
`Total buffers processed: ${this.bufferCount}, Total samples: ${this.totalSamples}`);
|
|
778
|
+
}
|
|
673
779
|
}
|
|
674
780
|
/**
|
|
675
781
|
* Finaliza o encoding e retorna o Blob OGG
|
|
@@ -681,7 +787,25 @@
|
|
|
681
787
|
if (!this.encoder) {
|
|
682
788
|
throw new Error('Encoder is not initialized');
|
|
683
789
|
}
|
|
684
|
-
|
|
790
|
+
// Verificar se há dados processados
|
|
791
|
+
if (this.bufferCount === 0) {
|
|
792
|
+
console.warn('OGG Encoder: finish() called but no buffers were encoded. This may cause issues with the Emscripten encoder.');
|
|
793
|
+
// Ainda tentar finalizar, mas avisar
|
|
794
|
+
}
|
|
795
|
+
try {
|
|
796
|
+
const blob = this.encoder.finish(mimeType);
|
|
797
|
+
// Validar que o blob não está vazio
|
|
798
|
+
if (blob.size === 0) {
|
|
799
|
+
console.warn('OGG Encoder: finish() returned empty blob. This may indicate insufficient audio data was encoded.');
|
|
800
|
+
}
|
|
801
|
+
return blob;
|
|
802
|
+
}
|
|
803
|
+
catch (error) {
|
|
804
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
805
|
+
throw new Error(`OGG finish() error: ${errorMsg}. ` +
|
|
806
|
+
`Buffers processed: ${this.bufferCount}, Total samples: ${this.totalSamples}, ` +
|
|
807
|
+
`Sample rate: ${this.sampleRate}, Channels: ${this.numChannels}, Quality: ${this.quality}`);
|
|
808
|
+
}
|
|
685
809
|
}
|
|
686
810
|
/**
|
|
687
811
|
* Cancela o encoding
|
|
@@ -691,6 +815,9 @@
|
|
|
691
815
|
this.encoder.cancel();
|
|
692
816
|
this.encoder = null;
|
|
693
817
|
}
|
|
818
|
+
// Reset contadores
|
|
819
|
+
this.bufferCount = 0;
|
|
820
|
+
this.totalSamples = 0;
|
|
694
821
|
}
|
|
695
822
|
}
|
|
696
823
|
/**
|
|
@@ -845,15 +972,43 @@
|
|
|
845
972
|
*/
|
|
846
973
|
constructor(sampleRate, numChannels, options = {}) {
|
|
847
974
|
this.encoder = null;
|
|
975
|
+
this.bufferCount = 0;
|
|
976
|
+
this.totalSamples = 0;
|
|
977
|
+
// Validar parâmetros
|
|
978
|
+
if (!Number.isFinite(sampleRate) || sampleRate <= 0) {
|
|
979
|
+
throw new Error(`Invalid sampleRate: ${sampleRate}. Must be a positive number.`);
|
|
980
|
+
}
|
|
981
|
+
if (!Number.isInteger(numChannels) || numChannels < 1 || numChannels > 2) {
|
|
982
|
+
throw new Error(`Invalid numChannels: ${numChannels}. Must be 1 (mono) or 2 (stereo).`);
|
|
983
|
+
}
|
|
848
984
|
this.sampleRate = sampleRate;
|
|
849
985
|
this.numChannels = numChannels;
|
|
850
|
-
|
|
986
|
+
// Validar e limitar bitrate (32 a 320 kbps para MP3)
|
|
987
|
+
const rawBitrate = options.bitrate ?? 128;
|
|
988
|
+
if (!Number.isFinite(rawBitrate) || !Number.isInteger(rawBitrate)) {
|
|
989
|
+
console.warn(`Invalid bitrate value: ${rawBitrate}. Using default 128`);
|
|
990
|
+
this.bitrate = 128;
|
|
991
|
+
}
|
|
992
|
+
else {
|
|
993
|
+
// Clamp bitrate to valid range
|
|
994
|
+
this.bitrate = Math.max(32, Math.min(320, rawBitrate));
|
|
995
|
+
if (rawBitrate !== this.bitrate) {
|
|
996
|
+
console.warn(`Bitrate value ${rawBitrate} clamped to valid range: ${this.bitrate}`);
|
|
997
|
+
}
|
|
998
|
+
}
|
|
851
999
|
// Verificar se Mp3LameEncoder está disponível
|
|
852
1000
|
if (typeof Mp3LameEncoder === 'undefined') {
|
|
853
1001
|
throw new Error('Mp3LameEncoder is not loaded. Make sure to load Mp3LameEncoder.min.js before using this encoder.');
|
|
854
1002
|
}
|
|
855
|
-
|
|
856
|
-
|
|
1003
|
+
try {
|
|
1004
|
+
// Criar instância do encoder
|
|
1005
|
+
this.encoder = new Mp3LameEncoder(sampleRate, numChannels, this.bitrate);
|
|
1006
|
+
}
|
|
1007
|
+
catch (error) {
|
|
1008
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
1009
|
+
throw new Error(`Failed to initialize MP3 encoder: ${errorMsg}. ` +
|
|
1010
|
+
`Parameters: sampleRate=${sampleRate}, numChannels=${numChannels}, bitrate=${this.bitrate}`);
|
|
1011
|
+
}
|
|
857
1012
|
}
|
|
858
1013
|
/**
|
|
859
1014
|
* Codifica buffers de áudio
|
|
@@ -867,7 +1022,60 @@
|
|
|
867
1022
|
if (buffers.length !== this.numChannels) {
|
|
868
1023
|
throw new Error(`Expected ${this.numChannels} channels, got ${buffers.length}`);
|
|
869
1024
|
}
|
|
870
|
-
|
|
1025
|
+
// Validar que todos os buffers têm o mesmo tamanho
|
|
1026
|
+
if (buffers.length > 0) {
|
|
1027
|
+
const expectedLength = buffers[0].length;
|
|
1028
|
+
for (let i = 1; i < buffers.length; i++) {
|
|
1029
|
+
if (buffers[i].length !== expectedLength) {
|
|
1030
|
+
throw new Error(`Channel ${i} has length ${buffers[i].length}, expected ${expectedLength}`);
|
|
1031
|
+
}
|
|
1032
|
+
}
|
|
1033
|
+
// Validar que há dados para processar
|
|
1034
|
+
if (expectedLength === 0) {
|
|
1035
|
+
// Buffer vazio, não há nada para codificar
|
|
1036
|
+
return;
|
|
1037
|
+
}
|
|
1038
|
+
}
|
|
1039
|
+
else {
|
|
1040
|
+
// Nenhum buffer fornecido
|
|
1041
|
+
return;
|
|
1042
|
+
}
|
|
1043
|
+
// Criar cópias dos buffers e validar valores (NaN, Infinity)
|
|
1044
|
+
const safeBuffers = buffers.map((buffer, channelIndex) => {
|
|
1045
|
+
const safeBuffer = new Float32Array(buffer.length);
|
|
1046
|
+
let hasInvalidValues = false;
|
|
1047
|
+
for (let i = 0; i < buffer.length; i++) {
|
|
1048
|
+
const value = buffer[i];
|
|
1049
|
+
// Verificar NaN e Infinity
|
|
1050
|
+
if (!Number.isFinite(value)) {
|
|
1051
|
+
hasInvalidValues = true;
|
|
1052
|
+
// Substituir valores inválidos por 0
|
|
1053
|
+
safeBuffer[i] = 0;
|
|
1054
|
+
}
|
|
1055
|
+
else {
|
|
1056
|
+
// Clamp valores para o range válido de áudio (-1.0 a 1.0)
|
|
1057
|
+
safeBuffer[i] = Math.max(-1, Math.min(1.0, value));
|
|
1058
|
+
}
|
|
1059
|
+
}
|
|
1060
|
+
if (hasInvalidValues) {
|
|
1061
|
+
console.warn(`MP3 Encoder: Found invalid values (NaN/Infinity) in channel ${channelIndex}. ` +
|
|
1062
|
+
`Replaced with 0. Buffer length: ${buffer.length}`);
|
|
1063
|
+
}
|
|
1064
|
+
return safeBuffer;
|
|
1065
|
+
});
|
|
1066
|
+
try {
|
|
1067
|
+
this.encoder.encode(safeBuffers);
|
|
1068
|
+
// Contar buffers processados para garantir que há dados antes de finalizar
|
|
1069
|
+
this.bufferCount++;
|
|
1070
|
+
this.totalSamples += safeBuffers[0].length;
|
|
1071
|
+
}
|
|
1072
|
+
catch (error) {
|
|
1073
|
+
// Melhorar mensagem de erro para incluir informações de debug
|
|
1074
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
1075
|
+
throw new Error(`MP3 encoding error: ${errorMsg}. ` +
|
|
1076
|
+
`Buffers: ${buffers.length} channels, lengths: ${buffers.map(b => b.length).join(', ')}, ` +
|
|
1077
|
+
`Total buffers processed: ${this.bufferCount}, Total samples: ${this.totalSamples}`);
|
|
1078
|
+
}
|
|
871
1079
|
}
|
|
872
1080
|
/**
|
|
873
1081
|
* Finaliza o encoding e retorna o Blob MP3
|
|
@@ -879,7 +1087,25 @@
|
|
|
879
1087
|
if (!this.encoder) {
|
|
880
1088
|
throw new Error('Encoder is not initialized');
|
|
881
1089
|
}
|
|
882
|
-
|
|
1090
|
+
// Verificar se há dados processados
|
|
1091
|
+
if (this.bufferCount === 0) {
|
|
1092
|
+
console.warn('MP3 Encoder: finish() called but no buffers were encoded. This may cause issues with the Emscripten encoder.');
|
|
1093
|
+
// Ainda tentar finalizar, mas avisar
|
|
1094
|
+
}
|
|
1095
|
+
try {
|
|
1096
|
+
const blob = this.encoder.finish(mimeType);
|
|
1097
|
+
// Validar que o blob não está vazio
|
|
1098
|
+
if (blob.size === 0) {
|
|
1099
|
+
console.warn('MP3 Encoder: finish() returned empty blob. This may indicate insufficient audio data was encoded.');
|
|
1100
|
+
}
|
|
1101
|
+
return blob;
|
|
1102
|
+
}
|
|
1103
|
+
catch (error) {
|
|
1104
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
1105
|
+
throw new Error(`MP3 finish() error: ${errorMsg}. ` +
|
|
1106
|
+
`Buffers processed: ${this.bufferCount}, Total samples: ${this.totalSamples}, ` +
|
|
1107
|
+
`Sample rate: ${this.sampleRate}, Channels: ${this.numChannels}, Bitrate: ${this.bitrate}`);
|
|
1108
|
+
}
|
|
883
1109
|
}
|
|
884
1110
|
/**
|
|
885
1111
|
* Cancela o encoding
|
|
@@ -889,6 +1115,9 @@
|
|
|
889
1115
|
this.encoder.cancel();
|
|
890
1116
|
this.encoder = null;
|
|
891
1117
|
}
|
|
1118
|
+
// Reset contadores
|
|
1119
|
+
this.bufferCount = 0;
|
|
1120
|
+
this.totalSamples = 0;
|
|
892
1121
|
}
|
|
893
1122
|
}
|
|
894
1123
|
/**
|
package/package.json
CHANGED