@vidtreo/recorder 0.8.3 → 0.8.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -484,6 +484,8 @@ export declare class AudioTrackManager {
484
484
  private isMuted;
485
485
  private isPaused;
486
486
  private onMuteStateChange?;
487
+ private readonly pendingAudioSamples;
488
+ private isCleanedUp;
487
489
  private validateStreamAndConfig;
488
490
  private prepareAudioTrack;
489
491
  private getAudioContextClass;
@@ -492,6 +494,11 @@ export declare class AudioTrackManager {
492
494
  private addWorkletModule;
493
495
  private createWorkletNode;
494
496
  private setupWorkletMessageHandler;
497
+ private closeSampleSafely;
498
+ private closePendingAudioSamples;
499
+ private cleanupAudioNodes;
500
+ private cleanupAudioContext;
501
+ private cleanupAudioTrack;
495
502
  private setupAudioNodes;
496
503
  setupAudioTrack(stream: MediaStream, config: TranscodeConfig): Promise<AudioSampleSource | null>;
497
504
  toggleMute(): void;
package/dist/index.js CHANGED
@@ -587,6 +587,8 @@ class AudioTrackManager {
587
587
  isMuted = false;
588
588
  isPaused = false;
589
589
  onMuteStateChange;
590
+ pendingAudioSamples = new Set;
591
+ isCleanedUp = false;
590
592
  validateStreamAndConfig(stream, config) {
591
593
  if (!stream) {
592
594
  logger.debug("[AudioTrackManager] No stream provided");
@@ -632,6 +634,7 @@ class AudioTrackManager {
632
634
  this.originalAudioTrack = clonedTrack;
633
635
  this.lastAudioTimestamp = 0;
634
636
  this.pendingAddOperation = null;
637
+ this.isCleanedUp = false;
635
638
  logger.debug("[AudioTrackManager] Reset state, originalAudioTrack set (cloned)");
636
639
  return clonedTrack;
637
640
  }
@@ -712,12 +715,12 @@ class AudioTrackManager {
712
715
  throw new Error("AudioWorkletNode not initialized");
713
716
  }
714
717
  this.audioWorkletNode.port.onmessage = (event) => {
715
- if (this.isPaused || this.isMuted || !this.audioSource) {
718
+ if (this.isCleanedUp || this.isPaused || this.isMuted || !this.audioSource) {
716
719
  return;
717
720
  }
718
721
  const { data, sampleRate, numberOfChannels, duration, bufferLength } = event.data;
719
722
  const float32Data = new Float32Array(data, 0, bufferLength);
720
- if (!this.audioSource) {
723
+ if (this.isCleanedUp || !this.audioSource) {
721
724
  return;
722
725
  }
723
726
  const audioSource = this.audioSource;
@@ -728,18 +731,100 @@ class AudioTrackManager {
728
731
  sampleRate,
729
732
  timestamp: this.lastAudioTimestamp
730
733
  });
734
+ this.pendingAudioSamples.add(audioSample);
731
735
  const currentTimestamp = this.lastAudioTimestamp;
732
- const addOperation = this.pendingAddOperation ? this.pendingAddOperation.then(() => audioSource.add(audioSample)).catch(() => audioSource.add(audioSample)) : audioSource.add(audioSample);
736
+ let addOperation;
737
+ if (this.pendingAddOperation) {
738
+ addOperation = this.pendingAddOperation.then(() => {
739
+ if (this.isCleanedUp || !this.audioSource) {
740
+ this.closeSampleSafely(audioSample);
741
+ return;
742
+ }
743
+ return audioSource.add(audioSample);
744
+ }).catch(() => {
745
+ if (this.isCleanedUp || !this.audioSource) {
746
+ this.closeSampleSafely(audioSample);
747
+ return;
748
+ }
749
+ return audioSource.add(audioSample);
750
+ });
751
+ } else if (this.isCleanedUp) {
752
+ this.closeSampleSafely(audioSample);
753
+ addOperation = Promise.resolve();
754
+ } else {
755
+ addOperation = audioSource.add(audioSample);
756
+ }
733
757
  this.pendingAddOperation = addOperation.then(() => {
734
- audioSample.close();
735
- this.lastAudioTimestamp = currentTimestamp + duration;
758
+ this.closeSampleSafely(audioSample);
759
+ if (!this.isCleanedUp) {
760
+ this.lastAudioTimestamp = currentTimestamp + duration;
761
+ }
736
762
  }).catch(() => {
737
- audioSample.close();
738
- this.lastAudioTimestamp = currentTimestamp + duration;
763
+ this.closeSampleSafely(audioSample);
764
+ if (!this.isCleanedUp) {
765
+ this.lastAudioTimestamp = currentTimestamp + duration;
766
+ }
739
767
  });
740
768
  };
741
769
  logger.debug("[AudioTrackManager] onmessage handler set");
742
770
  }
771
+ closeSampleSafely(audioSample) {
772
+ if (this.pendingAudioSamples.has(audioSample)) {
773
+ audioSample.close();
774
+ this.pendingAudioSamples.delete(audioSample);
775
+ }
776
+ }
777
+ closePendingAudioSamples() {
778
+ for (const audioSample of this.pendingAudioSamples) {
779
+ try {
780
+ audioSample.close();
781
+ } catch (error) {
782
+ const errorMessage = error instanceof Error ? error.message : String(error);
783
+ logger.warn("[AudioTrackManager] Error closing AudioSample during cleanup:", errorMessage);
784
+ }
785
+ }
786
+ this.pendingAudioSamples.clear();
787
+ }
788
+ cleanupAudioNodes() {
789
+ if (this.audioWorkletNode) {
790
+ logger.debug("[AudioTrackManager] Disconnecting and closing worklet node");
791
+ this.audioWorkletNode.disconnect();
792
+ this.audioWorkletNode.port.close();
793
+ this.audioWorkletNode = null;
794
+ }
795
+ if (this.gainNode) {
796
+ logger.debug("[AudioTrackManager] Disconnecting gain node");
797
+ this.gainNode.disconnect();
798
+ this.gainNode = null;
799
+ }
800
+ if (this.mediaStreamSource) {
801
+ logger.debug("[AudioTrackManager] Disconnecting media stream source");
802
+ this.mediaStreamSource.disconnect();
803
+ this.mediaStreamSource = null;
804
+ }
805
+ }
806
+ cleanupAudioContext() {
807
+ if (this.audioContext && this.audioContext.state !== "closed") {
808
+ logger.debug("[AudioTrackManager] Suspending AudioContext (not closing)");
809
+ if (typeof this.audioContext.suspend === "function") {
810
+ this.audioContext.suspend().catch((err) => {
811
+ logger.warn("[AudioTrackManager] Error suspending AudioContext:", err);
812
+ });
813
+ }
814
+ }
815
+ }
816
+ cleanupAudioTrack() {
817
+ if (this.originalAudioTrack) {
818
+ logger.debug("[AudioTrackManager] Stopping cloned audio track", {
819
+ trackState: this.originalAudioTrack.readyState
820
+ });
821
+ if (this.originalAudioTrack.readyState === "live" && typeof this.originalAudioTrack.stop === "function") {
822
+ this.originalAudioTrack.stop();
823
+ logger.debug("[AudioTrackManager] Cloned audio track stopped");
824
+ }
825
+ this.originalAudioTrack = null;
826
+ }
827
+ }
743
828
  setupAudioNodes() {
744
829
  if (!(this.audioContext && this.audioWorkletNode && this.mediaStreamSource)) {
745
830
  throw new Error("Audio nodes not initialized");
@@ -847,46 +932,18 @@ class AudioTrackManager {
847
932
  hasAudioSource: !!this.audioSource,
848
933
  hasAudioContext: !!this.audioContext,
849
934
  audioContextState: this.audioContext?.state,
850
- hasWorkletNode: !!this.audioWorkletNode
935
+ hasWorkletNode: !!this.audioWorkletNode,
936
+ pendingSamples: this.pendingAudioSamples.size
851
937
  });
938
+ this.isCleanedUp = true;
939
+ this.closePendingAudioSamples();
852
940
  this.audioSource = null;
853
941
  this.pendingAddOperation = null;
854
942
  this.isMuted = false;
855
943
  this.isPaused = false;
856
- if (this.audioWorkletNode) {
857
- logger.debug("[AudioTrackManager] Disconnecting and closing worklet node");
858
- this.audioWorkletNode.disconnect();
859
- this.audioWorkletNode.port.close();
860
- this.audioWorkletNode = null;
861
- }
862
- if (this.gainNode) {
863
- logger.debug("[AudioTrackManager] Disconnecting gain node");
864
- this.gainNode.disconnect();
865
- this.gainNode = null;
866
- }
867
- if (this.mediaStreamSource) {
868
- logger.debug("[AudioTrackManager] Disconnecting media stream source");
869
- this.mediaStreamSource.disconnect();
870
- this.mediaStreamSource = null;
871
- }
872
- if (this.audioContext && this.audioContext.state !== "closed") {
873
- logger.debug("[AudioTrackManager] Suspending AudioContext (not closing)");
874
- if (typeof this.audioContext.suspend === "function") {
875
- this.audioContext.suspend().catch((err) => {
876
- logger.warn("[AudioTrackManager] Error suspending AudioContext:", err);
877
- });
878
- }
879
- }
880
- if (this.originalAudioTrack) {
881
- logger.debug("[AudioTrackManager] Stopping cloned audio track", {
882
- trackState: this.originalAudioTrack.readyState
883
- });
884
- if (this.originalAudioTrack.readyState === "live" && typeof this.originalAudioTrack.stop === "function") {
885
- this.originalAudioTrack.stop();
886
- logger.debug("[AudioTrackManager] Cloned audio track stopped");
887
- }
888
- this.originalAudioTrack = null;
889
- }
944
+ this.cleanupAudioNodes();
945
+ this.cleanupAudioContext();
946
+ this.cleanupAudioTrack();
890
947
  this.lastAudioTimestamp = 0;
891
948
  }
892
949
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vidtreo/recorder",
3
- "version": "0.8.3",
3
+ "version": "0.8.4",
4
4
  "type": "module",
5
5
  "description": "Vidtreo SDK for browser-based video recording and transcoding. Features include camera/screen recording, real-time MP4 transcoding, audio level analysis, mute/pause controls, source switching, device selection, and automatic backend uploads. Similar to Ziggeo and Addpipe, Vidtreo provides enterprise-grade video processing capabilities for web applications.",
6
6
  "main": "./dist/index.js",