web-audio-recorder-ts 1.0.3 → 1.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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;
@@ -549,33 +552,47 @@ function configureEncoderPaths(baseUrl) {
549
552
  * Useful when auto-detection fails
550
553
  */
551
554
  async function findEncoderPath(filename) {
555
+ // Obter base URL atual para construir caminhos relativos
556
+ const currentOrigin = typeof window !== 'undefined' ? window.location.origin : '';
557
+ const currentPath = typeof window !== 'undefined' ? window.location.pathname : '';
552
558
  // Priorizar caminhos do node_modules primeiro
553
559
  const possiblePaths = [
554
- // Primeiro: tentar node_modules (prioridade máxima)
560
+ // Primeiro: tentar node_modules com caminhos absolutos (prioridade máxima)
555
561
  `/node_modules/web-audio-recorder-ts/lib/${filename}`,
562
+ `${currentOrigin}/node_modules/web-audio-recorder-ts/lib/${filename}`,
563
+ // Caminhos relativos do node_modules
556
564
  `./node_modules/web-audio-recorder-ts/lib/${filename}`,
557
565
  `../node_modules/web-audio-recorder-ts/lib/${filename}`,
558
566
  `../../node_modules/web-audio-recorder-ts/lib/${filename}`,
567
+ `../../../node_modules/web-audio-recorder-ts/lib/${filename}`,
568
+ // Com base no caminho atual
569
+ `${currentPath.replace(/\/[^/]*$/, '')}/node_modules/web-audio-recorder-ts/lib/${filename}`,
559
570
  // From dist (se os arquivos foram copiados para dist/lib)
560
571
  `/node_modules/web-audio-recorder-ts/dist/lib/${filename}`,
572
+ `${currentOrigin}/node_modules/web-audio-recorder-ts/dist/lib/${filename}`,
561
573
  `./node_modules/web-audio-recorder-ts/dist/lib/${filename}`,
562
574
  // Auto-detected path (pode apontar para node_modules)
563
575
  getEncoderScriptUrl(filename),
564
576
  // Para desenvolvimento/demo: try public folder (Vite serves public/ at root)
565
577
  `/${filename}`,
578
+ `${currentOrigin}/${filename}`,
566
579
  // Direct lib paths (for development or custom setups)
567
580
  `/lib/${filename}`,
581
+ `${currentOrigin}/lib/${filename}`,
568
582
  `./lib/${filename}`,
569
583
  `../lib/${filename}`,
570
584
  // CDN or absolute paths (if configured)
571
585
  filename.startsWith('http') ? filename : null
572
586
  ].filter((path) => path !== null);
587
+ console.log(`[web-audio-recorder-ts] Searching for ${filename} in ${possiblePaths.length} possible paths...`);
573
588
  // Try each path
574
- for (const path of possiblePaths) {
589
+ for (let i = 0; i < possiblePaths.length; i++) {
590
+ const path = possiblePaths[i];
575
591
  try {
576
592
  const testUrl = path.startsWith('http')
577
593
  ? path
578
594
  : new URL(path, typeof window !== 'undefined' ? window.location.href : 'file://').href;
595
+ console.log(`[web-audio-recorder-ts] Trying path ${i + 1}/${possiblePaths.length}: ${path} -> ${testUrl}`);
579
596
  // Usar GET para verificar se é JavaScript válido (não HTML)
580
597
  const response = await fetch(testUrl, { method: 'GET', cache: 'no-cache' });
581
598
  if (response.ok) {
@@ -584,21 +601,30 @@ async function findEncoderPath(filename) {
584
601
  const trimmedText = text.trim();
585
602
  // Se começar com '<', é HTML (404, etc) - pular este caminho
586
603
  if (trimmedText.startsWith('<')) {
587
- console.warn(`Path ${path} returned HTML instead of JavaScript, skipping...`);
604
+ console.warn(`[web-audio-recorder-ts] ❌ Path ${path} returned HTML instead of JavaScript (likely 404), skipping...`);
588
605
  continue;
589
606
  }
590
607
  // Se parece JavaScript, retornar este caminho
591
608
  if (trimmedText.includes('function') || trimmedText.includes('var') || trimmedText.includes('const') || trimmedText.includes('let') || trimmedText.length > 100) {
592
- console.log(`✅ Found encoder file at: ${path}`);
609
+ console.log(`[web-audio-recorder-ts] Found encoder file at: ${path}`);
593
610
  return path;
594
611
  }
612
+ else {
613
+ console.warn(`[web-audio-recorder-ts] ⚠️ Path ${path} returned content but doesn't look like JavaScript`);
614
+ }
615
+ }
616
+ else {
617
+ console.warn(`[web-audio-recorder-ts] ❌ Path ${path} returned status ${response.status} ${response.statusText}`);
595
618
  }
596
619
  }
597
620
  catch (e) {
621
+ console.warn(`[web-audio-recorder-ts] ❌ Error testing path ${path}:`, e);
598
622
  // Continue to next path
599
623
  continue;
600
624
  }
601
625
  }
626
+ console.error(`[web-audio-recorder-ts] ❌ Could not find ${filename} in any of the ${possiblePaths.length} paths tried`);
627
+ console.error(`[web-audio-recorder-ts] Tried paths:`, possiblePaths);
602
628
  return null;
603
629
  }
604
630
 
@@ -620,15 +646,43 @@ class OggVorbisEncoderWrapper {
620
646
  */
621
647
  constructor(sampleRate, numChannels, options = {}) {
622
648
  this.encoder = null;
649
+ this.bufferCount = 0;
650
+ this.totalSamples = 0;
651
+ // Validar parâmetros
652
+ if (!Number.isFinite(sampleRate) || sampleRate <= 0) {
653
+ throw new Error(`Invalid sampleRate: ${sampleRate}. Must be a positive number.`);
654
+ }
655
+ if (!Number.isInteger(numChannels) || numChannels < 1 || numChannels > 2) {
656
+ throw new Error(`Invalid numChannels: ${numChannels}. Must be 1 (mono) or 2 (stereo).`);
657
+ }
623
658
  this.sampleRate = sampleRate;
624
659
  this.numChannels = numChannels;
625
- 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
+ }
626
673
  // Verificar se OggVorbisEncoder está disponível
627
674
  if (typeof OggVorbisEncoder === 'undefined') {
628
675
  throw new Error('OggVorbisEncoder is not loaded. Make sure to load OggVorbisEncoder.min.js before using this encoder.');
629
676
  }
630
- // Criar instância do encoder
631
- 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
+ }
632
686
  }
633
687
  /**
634
688
  * Codifica buffers de áudio
@@ -642,7 +696,60 @@ class OggVorbisEncoderWrapper {
642
696
  if (buffers.length !== this.numChannels) {
643
697
  throw new Error(`Expected ${this.numChannels} channels, got ${buffers.length}`);
644
698
  }
645
- 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
+ }
646
753
  }
647
754
  /**
648
755
  * Finaliza o encoding e retorna o Blob OGG
@@ -654,7 +761,25 @@ class OggVorbisEncoderWrapper {
654
761
  if (!this.encoder) {
655
762
  throw new Error('Encoder is not initialized');
656
763
  }
657
- 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
+ }
658
783
  }
659
784
  /**
660
785
  * Cancela o encoding
@@ -664,6 +789,9 @@ class OggVorbisEncoderWrapper {
664
789
  this.encoder.cancel();
665
790
  this.encoder = null;
666
791
  }
792
+ // Reset contadores
793
+ this.bufferCount = 0;
794
+ this.totalSamples = 0;
667
795
  }
668
796
  }
669
797
  /**
@@ -680,8 +808,14 @@ async function loadOggVorbisEncoder(scriptUrl) {
680
808
  // Tentar encontrar o arquivo automaticamente
681
809
  const foundPath = await findEncoderPath('OggVorbisEncoder.min.js');
682
810
  if (!foundPath) {
683
- throw new Error('Could not find OggVorbisEncoder.min.js. ' +
684
- 'Please provide the path manually or ensure the package is installed correctly.');
811
+ const errorMsg = 'Could not find OggVorbisEncoder.min.js automatically.\n\n' +
812
+ 'Please try one of the following:\n' +
813
+ '1. Provide the path manually: await loadOggVorbisEncoder("/path/to/OggVorbisEncoder.min.js")\n' +
814
+ '2. Copy files to public/: cp node_modules/web-audio-recorder-ts/lib/*.js public/\n' +
815
+ '3. Configure server to serve node_modules (see NUXT_USAGE.md)\n' +
816
+ '4. Check browser console for detailed path information';
817
+ console.error(errorMsg);
818
+ throw new Error(errorMsg);
685
819
  }
686
820
  scriptUrl = foundPath;
687
821
  }
@@ -812,15 +946,43 @@ class Mp3LameEncoderWrapper {
812
946
  */
813
947
  constructor(sampleRate, numChannels, options = {}) {
814
948
  this.encoder = null;
949
+ this.bufferCount = 0;
950
+ this.totalSamples = 0;
951
+ // Validar parâmetros
952
+ if (!Number.isFinite(sampleRate) || sampleRate <= 0) {
953
+ throw new Error(`Invalid sampleRate: ${sampleRate}. Must be a positive number.`);
954
+ }
955
+ if (!Number.isInteger(numChannels) || numChannels < 1 || numChannels > 2) {
956
+ throw new Error(`Invalid numChannels: ${numChannels}. Must be 1 (mono) or 2 (stereo).`);
957
+ }
815
958
  this.sampleRate = sampleRate;
816
959
  this.numChannels = numChannels;
817
- 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
+ }
818
973
  // Verificar se Mp3LameEncoder está disponível
819
974
  if (typeof Mp3LameEncoder === 'undefined') {
820
975
  throw new Error('Mp3LameEncoder is not loaded. Make sure to load Mp3LameEncoder.min.js before using this encoder.');
821
976
  }
822
- // Criar instância do encoder
823
- 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
+ }
824
986
  }
825
987
  /**
826
988
  * Codifica buffers de áudio
@@ -834,7 +996,60 @@ class Mp3LameEncoderWrapper {
834
996
  if (buffers.length !== this.numChannels) {
835
997
  throw new Error(`Expected ${this.numChannels} channels, got ${buffers.length}`);
836
998
  }
837
- 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
+ }
838
1053
  }
839
1054
  /**
840
1055
  * Finaliza o encoding e retorna o Blob MP3
@@ -846,7 +1061,25 @@ class Mp3LameEncoderWrapper {
846
1061
  if (!this.encoder) {
847
1062
  throw new Error('Encoder is not initialized');
848
1063
  }
849
- 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
+ }
850
1083
  }
851
1084
  /**
852
1085
  * Cancela o encoding
@@ -856,6 +1089,9 @@ class Mp3LameEncoderWrapper {
856
1089
  this.encoder.cancel();
857
1090
  this.encoder = null;
858
1091
  }
1092
+ // Reset contadores
1093
+ this.bufferCount = 0;
1094
+ this.totalSamples = 0;
859
1095
  }
860
1096
  }
861
1097
  /**
@@ -872,8 +1108,14 @@ async function loadMp3LameEncoder(scriptUrl) {
872
1108
  // Tentar encontrar o arquivo automaticamente
873
1109
  const foundPath = await findEncoderPath('Mp3LameEncoder.min.js');
874
1110
  if (!foundPath) {
875
- throw new Error('Could not find Mp3LameEncoder.min.js. ' +
876
- 'Please provide the path manually or ensure the package is installed correctly.');
1111
+ const errorMsg = 'Could not find Mp3LameEncoder.min.js automatically.\n\n' +
1112
+ 'Please try one of the following:\n' +
1113
+ '1. Provide the path manually: await loadMp3LameEncoder("/path/to/Mp3LameEncoder.min.js")\n' +
1114
+ '2. Copy files to public/: cp node_modules/web-audio-recorder-ts/lib/*.js public/\n' +
1115
+ '3. Configure server to serve node_modules (see NUXT_USAGE.md)\n' +
1116
+ '4. Check browser console for detailed path information';
1117
+ console.error(errorMsg);
1118
+ throw new Error(errorMsg);
877
1119
  }
878
1120
  scriptUrl = foundPath;
879
1121
  }
package/dist/index.esm.js CHANGED
@@ -144,6 +144,9 @@ class WebAudioRecorder {
144
144
  if (!this.encoder) {
145
145
  throw new Error('Encoder is not initialized');
146
146
  }
147
+ // Aguardar um pouco para garantir que todos os callbacks de áudio foram processados
148
+ // Isso evita condições de corrida onde finish() é chamado antes de todos os buffers serem processados
149
+ await new Promise(resolve => setTimeout(resolve, 50));
147
150
  const blob = this.encoder.finish(mimeType);
148
151
  const url = URL.createObjectURL(blob);
149
152
  const timecode = Date.now() - this.startTime;
@@ -546,33 +549,47 @@ function configureEncoderPaths(baseUrl) {
546
549
  * Useful when auto-detection fails
547
550
  */
548
551
  async function findEncoderPath(filename) {
552
+ // Obter base URL atual para construir caminhos relativos
553
+ const currentOrigin = typeof window !== 'undefined' ? window.location.origin : '';
554
+ const currentPath = typeof window !== 'undefined' ? window.location.pathname : '';
549
555
  // Priorizar caminhos do node_modules primeiro
550
556
  const possiblePaths = [
551
- // Primeiro: tentar node_modules (prioridade máxima)
557
+ // Primeiro: tentar node_modules com caminhos absolutos (prioridade máxima)
552
558
  `/node_modules/web-audio-recorder-ts/lib/${filename}`,
559
+ `${currentOrigin}/node_modules/web-audio-recorder-ts/lib/${filename}`,
560
+ // Caminhos relativos do node_modules
553
561
  `./node_modules/web-audio-recorder-ts/lib/${filename}`,
554
562
  `../node_modules/web-audio-recorder-ts/lib/${filename}`,
555
563
  `../../node_modules/web-audio-recorder-ts/lib/${filename}`,
564
+ `../../../node_modules/web-audio-recorder-ts/lib/${filename}`,
565
+ // Com base no caminho atual
566
+ `${currentPath.replace(/\/[^/]*$/, '')}/node_modules/web-audio-recorder-ts/lib/${filename}`,
556
567
  // From dist (se os arquivos foram copiados para dist/lib)
557
568
  `/node_modules/web-audio-recorder-ts/dist/lib/${filename}`,
569
+ `${currentOrigin}/node_modules/web-audio-recorder-ts/dist/lib/${filename}`,
558
570
  `./node_modules/web-audio-recorder-ts/dist/lib/${filename}`,
559
571
  // Auto-detected path (pode apontar para node_modules)
560
572
  getEncoderScriptUrl(filename),
561
573
  // Para desenvolvimento/demo: try public folder (Vite serves public/ at root)
562
574
  `/${filename}`,
575
+ `${currentOrigin}/${filename}`,
563
576
  // Direct lib paths (for development or custom setups)
564
577
  `/lib/${filename}`,
578
+ `${currentOrigin}/lib/${filename}`,
565
579
  `./lib/${filename}`,
566
580
  `../lib/${filename}`,
567
581
  // CDN or absolute paths (if configured)
568
582
  filename.startsWith('http') ? filename : null
569
583
  ].filter((path) => path !== null);
584
+ console.log(`[web-audio-recorder-ts] Searching for ${filename} in ${possiblePaths.length} possible paths...`);
570
585
  // Try each path
571
- for (const path of possiblePaths) {
586
+ for (let i = 0; i < possiblePaths.length; i++) {
587
+ const path = possiblePaths[i];
572
588
  try {
573
589
  const testUrl = path.startsWith('http')
574
590
  ? path
575
591
  : new URL(path, typeof window !== 'undefined' ? window.location.href : 'file://').href;
592
+ console.log(`[web-audio-recorder-ts] Trying path ${i + 1}/${possiblePaths.length}: ${path} -> ${testUrl}`);
576
593
  // Usar GET para verificar se é JavaScript válido (não HTML)
577
594
  const response = await fetch(testUrl, { method: 'GET', cache: 'no-cache' });
578
595
  if (response.ok) {
@@ -581,21 +598,30 @@ async function findEncoderPath(filename) {
581
598
  const trimmedText = text.trim();
582
599
  // Se começar com '<', é HTML (404, etc) - pular este caminho
583
600
  if (trimmedText.startsWith('<')) {
584
- console.warn(`Path ${path} returned HTML instead of JavaScript, skipping...`);
601
+ console.warn(`[web-audio-recorder-ts] ❌ Path ${path} returned HTML instead of JavaScript (likely 404), skipping...`);
585
602
  continue;
586
603
  }
587
604
  // Se parece JavaScript, retornar este caminho
588
605
  if (trimmedText.includes('function') || trimmedText.includes('var') || trimmedText.includes('const') || trimmedText.includes('let') || trimmedText.length > 100) {
589
- console.log(`✅ Found encoder file at: ${path}`);
606
+ console.log(`[web-audio-recorder-ts] Found encoder file at: ${path}`);
590
607
  return path;
591
608
  }
609
+ else {
610
+ console.warn(`[web-audio-recorder-ts] ⚠️ Path ${path} returned content but doesn't look like JavaScript`);
611
+ }
612
+ }
613
+ else {
614
+ console.warn(`[web-audio-recorder-ts] ❌ Path ${path} returned status ${response.status} ${response.statusText}`);
592
615
  }
593
616
  }
594
617
  catch (e) {
618
+ console.warn(`[web-audio-recorder-ts] ❌ Error testing path ${path}:`, e);
595
619
  // Continue to next path
596
620
  continue;
597
621
  }
598
622
  }
623
+ console.error(`[web-audio-recorder-ts] ❌ Could not find ${filename} in any of the ${possiblePaths.length} paths tried`);
624
+ console.error(`[web-audio-recorder-ts] Tried paths:`, possiblePaths);
599
625
  return null;
600
626
  }
601
627
 
@@ -617,15 +643,43 @@ class OggVorbisEncoderWrapper {
617
643
  */
618
644
  constructor(sampleRate, numChannels, options = {}) {
619
645
  this.encoder = null;
646
+ this.bufferCount = 0;
647
+ this.totalSamples = 0;
648
+ // Validar parâmetros
649
+ if (!Number.isFinite(sampleRate) || sampleRate <= 0) {
650
+ throw new Error(`Invalid sampleRate: ${sampleRate}. Must be a positive number.`);
651
+ }
652
+ if (!Number.isInteger(numChannels) || numChannels < 1 || numChannels > 2) {
653
+ throw new Error(`Invalid numChannels: ${numChannels}. Must be 1 (mono) or 2 (stereo).`);
654
+ }
620
655
  this.sampleRate = sampleRate;
621
656
  this.numChannels = numChannels;
622
- 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
+ }
623
670
  // Verificar se OggVorbisEncoder está disponível
624
671
  if (typeof OggVorbisEncoder === 'undefined') {
625
672
  throw new Error('OggVorbisEncoder is not loaded. Make sure to load OggVorbisEncoder.min.js before using this encoder.');
626
673
  }
627
- // Criar instância do encoder
628
- 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
+ }
629
683
  }
630
684
  /**
631
685
  * Codifica buffers de áudio
@@ -639,7 +693,60 @@ class OggVorbisEncoderWrapper {
639
693
  if (buffers.length !== this.numChannels) {
640
694
  throw new Error(`Expected ${this.numChannels} channels, got ${buffers.length}`);
641
695
  }
642
- 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
+ }
643
750
  }
644
751
  /**
645
752
  * Finaliza o encoding e retorna o Blob OGG
@@ -651,7 +758,25 @@ class OggVorbisEncoderWrapper {
651
758
  if (!this.encoder) {
652
759
  throw new Error('Encoder is not initialized');
653
760
  }
654
- 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
+ }
655
780
  }
656
781
  /**
657
782
  * Cancela o encoding
@@ -661,6 +786,9 @@ class OggVorbisEncoderWrapper {
661
786
  this.encoder.cancel();
662
787
  this.encoder = null;
663
788
  }
789
+ // Reset contadores
790
+ this.bufferCount = 0;
791
+ this.totalSamples = 0;
664
792
  }
665
793
  }
666
794
  /**
@@ -677,8 +805,14 @@ async function loadOggVorbisEncoder(scriptUrl) {
677
805
  // Tentar encontrar o arquivo automaticamente
678
806
  const foundPath = await findEncoderPath('OggVorbisEncoder.min.js');
679
807
  if (!foundPath) {
680
- throw new Error('Could not find OggVorbisEncoder.min.js. ' +
681
- 'Please provide the path manually or ensure the package is installed correctly.');
808
+ const errorMsg = 'Could not find OggVorbisEncoder.min.js automatically.\n\n' +
809
+ 'Please try one of the following:\n' +
810
+ '1. Provide the path manually: await loadOggVorbisEncoder("/path/to/OggVorbisEncoder.min.js")\n' +
811
+ '2. Copy files to public/: cp node_modules/web-audio-recorder-ts/lib/*.js public/\n' +
812
+ '3. Configure server to serve node_modules (see NUXT_USAGE.md)\n' +
813
+ '4. Check browser console for detailed path information';
814
+ console.error(errorMsg);
815
+ throw new Error(errorMsg);
682
816
  }
683
817
  scriptUrl = foundPath;
684
818
  }
@@ -809,15 +943,43 @@ class Mp3LameEncoderWrapper {
809
943
  */
810
944
  constructor(sampleRate, numChannels, options = {}) {
811
945
  this.encoder = null;
946
+ this.bufferCount = 0;
947
+ this.totalSamples = 0;
948
+ // Validar parâmetros
949
+ if (!Number.isFinite(sampleRate) || sampleRate <= 0) {
950
+ throw new Error(`Invalid sampleRate: ${sampleRate}. Must be a positive number.`);
951
+ }
952
+ if (!Number.isInteger(numChannels) || numChannels < 1 || numChannels > 2) {
953
+ throw new Error(`Invalid numChannels: ${numChannels}. Must be 1 (mono) or 2 (stereo).`);
954
+ }
812
955
  this.sampleRate = sampleRate;
813
956
  this.numChannels = numChannels;
814
- 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
+ }
815
970
  // Verificar se Mp3LameEncoder está disponível
816
971
  if (typeof Mp3LameEncoder === 'undefined') {
817
972
  throw new Error('Mp3LameEncoder is not loaded. Make sure to load Mp3LameEncoder.min.js before using this encoder.');
818
973
  }
819
- // Criar instância do encoder
820
- 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
+ }
821
983
  }
822
984
  /**
823
985
  * Codifica buffers de áudio
@@ -831,7 +993,60 @@ class Mp3LameEncoderWrapper {
831
993
  if (buffers.length !== this.numChannels) {
832
994
  throw new Error(`Expected ${this.numChannels} channels, got ${buffers.length}`);
833
995
  }
834
- 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
+ }
835
1050
  }
836
1051
  /**
837
1052
  * Finaliza o encoding e retorna o Blob MP3
@@ -843,7 +1058,25 @@ class Mp3LameEncoderWrapper {
843
1058
  if (!this.encoder) {
844
1059
  throw new Error('Encoder is not initialized');
845
1060
  }
846
- 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
+ }
847
1080
  }
848
1081
  /**
849
1082
  * Cancela o encoding
@@ -853,6 +1086,9 @@ class Mp3LameEncoderWrapper {
853
1086
  this.encoder.cancel();
854
1087
  this.encoder = null;
855
1088
  }
1089
+ // Reset contadores
1090
+ this.bufferCount = 0;
1091
+ this.totalSamples = 0;
856
1092
  }
857
1093
  }
858
1094
  /**
@@ -869,8 +1105,14 @@ async function loadMp3LameEncoder(scriptUrl) {
869
1105
  // Tentar encontrar o arquivo automaticamente
870
1106
  const foundPath = await findEncoderPath('Mp3LameEncoder.min.js');
871
1107
  if (!foundPath) {
872
- throw new Error('Could not find Mp3LameEncoder.min.js. ' +
873
- 'Please provide the path manually or ensure the package is installed correctly.');
1108
+ const errorMsg = 'Could not find Mp3LameEncoder.min.js automatically.\n\n' +
1109
+ 'Please try one of the following:\n' +
1110
+ '1. Provide the path manually: await loadMp3LameEncoder("/path/to/Mp3LameEncoder.min.js")\n' +
1111
+ '2. Copy files to public/: cp node_modules/web-audio-recorder-ts/lib/*.js public/\n' +
1112
+ '3. Configure server to serve node_modules (see NUXT_USAGE.md)\n' +
1113
+ '4. Check browser console for detailed path information';
1114
+ console.error(errorMsg);
1115
+ throw new Error(errorMsg);
874
1116
  }
875
1117
  scriptUrl = foundPath;
876
1118
  }
package/dist/index.umd.js CHANGED
@@ -151,6 +151,9 @@
151
151
  if (!this.encoder) {
152
152
  throw new Error('Encoder is not initialized');
153
153
  }
154
+ // Aguardar um pouco para garantir que todos os callbacks de áudio foram processados
155
+ // Isso evita condições de corrida onde finish() é chamado antes de todos os buffers serem processados
156
+ await new Promise(resolve => setTimeout(resolve, 50));
154
157
  const blob = this.encoder.finish(mimeType);
155
158
  const url = URL.createObjectURL(blob);
156
159
  const timecode = Date.now() - this.startTime;
@@ -553,33 +556,47 @@
553
556
  * Useful when auto-detection fails
554
557
  */
555
558
  async function findEncoderPath(filename) {
559
+ // Obter base URL atual para construir caminhos relativos
560
+ const currentOrigin = typeof window !== 'undefined' ? window.location.origin : '';
561
+ const currentPath = typeof window !== 'undefined' ? window.location.pathname : '';
556
562
  // Priorizar caminhos do node_modules primeiro
557
563
  const possiblePaths = [
558
- // Primeiro: tentar node_modules (prioridade máxima)
564
+ // Primeiro: tentar node_modules com caminhos absolutos (prioridade máxima)
559
565
  `/node_modules/web-audio-recorder-ts/lib/${filename}`,
566
+ `${currentOrigin}/node_modules/web-audio-recorder-ts/lib/${filename}`,
567
+ // Caminhos relativos do node_modules
560
568
  `./node_modules/web-audio-recorder-ts/lib/${filename}`,
561
569
  `../node_modules/web-audio-recorder-ts/lib/${filename}`,
562
570
  `../../node_modules/web-audio-recorder-ts/lib/${filename}`,
571
+ `../../../node_modules/web-audio-recorder-ts/lib/${filename}`,
572
+ // Com base no caminho atual
573
+ `${currentPath.replace(/\/[^/]*$/, '')}/node_modules/web-audio-recorder-ts/lib/${filename}`,
563
574
  // From dist (se os arquivos foram copiados para dist/lib)
564
575
  `/node_modules/web-audio-recorder-ts/dist/lib/${filename}`,
576
+ `${currentOrigin}/node_modules/web-audio-recorder-ts/dist/lib/${filename}`,
565
577
  `./node_modules/web-audio-recorder-ts/dist/lib/${filename}`,
566
578
  // Auto-detected path (pode apontar para node_modules)
567
579
  getEncoderScriptUrl(filename),
568
580
  // Para desenvolvimento/demo: try public folder (Vite serves public/ at root)
569
581
  `/${filename}`,
582
+ `${currentOrigin}/${filename}`,
570
583
  // Direct lib paths (for development or custom setups)
571
584
  `/lib/${filename}`,
585
+ `${currentOrigin}/lib/${filename}`,
572
586
  `./lib/${filename}`,
573
587
  `../lib/${filename}`,
574
588
  // CDN or absolute paths (if configured)
575
589
  filename.startsWith('http') ? filename : null
576
590
  ].filter((path) => path !== null);
591
+ console.log(`[web-audio-recorder-ts] Searching for ${filename} in ${possiblePaths.length} possible paths...`);
577
592
  // Try each path
578
- for (const path of possiblePaths) {
593
+ for (let i = 0; i < possiblePaths.length; i++) {
594
+ const path = possiblePaths[i];
579
595
  try {
580
596
  const testUrl = path.startsWith('http')
581
597
  ? path
582
598
  : new URL(path, typeof window !== 'undefined' ? window.location.href : 'file://').href;
599
+ console.log(`[web-audio-recorder-ts] Trying path ${i + 1}/${possiblePaths.length}: ${path} -> ${testUrl}`);
583
600
  // Usar GET para verificar se é JavaScript válido (não HTML)
584
601
  const response = await fetch(testUrl, { method: 'GET', cache: 'no-cache' });
585
602
  if (response.ok) {
@@ -588,21 +605,30 @@
588
605
  const trimmedText = text.trim();
589
606
  // Se começar com '<', é HTML (404, etc) - pular este caminho
590
607
  if (trimmedText.startsWith('<')) {
591
- console.warn(`Path ${path} returned HTML instead of JavaScript, skipping...`);
608
+ console.warn(`[web-audio-recorder-ts] ❌ Path ${path} returned HTML instead of JavaScript (likely 404), skipping...`);
592
609
  continue;
593
610
  }
594
611
  // Se parece JavaScript, retornar este caminho
595
612
  if (trimmedText.includes('function') || trimmedText.includes('var') || trimmedText.includes('const') || trimmedText.includes('let') || trimmedText.length > 100) {
596
- console.log(`✅ Found encoder file at: ${path}`);
613
+ console.log(`[web-audio-recorder-ts] Found encoder file at: ${path}`);
597
614
  return path;
598
615
  }
616
+ else {
617
+ console.warn(`[web-audio-recorder-ts] ⚠️ Path ${path} returned content but doesn't look like JavaScript`);
618
+ }
619
+ }
620
+ else {
621
+ console.warn(`[web-audio-recorder-ts] ❌ Path ${path} returned status ${response.status} ${response.statusText}`);
599
622
  }
600
623
  }
601
624
  catch (e) {
625
+ console.warn(`[web-audio-recorder-ts] ❌ Error testing path ${path}:`, e);
602
626
  // Continue to next path
603
627
  continue;
604
628
  }
605
629
  }
630
+ console.error(`[web-audio-recorder-ts] ❌ Could not find ${filename} in any of the ${possiblePaths.length} paths tried`);
631
+ console.error(`[web-audio-recorder-ts] Tried paths:`, possiblePaths);
606
632
  return null;
607
633
  }
608
634
 
@@ -624,15 +650,43 @@
624
650
  */
625
651
  constructor(sampleRate, numChannels, options = {}) {
626
652
  this.encoder = null;
653
+ this.bufferCount = 0;
654
+ this.totalSamples = 0;
655
+ // Validar parâmetros
656
+ if (!Number.isFinite(sampleRate) || sampleRate <= 0) {
657
+ throw new Error(`Invalid sampleRate: ${sampleRate}. Must be a positive number.`);
658
+ }
659
+ if (!Number.isInteger(numChannels) || numChannels < 1 || numChannels > 2) {
660
+ throw new Error(`Invalid numChannels: ${numChannels}. Must be 1 (mono) or 2 (stereo).`);
661
+ }
627
662
  this.sampleRate = sampleRate;
628
663
  this.numChannels = numChannels;
629
- 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
+ }
630
677
  // Verificar se OggVorbisEncoder está disponível
631
678
  if (typeof OggVorbisEncoder === 'undefined') {
632
679
  throw new Error('OggVorbisEncoder is not loaded. Make sure to load OggVorbisEncoder.min.js before using this encoder.');
633
680
  }
634
- // Criar instância do encoder
635
- 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
+ }
636
690
  }
637
691
  /**
638
692
  * Codifica buffers de áudio
@@ -646,7 +700,60 @@
646
700
  if (buffers.length !== this.numChannels) {
647
701
  throw new Error(`Expected ${this.numChannels} channels, got ${buffers.length}`);
648
702
  }
649
- 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
+ }
650
757
  }
651
758
  /**
652
759
  * Finaliza o encoding e retorna o Blob OGG
@@ -658,7 +765,25 @@
658
765
  if (!this.encoder) {
659
766
  throw new Error('Encoder is not initialized');
660
767
  }
661
- 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
+ }
662
787
  }
663
788
  /**
664
789
  * Cancela o encoding
@@ -668,6 +793,9 @@
668
793
  this.encoder.cancel();
669
794
  this.encoder = null;
670
795
  }
796
+ // Reset contadores
797
+ this.bufferCount = 0;
798
+ this.totalSamples = 0;
671
799
  }
672
800
  }
673
801
  /**
@@ -684,8 +812,14 @@
684
812
  // Tentar encontrar o arquivo automaticamente
685
813
  const foundPath = await findEncoderPath('OggVorbisEncoder.min.js');
686
814
  if (!foundPath) {
687
- throw new Error('Could not find OggVorbisEncoder.min.js. ' +
688
- 'Please provide the path manually or ensure the package is installed correctly.');
815
+ const errorMsg = 'Could not find OggVorbisEncoder.min.js automatically.\n\n' +
816
+ 'Please try one of the following:\n' +
817
+ '1. Provide the path manually: await loadOggVorbisEncoder("/path/to/OggVorbisEncoder.min.js")\n' +
818
+ '2. Copy files to public/: cp node_modules/web-audio-recorder-ts/lib/*.js public/\n' +
819
+ '3. Configure server to serve node_modules (see NUXT_USAGE.md)\n' +
820
+ '4. Check browser console for detailed path information';
821
+ console.error(errorMsg);
822
+ throw new Error(errorMsg);
689
823
  }
690
824
  scriptUrl = foundPath;
691
825
  }
@@ -816,15 +950,43 @@
816
950
  */
817
951
  constructor(sampleRate, numChannels, options = {}) {
818
952
  this.encoder = null;
953
+ this.bufferCount = 0;
954
+ this.totalSamples = 0;
955
+ // Validar parâmetros
956
+ if (!Number.isFinite(sampleRate) || sampleRate <= 0) {
957
+ throw new Error(`Invalid sampleRate: ${sampleRate}. Must be a positive number.`);
958
+ }
959
+ if (!Number.isInteger(numChannels) || numChannels < 1 || numChannels > 2) {
960
+ throw new Error(`Invalid numChannels: ${numChannels}. Must be 1 (mono) or 2 (stereo).`);
961
+ }
819
962
  this.sampleRate = sampleRate;
820
963
  this.numChannels = numChannels;
821
- 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
+ }
822
977
  // Verificar se Mp3LameEncoder está disponível
823
978
  if (typeof Mp3LameEncoder === 'undefined') {
824
979
  throw new Error('Mp3LameEncoder is not loaded. Make sure to load Mp3LameEncoder.min.js before using this encoder.');
825
980
  }
826
- // Criar instância do encoder
827
- 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
+ }
828
990
  }
829
991
  /**
830
992
  * Codifica buffers de áudio
@@ -838,7 +1000,60 @@
838
1000
  if (buffers.length !== this.numChannels) {
839
1001
  throw new Error(`Expected ${this.numChannels} channels, got ${buffers.length}`);
840
1002
  }
841
- 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
+ }
842
1057
  }
843
1058
  /**
844
1059
  * Finaliza o encoding e retorna o Blob MP3
@@ -850,7 +1065,25 @@
850
1065
  if (!this.encoder) {
851
1066
  throw new Error('Encoder is not initialized');
852
1067
  }
853
- 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
+ }
854
1087
  }
855
1088
  /**
856
1089
  * Cancela o encoding
@@ -860,6 +1093,9 @@
860
1093
  this.encoder.cancel();
861
1094
  this.encoder = null;
862
1095
  }
1096
+ // Reset contadores
1097
+ this.bufferCount = 0;
1098
+ this.totalSamples = 0;
863
1099
  }
864
1100
  }
865
1101
  /**
@@ -876,8 +1112,14 @@
876
1112
  // Tentar encontrar o arquivo automaticamente
877
1113
  const foundPath = await findEncoderPath('Mp3LameEncoder.min.js');
878
1114
  if (!foundPath) {
879
- throw new Error('Could not find Mp3LameEncoder.min.js. ' +
880
- 'Please provide the path manually or ensure the package is installed correctly.');
1115
+ const errorMsg = 'Could not find Mp3LameEncoder.min.js automatically.\n\n' +
1116
+ 'Please try one of the following:\n' +
1117
+ '1. Provide the path manually: await loadMp3LameEncoder("/path/to/Mp3LameEncoder.min.js")\n' +
1118
+ '2. Copy files to public/: cp node_modules/web-audio-recorder-ts/lib/*.js public/\n' +
1119
+ '3. Configure server to serve node_modules (see NUXT_USAGE.md)\n' +
1120
+ '4. Check browser console for detailed path information';
1121
+ console.error(errorMsg);
1122
+ throw new Error(errorMsg);
881
1123
  }
882
1124
  scriptUrl = foundPath;
883
1125
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "web-audio-recorder-ts",
3
- "version": "1.0.3",
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",