talking-head-studio 0.2.6 → 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/README.md +8 -7
- package/dist/TalkingHead.d.ts +0 -1
- package/dist/TalkingHead.js +78 -35
- package/dist/TalkingHead.web.d.ts +1 -3
- package/dist/TalkingHead.web.js +118 -48
- package/dist/appearance/apply.d.ts +0 -1
- package/dist/appearance/apply.js +8 -5
- package/dist/appearance/index.d.ts +0 -1
- package/dist/appearance/index.js +9 -3
- package/dist/appearance/matchers.d.ts +0 -1
- package/dist/appearance/matchers.js +4 -1
- package/dist/appearance/schema.d.ts +0 -1
- package/dist/appearance/schema.js +4 -1
- package/dist/editor/AvatarCanvas.d.ts +1 -2
- package/dist/editor/AvatarCanvas.js +44 -60
- package/dist/editor/AvatarCanvasErrorBoundary.d.ts +1 -2
- package/dist/editor/AvatarCanvasErrorBoundary.js +9 -14
- package/dist/editor/AvatarModel.d.ts +1 -2
- package/dist/editor/AvatarModel.js +17 -13
- package/dist/editor/RigidAccessory.d.ts +1 -2
- package/dist/editor/RigidAccessory.js +19 -17
- package/dist/editor/SkinnedClothing.d.ts +1 -2
- package/dist/editor/SkinnedClothing.js +46 -9
- package/dist/editor/index.d.ts +0 -1
- package/dist/editor/index.js +11 -4
- package/dist/editor/types.d.ts +0 -1
- package/dist/editor/types.js +2 -1
- package/dist/html.d.ts +0 -1
- package/dist/html.js +21 -10
- package/dist/index.d.ts +0 -1
- package/dist/index.js +20 -2
- package/dist/index.web.d.ts +0 -1
- package/dist/index.web.js +20 -2
- package/dist/sketchfab/api.d.ts +0 -1
- package/dist/sketchfab/api.js +10 -4
- package/dist/sketchfab/categories.d.ts +0 -1
- package/dist/sketchfab/categories.js +5 -2
- package/dist/sketchfab/index.d.ts +0 -1
- package/dist/sketchfab/index.js +13 -3
- package/dist/sketchfab/types.d.ts +0 -1
- package/dist/sketchfab/types.js +2 -1
- package/dist/sketchfab/useSketchfabSearch.d.ts +0 -1
- package/dist/sketchfab/useSketchfabSearch.js +20 -17
- package/dist/voice/convertToWav.d.ts +0 -1
- package/dist/voice/convertToWav.js +4 -1
- package/dist/voice/index.d.ts +0 -1
- package/dist/voice/index.js +9 -3
- package/dist/voice/useAudioPlayer.d.ts +0 -1
- package/dist/voice/useAudioPlayer.js +7 -4
- package/dist/voice/useAudioRecording.d.ts +0 -1
- package/dist/voice/useAudioRecording.js +20 -17
- package/package.json +10 -8
- package/dist/TalkingHead.d.ts.map +0 -1
- package/dist/TalkingHead.web.d.ts.map +0 -1
- package/dist/__tests__/TalkingHead.test.d.ts +0 -2
- package/dist/__tests__/TalkingHead.test.d.ts.map +0 -1
- package/dist/__tests__/TalkingHead.test.js +0 -23
- package/dist/__tests__/sketchfab.test.d.ts +0 -2
- package/dist/__tests__/sketchfab.test.d.ts.map +0 -1
- package/dist/__tests__/sketchfab.test.js +0 -21
- package/dist/appearance/apply.d.ts.map +0 -1
- package/dist/appearance/index.d.ts.map +0 -1
- package/dist/appearance/matchers.d.ts.map +0 -1
- package/dist/appearance/schema.d.ts.map +0 -1
- package/dist/editor/AvatarCanvas.d.ts.map +0 -1
- package/dist/editor/AvatarCanvasErrorBoundary.d.ts.map +0 -1
- package/dist/editor/AvatarModel.d.ts.map +0 -1
- package/dist/editor/RigidAccessory.d.ts.map +0 -1
- package/dist/editor/SkinnedClothing.d.ts.map +0 -1
- package/dist/editor/index.d.ts.map +0 -1
- package/dist/editor/types.d.ts.map +0 -1
- package/dist/html.d.ts.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.web.d.ts.map +0 -1
- package/dist/sketchfab/api.d.ts.map +0 -1
- package/dist/sketchfab/categories.d.ts.map +0 -1
- package/dist/sketchfab/index.d.ts.map +0 -1
- package/dist/sketchfab/types.d.ts.map +0 -1
- package/dist/sketchfab/useSketchfabSearch.d.ts.map +0 -1
- package/dist/voice/convertToWav.d.ts.map +0 -1
- package/dist/voice/index.d.ts.map +0 -1
- package/dist/voice/useAudioPlayer.d.ts.map +0 -1
- package/dist/voice/useAudioRecording.d.ts.map +0 -1
- package/src/TalkingHead.tsx +0 -276
- package/src/TalkingHead.web.tsx +0 -220
- package/src/__tests__/TalkingHead.test.tsx +0 -32
- package/src/__tests__/sketchfab.test.ts +0 -24
- package/src/appearance/apply.ts +0 -94
- package/src/appearance/index.ts +0 -4
- package/src/appearance/matchers.ts +0 -43
- package/src/appearance/schema.ts +0 -35
- package/src/editor/AvatarCanvas.tsx +0 -167
- package/src/editor/AvatarCanvasErrorBoundary.tsx +0 -64
- package/src/editor/AvatarModel.tsx +0 -49
- package/src/editor/RigidAccessory.tsx +0 -130
- package/src/editor/SkinnedClothing.tsx +0 -114
- package/src/editor/index.ts +0 -5
- package/src/editor/r3f-shim.d.ts +0 -34
- package/src/editor/types.ts +0 -30
- package/src/html.ts +0 -678
- package/src/index.ts +0 -11
- package/src/index.web.ts +0 -8
- package/src/sketchfab/api.ts +0 -82
- package/src/sketchfab/categories.ts +0 -127
- package/src/sketchfab/index.ts +0 -6
- package/src/sketchfab/types.ts +0 -40
- package/src/sketchfab/useSketchfabSearch.ts +0 -110
- package/src/voice/convertToWav.ts +0 -87
- package/src/voice/index.ts +0 -7
- package/src/voice/useAudioPlayer.ts +0 -78
- package/src/voice/useAudioRecording.ts +0 -207
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
[](https://www.npmjs.com/package/talking-head-studio)
|
|
6
6
|
[](https://opensource.org/licenses/MIT)
|
|
7
|
-
[](https://github.com/sitebay/talking-head-studio/actions)
|
|
8
8
|
|
|
9
9
|
---
|
|
10
10
|
|
|
@@ -52,7 +52,7 @@ npm install talking-head-studio react-native-webview
|
|
|
52
52
|
npm install talking-head-studio
|
|
53
53
|
```
|
|
54
54
|
|
|
55
|
-
No WebView dependency needed. The package ships a
|
|
55
|
+
No `react-native` or WebView runtime dependency needed. The package ships a web entry point that renders via `<iframe srcdoc>` automatically when bundled for browser targets.
|
|
56
56
|
|
|
57
57
|
---
|
|
58
58
|
|
|
@@ -100,7 +100,8 @@ The package ships five independent entry points. Import only what you need — e
|
|
|
100
100
|
### `talking-head-studio` — Live talking avatar
|
|
101
101
|
```tsx
|
|
102
102
|
import { TalkingHead } from 'talking-head-studio';
|
|
103
|
-
// Peer deps: react
|
|
103
|
+
// Peer deps: react
|
|
104
|
+
// Native-only peers: react-native (optional), react-native-webview (optional)
|
|
104
105
|
```
|
|
105
106
|
|
|
106
107
|
### `talking-head-studio/editor` — 3D editor with gizmo (web)
|
|
@@ -398,7 +399,7 @@ For detailed model authoring guidance, see the [TalkingHead documentation](https
|
|
|
398
399
|
|
|
399
400
|
## Plain React / Next.js
|
|
400
401
|
|
|
401
|
-
|
|
402
|
+
This works on the web without `react-native` or `react-native-webview` installed at runtime.
|
|
402
403
|
|
|
403
404
|
On web, the component renders an `<iframe>` with `srcdoc` containing the full Three.js scene. No WebView, no native modules, no build plugins.
|
|
404
405
|
|
|
@@ -417,7 +418,7 @@ export default function Page() {
|
|
|
417
418
|
}
|
|
418
419
|
```
|
|
419
420
|
|
|
420
|
-
|
|
421
|
+
Metro and Expo use the native entry backed by `react-native-webview`. Standard web bundlers use the browser entry backed by a plain `<iframe>`. The API is identical.
|
|
421
422
|
|
|
422
423
|
---
|
|
423
424
|
|
|
@@ -434,8 +435,8 @@ Stay tuned.
|
|
|
434
435
|
Contributions are welcome. Please open an issue to discuss your idea before submitting a pull request.
|
|
435
436
|
|
|
436
437
|
```bash
|
|
437
|
-
git clone https://github.com/sitebay/
|
|
438
|
-
cd
|
|
438
|
+
git clone https://github.com/sitebay/talking-head-studio.git
|
|
439
|
+
cd talking-head-studio
|
|
439
440
|
npm install
|
|
440
441
|
npm run typecheck
|
|
441
442
|
npm test
|
package/dist/TalkingHead.d.ts
CHANGED
|
@@ -86,4 +86,3 @@ export interface TalkingHeadRef {
|
|
|
86
86
|
setAccessories: (accessories: TalkingHeadAccessory[]) => void;
|
|
87
87
|
}
|
|
88
88
|
export declare const TalkingHead: React.ForwardRefExoticComponent<TalkingHeadProps & React.RefAttributes<TalkingHeadRef>>;
|
|
89
|
-
//# sourceMappingURL=TalkingHead.d.ts.map
|
package/dist/TalkingHead.js
CHANGED
|
@@ -1,17 +1,25 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.TalkingHead = void 0;
|
|
4
|
+
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
5
|
+
const react_1 = require("react");
|
|
6
|
+
const react_native_1 = require("react-native");
|
|
7
|
+
const react_native_webview_1 = require("react-native-webview");
|
|
8
|
+
const html_1 = require("./html");
|
|
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
|
+
const webViewRef = (0, react_1.useRef)(null);
|
|
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);
|
|
16
|
+
const accessoriesRef = (0, react_1.useRef)(accessories);
|
|
9
17
|
// The WebView HTML is built once from stable initial values.
|
|
10
18
|
// avatarUrl + authToken changing causes a controlled key-based remount.
|
|
11
19
|
// All other prop changes (mood, colors, accessories) go via postMessage.
|
|
12
|
-
const [webViewKey, setWebViewKey] = useState(() => `${avatarUrl}__${authToken ?? ''}`);
|
|
13
|
-
const prevAvatarRef = useRef({ avatarUrl, authToken });
|
|
14
|
-
useEffect(() => {
|
|
20
|
+
const [webViewKey, setWebViewKey] = (0, react_1.useState)(() => `${avatarUrl}__${authToken ?? ''}`);
|
|
21
|
+
const prevAvatarRef = (0, react_1.useRef)({ avatarUrl, authToken });
|
|
22
|
+
(0, react_1.useEffect)(() => {
|
|
15
23
|
const prev = prevAvatarRef.current;
|
|
16
24
|
if (prev.avatarUrl !== avatarUrl || prev.authToken !== authToken) {
|
|
17
25
|
prevAvatarRef.current = { avatarUrl, authToken };
|
|
@@ -19,50 +27,75 @@ export const TalkingHead = forwardRef(({ avatarUrl, authToken, mood = 'neutral',
|
|
|
19
27
|
setWebViewKey(`${avatarUrl}__${authToken ?? ''}`);
|
|
20
28
|
}
|
|
21
29
|
}, [avatarUrl, authToken]);
|
|
22
|
-
const post = useCallback((msg) => {
|
|
30
|
+
const post = (0, react_1.useCallback)((msg) => {
|
|
23
31
|
webViewRef.current?.postMessage(JSON.stringify(msg));
|
|
24
32
|
}, []);
|
|
25
|
-
useImperativeHandle(ref, () => ({
|
|
33
|
+
(0, react_1.useImperativeHandle)(ref, () => ({
|
|
26
34
|
sendAmplitude: (amplitude) => post({ type: 'amplitude', value: amplitude }),
|
|
27
35
|
sendViseme: (viseme, weight = 1.0) => post({ type: 'viseme', viseme, weight }),
|
|
28
36
|
scheduleVisemes: (schedule) => post({ type: 'schedule_visemes', schedule }),
|
|
29
37
|
clearVisemes: () => post({ type: 'clear_visemes' }),
|
|
30
|
-
setMood: (nextMood) =>
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
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
|
+
},
|
|
35
64
|
}), [post]);
|
|
36
65
|
// Sync mood via postMessage only — never causes a WebView reload
|
|
37
|
-
useEffect(() => {
|
|
66
|
+
(0, react_1.useEffect)(() => {
|
|
67
|
+
pendingMoodRef.current = mood;
|
|
38
68
|
if (readyRef.current)
|
|
39
69
|
post({ type: 'mood', value: mood });
|
|
40
70
|
}, [mood, post]);
|
|
41
|
-
useEffect(() => {
|
|
71
|
+
(0, react_1.useEffect)(() => {
|
|
42
72
|
accessoriesRef.current = accessories;
|
|
43
73
|
if (accessories && readyRef.current) {
|
|
44
74
|
post({ type: 'set_accessories', accessories });
|
|
45
75
|
}
|
|
46
76
|
}, [accessories, post]);
|
|
47
|
-
useEffect(() => {
|
|
77
|
+
(0, react_1.useEffect)(() => {
|
|
78
|
+
pendingHairColorRef.current = hairColor;
|
|
48
79
|
if (hairColor && readyRef.current)
|
|
49
80
|
post({ type: 'hair_color', value: hairColor });
|
|
50
81
|
}, [hairColor, post]);
|
|
51
|
-
useEffect(() => {
|
|
82
|
+
(0, react_1.useEffect)(() => {
|
|
83
|
+
pendingSkinColorRef.current = skinColor;
|
|
52
84
|
if (skinColor && readyRef.current)
|
|
53
85
|
post({ type: 'skin_color', value: skinColor });
|
|
54
86
|
}, [skinColor, post]);
|
|
55
|
-
useEffect(() => {
|
|
87
|
+
(0, react_1.useEffect)(() => {
|
|
88
|
+
pendingEyeColorRef.current = eyeColor;
|
|
56
89
|
if (eyeColor && readyRef.current)
|
|
57
90
|
post({ type: 'eye_color', value: eyeColor });
|
|
58
91
|
}, [eyeColor, post]);
|
|
59
92
|
// Capture stable initial values at first mount only
|
|
60
|
-
const [initialMood] = useState(mood);
|
|
61
|
-
const [initialHairColor] = useState(hairColor);
|
|
62
|
-
const [initialSkinColor] = useState(skinColor);
|
|
63
|
-
const [initialEyeColor] = useState(eyeColor);
|
|
93
|
+
const [initialMood] = (0, react_1.useState)(mood);
|
|
94
|
+
const [initialHairColor] = (0, react_1.useState)(hairColor);
|
|
95
|
+
const [initialSkinColor] = (0, react_1.useState)(skinColor);
|
|
96
|
+
const [initialEyeColor] = (0, react_1.useState)(eyeColor);
|
|
64
97
|
// html is stable — only rebuilds when webViewKey changes (avatarUrl/authToken)
|
|
65
|
-
const html = useMemo(() => buildAvatarHtml({
|
|
98
|
+
const html = (0, react_1.useMemo)(() => (0, html_1.buildAvatarHtml)({
|
|
66
99
|
avatarUrl,
|
|
67
100
|
authToken,
|
|
68
101
|
mood: initialMood,
|
|
@@ -74,12 +107,24 @@ export const TalkingHead = forwardRef(({ avatarUrl, authToken, mood = 'neutral',
|
|
|
74
107
|
}),
|
|
75
108
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
76
109
|
[webViewKey]);
|
|
77
|
-
const onMessage = useCallback((event) => {
|
|
110
|
+
const onMessage = (0, react_1.useCallback)((event) => {
|
|
78
111
|
try {
|
|
79
112
|
const msg = JSON.parse(event.nativeEvent.data);
|
|
80
113
|
if (msg.type === 'ready') {
|
|
81
114
|
readyRef.current = true;
|
|
82
|
-
// 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
|
+
}
|
|
83
128
|
if (accessoriesRef.current?.length) {
|
|
84
129
|
post({ type: 'set_accessories', accessories: accessoriesRef.current });
|
|
85
130
|
}
|
|
@@ -96,12 +141,10 @@ export const TalkingHead = forwardRef(({ avatarUrl, authToken, mood = 'neutral',
|
|
|
96
141
|
console.warn('[TalkingHead] Invalid message from WebView:', err);
|
|
97
142
|
}
|
|
98
143
|
}, [onReady, onError, post]);
|
|
99
|
-
return (
|
|
100
|
-
<WebView key={webViewKey} ref={webViewRef} source={{ html }} style={styles.webview} javaScriptEnabled domStorageEnabled allowsInlineMediaPlayback mediaPlaybackRequiresUserAction={false} onMessage={onMessage} originWhitelist={['*']} mixedContentMode="always"/>
|
|
101
|
-
</View>);
|
|
144
|
+
return ((0, jsx_runtime_1.jsx)(react_native_1.View, { style: [styles.container, style], children: (0, jsx_runtime_1.jsx)(react_native_webview_1.WebView, { ref: webViewRef, source: { html }, style: styles.webview, javaScriptEnabled: true, domStorageEnabled: true, allowsInlineMediaPlayback: true, mediaPlaybackRequiresUserAction: false, onMessage: onMessage, originWhitelist: ['*'], mixedContentMode: "always" }, webViewKey) }));
|
|
102
145
|
});
|
|
103
|
-
TalkingHead.displayName = 'TalkingHead';
|
|
104
|
-
const styles = StyleSheet.create({
|
|
146
|
+
exports.TalkingHead.displayName = 'TalkingHead';
|
|
147
|
+
const styles = react_native_1.StyleSheet.create({
|
|
105
148
|
container: {
|
|
106
149
|
overflow: 'hidden',
|
|
107
150
|
borderRadius: 12,
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import { type StyleProp, type ViewStyle } from 'react-native';
|
|
3
2
|
import type { TalkingHeadVisemeCue, TalkingHeadVisemeSchedule } from './TalkingHead';
|
|
4
3
|
export type { TalkingHeadVisemeCue, TalkingHeadVisemeSchedule };
|
|
5
4
|
export type TalkingHeadMood = 'neutral' | 'happy' | 'sad' | 'angry' | 'excited' | 'thinking' | 'concerned' | 'surprised';
|
|
@@ -23,7 +22,7 @@ export interface TalkingHeadProps {
|
|
|
23
22
|
accessories?: TalkingHeadAccessory[];
|
|
24
23
|
onReady?: () => void;
|
|
25
24
|
onError?: (message: string) => void;
|
|
26
|
-
style?:
|
|
25
|
+
style?: React.CSSProperties;
|
|
27
26
|
}
|
|
28
27
|
export interface TalkingHeadRef {
|
|
29
28
|
sendAmplitude: (amplitude: number) => void;
|
|
@@ -36,4 +35,3 @@ export interface TalkingHeadRef {
|
|
|
36
35
|
setAccessories: (accessories: TalkingHeadAccessory[]) => void;
|
|
37
36
|
}
|
|
38
37
|
export declare const TalkingHead: React.ForwardRefExoticComponent<TalkingHeadProps & React.RefAttributes<TalkingHeadRef>>;
|
|
39
|
-
//# sourceMappingURL=TalkingHead.web.d.ts.map
|
package/dist/TalkingHead.web.js
CHANGED
|
@@ -1,52 +1,128 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.TalkingHead = void 0;
|
|
37
|
+
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
38
|
+
const react_1 = __importStar(require("react"));
|
|
39
|
+
const html_1 = require("./html");
|
|
40
|
+
const containerStyle = {
|
|
41
|
+
overflow: 'hidden',
|
|
42
|
+
borderRadius: 12,
|
|
43
|
+
backgroundColor: 'transparent',
|
|
44
|
+
};
|
|
45
|
+
const iframeStyle = {
|
|
46
|
+
width: '100%',
|
|
47
|
+
height: '100%',
|
|
48
|
+
border: 'none',
|
|
49
|
+
backgroundColor: 'transparent',
|
|
50
|
+
};
|
|
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
|
+
const iframeRef = (0, react_1.useRef)(null);
|
|
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);
|
|
58
|
+
const accessoriesRef = (0, react_1.useRef)(accessories);
|
|
59
|
+
(0, react_1.useEffect)(() => {
|
|
9
60
|
accessoriesRef.current = accessories;
|
|
10
61
|
}, [accessories]);
|
|
11
|
-
const post = useCallback((msg) => {
|
|
62
|
+
const post = (0, react_1.useCallback)((msg) => {
|
|
12
63
|
iframeRef.current?.contentWindow?.postMessage(JSON.stringify(msg), '*');
|
|
13
64
|
}, []);
|
|
14
|
-
useImperativeHandle(ref, () => ({
|
|
65
|
+
(0, react_1.useImperativeHandle)(ref, () => ({
|
|
15
66
|
sendAmplitude: (amplitude) => post({ type: 'amplitude', value: amplitude }),
|
|
16
67
|
scheduleVisemes: (schedule) => post({ type: 'schedule_visemes', schedule }),
|
|
17
68
|
clearVisemes: () => post({ type: 'clear_visemes' }),
|
|
18
|
-
setMood: (nextMood) =>
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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
|
+
},
|
|
23
95
|
}), [post]);
|
|
24
|
-
useEffect(() => {
|
|
96
|
+
(0, react_1.useEffect)(() => {
|
|
97
|
+
pendingMoodRef.current = mood;
|
|
25
98
|
if (readyRef.current)
|
|
26
99
|
post({ type: 'mood', value: mood });
|
|
27
100
|
}, [mood, post]);
|
|
28
|
-
useEffect(() => {
|
|
101
|
+
(0, react_1.useEffect)(() => {
|
|
29
102
|
if (accessories && readyRef.current) {
|
|
30
103
|
post({ type: 'set_accessories', accessories });
|
|
31
104
|
}
|
|
32
105
|
}, [accessories, post]);
|
|
33
|
-
useEffect(() => {
|
|
106
|
+
(0, react_1.useEffect)(() => {
|
|
107
|
+
pendingHairColorRef.current = hairColor;
|
|
34
108
|
if (hairColor && readyRef.current)
|
|
35
109
|
post({ type: 'hair_color', value: hairColor });
|
|
36
110
|
}, [hairColor, post]);
|
|
37
|
-
useEffect(() => {
|
|
111
|
+
(0, react_1.useEffect)(() => {
|
|
112
|
+
pendingSkinColorRef.current = skinColor;
|
|
38
113
|
if (skinColor && readyRef.current)
|
|
39
114
|
post({ type: 'skin_color', value: skinColor });
|
|
40
115
|
}, [skinColor, post]);
|
|
41
|
-
useEffect(() => {
|
|
116
|
+
(0, react_1.useEffect)(() => {
|
|
117
|
+
pendingEyeColorRef.current = eyeColor;
|
|
42
118
|
if (eyeColor && readyRef.current)
|
|
43
119
|
post({ type: 'eye_color', value: eyeColor });
|
|
44
120
|
}, [eyeColor, post]);
|
|
45
|
-
const [initialMood] =
|
|
46
|
-
const [initialHairColor] =
|
|
47
|
-
const [initialSkinColor] =
|
|
48
|
-
const [initialEyeColor] =
|
|
49
|
-
const html = useMemo(() => buildAvatarHtml({
|
|
121
|
+
const [initialMood] = react_1.default.useState(mood);
|
|
122
|
+
const [initialHairColor] = react_1.default.useState(hairColor);
|
|
123
|
+
const [initialSkinColor] = react_1.default.useState(skinColor);
|
|
124
|
+
const [initialEyeColor] = react_1.default.useState(eyeColor);
|
|
125
|
+
const html = (0, react_1.useMemo)(() => (0, html_1.buildAvatarHtml)({
|
|
50
126
|
avatarUrl,
|
|
51
127
|
authToken,
|
|
52
128
|
mood: initialMood,
|
|
@@ -65,22 +141,30 @@ export const TalkingHead = forwardRef(({ avatarUrl, authToken, mood = 'neutral',
|
|
|
65
141
|
initialSkinColor,
|
|
66
142
|
initialEyeColor,
|
|
67
143
|
]);
|
|
68
|
-
|
|
69
|
-
// inject that shim so messages come back to us via window.addEventListener.
|
|
70
|
-
const srcdoc = useMemo(() => {
|
|
144
|
+
const srcdoc = (0, react_1.useMemo)(() => {
|
|
71
145
|
const shim = `<script>window.ReactNativeWebView = { postMessage: function(d) { window.parent.postMessage(d, '*'); } };</script>`;
|
|
72
|
-
// Insert the shim right after <head> so it's available before the module script runs
|
|
73
146
|
return html.replace('<head>', '<head>' + shim);
|
|
74
147
|
}, [html]);
|
|
75
|
-
useEffect(() => {
|
|
148
|
+
(0, react_1.useEffect)(() => {
|
|
76
149
|
const onMessage = (event) => {
|
|
77
|
-
// Only accept messages from our iframe
|
|
78
150
|
if (iframeRef.current && event.source !== iframeRef.current.contentWindow)
|
|
79
151
|
return;
|
|
80
152
|
try {
|
|
81
153
|
const msg = typeof event.data === 'string' ? JSON.parse(event.data) : event.data;
|
|
82
154
|
if (msg.type === 'ready') {
|
|
83
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
|
+
}
|
|
84
168
|
if (accessoriesRef.current?.length) {
|
|
85
169
|
post({ type: 'set_accessories', accessories: accessoriesRef.current });
|
|
86
170
|
}
|
|
@@ -94,26 +178,12 @@ export const TalkingHead = forwardRef(({ avatarUrl, authToken, mood = 'neutral',
|
|
|
94
178
|
}
|
|
95
179
|
}
|
|
96
180
|
catch {
|
|
97
|
-
//
|
|
181
|
+
// Ignore non-JSON window messages from other browser integrations.
|
|
98
182
|
}
|
|
99
183
|
};
|
|
100
184
|
window.addEventListener('message', onMessage);
|
|
101
185
|
return () => window.removeEventListener('message', onMessage);
|
|
102
186
|
}, [onReady, onError, post]);
|
|
103
|
-
return (
|
|
104
|
-
<iframe ref={iframeRef} srcDoc={srcdoc} style={{
|
|
105
|
-
width: '100%',
|
|
106
|
-
height: '100%',
|
|
107
|
-
border: 'none',
|
|
108
|
-
backgroundColor: 'transparent',
|
|
109
|
-
}} sandbox="allow-scripts allow-same-origin" title="TalkingHead Avatar"/>
|
|
110
|
-
</View>);
|
|
111
|
-
});
|
|
112
|
-
TalkingHead.displayName = 'TalkingHead';
|
|
113
|
-
const styles = StyleSheet.create({
|
|
114
|
-
container: {
|
|
115
|
-
overflow: 'hidden',
|
|
116
|
-
borderRadius: 12,
|
|
117
|
-
backgroundColor: 'transparent',
|
|
118
|
-
},
|
|
187
|
+
return ((0, jsx_runtime_1.jsx)("div", { style: { ...containerStyle, ...style }, children: (0, jsx_runtime_1.jsx)("iframe", { ref: iframeRef, srcDoc: srcdoc, style: iframeStyle, sandbox: "allow-scripts allow-same-origin", title: "TalkingHead Avatar" }) }));
|
|
119
188
|
});
|
|
189
|
+
exports.TalkingHead.displayName = 'TalkingHead';
|
package/dist/appearance/apply.js
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.applyAppearanceToObject3D = applyAppearanceToObject3D;
|
|
4
|
+
const matchers_1 = require("./matchers");
|
|
5
|
+
const schema_1 = require("./schema");
|
|
3
6
|
function asMaterialArray(material) {
|
|
4
7
|
if (!material) {
|
|
5
8
|
return [];
|
|
@@ -9,11 +12,11 @@ function asMaterialArray(material) {
|
|
|
9
12
|
function getVertexCount(mesh) {
|
|
10
13
|
return mesh.geometry?.attributes?.position?.count ?? 0;
|
|
11
14
|
}
|
|
12
|
-
|
|
15
|
+
function applyAppearanceToObject3D(object3d, appearance) {
|
|
13
16
|
if (typeof object3d.traverse !== 'function') {
|
|
14
17
|
return;
|
|
15
18
|
}
|
|
16
|
-
const normalizedAppearance = normalizeAppearance(appearance);
|
|
19
|
+
const normalizedAppearance = (0, schema_1.normalizeAppearance)(appearance);
|
|
17
20
|
// First pass: apply name-matched colors and track whether skin was matched,
|
|
18
21
|
// also track the largest mesh for fallback skin coloring.
|
|
19
22
|
let skinMatched = false;
|
|
@@ -29,7 +32,7 @@ export function applyAppearanceToObject3D(object3d, appearance) {
|
|
|
29
32
|
if (!material || typeof material.name !== 'string') {
|
|
30
33
|
continue;
|
|
31
34
|
}
|
|
32
|
-
const target = pickTargetForMaterialName(material.name);
|
|
35
|
+
const target = (0, matchers_1.pickTargetForMaterialName)(material.name);
|
|
33
36
|
if (!target) {
|
|
34
37
|
// Track the largest unmatched mesh as a skin fallback candidate
|
|
35
38
|
if (vertexCount > largestVertexCount && material.color && typeof material.color.set === 'function') {
|
package/dist/appearance/index.js
CHANGED
|
@@ -1,3 +1,9 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.normalizeAppearance = exports.pickTargetForMaterialName = exports.applyAppearanceToObject3D = void 0;
|
|
4
|
+
var apply_1 = require("./apply");
|
|
5
|
+
Object.defineProperty(exports, "applyAppearanceToObject3D", { enumerable: true, get: function () { return apply_1.applyAppearanceToObject3D; } });
|
|
6
|
+
var matchers_1 = require("./matchers");
|
|
7
|
+
Object.defineProperty(exports, "pickTargetForMaterialName", { enumerable: true, get: function () { return matchers_1.pickTargetForMaterialName; } });
|
|
8
|
+
var schema_1 = require("./schema");
|
|
9
|
+
Object.defineProperty(exports, "normalizeAppearance", { enumerable: true, get: function () { return schema_1.normalizeAppearance; } });
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.pickTargetForMaterialName = pickTargetForMaterialName;
|
|
1
4
|
function tokenizeMaterialName(name) {
|
|
2
5
|
return name
|
|
3
6
|
.replace(/([a-z\d])([A-Z])/g, '$1 $2')
|
|
@@ -15,7 +18,7 @@ function hasToken(tokens, candidates) {
|
|
|
15
18
|
function hasTokenPrefix(tokens, candidates) {
|
|
16
19
|
return candidates.some((candidate) => tokens.some((token) => token.startsWith(candidate) && token.length > candidate.length));
|
|
17
20
|
}
|
|
18
|
-
|
|
21
|
+
function pickTargetForMaterialName(name) {
|
|
19
22
|
const tokens = tokenizeMaterialName(name);
|
|
20
23
|
if (hasToken(tokens, ['hair', 'fur']) || hasTokenPrefix(tokens, ['hair', 'fur'])) {
|
|
21
24
|
return 'hairColor';
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.normalizeAppearance = normalizeAppearance;
|
|
1
4
|
const HEX_COLOR_PATTERN = /^#(?:[\dA-Fa-f]{3}|[\dA-Fa-f]{6})$/;
|
|
2
5
|
function normalizeHexColor(value) {
|
|
3
6
|
if (!HEX_COLOR_PATTERN.test(value)) {
|
|
@@ -7,7 +10,7 @@ function normalizeHexColor(value) {
|
|
|
7
10
|
const expanded = raw.length === 3 ? `${raw[0]}${raw[0]}${raw[1]}${raw[1]}${raw[2]}${raw[2]}` : raw;
|
|
8
11
|
return `#${expanded.toUpperCase()}`;
|
|
9
12
|
}
|
|
10
|
-
|
|
13
|
+
function normalizeAppearance(input) {
|
|
11
14
|
if (input.version !== 1) {
|
|
12
15
|
throw new Error(`Unsupported appearance version: ${input.version}`);
|
|
13
16
|
}
|
|
@@ -11,6 +11,5 @@ interface AvatarCanvasProps {
|
|
|
11
11
|
style?: React.CSSProperties;
|
|
12
12
|
className?: string;
|
|
13
13
|
}
|
|
14
|
-
export declare function AvatarCanvas({ avatarUrl, appearance, equipped, placements, editingAssetId, onPlacementChange, onSceneRef, style, className, }: AvatarCanvasProps): import("react").JSX.Element;
|
|
14
|
+
export declare function AvatarCanvas({ avatarUrl, appearance, equipped, placements, editingAssetId, onPlacementChange, onSceneRef, style, className, }: AvatarCanvasProps): import("react/jsx-runtime").JSX.Element;
|
|
15
15
|
export {};
|
|
16
|
-
//# sourceMappingURL=AvatarCanvas.d.ts.map
|