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.
@@ -12,6 +12,8 @@ export declare class Mp3LameEncoderWrapper implements AudioEncoder {
12
12
  private sampleRate;
13
13
  private numChannels;
14
14
  private bitrate;
15
+ private bufferCount;
16
+ private totalSamples;
15
17
  /**
16
18
  * Cria uma instância do encoder MP3 LAME
17
19
  *
@@ -12,6 +12,8 @@ export declare class OggVorbisEncoderWrapper implements AudioEncoder {
12
12
  private sampleRate;
13
13
  private numChannels;
14
14
  private quality;
15
+ private bufferCount;
16
+ private totalSamples;
15
17
  /**
16
18
  * Cria uma instância do encoder OGG Vorbis
17
19
  *
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
- this.quality = options.quality ?? 0.5;
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
- // Criar instância do encoder
654
- this.encoder = new OggVorbisEncoder(sampleRate, numChannels, this.quality);
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
- this.encoder.encode(buffers);
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
- return this.encoder.finish(mimeType);
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
- this.bitrate = options.bitrate ?? 128;
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
- // Criar instância do encoder
852
- this.encoder = new Mp3LameEncoder(sampleRate, numChannels, this.bitrate);
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
- this.encoder.encode(buffers);
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
- return this.encoder.finish(mimeType);
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
- this.quality = options.quality ?? 0.5;
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
- // Criar instância do encoder
651
- this.encoder = new OggVorbisEncoder(sampleRate, numChannels, this.quality);
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
- this.encoder.encode(buffers);
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
- return this.encoder.finish(mimeType);
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
- this.bitrate = options.bitrate ?? 128;
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
- // Criar instância do encoder
849
- this.encoder = new Mp3LameEncoder(sampleRate, numChannels, this.bitrate);
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
- this.encoder.encode(buffers);
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
- return this.encoder.finish(mimeType);
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
- this.quality = options.quality ?? 0.5;
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
- // Criar instância do encoder
658
- this.encoder = new OggVorbisEncoder(sampleRate, numChannels, this.quality);
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
- this.encoder.encode(buffers);
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
- return this.encoder.finish(mimeType);
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
- this.bitrate = options.bitrate ?? 128;
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
- // Criar instância do encoder
856
- this.encoder = new Mp3LameEncoder(sampleRate, numChannels, this.bitrate);
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
- this.encoder.encode(buffers);
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
- return this.encoder.finish(mimeType);
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "web-audio-recorder-ts",
3
- "version": "1.0.4",
3
+ "version": "1.0.5",
4
4
  "type": "module",
5
5
  "description": "TypeScript port of web-audio-recorder-js with full type support for WAV, OGG Vorbis, and MP3 audio recording",
6
6
  "main": "dist/index.cjs.js",