@aether-stack-dev/client-sdk 1.2.0 → 1.2.2

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.
@@ -67,6 +67,7 @@ export declare class AStackCSRClient extends EventEmitter<AStackCSREvents> {
67
67
  startCall(options?: CallOptions): Promise<void>;
68
68
  private startImageCapture;
69
69
  private captureAndSendImage;
70
+ private cleanupLocalAudio;
70
71
  stopCall(): void;
71
72
  sendText(message: string): void;
72
73
  disconnect(): Promise<void>;
package/dist/index.esm.js CHANGED
@@ -92,6 +92,10 @@ class AudioPlayer extends EventEmitter {
92
92
  try {
93
93
  const ctx = await this.ensureAudioContext();
94
94
  const int16Array = new Int16Array(chunk.audio);
95
+ if (int16Array.length === 0) {
96
+ this.playNext();
97
+ return;
98
+ }
95
99
  const floatArray = new Float32Array(int16Array.length);
96
100
  for (let i = 0; i < int16Array.length; i++) {
97
101
  floatArray[i] = int16Array[i] / 32768.0;
@@ -108,15 +112,19 @@ class AudioPlayer extends EventEmitter {
108
112
  const duration = audioBuffer.duration;
109
113
  const audioStartTime = ctx.currentTime;
110
114
  let frameIndex = 0;
115
+ const wasPlaying = this.isPlaying;
111
116
  this.isPlaying = true;
112
- this.emit('playbackStarted');
117
+ if (!wasPlaying)
118
+ this.emit('playbackStarted');
113
119
  source.onended = () => {
114
120
  if (this.animationFrameId !== null) {
115
121
  cancelAnimationFrame(this.animationFrameId);
116
122
  this.animationFrameId = null;
117
123
  }
118
124
  this.emit('blendshapeUpdate', new Array(BLENDSHAPE_COUNT).fill(0));
119
- this.emit('playbackEnded');
125
+ if (this.audioQueue.length === 0) {
126
+ this.emit('playbackEnded');
127
+ }
120
128
  this.playNext();
121
129
  };
122
130
  const animate = () => {
@@ -239,7 +247,7 @@ class AStackCSRClient extends EventEmitter {
239
247
  this.hasConnected = true;
240
248
  if (isReconnect) {
241
249
  if (this.callStatus === 'active' || this.callStatus === 'starting') {
242
- this.stopCall();
250
+ this.cleanupLocalAudio();
243
251
  this.emit('callStopped');
244
252
  }
245
253
  this.emit('reconnected');
@@ -568,6 +576,33 @@ class AStackCSRClient extends EventEmitter {
568
576
  timestamp: Date.now()
569
577
  }));
570
578
  }
579
+ cleanupLocalAudio() {
580
+ if (this.imageCaptureInterval) {
581
+ clearInterval(this.imageCaptureInterval);
582
+ this.imageCaptureInterval = null;
583
+ }
584
+ if (this.videoRef) {
585
+ this.videoRef.srcObject = null;
586
+ this.videoRef = null;
587
+ }
588
+ if (this.mediaStream) {
589
+ this.mediaStream.getTracks().forEach(track => track.stop());
590
+ this.mediaStream = null;
591
+ }
592
+ if (this.audioProcessor) {
593
+ this.audioProcessor.disconnect();
594
+ this.audioProcessor.port.onmessage = null;
595
+ this.audioProcessor = null;
596
+ }
597
+ if (this.audioContext) {
598
+ this.audioContext.close();
599
+ this.audioContext = null;
600
+ }
601
+ this.audioPlayer.clearQueue();
602
+ this.callStatus = 'idle';
603
+ this.currentBlendshapes = new Array(BLENDSHAPE_COUNT).fill(0);
604
+ this.emit('blendshapeUpdate', this.currentBlendshapes);
605
+ }
571
606
  stopCall() {
572
607
  if (this.imageCaptureInterval) {
573
608
  clearInterval(this.imageCaptureInterval);
package/dist/index.js CHANGED
@@ -96,6 +96,10 @@ class AudioPlayer extends eventemitter3.EventEmitter {
96
96
  try {
97
97
  const ctx = await this.ensureAudioContext();
98
98
  const int16Array = new Int16Array(chunk.audio);
99
+ if (int16Array.length === 0) {
100
+ this.playNext();
101
+ return;
102
+ }
99
103
  const floatArray = new Float32Array(int16Array.length);
100
104
  for (let i = 0; i < int16Array.length; i++) {
101
105
  floatArray[i] = int16Array[i] / 32768.0;
@@ -112,15 +116,19 @@ class AudioPlayer extends eventemitter3.EventEmitter {
112
116
  const duration = audioBuffer.duration;
113
117
  const audioStartTime = ctx.currentTime;
114
118
  let frameIndex = 0;
119
+ const wasPlaying = this.isPlaying;
115
120
  this.isPlaying = true;
116
- this.emit('playbackStarted');
121
+ if (!wasPlaying)
122
+ this.emit('playbackStarted');
117
123
  source.onended = () => {
118
124
  if (this.animationFrameId !== null) {
119
125
  cancelAnimationFrame(this.animationFrameId);
120
126
  this.animationFrameId = null;
121
127
  }
122
128
  this.emit('blendshapeUpdate', new Array(BLENDSHAPE_COUNT).fill(0));
123
- this.emit('playbackEnded');
129
+ if (this.audioQueue.length === 0) {
130
+ this.emit('playbackEnded');
131
+ }
124
132
  this.playNext();
125
133
  };
126
134
  const animate = () => {
@@ -243,7 +251,7 @@ class AStackCSRClient extends eventemitter3.EventEmitter {
243
251
  this.hasConnected = true;
244
252
  if (isReconnect) {
245
253
  if (this.callStatus === 'active' || this.callStatus === 'starting') {
246
- this.stopCall();
254
+ this.cleanupLocalAudio();
247
255
  this.emit('callStopped');
248
256
  }
249
257
  this.emit('reconnected');
@@ -572,6 +580,33 @@ class AStackCSRClient extends eventemitter3.EventEmitter {
572
580
  timestamp: Date.now()
573
581
  }));
574
582
  }
583
+ cleanupLocalAudio() {
584
+ if (this.imageCaptureInterval) {
585
+ clearInterval(this.imageCaptureInterval);
586
+ this.imageCaptureInterval = null;
587
+ }
588
+ if (this.videoRef) {
589
+ this.videoRef.srcObject = null;
590
+ this.videoRef = null;
591
+ }
592
+ if (this.mediaStream) {
593
+ this.mediaStream.getTracks().forEach(track => track.stop());
594
+ this.mediaStream = null;
595
+ }
596
+ if (this.audioProcessor) {
597
+ this.audioProcessor.disconnect();
598
+ this.audioProcessor.port.onmessage = null;
599
+ this.audioProcessor = null;
600
+ }
601
+ if (this.audioContext) {
602
+ this.audioContext.close();
603
+ this.audioContext = null;
604
+ }
605
+ this.audioPlayer.clearQueue();
606
+ this.callStatus = 'idle';
607
+ this.currentBlendshapes = new Array(BLENDSHAPE_COUNT).fill(0);
608
+ this.emit('blendshapeUpdate', this.currentBlendshapes);
609
+ }
575
610
  stopCall() {
576
611
  if (this.imageCaptureInterval) {
577
612
  clearInterval(this.imageCaptureInterval);
package/dist/react.esm.js CHANGED
@@ -98,6 +98,10 @@ class AudioPlayer extends EventEmitter {
98
98
  try {
99
99
  const ctx = await this.ensureAudioContext();
100
100
  const int16Array = new Int16Array(chunk.audio);
101
+ if (int16Array.length === 0) {
102
+ this.playNext();
103
+ return;
104
+ }
101
105
  const floatArray = new Float32Array(int16Array.length);
102
106
  for (let i = 0; i < int16Array.length; i++) {
103
107
  floatArray[i] = int16Array[i] / 32768.0;
@@ -114,15 +118,19 @@ class AudioPlayer extends EventEmitter {
114
118
  const duration = audioBuffer.duration;
115
119
  const audioStartTime = ctx.currentTime;
116
120
  let frameIndex = 0;
121
+ const wasPlaying = this.isPlaying;
117
122
  this.isPlaying = true;
118
- this.emit('playbackStarted');
123
+ if (!wasPlaying)
124
+ this.emit('playbackStarted');
119
125
  source.onended = () => {
120
126
  if (this.animationFrameId !== null) {
121
127
  cancelAnimationFrame(this.animationFrameId);
122
128
  this.animationFrameId = null;
123
129
  }
124
130
  this.emit('blendshapeUpdate', new Array(BLENDSHAPE_COUNT).fill(0));
125
- this.emit('playbackEnded');
131
+ if (this.audioQueue.length === 0) {
132
+ this.emit('playbackEnded');
133
+ }
126
134
  this.playNext();
127
135
  };
128
136
  const animate = () => {
@@ -245,7 +253,7 @@ class AStackCSRClient extends EventEmitter {
245
253
  this.hasConnected = true;
246
254
  if (isReconnect) {
247
255
  if (this.callStatus === 'active' || this.callStatus === 'starting') {
248
- this.stopCall();
256
+ this.cleanupLocalAudio();
249
257
  this.emit('callStopped');
250
258
  }
251
259
  this.emit('reconnected');
@@ -574,6 +582,33 @@ class AStackCSRClient extends EventEmitter {
574
582
  timestamp: Date.now()
575
583
  }));
576
584
  }
585
+ cleanupLocalAudio() {
586
+ if (this.imageCaptureInterval) {
587
+ clearInterval(this.imageCaptureInterval);
588
+ this.imageCaptureInterval = null;
589
+ }
590
+ if (this.videoRef) {
591
+ this.videoRef.srcObject = null;
592
+ this.videoRef = null;
593
+ }
594
+ if (this.mediaStream) {
595
+ this.mediaStream.getTracks().forEach(track => track.stop());
596
+ this.mediaStream = null;
597
+ }
598
+ if (this.audioProcessor) {
599
+ this.audioProcessor.disconnect();
600
+ this.audioProcessor.port.onmessage = null;
601
+ this.audioProcessor = null;
602
+ }
603
+ if (this.audioContext) {
604
+ this.audioContext.close();
605
+ this.audioContext = null;
606
+ }
607
+ this.audioPlayer.clearQueue();
608
+ this.callStatus = 'idle';
609
+ this.currentBlendshapes = new Array(BLENDSHAPE_COUNT).fill(0);
610
+ this.emit('blendshapeUpdate', this.currentBlendshapes);
611
+ }
577
612
  stopCall() {
578
613
  if (this.imageCaptureInterval) {
579
614
  clearInterval(this.imageCaptureInterval);
package/dist/react.js CHANGED
@@ -119,6 +119,10 @@ class AudioPlayer extends eventemitter3.EventEmitter {
119
119
  try {
120
120
  const ctx = await this.ensureAudioContext();
121
121
  const int16Array = new Int16Array(chunk.audio);
122
+ if (int16Array.length === 0) {
123
+ this.playNext();
124
+ return;
125
+ }
122
126
  const floatArray = new Float32Array(int16Array.length);
123
127
  for (let i = 0; i < int16Array.length; i++) {
124
128
  floatArray[i] = int16Array[i] / 32768.0;
@@ -135,15 +139,19 @@ class AudioPlayer extends eventemitter3.EventEmitter {
135
139
  const duration = audioBuffer.duration;
136
140
  const audioStartTime = ctx.currentTime;
137
141
  let frameIndex = 0;
142
+ const wasPlaying = this.isPlaying;
138
143
  this.isPlaying = true;
139
- this.emit('playbackStarted');
144
+ if (!wasPlaying)
145
+ this.emit('playbackStarted');
140
146
  source.onended = () => {
141
147
  if (this.animationFrameId !== null) {
142
148
  cancelAnimationFrame(this.animationFrameId);
143
149
  this.animationFrameId = null;
144
150
  }
145
151
  this.emit('blendshapeUpdate', new Array(BLENDSHAPE_COUNT).fill(0));
146
- this.emit('playbackEnded');
152
+ if (this.audioQueue.length === 0) {
153
+ this.emit('playbackEnded');
154
+ }
147
155
  this.playNext();
148
156
  };
149
157
  const animate = () => {
@@ -266,7 +274,7 @@ class AStackCSRClient extends eventemitter3.EventEmitter {
266
274
  this.hasConnected = true;
267
275
  if (isReconnect) {
268
276
  if (this.callStatus === 'active' || this.callStatus === 'starting') {
269
- this.stopCall();
277
+ this.cleanupLocalAudio();
270
278
  this.emit('callStopped');
271
279
  }
272
280
  this.emit('reconnected');
@@ -595,6 +603,33 @@ class AStackCSRClient extends eventemitter3.EventEmitter {
595
603
  timestamp: Date.now()
596
604
  }));
597
605
  }
606
+ cleanupLocalAudio() {
607
+ if (this.imageCaptureInterval) {
608
+ clearInterval(this.imageCaptureInterval);
609
+ this.imageCaptureInterval = null;
610
+ }
611
+ if (this.videoRef) {
612
+ this.videoRef.srcObject = null;
613
+ this.videoRef = null;
614
+ }
615
+ if (this.mediaStream) {
616
+ this.mediaStream.getTracks().forEach(track => track.stop());
617
+ this.mediaStream = null;
618
+ }
619
+ if (this.audioProcessor) {
620
+ this.audioProcessor.disconnect();
621
+ this.audioProcessor.port.onmessage = null;
622
+ this.audioProcessor = null;
623
+ }
624
+ if (this.audioContext) {
625
+ this.audioContext.close();
626
+ this.audioContext = null;
627
+ }
628
+ this.audioPlayer.clearQueue();
629
+ this.callStatus = 'idle';
630
+ this.currentBlendshapes = new Array(BLENDSHAPE_COUNT).fill(0);
631
+ this.emit('blendshapeUpdate', this.currentBlendshapes);
632
+ }
598
633
  stopCall() {
599
634
  if (this.imageCaptureInterval) {
600
635
  clearInterval(this.imageCaptureInterval);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aether-stack-dev/client-sdk",
3
- "version": "1.2.0",
3
+ "version": "1.2.2",
4
4
  "type": "module",
5
5
  "description": "JavaScript/TypeScript SDK for AStack video-to-video AI conversations",
6
6
  "main": "dist/index.js",