talking-head-studio 0.2.7 → 0.2.8
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/TalkingHead.js +47 -6
- package/dist/TalkingHead.web.js +46 -5
- package/dist/html.js +13 -5
- package/package.json +1 -1
package/dist/TalkingHead.js
CHANGED
|
@@ -9,6 +9,10 @@ const html_1 = require("./html");
|
|
|
9
9
|
exports.TalkingHead = (0, react_1.forwardRef)(({ avatarUrl, authToken, mood = 'neutral', cameraView = 'upper', cameraDistance = -0.5, hairColor, skinColor, eyeColor, accessories, onReady, onError, style, }, ref) => {
|
|
10
10
|
const webViewRef = (0, react_1.useRef)(null);
|
|
11
11
|
const readyRef = (0, react_1.useRef)(false);
|
|
12
|
+
const pendingMoodRef = (0, react_1.useRef)(mood);
|
|
13
|
+
const pendingHairColorRef = (0, react_1.useRef)(hairColor);
|
|
14
|
+
const pendingSkinColorRef = (0, react_1.useRef)(skinColor);
|
|
15
|
+
const pendingEyeColorRef = (0, react_1.useRef)(eyeColor);
|
|
12
16
|
const accessoriesRef = (0, react_1.useRef)(accessories);
|
|
13
17
|
// The WebView HTML is built once from stable initial values.
|
|
14
18
|
// avatarUrl + authToken changing causes a controlled key-based remount.
|
|
@@ -31,14 +35,36 @@ exports.TalkingHead = (0, react_1.forwardRef)(({ avatarUrl, authToken, mood = 'n
|
|
|
31
35
|
sendViseme: (viseme, weight = 1.0) => post({ type: 'viseme', viseme, weight }),
|
|
32
36
|
scheduleVisemes: (schedule) => post({ type: 'schedule_visemes', schedule }),
|
|
33
37
|
clearVisemes: () => post({ type: 'clear_visemes' }),
|
|
34
|
-
setMood: (nextMood) =>
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
38
|
+
setMood: (nextMood) => {
|
|
39
|
+
pendingMoodRef.current = nextMood;
|
|
40
|
+
if (readyRef.current)
|
|
41
|
+
post({ type: 'mood', value: nextMood });
|
|
42
|
+
},
|
|
43
|
+
setHairColor: (color) => {
|
|
44
|
+
pendingHairColorRef.current = color;
|
|
45
|
+
if (readyRef.current)
|
|
46
|
+
post({ type: 'hair_color', value: color });
|
|
47
|
+
},
|
|
48
|
+
setSkinColor: (color) => {
|
|
49
|
+
pendingSkinColorRef.current = color;
|
|
50
|
+
if (readyRef.current)
|
|
51
|
+
post({ type: 'skin_color', value: color });
|
|
52
|
+
},
|
|
53
|
+
setEyeColor: (color) => {
|
|
54
|
+
pendingEyeColorRef.current = color;
|
|
55
|
+
if (readyRef.current)
|
|
56
|
+
post({ type: 'eye_color', value: color });
|
|
57
|
+
},
|
|
58
|
+
setAccessories: (newAccessories) => {
|
|
59
|
+
accessoriesRef.current = newAccessories;
|
|
60
|
+
if (readyRef.current) {
|
|
61
|
+
post({ type: 'set_accessories', accessories: newAccessories });
|
|
62
|
+
}
|
|
63
|
+
},
|
|
39
64
|
}), [post]);
|
|
40
65
|
// Sync mood via postMessage only — never causes a WebView reload
|
|
41
66
|
(0, react_1.useEffect)(() => {
|
|
67
|
+
pendingMoodRef.current = mood;
|
|
42
68
|
if (readyRef.current)
|
|
43
69
|
post({ type: 'mood', value: mood });
|
|
44
70
|
}, [mood, post]);
|
|
@@ -49,14 +75,17 @@ exports.TalkingHead = (0, react_1.forwardRef)(({ avatarUrl, authToken, mood = 'n
|
|
|
49
75
|
}
|
|
50
76
|
}, [accessories, post]);
|
|
51
77
|
(0, react_1.useEffect)(() => {
|
|
78
|
+
pendingHairColorRef.current = hairColor;
|
|
52
79
|
if (hairColor && readyRef.current)
|
|
53
80
|
post({ type: 'hair_color', value: hairColor });
|
|
54
81
|
}, [hairColor, post]);
|
|
55
82
|
(0, react_1.useEffect)(() => {
|
|
83
|
+
pendingSkinColorRef.current = skinColor;
|
|
56
84
|
if (skinColor && readyRef.current)
|
|
57
85
|
post({ type: 'skin_color', value: skinColor });
|
|
58
86
|
}, [skinColor, post]);
|
|
59
87
|
(0, react_1.useEffect)(() => {
|
|
88
|
+
pendingEyeColorRef.current = eyeColor;
|
|
60
89
|
if (eyeColor && readyRef.current)
|
|
61
90
|
post({ type: 'eye_color', value: eyeColor });
|
|
62
91
|
}, [eyeColor, post]);
|
|
@@ -83,7 +112,19 @@ exports.TalkingHead = (0, react_1.forwardRef)(({ avatarUrl, authToken, mood = 'n
|
|
|
83
112
|
const msg = JSON.parse(event.nativeEvent.data);
|
|
84
113
|
if (msg.type === 'ready') {
|
|
85
114
|
readyRef.current = true;
|
|
86
|
-
// Flush pending
|
|
115
|
+
// Flush pending appearance updates that arrived before the WebView was ready.
|
|
116
|
+
if (pendingMoodRef.current) {
|
|
117
|
+
post({ type: 'mood', value: pendingMoodRef.current });
|
|
118
|
+
}
|
|
119
|
+
if (pendingHairColorRef.current) {
|
|
120
|
+
post({ type: 'hair_color', value: pendingHairColorRef.current });
|
|
121
|
+
}
|
|
122
|
+
if (pendingSkinColorRef.current) {
|
|
123
|
+
post({ type: 'skin_color', value: pendingSkinColorRef.current });
|
|
124
|
+
}
|
|
125
|
+
if (pendingEyeColorRef.current) {
|
|
126
|
+
post({ type: 'eye_color', value: pendingEyeColorRef.current });
|
|
127
|
+
}
|
|
87
128
|
if (accessoriesRef.current?.length) {
|
|
88
129
|
post({ type: 'set_accessories', accessories: accessoriesRef.current });
|
|
89
130
|
}
|
package/dist/TalkingHead.web.js
CHANGED
|
@@ -51,6 +51,10 @@ const iframeStyle = {
|
|
|
51
51
|
exports.TalkingHead = (0, react_1.forwardRef)(({ avatarUrl, authToken, mood = 'neutral', cameraView = 'upper', cameraDistance = -0.5, hairColor, skinColor, eyeColor, accessories, onReady, onError, style, }, ref) => {
|
|
52
52
|
const iframeRef = (0, react_1.useRef)(null);
|
|
53
53
|
const readyRef = (0, react_1.useRef)(false);
|
|
54
|
+
const pendingMoodRef = (0, react_1.useRef)(mood);
|
|
55
|
+
const pendingHairColorRef = (0, react_1.useRef)(hairColor);
|
|
56
|
+
const pendingSkinColorRef = (0, react_1.useRef)(skinColor);
|
|
57
|
+
const pendingEyeColorRef = (0, react_1.useRef)(eyeColor);
|
|
54
58
|
const accessoriesRef = (0, react_1.useRef)(accessories);
|
|
55
59
|
(0, react_1.useEffect)(() => {
|
|
56
60
|
accessoriesRef.current = accessories;
|
|
@@ -62,13 +66,35 @@ exports.TalkingHead = (0, react_1.forwardRef)(({ avatarUrl, authToken, mood = 'n
|
|
|
62
66
|
sendAmplitude: (amplitude) => post({ type: 'amplitude', value: amplitude }),
|
|
63
67
|
scheduleVisemes: (schedule) => post({ type: 'schedule_visemes', schedule }),
|
|
64
68
|
clearVisemes: () => post({ type: 'clear_visemes' }),
|
|
65
|
-
setMood: (nextMood) =>
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
69
|
+
setMood: (nextMood) => {
|
|
70
|
+
pendingMoodRef.current = nextMood;
|
|
71
|
+
if (readyRef.current)
|
|
72
|
+
post({ type: 'mood', value: nextMood });
|
|
73
|
+
},
|
|
74
|
+
setHairColor: (color) => {
|
|
75
|
+
pendingHairColorRef.current = color;
|
|
76
|
+
if (readyRef.current)
|
|
77
|
+
post({ type: 'hair_color', value: color });
|
|
78
|
+
},
|
|
79
|
+
setSkinColor: (color) => {
|
|
80
|
+
pendingSkinColorRef.current = color;
|
|
81
|
+
if (readyRef.current)
|
|
82
|
+
post({ type: 'skin_color', value: color });
|
|
83
|
+
},
|
|
84
|
+
setEyeColor: (color) => {
|
|
85
|
+
pendingEyeColorRef.current = color;
|
|
86
|
+
if (readyRef.current)
|
|
87
|
+
post({ type: 'eye_color', value: color });
|
|
88
|
+
},
|
|
89
|
+
setAccessories: (newAccessories) => {
|
|
90
|
+
accessoriesRef.current = newAccessories;
|
|
91
|
+
if (readyRef.current) {
|
|
92
|
+
post({ type: 'set_accessories', accessories: newAccessories });
|
|
93
|
+
}
|
|
94
|
+
},
|
|
70
95
|
}), [post]);
|
|
71
96
|
(0, react_1.useEffect)(() => {
|
|
97
|
+
pendingMoodRef.current = mood;
|
|
72
98
|
if (readyRef.current)
|
|
73
99
|
post({ type: 'mood', value: mood });
|
|
74
100
|
}, [mood, post]);
|
|
@@ -78,14 +104,17 @@ exports.TalkingHead = (0, react_1.forwardRef)(({ avatarUrl, authToken, mood = 'n
|
|
|
78
104
|
}
|
|
79
105
|
}, [accessories, post]);
|
|
80
106
|
(0, react_1.useEffect)(() => {
|
|
107
|
+
pendingHairColorRef.current = hairColor;
|
|
81
108
|
if (hairColor && readyRef.current)
|
|
82
109
|
post({ type: 'hair_color', value: hairColor });
|
|
83
110
|
}, [hairColor, post]);
|
|
84
111
|
(0, react_1.useEffect)(() => {
|
|
112
|
+
pendingSkinColorRef.current = skinColor;
|
|
85
113
|
if (skinColor && readyRef.current)
|
|
86
114
|
post({ type: 'skin_color', value: skinColor });
|
|
87
115
|
}, [skinColor, post]);
|
|
88
116
|
(0, react_1.useEffect)(() => {
|
|
117
|
+
pendingEyeColorRef.current = eyeColor;
|
|
89
118
|
if (eyeColor && readyRef.current)
|
|
90
119
|
post({ type: 'eye_color', value: eyeColor });
|
|
91
120
|
}, [eyeColor, post]);
|
|
@@ -124,6 +153,18 @@ exports.TalkingHead = (0, react_1.forwardRef)(({ avatarUrl, authToken, mood = 'n
|
|
|
124
153
|
const msg = typeof event.data === 'string' ? JSON.parse(event.data) : event.data;
|
|
125
154
|
if (msg.type === 'ready') {
|
|
126
155
|
readyRef.current = true;
|
|
156
|
+
if (pendingMoodRef.current) {
|
|
157
|
+
post({ type: 'mood', value: pendingMoodRef.current });
|
|
158
|
+
}
|
|
159
|
+
if (pendingHairColorRef.current) {
|
|
160
|
+
post({ type: 'hair_color', value: pendingHairColorRef.current });
|
|
161
|
+
}
|
|
162
|
+
if (pendingSkinColorRef.current) {
|
|
163
|
+
post({ type: 'skin_color', value: pendingSkinColorRef.current });
|
|
164
|
+
}
|
|
165
|
+
if (pendingEyeColorRef.current) {
|
|
166
|
+
post({ type: 'eye_color', value: pendingEyeColorRef.current });
|
|
167
|
+
}
|
|
127
168
|
if (accessoriesRef.current?.length) {
|
|
128
169
|
post({ type: 'set_accessories', accessories: accessoriesRef.current });
|
|
129
170
|
}
|
package/dist/html.js
CHANGED
|
@@ -1,10 +1,18 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.buildAvatarHtml = buildAvatarHtml;
|
|
4
|
-
const
|
|
4
|
+
const UPSTREAM_SAFE_MOOD_MAP = {
|
|
5
|
+
neutral: 'neutral',
|
|
6
|
+
happy: 'happy',
|
|
7
|
+
sad: 'sad',
|
|
8
|
+
angry: 'angry',
|
|
9
|
+
excited: 'happy',
|
|
10
|
+
thinking: 'neutral',
|
|
11
|
+
concerned: 'sad',
|
|
12
|
+
surprised: 'happy',
|
|
13
|
+
};
|
|
5
14
|
function buildAvatarHtml(config) {
|
|
6
|
-
|
|
7
|
-
const safeMood = VALID_MOODS.has(config.mood) ? config.mood : 'neutral';
|
|
15
|
+
const safeMood = UPSTREAM_SAFE_MOOD_MAP[config.mood] ?? 'neutral';
|
|
8
16
|
return `
|
|
9
17
|
<!DOCTYPE html>
|
|
10
18
|
<html>
|
|
@@ -33,6 +41,7 @@ const TALKING_HEAD_URL = 'https://cdn.jsdelivr.net/gh/met4citizen/TalkingHead@1.
|
|
|
33
41
|
const HEAD_AUDIO_URL = 'https://cdn.jsdelivr.net/gh/met4citizen/HeadAudio@v0.1.0-alpha/dist/headaudio.min.mjs';
|
|
34
42
|
const HEAD_AUDIO_WORKLET = 'https://cdn.jsdelivr.net/gh/met4citizen/HeadAudio@v0.1.0-alpha/dist/headworklet.min.mjs';
|
|
35
43
|
const HEAD_AUDIO_MODEL = 'https://cdn.jsdelivr.net/gh/met4citizen/HeadAudio@v0.1.0-alpha/dist/model-en-mixed.bin';
|
|
44
|
+
const MOOD_MAP = ${JSON.stringify(UPSTREAM_SAFE_MOOD_MAP)};
|
|
36
45
|
|
|
37
46
|
let AVATAR_URL = ${JSON.stringify(config.avatarUrl)};
|
|
38
47
|
const INITIAL_MOOD = ${JSON.stringify(safeMood)};
|
|
@@ -640,8 +649,7 @@ function onIncomingMessage(event) {
|
|
|
640
649
|
} else if (msg.type === 'clear_visemes') {
|
|
641
650
|
clearScheduledVisemes();
|
|
642
651
|
} else if (msg.type === 'mood' && head) {
|
|
643
|
-
|
|
644
|
-
head.setMood(moodMap[msg.value] || 'neutral');
|
|
652
|
+
head.setMood(MOOD_MAP[msg.value] || 'neutral');
|
|
645
653
|
} else if (msg.type === 'hair_color') {
|
|
646
654
|
HAIR_COLOR = msg.value; applyColorOverrides();
|
|
647
655
|
} else if (msg.type === 'skin_color') {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "talking-head-studio",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.8",
|
|
4
4
|
"description": "Cross-platform 3D avatar component for React Native & web — lip-sync, gestures, accessories, and LLM integration. Powered by TalkingHead + Three.js.",
|
|
5
5
|
"main": "dist/index.web.js",
|
|
6
6
|
"browser": "dist/index.web.js",
|