UNPKG

avatoon

Version:

A React Three Fiber component for realistic avatar lip-syncing and animations.

2 lines (1 loc) 5.06 kB
(function(c,n){typeof exports=="object"&&typeof module<"u"?n(exports,require("react/jsx-runtime"),require("react"),require("@react-three/fiber"),require("@react-three/drei"),require("three")):typeof define=="function"&&define.amd?define(["exports","react/jsx-runtime","react","@react-three/fiber","@react-three/drei","three"],n):(c=typeof globalThis<"u"?globalThis:c||self,n(c.Avatoon={},c.jsxRuntime,c.React,c.fiber,c.drei,c.THREE))})(this,function(c,n,r,v,I,q){"use strict";function x(i){const u=Object.create(null,{[Symbol.toStringTag]:{value:"Module"}});if(i){for(const s in i)if(s!=="default"){const o=Object.getOwnPropertyDescriptor(i,s);Object.defineProperty(u,s,o.get?o:{enumerable:!0,get:()=>i[s]})}}return u.default=i,Object.freeze(u)}const P=x(q),R={A:"viseme_aa",B:"viseme_PP",C:"viseme_CH",D:"viseme_DD",E:"viseme_E",F:"viseme_FF",G:"viseme_kk",H:"viseme_sil",I:"viseme_I",J:"viseme_RR",K:"viseme_oo",X:"viseme_sil"};function L({url:i,goal:u,onRenderComplete:s,shouldPlay:o,visemeJson:h}){const[p,_]=r.useState(!1),F=r.useRef(!1),{gl:b}=v.useThree(),[H,O]=r.useState(!1),[G,K]=r.useState([]),d=r.useRef(null),N=r.useRef(null),{scene:g}=I.useGLTF(i),A=r.useRef(null),S=r.useRef(null),w=r.useRef(null),D=r.useRef(null),T=r.useRef(null),k=r.useRef(null),m=r.useRef(null),Y=r.useRef(null),z=r.useRef(null),M=r.useRef([]),C=r.useRef(null),U=e=>e.isBone===!0;return r.useEffect(()=>{(()=>{if(!h)return;const a=h.visemes.map(t=>({time:t.time,viseme:R[t.viseme||""]||null})).filter(t=>t.viseme!==null);if(K(a),h&&h.audio_base64){const t=new Audio(`data:audio/wav;base64,${h.audio_base64}`);d.current=t}})()},[]),r.useEffect(()=>{o&&d.current?d.current.play().then(()=>{O(!0)}).catch(e=>{console.warn("User interaction required to start audio:",e)}):!o&&d.current&&(d.current.pause(),O(!1))},[o]),r.useEffect(()=>{g&&g.traverse(e=>{const a=e.name.toLowerCase();if(e.name==="Head"&&(m.current=e),e.isMesh){const t=e,l=t.morphTargetDictionary;if(l){const f={};for(const y of Object.keys(R)){const E=R[y];l[E]!==void 0&&(f[E]=l[E])}Object.keys(f).length>0&&M.current.push({mesh:t,visemes:f})}}if(U(e)){switch(e.name){case"LeftArm":A.current=e;break;case"RightArm":w.current=e;break;case"LeftForeArm":T.current=e;break;case"RightForeArm":k.current=e;break;case"LeftHand":S.current=e;break;case"RightHand":D.current=e;break;case"Head":m.current=e;break;case"LeftEye":Y.current=e;break;case"RightEye":z.current=e;break}a.includes("leftarm")&&e.quaternion.setFromEuler(new P.Euler(Math.PI/2,0,0)),a.includes("rightarm")&&e.quaternion.setFromEuler(new P.Euler(Math.PI/2,0,0))}})},[g]),v.useFrame(()=>{if(!F.current&&g&&b&&!p&&(F.current=!0,_(!0),requestAnimationFrame(()=>{s==null||s()})),!H||!d.current||M.current.length===0)return;const e=d.current.currentTime,a=[...G].reverse().find(t=>t.time<=e);if(!(!a||a.viseme===C.current)){for(const{mesh:t,visemes:l}of M.current)if(t.morphTargetInfluences)for(const f of Object.keys(l)){const y=l[f];t.morphTargetInfluences[y]=0}for(const{mesh:t,visemes:l}of M.current){if(!t.morphTargetInfluences||!a.viseme)continue;const f=l[a.viseme];f!==void 0&&(t.morphTargetInfluences[f]=.6,setTimeout(()=>{t.morphTargetInfluences&&(t.morphTargetInfluences[f]=0)},50))}}}),v.useFrame(({clock:e})=>{const a=e.getElapsedTime();if(H){const t=e.getElapsedTime();if(m.current){const E=.03*Math.sin(t*2),X=.015*Math.sin(t*2.2),Z=.025*Math.sin(t*1.5);m.current.rotation.set(-.2+E,0+Z,0+X)}}if(u==="Muscle"){const t=Math.sin(a*4)*.03;A.current&&A.current.rotation.set(0,0,Math.PI/2+t),w.current&&w.current.rotation.set(0,0,-Math.PI/2-t),T.current&&T.current.rotation.set(0,0,Math.PI/2),k.current&&k.current.rotation.set(0,0,-Math.PI/2),S.current&&S.current.rotation.set(0,0,Math.PI/2),D.current&&D.current.rotation.set(0,0,-Math.PI/2),m.current&&m.current.rotation.set(0,0,0)}else u==="Sleep"?m.current&&(m.current.rotation.x=Math.sin(a*1.5)*.05):(T.current&&T.current.rotation.set(0,0,0),k.current&&k.current.rotation.set(0,0,0))}),n.jsx("group",{ref:N,position:[0,-.4,0],children:n.jsx("primitive",{object:g})})}function B({readyToPlay:i}){const{camera:u}=v.useThree(),[s,o]=r.useState(i?17:15);return r.useEffect(()=>{o(i?17:15)},[i]),v.useFrame(()=>{u instanceof P.PerspectiveCamera&&(u.fov+=(s-u.fov)*.05,u.updateProjectionMatrix())}),null}function V({glbUrl:i,goal:u,onRenderComplete:s,visemeJson:o,showPlayVoiceButton:h=!1}){const[p,_]=r.useState(!1),[F,b]=r.useState(!1);return n.jsxs(n.Fragment,{children:[h&&F&&n.jsx("button",{className:"talk-button",onClick:()=>_(!p),children:p?"⏹️ Stop talking":"▶️ Talk with me now"}),n.jsxs(v.Canvas,{"data-testid":"avatoon-canvas",camera:{position:[0,1.5,2.2],fov:17},children:[n.jsx("ambientLight",{intensity:.6}),n.jsxs(r.Suspense,{fallback:null,children:[n.jsx(L,{url:i,goal:u,onRenderComplete:()=>{b(!0),s==null||s()},shouldPlay:p,visemeJson:o}),n.jsx(I.Environment,{preset:"sunset"})]}),n.jsx(B,{readyToPlay:p}),n.jsx(I.OrbitControls,{target:[0,1.2,0],enablePan:!0,enableZoom:!0,enableRotate:!0})]})]})}c.Avatoon=V,Object.defineProperty(c,Symbol.toStringTag,{value:"Module"})});