@sage-rsc/talking-head-react 1.7.8 → 1.7.9

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sage-rsc/talking-head-react",
3
- "version": "1.7.8",
3
+ "version": "1.7.9",
4
4
  "description": "A reusable React component for 3D talking avatars with lip-sync and text-to-speech",
5
5
  "main": "./dist/index.cjs",
6
6
  "module": "./dist/index.js",
@@ -777,10 +777,26 @@ const SimpleTalkingAvatar = forwardRef(({
777
777
  isMidSentence: audioPlaylist.length > 0
778
778
  };
779
779
 
780
- // Clear speech queue and pause (this will stop audio and clear audioPlaylist)
781
- // pauseSpeaking() now returns trimmed audio data if audio was playing
782
- talkingHeadRef.current.speechQueue.length = 0;
780
+ // IMPORTANT: Save speech queue BEFORE pausing if audio is playing
781
+ // We need to preserve it so remaining text continues after trimmed audio
782
+ const wasAudioPlaying = talkingHeadRef.current.isAudioPlaying || false;
783
+ const savedSpeechQueue = wasAudioPlaying ? [...(talkingHeadRef.current.speechQueue || [])] : null;
784
+
785
+ // Pause and get trimmed buffer if available
786
+ // pauseSpeaking() clears audioPlaylist but doesn't clear speechQueue
783
787
  const pausedAudioData = talkingHeadRef.current.pauseSpeaking();
788
+
789
+ // If we have trimmed buffer, restore speech queue for continuation
790
+ // Otherwise, clear it (normal pause behavior)
791
+ if (pausedAudioData && pausedAudioData.audio && savedSpeechQueue) {
792
+ // Restore speech queue so remaining text continues after trimmed audio
793
+ talkingHeadRef.current.speechQueue.length = 0;
794
+ talkingHeadRef.current.speechQueue.push(...savedSpeechQueue);
795
+ } else {
796
+ // No trimmed buffer, clear speech queue normally
797
+ talkingHeadRef.current.speechQueue.length = 0;
798
+ }
799
+
784
800
  pausedAudioDataRef.current = pausedAudioData; // Store trimmed buffer for exact resume
785
801
  setIsPaused(true);
786
802
  isPausedRef.current = true;
@@ -803,8 +819,47 @@ const SimpleTalkingAvatar = forwardRef(({
803
819
 
804
820
  // If we have trimmed audio data from pause, resume from exact position
805
821
  if (pausedAudioDataRef.current && pausedAudioDataRef.current.audio) {
822
+ // Ensure speaking state is set for animations to continue
823
+ isSpeakingRef.current = true;
824
+
825
+ // Restore animation group if it was set
826
+ const originalOptions = speechProgressRef.current?.options || pausedSpeechRef.current?.options || {};
827
+ const animationGroup = originalOptions.animationGroup || autoAnimationGroup;
828
+ if (animationGroup) {
829
+ currentAnimationGroupRef.current = animationGroup;
830
+ }
831
+
832
+ // Ensure remaining text is in speech queue so it continues after trimmed buffer
833
+ const remainingText = speechProgressRef.current?.remainingText;
834
+ if (remainingText && talkingHeadRef.current.speechQueue) {
835
+ // Re-add remaining text to speech queue so it continues after trimmed audio
836
+ // Split into sentences and add to queue
837
+ const sentences = remainingText.split(/[.!?]+/).filter(s => s.trim().length > 0);
838
+ sentences.forEach(sentence => {
839
+ talkingHeadRef.current.speechQueue.push({
840
+ text: sentence.trim(),
841
+ options: originalOptions
842
+ });
843
+ });
844
+ }
845
+
846
+ // Mark as speaking so audio continues and speech queue processes
847
+ talkingHeadRef.current.isSpeaking = true;
848
+
806
849
  // Resume with trimmed buffer (exact position)
850
+ // After trimmed buffer finishes, playAudio will call startSpeaking() which processes speechQueue
807
851
  await talkingHeadRef.current.playAudio(false, pausedAudioDataRef.current);
852
+
853
+ // Continue animations if animation group is set
854
+ // Start immediately and they will continue as long as isSpeakingRef is true
855
+ if (animationGroup && !originalOptions.skipAnimation) {
856
+ // Reset animation queue for smooth continuation
857
+ animationQueueRef.current = [];
858
+ playedAnimationsRef.current = [];
859
+ // Start playing animations immediately
860
+ playRandomAnimation(animationGroup);
861
+ }
862
+
808
863
  pausedAudioDataRef.current = null; // Clear after use
809
864
  return;
810
865
  }
@@ -828,7 +883,7 @@ const SimpleTalkingAvatar = forwardRef(({
828
883
  isPausedRef.current = false;
829
884
  pausedAudioDataRef.current = null;
830
885
  }
831
- }, [isPaused, speakText, resumeAudioContext]);
886
+ }, [isPaused, speakText, resumeAudioContext, autoAnimationGroup, playRandomAnimation]);
832
887
 
833
888
  // Stop speaking
834
889
  const stopSpeaking = useCallback(() => {
@@ -3773,6 +3773,8 @@ class TalkingHead {
3773
3773
  this.audioSpeechSource.disconnect();
3774
3774
  this.audioStartTime = null;
3775
3775
  this.currentAudioItem = null;
3776
+ // Ensure isSpeaking is true so startSpeaking() processes the queue after trimmed audio
3777
+ this.isSpeaking = true;
3776
3778
  this.playAudio(true);
3777
3779
  }, { once: true });
3778
3780
 
@@ -3853,6 +3855,8 @@ class TalkingHead {
3853
3855
  this.audioSpeechSource.disconnect();
3854
3856
  this.audioStartTime = null;
3855
3857
  this.currentAudioItem = null;
3858
+ // Ensure isSpeaking is true so startSpeaking() processes the queue
3859
+ this.isSpeaking = true;
3856
3860
  this.playAudio(true);
3857
3861
  }, { once: true });
3858
3862