@streamoji/aitwin 0.3.5 → 0.3.7

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.
@@ -0,0 +1 @@
1
+ (function(){"use strict";function et(e,t,n){return{width:e.crop?.source_width??t,height:e.crop?.source_height??n}}function nt(e,t){const n=e.crop;return{x:t?.source_x??n?.x??0,y:t?.source_y??n?.y??0,width:t?.source_w??n?.width??e.frame_width??0,height:t?.source_h??n?.height??e.frame_height??0}}function rt(e){return e?(e.w??0)>0&&(e.h??0)>0:!1}function k(e){return e.includes("/custom-faces/")}function z(e){const t=e.binBase.trim().replace(/\/$/,""),n=e.twinBase.trim().replace(/\/$/,"");if(!(e.encrypted||k(t)||k(n)))return{twinBase:n,binBase:t,encrypted:!1};const s=k(t)?t:k(n)?n:t;return{twinBase:s,binBase:s,encrypted:!0}}function m(e){return z(e).encrypted}function st(e){const t=e.trim();if(t.length%2!==0)throw new Error("Invalid hex key");const n=new Uint8Array(t.length/2);for(let r=0;r<t.length;r+=2)n[r/2]=Number.parseInt(t.slice(r,r+2),16);return n}function P(e){return Uint8Array.from(e)}function at(e,t){const n=new Uint8Array(e.length+t.length);return n.set(e,0),n.set(t,e.length),n}async function ot(e,t){if(!t)throw new Error("Asset key is required for encrypted assets");if(e.byteLength<28)throw new Error("Encrypted payload too small");const n=new Uint8Array(e),r=P(n.subarray(0,12)),s=P(n.subarray(12,28)),a=P(n.subarray(28)),o=P(at(a,s)),l=await crypto.subtle.importKey("raw",st(t),{name:"AES-GCM"},!1,["decrypt"]);return crypto.subtle.decrypt({name:"AES-GCM",iv:r,tagLength:128},l,o)}async function O(e,t){const n=await fetch(e);if(!n.ok)throw new Error(`Failed to load encrypted asset: ${e}`);const r=await n.arrayBuffer();return ot(r,t)}async function it(e,t){if(!t)throw new Error("Missing asset key for encrypted JSON");const n=await O(e,t),r=new TextDecoder().decode(n);return JSON.parse(r)}async function ct(e){const t=new Blob([e],{type:"image/webp"});return createImageBitmap(t)}async function lt(e){const t=new Blob([e],{type:"image/png"});return createImageBitmap(t)}const W=5;function K(e){const t=e?.composite_threshold;if(typeof t=="number"&&t>0)return t;const n=e?.crop?.content_threshold;return typeof n=="number"&&n>0?n:W}const T=["t01_0.25","t02_0.50","t03_0.75"],_t=T.length,J=new Set(["aa__kk","aa__nn","aa__sil","CH__aa","CH__DD","CH__E","CH__FF","CH__I","CH__kk","CH__nn","CH__O","CH__PP","CH__RR","CH__sil","CH__SS","CH__TH","CH__U","DD__aa","DD__E","DD__FF","DD__I","DD__kk","DD__nn","DD__O","DD__PP","DD__RR","DD__sil","DD__SS","DD__TH","DD__U","E__aa","E__FF","E__I","E__kk","E__nn","E__O","E__PP","E__RR","E__sil","E__SS","E__TH","E__U","FF__aa","FF__I","FF__kk","FF__nn","FF__O","FF__PP","FF__RR","FF__sil","FF__SS","FF__TH","FF__U","I__aa","I__kk","I__nn","I__O","I__PP","I__RR","I__sil","I__SS","I__TH","I__U","kk__nn","kk__sil","nn__sil","O__aa","O__kk","O__nn","O__PP","O__RR","O__sil","O__SS","O__TH","O__U","PP__aa","PP__kk","PP__nn","PP__RR","PP__sil","PP__SS","PP__TH","PP__U","RR__aa","RR__kk","RR__nn","RR__sil","RR__SS","RR__TH","RR__U","SS__aa","SS__kk","SS__nn","SS__sil","SS__TH","SS__U","TH__aa","TH__kk","TH__nn","TH__sil","TH__U","U__aa","U__kk","U__nn","U__sil"]);function G(e,t){return`${e}__${t}`}function Q(e,t){return`pairs/${e}/${t}_minus_sil.png`}function $(e){return`${e}_minus_sil.png`}function ft(e,t){if(t.has(e))return e;const n=/^pairs\/([^/]+)\//.exec(e);if(!n)return null;const r=n[1].split("__")[1];if(!r||r==="sil")return null;const s=$(r);return t.has(s)?s:null}function ut(e,t){return e.filter(n=>t.has(n))}function dt(e,t){if(e===t)return[];const n=[],r=G(e,t);if(J.has(r)){for(const a of T)n.push(Q(r,a));return n}const s=G(t,e);if(J.has(s)){for(let a=_t-1;a>=0;a--)n.push(Q(s,T[a]));return n}return[]}function wt(e){return e<16?[1]:e<24?[1]:e<35?[0,1,2]:[0,1,2]}function ht(e,t){return wt(t).map(r=>e[r]).filter(r=>r!=null)}function pt(e,t,n){if(!n)return`${e}/${t}`;const r=t.split("/").pop()??t,s=r.includes(".")?r.slice(0,r.lastIndexOf(".")):r;return`${e}/${s}.bin`}function yt(e,t,n,r,s=W){const a=new OffscreenCanvas(n,r),o=a.getContext("2d",{willReadFrequently:!0});o.drawImage(e,0,0,n,r);const l=o.getImageData(0,0,n,r),i=o.createImageData(n,r);i.data.set(l.data);const u=new OffscreenCanvas(n,r).getContext("2d",{willReadFrequently:!0});for(const w of t){u.clearRect(0,0,n,r),u.drawImage(w,0,0,n,r);const _=u.getImageData(0,0,n,r);for(let f=0;f<i.data.length;f+=4){const y=_.data[f],F=_.data[f+1],x=_.data[f+2];Math.max(y,F,x)>s&&(i.data[f]=y,i.data[f+1]=F,i.data[f+2]=x,i.data[f+3]=255)}}return o.putImageData(i,0,0),a}const j=-1,mt=1e5;function gt(e){return e>=mt}function Dt(e){return e<0}let g=0,X=Promise.resolve(),D=!1;function v(e){const t=X.then(e);return X=t.then(()=>{},()=>{}),t}function E(e,t){const n=t>0?-t:j,r={type:"renderDone",requestId:e,generation:n};self.postMessage(r)}async function Et(e){const t=typeof e.liveSeq=="number"?e.liveSeq:0,n=gt(t);if(D&&!n){E(e.requestId,t);return}if(t>0&&!n&&(g=t),!d)throw new Error("Worker not initialized");if(t>0&&!n&&t!==g){E(e.requestId,t);return}if(await b(p??void 0),t>0&&!n&&t!==g){E(e.requestId,t);return}if(await tt(d,e.diffPath,e.expressionDiffPath??null,e.threshold),t>0&&!n&&t!==g){E(e.requestId,t);return}const r=t>0?-t:j;await U(r),E(e.requestId,t)}let d=null,I=null,p=null,q=null,A="sil";const B=new Map;function S(){if(!q)throw new Error("Worker assets not configured");return q}function Y(){const e=S();return m(e)?`${e.binBase}/sil.bin`:`${e.twinBase}/sil.png`}function It(){const e=S();return m(e)?`${e.binBase}/atlas.bin`:`${e.twinBase}/atlas.json`}function St(){const e=S();return m(e)?`${e.binBase}/expression_atlas.bin`:`${e.twinBase}/expression_atlas.json`}function bt(){const e=S();return m(e)?e.binBase:e.twinBase}function M(){return m(S())}async function H(e){const t=await fetch(e);if(!t.ok)throw new Error(`Failed to load image: ${e}`);const n=await t.blob();return createImageBitmap(n)}async function Ft(e){return M()?lt(await O(Y(),e??"")):H(Y())}async function Z(e,t,n){const r=M(),s=r?await it(e,n):await fetch(e).then(c=>{if(!c.ok)throw new Error(`Failed to load ${e}`);return c.json()}),a=s.sheets?.[0];if(!a?.path)throw new Error(`${e} has no sheets[0].path`);const o=r?await ct(await O(pt(t,a.path,r),n??"")):await H(`${t}/${a.path}`),l=new Map;for(const c of s.cells??[])c.path&&l.set(c.path,c);const i=new Map;for(const c of s.sheets??[])i.set(c.index,c);return{atlas:o,atlasMeta:s,cellByPath:l,sheetByIndex:i,diffBase:t}}async function b(e){if(M()&&!e)throw new Error("Encrypted assets enabled but no key provided");if(I&&(!M()||p===(e??null)))return I;p=e??null,B.clear();const t=bt();return I=(async()=>{const n=await Ft(e),r=await Z(It(),t,e);let s=null;try{s=await Z(St(),t,e)}catch{s=null}return{sil:n,viseme:r,expression:s}})(),I}function R(e){return et(e.viseme.atlasMeta,e.sil.width,e.sil.height)}function kt(e,t){if(!e.sheetByIndex.get(t.sheet??0))throw new Error(`Unknown sheet index: ${t.sheet}`);const r=new OffscreenCanvas(t.w,t.h);return r.getContext("2d",{willReadFrequently:!0}).drawImage(e.atlas,t.x,t.y,t.w,t.h,0,0,t.w,t.h),r}async function Pt(e){return createImageBitmap(e)}async function V(e,t,n){const r=`${t.diffBase}::${n}`,s=B.get(r);if(s)return s;const a=(async()=>{const o=t.cellByPath.get(n);if(!o)throw new Error(`No atlas cell for path: ${n}`);const l=nt(t.atlasMeta,o),i=R(e);if(rt(o)){const w=kt(t,o),_=new OffscreenCanvas(i.width,i.height),f=_.getContext("2d");return f.fillStyle="#000",f.fillRect(0,0,i.width,i.height),f.drawImage(w,l.x,l.y),Pt(_)}const c=`${t.diffBase}/${n}`,u=await H(c);if(u.width===i.width&&u.height===i.height)return u;throw u.close(),new Error(`Diff PNG wrong size for ${n}`)})();return B.set(r,a),a}function C(e,t){const n={type:"status",label:e,generation:t};self.postMessage(n)}function At(e,t){const n={type:"error",requestId:e,message:t};self.postMessage(n)}async function U(e){if(!d)return;const t=Dt(e);if(!t&&e!==h)return;const n=await createImageBitmap(d);if(!t&&e!==h){n.close();return}const r={type:"frame",bitmap:n,generation:e};self.postMessage(r,{transfer:[n]})}function Bt(e,t,n){return new Promise((r,s)=>{setTimeout(()=>{t!==n?s(new DOMException("Aborted","AbortError")):r()},e)})}async function tt(e,t,n,r){const s=await b(p??void 0),a=r??K(s.viseme.atlasMeta),{width:o,height:l}=R(s);(e.width!==o||e.height!==l)&&(e.width=o,e.height=l);const i=[];if(t){const w=new Set(s.viseme.cellByPath.keys()),_=ft(t,w)??(w.has(t)?t:null);_&&i.push(await V(s,s.viseme,_))}n&&s.expression&&s.expression.cellByPath.get(n)&&i.push(await V(s,s.expression,n));const c=e.getContext("2d");if(c.clearRect(0,0,o,l),i.length===0){c.drawImage(s.sil,0,0,o,l);return}const u=yt(s.sil,i,o,l,a);c.drawImage(u,0,0)}async function L(e,t,n,r,s){await tt(e,t,n,s),await U(r)}async function N(e,t,n){A="sil";const r=await b(p??void 0);if(t!==n)throw new DOMException("Aborted","AbortError");const{width:s,height:a}=R(r);e.width=s,e.height=a;const o=e.getContext("2d");o.clearRect(0,0,s,a),o.drawImage(r.sil,0,0,s,a),await U(t)}async function Mt(e,t,n,r,s={}){const a=s.from??A,o=s.transitionDurationMs??400,l=s.gapMs??o,i=await b(p??void 0),c=s.threshold??K(i.viseme.atlasMeta);if(a===t){if(C(t,n),t==="sil"?await N(e,n,r):await L(e,$(t),null,n,c),n!==r)throw new DOMException("Aborted","AbortError");A=t;return}const u=dt(a,t),w=new Set(i.viseme.cellByPath.keys());let _=ht(u,l);_=ut(_,w);const f=_.length>0?o/_.length:0;_.length===0&&C(`${a} → ${t} (no pair; final only)`,n);for(let y=0;y<_.length;y++){if(n!==r)throw new DOMException("Aborted","AbortError");const F=_[y],x=F.split("/").pop()?.replace("_minus_sil.png","")??`frame ${y}`;if(C(`${a} → ${t} (${x})`,n),await L(e,F,null,n,c),n!==r)throw new DOMException("Aborted","AbortError");f>0&&await Bt(f,n,r)}if(n!==r)throw new DOMException("Aborted","AbortError");if(C(t,n),t==="sil"?await N(e,n,r):await L(e,$(t),null,n,c),n!==r)throw new DOMException("Aborted","AbortError");A=t}let h=0;self.onmessage=e=>{const t=e.data;(async()=>{try{switch(t.type){case"init":{d=new OffscreenCanvas(1,1);const n={type:"ready",requestId:t.requestId};self.postMessage(n);break}case"loadAssets":{q=z(t.urls),I=null,p=null,B.clear();const n=await b(t.keyHex),{width:r,height:s}=R(n),a={type:"loadAssetsDone",requestId:t.requestId,width:r,height:s,visemePaths:[...n.viseme.cellByPath.keys()]};self.postMessage(a);break}case"cancelLiveDraws":{g+=1;const n={type:"renderDone",requestId:t.requestId,generation:h};self.postMessage(n);break}case"drawFrame":{await v(()=>Et(t));break}case"renderSil":{await v(async()=>{D=!0;try{if(h=t.generation,!d)throw new Error("Worker not initialized");await N(d,t.generation,h)}finally{D=!1}});const n={type:"renderDone",requestId:t.requestId,generation:t.generation};self.postMessage(n);break}case"renderViseme":{await v(async()=>{D=!0;try{if(h=t.generation,!d)throw new Error("Worker not initialized");await Mt(d,t.to,t.generation,h,{from:t.from,transitionDurationMs:t.transitionDurationMs,gapMs:t.gapMs,threshold:t.threshold})}finally{D=!1}});const n={type:"renderDone",requestId:t.requestId,generation:t.generation};self.postMessage(n);break}default:break}}catch(n){if(n instanceof DOMException&&n.name==="AbortError"){const r={type:"renderAborted",requestId:t.requestId,generation:"generation"in t?t.generation:h};self.postMessage(r);return}At(t.requestId,n instanceof Error?n.message:String(n))}})()}})();
package/dist/index.d.ts CHANGED
@@ -81,6 +81,16 @@ export declare type AiTwinProps = {
81
81
  onRealtimeLipsyncDebug?: (snapshot: RealtimeLipsyncDebugSnapshot) => void;
82
82
  /** Canvas render skip stats (RIFE transitions, draw coalescing, visemes). */
83
83
  onFrameRenderStats?: (stats: FrameRenderStatsSnapshot) => void;
84
+ /**
85
+ * Pre-render upcoming mouth frames into a lookahead buffer (smoother lipsync).
86
+ * Default: true.
87
+ */
88
+ lipsyncBufferedPlayback?: boolean;
89
+ /**
90
+ * Milliseconds of lipsync timeline to prefetch before audio starts (buffered mode).
91
+ * Default: 1500. Set to 0 to start audio immediately.
92
+ */
93
+ lipsyncPlaybackWarmupMs?: number;
84
94
  };
85
95
 
86
96
  export declare type AiTwinRecord = {
@@ -118,6 +128,9 @@ export declare type AvatarTtsLipsyncController = {
118
128
  getStreamMood: () => string;
119
129
  getPlaybackElapsedMs: () => number;
120
130
  isSpeaking: () => boolean;
131
+ isWarmingUp: () => boolean;
132
+ releaseWarmupHold: () => void;
133
+ setLipsyncWarmupHold: (enabled: boolean, shouldHold?: () => boolean) => void;
121
134
  /** Live MediaStream of the synthesized voice for recording/mixing (null until audio is primed). */
122
135
  getVoiceStream: () => MediaStream | null;
123
136
  setPlaybackSpeed: (rate: number) => void;
@@ -180,6 +193,11 @@ export declare type FrameRenderStatsSnapshot = {
180
193
  rifeFramesOmittedByGap: number;
181
194
  transitionFramesSampled: number;
182
195
  transitionFramesDisplayed: number;
196
+ bufferTicks: number;
197
+ bufferHits: number;
198
+ bufferUnderruns: number;
199
+ bufferPrefetched: number;
200
+ bufferHitRate: number;
183
201
  /** @deprecated Use `rafTicksDeduped`. */
184
202
  drawsDeduped: number;
185
203
  /** @deprecated Use `renderLoss`. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@streamoji/aitwin",
3
- "version": "0.3.5",
3
+ "version": "0.3.7",
4
4
  "description": "Embeddable React AI twin face with TTS lipsync",
5
5
  "type": "module",
6
6
  "main": "./dist/aitwin.cjs",
@@ -1 +0,0 @@
1
- (function(){"use strict";function et(e,t,n){return{width:e.crop?.source_width??t,height:e.crop?.source_height??n}}function nt(e,t){const n=e.crop;return{x:t?.source_x??n?.x??0,y:t?.source_y??n?.y??0,width:t?.source_w??n?.width??e.frame_width??0,height:t?.source_h??n?.height??e.frame_height??0}}function rt(e){return e?(e.w??0)>0&&(e.h??0)>0:!1}function k(e){return e.includes("/custom-faces/")}function z(e){const t=e.binBase.trim().replace(/\/$/,""),n=e.twinBase.trim().replace(/\/$/,"");if(!(e.encrypted||k(t)||k(n)))return{twinBase:n,binBase:t,encrypted:!1};const s=k(t)?t:k(n)?n:t;return{twinBase:s,binBase:s,encrypted:!0}}function m(e){return z(e).encrypted}function st(e){const t=e.trim();if(t.length%2!==0)throw new Error("Invalid hex key");const n=new Uint8Array(t.length/2);for(let r=0;r<t.length;r+=2)n[r/2]=Number.parseInt(t.slice(r,r+2),16);return n}function P(e){return Uint8Array.from(e)}function at(e,t){const n=new Uint8Array(e.length+t.length);return n.set(e,0),n.set(t,e.length),n}async function ot(e,t){if(!t)throw new Error("Asset key is required for encrypted assets");if(e.byteLength<28)throw new Error("Encrypted payload too small");const n=new Uint8Array(e),r=P(n.subarray(0,12)),s=P(n.subarray(12,28)),a=P(n.subarray(28)),o=P(at(a,s)),l=await crypto.subtle.importKey("raw",st(t),{name:"AES-GCM"},!1,["decrypt"]);return crypto.subtle.decrypt({name:"AES-GCM",iv:r,tagLength:128},l,o)}async function O(e,t){const n=await fetch(e);if(!n.ok)throw new Error(`Failed to load encrypted asset: ${e}`);const r=await n.arrayBuffer();return ot(r,t)}async function it(e,t){if(!t)throw new Error("Missing asset key for encrypted JSON");const n=await O(e,t),r=new TextDecoder().decode(n);return JSON.parse(r)}async function ct(e){const t=new Blob([e],{type:"image/webp"});return createImageBitmap(t)}async function lt(e){const t=new Blob([e],{type:"image/png"});return createImageBitmap(t)}const W=5;function K(e){const t=e?.composite_threshold;if(typeof t=="number"&&t>0)return t;const n=e?.crop?.content_threshold;return typeof n=="number"&&n>0?n:W}const T=["t01_0.25","t02_0.50","t03_0.75"],_t=T.length,J=new Set(["aa__kk","aa__nn","aa__sil","CH__aa","CH__DD","CH__E","CH__FF","CH__I","CH__kk","CH__nn","CH__O","CH__PP","CH__RR","CH__sil","CH__SS","CH__TH","CH__U","DD__aa","DD__E","DD__FF","DD__I","DD__kk","DD__nn","DD__O","DD__PP","DD__RR","DD__sil","DD__SS","DD__TH","DD__U","E__aa","E__FF","E__I","E__kk","E__nn","E__O","E__PP","E__RR","E__sil","E__SS","E__TH","E__U","FF__aa","FF__I","FF__kk","FF__nn","FF__O","FF__PP","FF__RR","FF__sil","FF__SS","FF__TH","FF__U","I__aa","I__kk","I__nn","I__O","I__PP","I__RR","I__sil","I__SS","I__TH","I__U","kk__nn","kk__sil","nn__sil","O__aa","O__kk","O__nn","O__PP","O__RR","O__sil","O__SS","O__TH","O__U","PP__aa","PP__kk","PP__nn","PP__RR","PP__sil","PP__SS","PP__TH","PP__U","RR__aa","RR__kk","RR__nn","RR__sil","RR__SS","RR__TH","RR__U","SS__aa","SS__kk","SS__nn","SS__sil","SS__TH","SS__U","TH__aa","TH__kk","TH__nn","TH__sil","TH__U","U__aa","U__kk","U__nn","U__sil"]);function G(e,t){return`${e}__${t}`}function j(e,t){return`pairs/${e}/${t}_minus_sil.png`}function $(e){return`${e}_minus_sil.png`}function ft(e,t){if(t.has(e))return e;const n=/^pairs\/([^/]+)\//.exec(e);if(!n)return null;const r=n[1].split("__")[1];if(!r||r==="sil")return null;const s=$(r);return t.has(s)?s:null}function ut(e,t){return e.filter(n=>t.has(n))}function dt(e,t){if(e===t)return[];const n=[],r=G(e,t);if(J.has(r)){for(const a of T)n.push(j(r,a));return n}const s=G(t,e);if(J.has(s)){for(let a=_t-1;a>=0;a--)n.push(j(s,T[a]));return n}return[]}function wt(e){return e<16?[]:e<35?[1]:e<51?[0,2]:[0,1,2]}function ht(e,t){return wt(t).map(r=>e[r]).filter(r=>r!=null)}function pt(e,t,n){if(!n)return`${e}/${t}`;const r=t.split("/").pop()??t,s=r.includes(".")?r.slice(0,r.lastIndexOf(".")):r;return`${e}/${s}.bin`}function yt(e,t,n,r,s=W){const a=new OffscreenCanvas(n,r),o=a.getContext("2d",{willReadFrequently:!0});o.drawImage(e,0,0,n,r);const l=o.getImageData(0,0,n,r),i=o.createImageData(n,r);i.data.set(l.data);const u=new OffscreenCanvas(n,r).getContext("2d",{willReadFrequently:!0});for(const w of t){u.clearRect(0,0,n,r),u.drawImage(w,0,0,n,r);const _=u.getImageData(0,0,n,r);for(let f=0;f<i.data.length;f+=4){const y=_.data[f],F=_.data[f+1],x=_.data[f+2];Math.max(y,F,x)>s&&(i.data[f]=y,i.data[f+1]=F,i.data[f+2]=x,i.data[f+3]=255)}}return o.putImageData(i,0,0),a}const Q=-1;function mt(e){return e<0}let g=0,X=Promise.resolve(),D=!1;function v(e){const t=X.then(e);return X=t.then(()=>{},()=>{}),t}function I(e,t){const n=t>0?-t:Q,r={type:"renderDone",requestId:e,generation:n};self.postMessage(r)}async function gt(e){const t=typeof e.liveSeq=="number"?e.liveSeq:0;if(D){I(e.requestId,t);return}if(t>0&&(g=t),!d)throw new Error("Worker not initialized");if(t>0&&t!==g){I(e.requestId,t);return}if(await S(p??void 0),t>0&&t!==g){I(e.requestId,t);return}if(await tt(d,e.diffPath,e.expressionDiffPath??null,e.threshold),t>0&&t!==g){I(e.requestId,t);return}const n=t>0?-t:Q;await U(n),I(e.requestId,t)}let d=null,E=null,p=null,q=null,A="sil";const B=new Map;function b(){if(!q)throw new Error("Worker assets not configured");return q}function Y(){const e=b();return m(e)?`${e.binBase}/sil.bin`:`${e.twinBase}/sil.png`}function Dt(){const e=b();return m(e)?`${e.binBase}/atlas.bin`:`${e.twinBase}/atlas.json`}function It(){const e=b();return m(e)?`${e.binBase}/expression_atlas.bin`:`${e.twinBase}/expression_atlas.json`}function Et(){const e=b();return m(e)?e.binBase:e.twinBase}function M(){return m(b())}async function H(e){const t=await fetch(e);if(!t.ok)throw new Error(`Failed to load image: ${e}`);const n=await t.blob();return createImageBitmap(n)}async function bt(e){return M()?lt(await O(Y(),e??"")):H(Y())}async function Z(e,t,n){const r=M(),s=r?await it(e,n):await fetch(e).then(c=>{if(!c.ok)throw new Error(`Failed to load ${e}`);return c.json()}),a=s.sheets?.[0];if(!a?.path)throw new Error(`${e} has no sheets[0].path`);const o=r?await ct(await O(pt(t,a.path,r),n??"")):await H(`${t}/${a.path}`),l=new Map;for(const c of s.cells??[])c.path&&l.set(c.path,c);const i=new Map;for(const c of s.sheets??[])i.set(c.index,c);return{atlas:o,atlasMeta:s,cellByPath:l,sheetByIndex:i,diffBase:t}}async function S(e){if(M()&&!e)throw new Error("Encrypted assets enabled but no key provided");if(E&&(!M()||p===(e??null)))return E;p=e??null,B.clear();const t=Et();return E=(async()=>{const n=await bt(e),r=await Z(Dt(),t,e);let s=null;try{s=await Z(It(),t,e)}catch{s=null}return{sil:n,viseme:r,expression:s}})(),E}function R(e){return et(e.viseme.atlasMeta,e.sil.width,e.sil.height)}function St(e,t){if(!e.sheetByIndex.get(t.sheet??0))throw new Error(`Unknown sheet index: ${t.sheet}`);const r=new OffscreenCanvas(t.w,t.h);return r.getContext("2d",{willReadFrequently:!0}).drawImage(e.atlas,t.x,t.y,t.w,t.h,0,0,t.w,t.h),r}async function Ft(e){return createImageBitmap(e)}async function V(e,t,n){const r=`${t.diffBase}::${n}`,s=B.get(r);if(s)return s;const a=(async()=>{const o=t.cellByPath.get(n);if(!o)throw new Error(`No atlas cell for path: ${n}`);const l=nt(t.atlasMeta,o),i=R(e);if(rt(o)){const w=St(t,o),_=new OffscreenCanvas(i.width,i.height),f=_.getContext("2d");return f.fillStyle="#000",f.fillRect(0,0,i.width,i.height),f.drawImage(w,l.x,l.y),Ft(_)}const c=`${t.diffBase}/${n}`,u=await H(c);if(u.width===i.width&&u.height===i.height)return u;throw u.close(),new Error(`Diff PNG wrong size for ${n}`)})();return B.set(r,a),a}function C(e,t){const n={type:"status",label:e,generation:t};self.postMessage(n)}function kt(e,t){const n={type:"error",requestId:e,message:t};self.postMessage(n)}async function U(e){if(!d)return;const t=mt(e);if(!t&&e!==h)return;const n=await createImageBitmap(d);if(!t&&e!==h){n.close();return}const r={type:"frame",bitmap:n,generation:e};self.postMessage(r,{transfer:[n]})}function Pt(e,t,n){return new Promise((r,s)=>{setTimeout(()=>{t!==n?s(new DOMException("Aborted","AbortError")):r()},e)})}async function tt(e,t,n,r){const s=await S(p??void 0),a=r??K(s.viseme.atlasMeta),{width:o,height:l}=R(s);(e.width!==o||e.height!==l)&&(e.width=o,e.height=l);const i=[];if(t){const w=new Set(s.viseme.cellByPath.keys()),_=ft(t,w)??(w.has(t)?t:null);_&&i.push(await V(s,s.viseme,_))}n&&s.expression&&s.expression.cellByPath.get(n)&&i.push(await V(s,s.expression,n));const c=e.getContext("2d");if(c.clearRect(0,0,o,l),i.length===0){c.drawImage(s.sil,0,0,o,l);return}const u=yt(s.sil,i,o,l,a);c.drawImage(u,0,0)}async function N(e,t,n,r,s){await tt(e,t,n,s),await U(r)}async function L(e,t,n){A="sil";const r=await S(p??void 0);if(t!==n)throw new DOMException("Aborted","AbortError");const{width:s,height:a}=R(r);e.width=s,e.height=a;const o=e.getContext("2d");o.clearRect(0,0,s,a),o.drawImage(r.sil,0,0,s,a),await U(t)}async function At(e,t,n,r,s={}){const a=s.from??A,o=s.transitionDurationMs??400,l=s.gapMs??o,i=await S(p??void 0),c=s.threshold??K(i.viseme.atlasMeta);if(a===t){if(C(t,n),t==="sil"?await L(e,n,r):await N(e,$(t),null,n,c),n!==r)throw new DOMException("Aborted","AbortError");A=t;return}const u=dt(a,t),w=new Set(i.viseme.cellByPath.keys());let _=ht(u,l);_=ut(_,w);const f=_.length>0?o/_.length:0;_.length===0&&C(`${a} → ${t} (no pair; final only)`,n);for(let y=0;y<_.length;y++){if(n!==r)throw new DOMException("Aborted","AbortError");const F=_[y],x=F.split("/").pop()?.replace("_minus_sil.png","")??`frame ${y}`;if(C(`${a} → ${t} (${x})`,n),await N(e,F,null,n,c),n!==r)throw new DOMException("Aborted","AbortError");f>0&&await Pt(f,n,r)}if(n!==r)throw new DOMException("Aborted","AbortError");if(C(t,n),t==="sil"?await L(e,n,r):await N(e,$(t),null,n,c),n!==r)throw new DOMException("Aborted","AbortError");A=t}let h=0;self.onmessage=e=>{const t=e.data;(async()=>{try{switch(t.type){case"init":{d=new OffscreenCanvas(1,1);const n={type:"ready",requestId:t.requestId};self.postMessage(n);break}case"loadAssets":{q=z(t.urls),E=null,p=null,B.clear();const n=await S(t.keyHex),{width:r,height:s}=R(n),a={type:"loadAssetsDone",requestId:t.requestId,width:r,height:s,visemePaths:[...n.viseme.cellByPath.keys()]};self.postMessage(a);break}case"cancelLiveDraws":{g+=1;const n={type:"renderDone",requestId:t.requestId,generation:h};self.postMessage(n);break}case"drawFrame":{await v(()=>gt(t));break}case"renderSil":{await v(async()=>{D=!0;try{if(h=t.generation,!d)throw new Error("Worker not initialized");await L(d,t.generation,h)}finally{D=!1}});const n={type:"renderDone",requestId:t.requestId,generation:t.generation};self.postMessage(n);break}case"renderViseme":{await v(async()=>{D=!0;try{if(h=t.generation,!d)throw new Error("Worker not initialized");await At(d,t.to,t.generation,h,{from:t.from,transitionDurationMs:t.transitionDurationMs,gapMs:t.gapMs,threshold:t.threshold})}finally{D=!1}});const n={type:"renderDone",requestId:t.requestId,generation:t.generation};self.postMessage(n);break}default:break}}catch(n){if(n instanceof DOMException&&n.name==="AbortError"){const r={type:"renderAborted",requestId:t.requestId,generation:"generation"in t?t.generation:h};self.postMessage(r);return}kt(t.requestId,n instanceof Error?n.message:String(n))}})()}})();