@sage-rsc/talking-head-react 1.0.63 → 1.0.64

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.0.63",
3
+ "version": "1.0.64",
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",
@@ -442,22 +442,22 @@ const TalkingHeadAvatar = forwardRef(({
442
442
 
443
443
  // Save remaining text from speechQueue before clearing it
444
444
  let remainingText = '';
445
- if (talkingHead.speechQueue && talkingHead.speechQueue.length > 0) {
445
+ if (talkingHead.speechQueue && talkingHead.speechQueue.length > 0 && pausedSpeechRef.current) {
446
446
  // Extract text from remaining queue items
447
447
  const remainingParts = talkingHead.speechQueue
448
- .filter(item => item.text) // Only get items with text
448
+ .filter(item => item && item.text) // Only get items with text
449
449
  .map(item => item.text)
450
450
  .join(' ');
451
451
 
452
- if (remainingParts) {
452
+ if (remainingParts && remainingParts.trim()) {
453
453
  remainingText = remainingParts.trim();
454
454
  }
455
455
  }
456
456
 
457
- // If we have remaining text, save it for resume
458
- if (remainingText && pausedSpeechRef.current) {
457
+ // Always save progress for resume (even if no remaining text, we'll resume from beginning)
458
+ if (pausedSpeechRef.current) {
459
459
  speechProgressRef.current = {
460
- remainingText: remainingText,
460
+ remainingText: remainingText || null,
461
461
  originalText: pausedSpeechRef.current.text,
462
462
  options: pausedSpeechRef.current.options
463
463
  };
@@ -477,49 +477,55 @@ const TalkingHeadAvatar = forwardRef(({
477
477
  }, []);
478
478
 
479
479
  const resumeSpeaking = useCallback(async () => {
480
- if (talkingHeadRef.current && isPaused) {
481
- // Clear pause state first
480
+ if (!talkingHeadRef.current || !isPaused) {
481
+ return;
482
+ }
483
+
484
+ // Determine what text to speak - use remaining text if available, otherwise full text
485
+ let textToSpeak = '';
486
+ let optionsToUse = {};
487
+
488
+ if (speechProgressRef.current && speechProgressRef.current.remainingText) {
489
+ // Resume from where we paused - speak only the remaining text
490
+ textToSpeak = speechProgressRef.current.remainingText;
491
+ optionsToUse = speechProgressRef.current.options || {};
492
+ // Clear progress after using it
493
+ speechProgressRef.current = { remainingText: null, originalText: null, options: null };
494
+ } else if (pausedSpeechRef.current && pausedSpeechRef.current.text) {
495
+ // Fallback: if no progress tracked, resume from beginning
496
+ textToSpeak = pausedSpeechRef.current.text;
497
+ optionsToUse = pausedSpeechRef.current.options || {};
498
+ } else {
499
+ // Nothing to resume
500
+ console.warn('Resume called but no paused speech found');
501
+ setIsPaused(false);
502
+ isPausedRef.current = false;
503
+ return;
504
+ }
505
+
506
+ // Clear pause state before speaking
507
+ setIsPaused(false);
508
+ isPausedRef.current = false;
509
+
510
+ // Resume audio context
511
+ await resumeAudioContext();
512
+
513
+ // Prepare speak options
514
+ const speakOptions = {
515
+ ...optionsToUse,
516
+ lipsyncLang: optionsToUse.lipsyncLang || defaultAvatarConfig.lipsyncLang || 'en'
517
+ };
518
+
519
+ // Use speakText method which will set up the onSpeechEnd callback again
520
+ try {
521
+ await speakText(textToSpeak, speakOptions);
522
+ } catch (error) {
523
+ console.error('Error resuming speech:', error);
524
+ // Reset pause state on error
482
525
  setIsPaused(false);
483
526
  isPausedRef.current = false;
484
-
485
- // Resume audio context
486
- await resumeAudioContext();
487
-
488
- // Determine what text to speak - use remaining text if available, otherwise full text
489
- let textToSpeak = '';
490
- let optionsToUse = {};
491
-
492
- if (speechProgressRef.current && speechProgressRef.current.remainingText) {
493
- // Resume from where we paused - speak only the remaining text
494
- textToSpeak = speechProgressRef.current.remainingText;
495
- optionsToUse = speechProgressRef.current.options || {};
496
- // Clear progress after using it
497
- speechProgressRef.current = { remainingText: null, originalText: null, options: null };
498
- } else if (pausedSpeechRef.current && pausedSpeechRef.current.text) {
499
- // Fallback: if no progress tracked, resume from beginning
500
- textToSpeak = pausedSpeechRef.current.text;
501
- optionsToUse = pausedSpeechRef.current.options || {};
502
- } else {
503
- // Nothing to resume
504
- return;
505
- }
506
-
507
- // Prepare speak options
508
- const speakOptions = {
509
- ...optionsToUse,
510
- lipsyncLang: optionsToUse.lipsyncLang || defaultAvatarConfig.lipsyncLang || 'en'
511
- };
512
-
513
- // Use speakText method which will set up the onSpeechEnd callback again
514
- if (talkingHeadRef.current.lipsync && Object.keys(talkingHeadRef.current.lipsync).length > 0) {
515
- if (talkingHeadRef.current.setSlowdownRate) {
516
- talkingHeadRef.current.setSlowdownRate(1.05);
517
- }
518
- // Call speakText which will handle everything including onSpeechEnd
519
- await speakText(textToSpeak, speakOptions);
520
- }
521
527
  }
522
- }, [resumeAudioContext, isPaused, speakText]);
528
+ }, [resumeAudioContext, isPaused, speakText, defaultAvatarConfig]);
523
529
 
524
530
  const setMood = useCallback((mood) => {
525
531
  if (talkingHeadRef.current) {