@sage-rsc/talking-head-react 1.4.0 → 1.4.1

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.4.0",
3
+ "version": "1.4.1",
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",
@@ -1128,7 +1128,7 @@ class TalkingHead {
1128
1128
  });
1129
1129
  } else {
1130
1130
  if (obj.material.map) obj.material.map.dispose();
1131
- obj.material.dispose();
1131
+ obj.material.dispose();
1132
1132
  }
1133
1133
  }
1134
1134
  }
@@ -1244,7 +1244,7 @@ class TalkingHead {
1244
1244
  this.lockedPosition = null;
1245
1245
  this.originalPosition = null;
1246
1246
  this.positionWasLocked = false;
1247
-
1247
+
1248
1248
  // Initialize FBX animation loader
1249
1249
  this.fbxAnimationLoader = null;
1250
1250
 
@@ -1256,7 +1256,7 @@ class TalkingHead {
1256
1256
  this.mixer.removeEventListener('finished', this._mixerHandler);
1257
1257
  this.mixer.stopAllAction();
1258
1258
  this.mixer.uncacheRoot(this.armature);
1259
- this.mixer = null;
1259
+ this.mixer = null;
1260
1260
  this._mixerHandler = null;
1261
1261
  }
1262
1262
  if ( this.isAvatarOnly ) {
@@ -2040,6 +2040,11 @@ class TalkingHead {
2040
2040
  */
2041
2041
  setPoseFromTemplate(template, ms=2000) {
2042
2042
 
2043
+ // Guard against disposal: check if required objects exist
2044
+ if (!this.poseFactory || !this.poseTemplates) {
2045
+ return;
2046
+ }
2047
+
2043
2048
  // Special cases
2044
2049
  const isIntermediate = template && this.poseTarget && this.poseTarget.template && ((this.poseTarget.template.standing && template.lying) || (this.poseTarget.template.lying && template.standing));
2045
2050
  const isSameTemplate = template && (template === this.poseCurrentTemplate);
@@ -2053,13 +2058,18 @@ class TalkingHead {
2053
2058
  this.setPoseFromTemplate(template,ms);
2054
2059
  }, duration);
2055
2060
  } else {
2056
- this.poseCurrentTemplate = template || this.poseCurrentTemplate;
2061
+ this.poseCurrentTemplate = template || this.poseCurrentTemplate;
2057
2062
  }
2058
2063
 
2059
2064
  // Set target
2060
2065
  this.poseTarget = this.poseFactory(this.poseCurrentTemplate, duration);
2061
2066
  this.poseWeightOnLeft = true;
2062
2067
 
2068
+ // Guard: ensure poseTarget was created successfully
2069
+ if (!this.poseTarget || !this.poseTarget.props) {
2070
+ return;
2071
+ }
2072
+
2063
2073
  // Mirror properties, if necessary
2064
2074
  if ( (!isSameTemplate && !isWeightOnLeft) || (isSameTemplate && isWeightOnLeft ) ) {
2065
2075
  this.poseTarget.props = this.mirrorPose(this.poseTarget.props);
@@ -2078,13 +2088,16 @@ class TalkingHead {
2078
2088
  }
2079
2089
 
2080
2090
  // Make sure deltas are included in the target
2091
+ // Guard against disposal: check if poseBase and its props exist
2092
+ if (this.poseBase && this.poseBase.props && this.poseDelta && this.poseDelta.props) {
2081
2093
  Object.keys(this.poseDelta.props).forEach( key => {
2082
- if ( !this.poseTarget.props.hasOwnProperty(key) ) {
2094
+ if ( !this.poseTarget.props.hasOwnProperty(key) && this.poseBase.props[key] ) {
2083
2095
  this.poseTarget.props[key] = this.poseBase.props[key].clone();
2084
2096
  this.poseTarget.props[key].t = this.animClock;
2085
2097
  this.poseTarget.props[key].d = duration;
2086
2098
  }
2087
2099
  });
2100
+ }
2088
2101
 
2089
2102
  }
2090
2103
 
@@ -2868,17 +2881,24 @@ class TalkingHead {
2868
2881
  }
2869
2882
 
2870
2883
  /**
2871
- * Get lip-sync processor based on language. Import module dynamically.
2884
+ * Get lip-sync processor based on language. Use statically imported modules.
2872
2885
  * @param {string} lang Language
2873
- * @param {string} [path="./"] Module path
2886
+ * @param {string} [path="./"] Module path (ignored, using static imports)
2874
2887
  */
2875
2888
  lipsyncGetProcessor(lang, path="./") {
2876
2889
  if ( !this.lipsync.hasOwnProperty(lang) ) {
2877
- const moduleName = path + 'lipsync-' + lang.toLowerCase() + '.mjs';
2878
- const className = 'Lipsync' + lang.charAt(0).toUpperCase() + lang.slice(1);
2879
- import(moduleName).then( module => {
2880
- this.lipsync[lang] = new module[className];
2881
- });
2890
+ const langLower = lang.toLowerCase();
2891
+ // Use statically imported modules from LIPSYNC_MODULES
2892
+ if (LIPSYNC_MODULES[langLower]) {
2893
+ const className = 'Lipsync' + lang.charAt(0).toUpperCase() + lang.slice(1);
2894
+ if (LIPSYNC_MODULES[langLower][className]) {
2895
+ this.lipsync[lang] = new LIPSYNC_MODULES[langLower][className];
2896
+ } else {
2897
+ console.warn(`Lipsync class ${className} not found in module for language ${lang}`);
2898
+ }
2899
+ } else {
2900
+ console.warn(`Lipsync module for language ${lang} not found in static imports`);
2901
+ }
2882
2902
  }
2883
2903
  }
2884
2904
 
@@ -4326,10 +4346,10 @@ class TalkingHead {
4326
4346
  setSlowdownRate(k) {
4327
4347
  this.animSlowdownRate = k;
4328
4348
  if ( this.audioSpeechSource ) {
4329
- this.audioSpeechSource.playbackRate.value = 1 / this.animSlowdownRate;
4349
+ this.audioSpeechSource.playbackRate.value = 1 / this.animSlowdownRate;
4330
4350
  }
4331
4351
  if ( this.audioBackgroundSource ) {
4332
- this.audioBackgroundSource.playbackRate.value = 1 / this.animSlowdownRate;
4352
+ this.audioBackgroundSource.playbackRate.value = 1 / this.animSlowdownRate;
4333
4353
  }
4334
4354
  }
4335
4355
 
@@ -4737,7 +4757,7 @@ class TalkingHead {
4737
4757
  this.mixer = null;
4738
4758
  this._mixerHandler = null;
4739
4759
  }
4740
-
4760
+
4741
4761
  // Unlock position if it was locked
4742
4762
  if (this.positionWasLocked) {
4743
4763
  this.unlockAvatarPosition();
@@ -4747,7 +4767,7 @@ class TalkingHead {
4747
4767
  }
4748
4768
 
4749
4769
  // Restart gesture
4750
- if ( this.gesture ) {
4770
+ if ( this.gesture && this.poseTarget && this.poseTarget.props ) {
4751
4771
  for( let [p,v] of Object.entries(this.gesture) ) {
4752
4772
  v.t = this.animClock;
4753
4773
  v.d = 1000;
@@ -4760,11 +4780,17 @@ class TalkingHead {
4760
4780
  }
4761
4781
 
4762
4782
  // Restart pose animation
4783
+ if ( this.animQueue ) {
4763
4784
  let anim = this.animQueue.find( x => x.template.name === 'pose' );
4764
4785
  if ( anim ) {
4765
4786
  anim.ts[0] = this.animClock;
4766
4787
  }
4788
+ }
4789
+
4790
+ // Only call setPoseFromTemplate if poseFactory exists (not disposed)
4791
+ if ( this.poseFactory ) {
4767
4792
  this.setPoseFromTemplate( null );
4793
+ }
4768
4794
 
4769
4795
  }
4770
4796
 
@@ -4799,7 +4825,7 @@ class TalkingHead {
4799
4825
  this.mixer.removeEventListener('finished', this._mixerHandler);
4800
4826
  this.mixer.stopAllAction();
4801
4827
  this.mixer.uncacheRoot(this.armature);
4802
- this.mixer = null;
4828
+ this.mixer = null;
4803
4829
  this._mixerHandler = null;
4804
4830
  }
4805
4831
  let anim = this.animQueue.find( x => x.template.name === 'pose' );
@@ -5514,7 +5540,7 @@ class TalkingHead {
5514
5540
  this.clearThree(this.scene);
5515
5541
  this.resizeobserver.disconnect();
5516
5542
  this.resizeobserver = null;
5517
-
5543
+
5518
5544
  if ( this.renderer ) {
5519
5545
  this.renderer.dispose();
5520
5546
  const gl = this.renderer.getContext();
@@ -5527,12 +5553,12 @@ class TalkingHead {
5527
5553
  if ( this.controls ) {
5528
5554
  this.controls.dispose();
5529
5555
  this.controls = null;
5530
- }
5556
+ }
5531
5557
  }
5532
5558
 
5533
5559
  this.clearThree( this.ikMesh );
5534
5560
  this.dynamicbones.dispose();
5535
-
5561
+
5536
5562
  // Clean up FBX animation loader
5537
5563
  if (this.fbxAnimationLoader) {
5538
5564
  this.fbxAnimationLoader.stopCurrentAnimation();
@@ -4448,7 +4448,7 @@ class TalkingHead {
4448
4448
 
4449
4449
  // Use existing mixer or create new one if none exists
4450
4450
  if (!this.mixer) {
4451
- this.mixer = new THREE.AnimationMixer(this.armature);
4451
+ this.mixer = new THREE.AnimationMixer(this.armature);
4452
4452
  console.log('Created new mixer for FBX animation');
4453
4453
  } else {
4454
4454
  console.log('Using existing mixer for FBX animation, preserving morph targets');
@@ -4465,7 +4465,7 @@ class TalkingHead {
4465
4465
  this.currentFBXAction = action;
4466
4466
 
4467
4467
  try {
4468
- action.fadeIn(0.5).play();
4468
+ action.fadeIn(0.5).play();
4469
4469
  console.log('FBX animation started successfully:', url);
4470
4470
  } catch (error) {
4471
4471
  console.warn('FBX animation failed to start:', error);