@sangwonl/pocato-core 0.4.13 → 0.4.15

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,7 +368,6 @@ 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;
372
371
  function isVideoSource(layer) {
373
372
  if (layer.type) return layer.type === "video";
374
373
  return VIDEO_EXTENSIONS.test(layer.src);
@@ -459,9 +458,13 @@ function loadVideoTexture(layer, options) {
459
458
  const video = document.createElement("video");
460
459
  video.crossOrigin = "anonymous";
461
460
  video.muted = true;
461
+ video.autoplay = true;
462
462
  video.loop = layer.loop ?? true;
463
463
  applyVideoPlaybackOptions(video, layer);
464
464
  video.playsInline = true;
465
+ video.setAttribute("muted", "");
466
+ video.setAttribute("playsinline", "");
467
+ video.setAttribute("webkit-playsinline", "");
465
468
  video.preload = "auto";
466
469
  video.style.position = "fixed";
467
470
  video.style.width = "1px";
@@ -503,7 +506,6 @@ function loadVideoTexture(layer, options) {
503
506
  }, { once: true });
504
507
  const resolveWithFrame = () => {
505
508
  if (settled) return;
506
- video.pause();
507
509
  const canvas = document.createElement("canvas");
508
510
  canvas.width = video.videoWidth || 1;
509
511
  canvas.height = video.videoHeight || 1;
@@ -523,49 +525,29 @@ function loadVideoTexture(layer, options) {
523
525
  }
524
526
  }
525
527
  const liveTexture = new THREE2.VideoTexture(video);
528
+ const play = () => {
529
+ const playPromise = video.play();
530
+ playPromise.catch((error) => {
531
+ const message = error instanceof Error ? error.message : String(error);
532
+ debugLog("video play rejected", { src: layer.src, error: message });
533
+ onError?.(new Error(`Video autoplay blocked: ${layer.src} (${message})`));
534
+ });
535
+ };
536
+ play();
526
537
  finish({
527
538
  texture: posterTexture,
528
539
  liveTexture,
529
540
  videoEl: video,
530
- play: () => {
531
- video.play().catch(() => {
532
- onError?.(new Error(`Video autoplay blocked: ${layer.src}`));
533
- });
534
- }
541
+ play
535
542
  });
536
543
  };
537
544
  video.addEventListener("loadeddata", () => {
538
- waitForDecodedVideoFrame(video, signal).then(() => {
539
- resolveWithFrame();
540
- });
545
+ resolveWithFrame();
541
546
  }, { once: true });
542
547
  video.src = layer.src;
543
548
  video.load();
544
549
  });
545
550
  }
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
- }
569
551
  function loadFrozenVideoTexture(layer, freezeTime, options) {
570
552
  return new Promise((resolve) => {
571
553
  const { onError, signal } = options;
@@ -2056,7 +2038,6 @@ var FRAG_SHADERS = {
2056
2038
  lenticular: lenticular_frag_default
2057
2039
  };
2058
2040
  var MAX_LAYERS = 8;
2059
- var VIDEO_FRAME_WAIT_TIMEOUT_MS = 750;
2060
2041
  var FaceRenderer = class {
2061
2042
  constructor(shader, width, height, effectUniforms) {
2062
2043
  this.loadedLayers = [];
@@ -2158,24 +2139,15 @@ var FaceRenderer = class {
2158
2139
  }
2159
2140
  async playLoadedVideos() {
2160
2141
  const activationVersion = ++this.videoActivationVersion;
2161
- this.videoActivationAbort?.abort();
2162
- const activationAbort = new AbortController();
2163
- this.videoActivationAbort = activationAbort;
2164
2142
  await Promise.all(this.loadedLayers.map(async (loaded, i) => {
2165
- if (this.destroyed || activationAbort.signal.aborted || activationVersion !== this.videoActivationVersion) return;
2143
+ if (this.destroyed || activationVersion !== this.videoActivationVersion) return;
2166
2144
  if (!loaded.liveTexture) {
2167
2145
  loaded.play?.();
2168
2146
  return;
2169
2147
  }
2148
+ this.material.uniforms[`uLayer${i}`].value = loaded.liveTexture;
2170
2149
  loaded.play?.();
2171
- const hasFrame = await waitForVideoFrame(loaded.videoEl, activationAbort.signal);
2172
- if (hasFrame && !this.destroyed && !activationAbort.signal.aborted && activationVersion === this.videoActivationVersion) {
2173
- this.material.uniforms[`uLayer${i}`].value = loaded.liveTexture;
2174
- }
2175
2150
  }));
2176
- if (this.videoActivationAbort === activationAbort) {
2177
- this.videoActivationAbort = null;
2178
- }
2179
2151
  }
2180
2152
  updateShader(fragmentShader) {
2181
2153
  this.material.fragmentShader = resolveIncludes(fragmentShader);
@@ -2205,39 +2177,6 @@ var FaceRenderer = class {
2205
2177
  this.material.dispose();
2206
2178
  }
2207
2179
  };
2208
- function waitForVideoFrame(video, signal) {
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);
2228
- if (video && "requestVideoFrameCallback" in video) {
2229
- timeoutId = globalThis.setTimeout(() => finish(false), VIDEO_FRAME_WAIT_TIMEOUT_MS);
2230
- signal?.addEventListener("abort", abort, { once: true });
2231
- videoFrameId = video.requestVideoFrameCallback(() => finish(true));
2232
- return;
2233
- }
2234
- const rafId2 = requestAnimationFrame(() => finish(true));
2235
- signal?.addEventListener("abort", () => {
2236
- cancelAnimationFrame(rafId2);
2237
- finish(false);
2238
- }, { once: true });
2239
- });
2240
- }
2241
2180
  function toThreeUniformValue(value) {
2242
2181
  if (typeof value === "string") {
2243
2182
  return value.startsWith("#") ? new THREE3.Color(value) : value;
@@ -2251,6 +2190,7 @@ function toThreeUniformValue(value) {
2251
2190
  }
2252
2191
 
2253
2192
  // src/renderer/index.ts
2193
+ var READY_REVEAL_TRANSITION_MS = 500;
2254
2194
  var Renderer = class {
2255
2195
  constructor(container, options, onError, onReady, onFirstFrame) {
2256
2196
  this.container = container;
@@ -2273,6 +2213,7 @@ var Renderer = class {
2273
2213
  this.firstFrameScheduled = false;
2274
2214
  this.mediaActivated = false;
2275
2215
  this.mediaActivationPromise = null;
2216
+ this.readyRevealTimeoutId = null;
2276
2217
  if (!options.front?.layers?.length) {
2277
2218
  console.warn("[pocato] front.layers must have at least 1 element");
2278
2219
  }
@@ -2432,6 +2373,19 @@ var Renderer = class {
2432
2373
  this.layersReady = true;
2433
2374
  this.onReady?.();
2434
2375
  }
2376
+ resetReadyState() {
2377
+ this.layersReady = false;
2378
+ this.firstFrameEmitted = false;
2379
+ this.firstFrameScheduled = false;
2380
+ this.mediaActivated = false;
2381
+ this.mediaActivationPromise = null;
2382
+ if (this.readyRevealTimeoutId != null) {
2383
+ window.clearTimeout(this.readyRevealTimeoutId);
2384
+ this.readyRevealTimeoutId = null;
2385
+ }
2386
+ this.cardEl.classList.add("pocato-loading");
2387
+ this.cardEl.classList.remove("pocato-ready");
2388
+ }
2435
2389
  scheduleFirstFrame() {
2436
2390
  if (!this.layersReady || this.firstFrameEmitted || this.firstFrameScheduled) return;
2437
2391
  if (!this.mediaActivated) {
@@ -2447,10 +2401,14 @@ var Renderer = class {
2447
2401
  if (this.mediaActivationPromise) return;
2448
2402
  requestAnimationFrame(() => {
2449
2403
  if (this.destroyed || this.firstFrameEmitted) return;
2450
- this.firstFrameEmitted = true;
2451
2404
  this.cardEl.classList.remove("pocato-loading");
2452
2405
  this.cardEl.classList.add("pocato-ready");
2453
- this.onFirstFrame?.();
2406
+ this.readyRevealTimeoutId = window.setTimeout(() => {
2407
+ this.readyRevealTimeoutId = null;
2408
+ if (this.destroyed || this.firstFrameEmitted) return;
2409
+ this.firstFrameEmitted = true;
2410
+ this.onFirstFrame?.();
2411
+ }, READY_REVEAL_TRANSITION_MS);
2454
2412
  });
2455
2413
  this.firstFrameScheduled = true;
2456
2414
  }
@@ -2692,6 +2650,7 @@ var Renderer = class {
2692
2650
  face.updateResolution(canvas.width, canvas.height);
2693
2651
  }
2694
2652
  async updateLayers(frontLayers, backLayers) {
2653
+ this.resetReadyState();
2695
2654
  const promises = [];
2696
2655
  if (frontLayers) {
2697
2656
  promises.push(this.frontFace.loadLayers(frontLayers, this.onError));
@@ -2700,6 +2659,9 @@ var Renderer = class {
2700
2659
  promises.push(this.backFace.loadLayers(backLayers, this.onError));
2701
2660
  }
2702
2661
  await Promise.all(promises);
2662
+ if (this.destroyed) return;
2663
+ this.layersReady = true;
2664
+ this.onReady?.();
2703
2665
  }
2704
2666
  getRotatorEl() {
2705
2667
  return this.rotatorEl;
@@ -2713,6 +2675,10 @@ var Renderer = class {
2713
2675
  destroy() {
2714
2676
  this.destroyed = true;
2715
2677
  this.stopRenderLoop();
2678
+ if (this.readyRevealTimeoutId != null) {
2679
+ window.clearTimeout(this.readyRevealTimeoutId);
2680
+ this.readyRevealTimeoutId = null;
2681
+ }
2716
2682
  this.resizeObserver?.disconnect();
2717
2683
  this.intersectionObserver?.disconnect();
2718
2684
  this.frontFace.destroy();