@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 +7 -0
- package/dist/index.js +99 -42
- package/package.json +1 -1
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
|
-
|
|
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
|
-
|
|
735
|
-
this.
|
|
758
|
+
this.closeSampleSafely(audioSample);
|
|
759
|
+
if (!this.isCleanedUp) {
|
|
760
|
+
this.lastAudioTimestamp = currentTimestamp + duration;
|
|
761
|
+
}
|
|
736
762
|
}).catch(() => {
|
|
737
|
-
|
|
738
|
-
this.
|
|
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
|
-
|
|
857
|
-
|
|
858
|
-
|
|
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
|
+
"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",
|