@sage-rsc/talking-head-react 1.5.9 → 1.5.11
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.cjs +5 -5
- package/dist/index.js +1661 -1651
- package/package.json +1 -1
- package/src/components/SimpleTalkingAvatar.jsx +34 -3
- package/src/lib/talkinghead.mjs +37 -4
package/package.json
CHANGED
|
@@ -75,6 +75,8 @@ const SimpleTalkingAvatar = forwardRef(({
|
|
|
75
75
|
const [isPaused, setIsPaused] = useState(false);
|
|
76
76
|
const [loadedAnimations, setLoadedAnimations] = useState(animations);
|
|
77
77
|
const idleAnimationIntervalRef = useRef(null);
|
|
78
|
+
const isSpeakingRef = useRef(false);
|
|
79
|
+
const currentAnimationGroupRef = useRef(null);
|
|
78
80
|
|
|
79
81
|
// Keep ref in sync with state
|
|
80
82
|
useEffect(() => {
|
|
@@ -321,8 +323,8 @@ const SimpleTalkingAvatar = forwardRef(({
|
|
|
321
323
|
return null;
|
|
322
324
|
}, [loadedAnimations, avatarBody]);
|
|
323
325
|
|
|
324
|
-
// Helper function to play random animation from a group
|
|
325
|
-
const playRandomAnimation = useCallback((groupName, disablePositionLock = false) => {
|
|
326
|
+
// Helper function to play random animation from a group with smooth transitions
|
|
327
|
+
const playRandomAnimation = useCallback((groupName, disablePositionLock = false, onFinished = null) => {
|
|
326
328
|
if (!talkingHeadRef.current) {
|
|
327
329
|
console.warn('TalkingHead not initialized yet');
|
|
328
330
|
return null;
|
|
@@ -331,8 +333,23 @@ const SimpleTalkingAvatar = forwardRef(({
|
|
|
331
333
|
const animationPath = getRandomAnimation(groupName);
|
|
332
334
|
if (animationPath) {
|
|
333
335
|
try {
|
|
334
|
-
|
|
336
|
+
// Create callback that will play next animation if still speaking
|
|
337
|
+
const animationFinishedCallback = () => {
|
|
338
|
+
// If still speaking and same group, play next random animation
|
|
339
|
+
if (isSpeakingRef.current && currentAnimationGroupRef.current === groupName) {
|
|
340
|
+
// Small delay for smooth transition
|
|
341
|
+
setTimeout(() => {
|
|
342
|
+
playRandomAnimation(groupName, disablePositionLock, onFinished);
|
|
343
|
+
}, 100);
|
|
344
|
+
} else if (onFinished) {
|
|
345
|
+
onFinished();
|
|
346
|
+
}
|
|
347
|
+
};
|
|
348
|
+
|
|
349
|
+
// Play animation with callback - fade transitions are handled in playAnimation
|
|
350
|
+
talkingHeadRef.current.playAnimation(animationPath, null, 10, 0, 0.01, disablePositionLock, animationFinishedCallback);
|
|
335
351
|
console.log(`✅ Playing random animation from "${groupName}" group:`, animationPath);
|
|
352
|
+
|
|
336
353
|
return animationPath;
|
|
337
354
|
} catch (error) {
|
|
338
355
|
console.error(`❌ Failed to play random animation from "${groupName}" group:`, error);
|
|
@@ -365,6 +382,11 @@ const SimpleTalkingAvatar = forwardRef(({
|
|
|
365
382
|
if (animationGroup && !options.skipAnimation) {
|
|
366
383
|
console.log(`🎬 Attempting to play animation from group: "${animationGroup}"`);
|
|
367
384
|
console.log(`📊 Current avatarBody: "${avatarBody}", loadedAnimations:`, loadedAnimations);
|
|
385
|
+
|
|
386
|
+
// Mark as speaking and track animation group for continuous playback
|
|
387
|
+
isSpeakingRef.current = true;
|
|
388
|
+
currentAnimationGroupRef.current = animationGroup;
|
|
389
|
+
|
|
368
390
|
playRandomAnimation(animationGroup);
|
|
369
391
|
} else {
|
|
370
392
|
console.log(`⏭️ Skipping animation (group: ${animationGroup}, skipAnimation: ${options.skipAnimation})`);
|
|
@@ -400,6 +422,10 @@ const SimpleTalkingAvatar = forwardRef(({
|
|
|
400
422
|
speechEndIntervalRef.current = null;
|
|
401
423
|
}
|
|
402
424
|
|
|
425
|
+
// Stop continuous animation playback
|
|
426
|
+
isSpeakingRef.current = false;
|
|
427
|
+
currentAnimationGroupRef.current = null;
|
|
428
|
+
|
|
403
429
|
// Call user's onSpeechEnd callback
|
|
404
430
|
if (options.onSpeechEnd) {
|
|
405
431
|
options.onSpeechEnd();
|
|
@@ -538,6 +564,11 @@ const SimpleTalkingAvatar = forwardRef(({
|
|
|
538
564
|
clearInterval(speechEndIntervalRef.current);
|
|
539
565
|
speechEndIntervalRef.current = null;
|
|
540
566
|
}
|
|
567
|
+
|
|
568
|
+
// Stop continuous animation playback
|
|
569
|
+
isSpeakingRef.current = false;
|
|
570
|
+
currentAnimationGroupRef.current = null;
|
|
571
|
+
|
|
541
572
|
setIsPaused(false);
|
|
542
573
|
isPausedRef.current = false;
|
|
543
574
|
}
|
package/src/lib/talkinghead.mjs
CHANGED
|
@@ -1609,8 +1609,8 @@ class TalkingHead {
|
|
|
1609
1609
|
}
|
|
1610
1610
|
}
|
|
1611
1611
|
|
|
1612
|
-
//
|
|
1613
|
-
this.applyShoulderAdjustment();
|
|
1612
|
+
// TEMPORARILY DISABLED - No shoulder adjustments
|
|
1613
|
+
// this.applyShoulderAdjustment();
|
|
1614
1614
|
}
|
|
1615
1615
|
|
|
1616
1616
|
/**
|
|
@@ -1618,6 +1618,9 @@ class TalkingHead {
|
|
|
1618
1618
|
* This is called from updatePoseBase for pose-based animations
|
|
1619
1619
|
*/
|
|
1620
1620
|
applyShoulderAdjustment() {
|
|
1621
|
+
// TEMPORARILY DISABLED - No arm/shoulder adjustments
|
|
1622
|
+
return;
|
|
1623
|
+
|
|
1621
1624
|
const tempEuler = new THREE.Euler();
|
|
1622
1625
|
const targetX = 0.6; // Target X rotation for relaxed shoulders - lowered significantly
|
|
1623
1626
|
const maxX = 0.7; // Maximum X rotation - lowered significantly
|
|
@@ -5464,7 +5467,7 @@ class TalkingHead {
|
|
|
5464
5467
|
* @param {number} [scale=0.01] Position scale factor
|
|
5465
5468
|
*/
|
|
5466
5469
|
|
|
5467
|
-
async playAnimation(url, onprogress=null, dur=10, ndx=0, scale=0.01, disablePositionLock=false) {
|
|
5470
|
+
async playAnimation(url, onprogress=null, dur=10, ndx=0, scale=0.01, disablePositionLock=false, onFinished=null) {
|
|
5468
5471
|
if ( !this.armature ) return;
|
|
5469
5472
|
|
|
5470
5473
|
// Track whether position was locked for this animation
|
|
@@ -5502,7 +5505,20 @@ class TalkingHead {
|
|
|
5502
5505
|
} else {
|
|
5503
5506
|
console.log('Using existing mixer for FBX animation, preserving morph targets');
|
|
5504
5507
|
}
|
|
5505
|
-
|
|
5508
|
+
|
|
5509
|
+
// Store callback for when animation finishes
|
|
5510
|
+
this.animationFinishedCallback = onFinished;
|
|
5511
|
+
|
|
5512
|
+
// Create handler that calls callback before stopping
|
|
5513
|
+
const finishedHandler = () => {
|
|
5514
|
+
if (this.animationFinishedCallback) {
|
|
5515
|
+
this.animationFinishedCallback();
|
|
5516
|
+
this.animationFinishedCallback = null;
|
|
5517
|
+
}
|
|
5518
|
+
this.stopAnimation();
|
|
5519
|
+
};
|
|
5520
|
+
|
|
5521
|
+
this.mixer.addEventListener( 'finished', finishedHandler, { once: true });
|
|
5506
5522
|
|
|
5507
5523
|
// Play action with error handling
|
|
5508
5524
|
const repeat = Math.ceil(dur / item.clip.duration);
|
|
@@ -5510,6 +5526,23 @@ class TalkingHead {
|
|
|
5510
5526
|
action.setLoop( THREE.LoopRepeat, repeat );
|
|
5511
5527
|
action.clampWhenFinished = true;
|
|
5512
5528
|
|
|
5529
|
+
// Fade out previous action smoothly if one exists and is running
|
|
5530
|
+
if (this.currentFBXAction && this.currentFBXAction.isRunning()) {
|
|
5531
|
+
this.currentFBXAction.fadeOut(0.3);
|
|
5532
|
+
// Small delay for smooth transition
|
|
5533
|
+
setTimeout(() => {
|
|
5534
|
+
this.currentFBXAction = action;
|
|
5535
|
+
try {
|
|
5536
|
+
action.fadeIn(0.5).play();
|
|
5537
|
+
console.log('FBX animation started successfully (with fade transition):', url);
|
|
5538
|
+
} catch (error) {
|
|
5539
|
+
console.warn('FBX animation failed to start:', error);
|
|
5540
|
+
this.stopAnimation();
|
|
5541
|
+
}
|
|
5542
|
+
}, 300);
|
|
5543
|
+
return;
|
|
5544
|
+
}
|
|
5545
|
+
|
|
5513
5546
|
// Store the current FBX action for proper cleanup
|
|
5514
5547
|
this.currentFBXAction = action;
|
|
5515
5548
|
|