@sangwonl/pocato-core 0.4.10 → 0.4.12

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/index.js CHANGED
@@ -368,6 +368,7 @@ function bootstrapShaders() {
368
368
  import * as THREE2 from "three";
369
369
  var VIDEO_EXTENSIONS = /\.(mp4|webm|mov|ogg)(\?|$)/i;
370
370
  var VIDEO_LOAD_TIMEOUT_MS = 15e3;
371
+ var VIDEO_FRAME_CAPTURE_TIMEOUT_MS = 750;
371
372
  function isVideoSource(layer) {
372
373
  if (layer.type) return layer.type === "video";
373
374
  return VIDEO_EXTENSIONS.test(layer.src);
@@ -438,7 +439,15 @@ function loadImageTexture(src, onError) {
438
439
  });
439
440
  inflight.set(src, pending);
440
441
  }
441
- return pending.then((texture) => ({ texture }));
442
+ return pending.then((texture) => ({
443
+ texture: cloneDisposableTexture(texture)
444
+ }));
445
+ }
446
+ function cloneDisposableTexture(texture) {
447
+ if (texture === getTransparentTexture()) return texture;
448
+ const clone = texture.clone();
449
+ clone.needsUpdate = true;
450
+ return clone;
442
451
  }
443
452
  function loadVideoTexture(layer, options) {
444
453
  return new Promise((resolve) => {
@@ -526,12 +535,37 @@ function loadVideoTexture(layer, options) {
526
535
  });
527
536
  };
528
537
  video.addEventListener("loadeddata", () => {
529
- resolveWithFrame();
538
+ waitForDecodedVideoFrame(video, signal).then(() => {
539
+ resolveWithFrame();
540
+ });
530
541
  }, { once: true });
531
542
  video.src = layer.src;
532
543
  video.load();
533
544
  });
534
545
  }
546
+ function waitForDecodedVideoFrame(video, signal) {
547
+ return new Promise((resolve) => {
548
+ if (signal?.aborted || !("requestVideoFrameCallback" in video)) {
549
+ resolve();
550
+ return;
551
+ }
552
+ let settled = false;
553
+ let frameId = null;
554
+ const finish = () => {
555
+ if (settled) return;
556
+ settled = true;
557
+ globalThis.clearTimeout(timeoutId);
558
+ if (frameId != null && "cancelVideoFrameCallback" in video) {
559
+ video.cancelVideoFrameCallback(frameId);
560
+ }
561
+ signal?.removeEventListener("abort", finish);
562
+ resolve();
563
+ };
564
+ const timeoutId = globalThis.setTimeout(finish, VIDEO_FRAME_CAPTURE_TIMEOUT_MS);
565
+ signal?.addEventListener("abort", finish, { once: true });
566
+ frameId = video.requestVideoFrameCallback(finish);
567
+ });
568
+ }
535
569
  function loadFrozenVideoTexture(layer, freezeTime, options) {
536
570
  return new Promise((resolve) => {
537
571
  const { onError, signal } = options;
@@ -2028,6 +2062,9 @@ var FaceRenderer = class {
2028
2062
  this.loadedLayers = [];
2029
2063
  this.layerLoadVersion = 0;
2030
2064
  this.layerLoadAbort = null;
2065
+ this.videoActivationVersion = 0;
2066
+ this.videoActivationAbort = null;
2067
+ this.destroyed = false;
2031
2068
  bootstrapShaders();
2032
2069
  this.scene = new THREE3.Scene();
2033
2070
  this.camera = new THREE3.Camera();
@@ -2078,6 +2115,9 @@ var FaceRenderer = class {
2078
2115
  }
2079
2116
  async loadLayers(layers, onError) {
2080
2117
  const version = ++this.layerLoadVersion;
2118
+ this.videoActivationVersion++;
2119
+ this.videoActivationAbort?.abort();
2120
+ this.videoActivationAbort = null;
2081
2121
  this.layerLoadAbort?.abort();
2082
2122
  const abort = new AbortController();
2083
2123
  this.layerLoadAbort = abort;
@@ -2117,16 +2157,25 @@ var FaceRenderer = class {
2117
2157
  this.material.uniforms.uResolution.value.set(width, height);
2118
2158
  }
2119
2159
  async playLoadedVideos() {
2160
+ const activationVersion = ++this.videoActivationVersion;
2161
+ this.videoActivationAbort?.abort();
2162
+ const activationAbort = new AbortController();
2163
+ this.videoActivationAbort = activationAbort;
2120
2164
  await Promise.all(this.loadedLayers.map(async (loaded, i) => {
2165
+ if (this.destroyed || activationAbort.signal.aborted || activationVersion !== this.videoActivationVersion) return;
2121
2166
  if (!loaded.liveTexture) {
2122
2167
  loaded.play?.();
2123
2168
  return;
2124
2169
  }
2125
2170
  loaded.play?.();
2126
- if (await waitForVideoFrame(loaded.videoEl)) {
2171
+ const hasFrame = await waitForVideoFrame(loaded.videoEl, activationAbort.signal);
2172
+ if (hasFrame && !this.destroyed && !activationAbort.signal.aborted && activationVersion === this.videoActivationVersion) {
2127
2173
  this.material.uniforms[`uLayer${i}`].value = loaded.liveTexture;
2128
2174
  }
2129
2175
  }));
2176
+ if (this.videoActivationAbort === activationAbort) {
2177
+ this.videoActivationAbort = null;
2178
+ }
2130
2179
  }
2131
2180
  updateShader(fragmentShader) {
2132
2181
  this.material.fragmentShader = resolveIncludes(fragmentShader);
@@ -2144,7 +2193,11 @@ var FaceRenderer = class {
2144
2193
  this.material.uniforms.uLayerCount.value = 0;
2145
2194
  }
2146
2195
  destroy() {
2196
+ this.destroyed = true;
2147
2197
  this.layerLoadVersion++;
2198
+ this.videoActivationVersion++;
2199
+ this.videoActivationAbort?.abort();
2200
+ this.videoActivationAbort = null;
2148
2201
  this.layerLoadAbort?.abort();
2149
2202
  this.layerLoadAbort = null;
2150
2203
  this.disposeLayers();
@@ -2152,17 +2205,37 @@ var FaceRenderer = class {
2152
2205
  this.material.dispose();
2153
2206
  }
2154
2207
  };
2155
- function waitForVideoFrame(video) {
2208
+ function waitForVideoFrame(video, signal) {
2156
2209
  return new Promise((resolve) => {
2210
+ if (signal?.aborted) {
2211
+ resolve(false);
2212
+ return;
2213
+ }
2214
+ let settled = false;
2215
+ let timeoutId = null;
2216
+ let videoFrameId = null;
2217
+ const finish = (hasFrame) => {
2218
+ if (settled) return;
2219
+ settled = true;
2220
+ if (timeoutId != null) globalThis.clearTimeout(timeoutId);
2221
+ if (video && videoFrameId != null && "cancelVideoFrameCallback" in video) {
2222
+ video.cancelVideoFrameCallback(videoFrameId);
2223
+ }
2224
+ signal?.removeEventListener("abort", abort);
2225
+ resolve(hasFrame);
2226
+ };
2227
+ const abort = () => finish(false);
2157
2228
  if (video && "requestVideoFrameCallback" in video) {
2158
- const timeoutId = globalThis.setTimeout(() => resolve(false), VIDEO_FRAME_WAIT_TIMEOUT_MS);
2159
- video.requestVideoFrameCallback(() => {
2160
- globalThis.clearTimeout(timeoutId);
2161
- resolve(true);
2162
- });
2229
+ timeoutId = globalThis.setTimeout(() => finish(false), VIDEO_FRAME_WAIT_TIMEOUT_MS);
2230
+ signal?.addEventListener("abort", abort, { once: true });
2231
+ videoFrameId = video.requestVideoFrameCallback(() => finish(true));
2163
2232
  return;
2164
2233
  }
2165
- requestAnimationFrame(() => resolve(true));
2234
+ const rafId2 = requestAnimationFrame(() => finish(true));
2235
+ signal?.addEventListener("abort", () => {
2236
+ cancelAnimationFrame(rafId2);
2237
+ finish(false);
2238
+ }, { once: true });
2166
2239
  });
2167
2240
  }
2168
2241
  function toThreeUniformValue(value) {