@sangwonl/pocato-core 0.4.4 → 0.4.6
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 +92 -14
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -367,6 +367,7 @@ function bootstrapShaders() {
|
|
|
367
367
|
// src/renderer/texture-loader.ts
|
|
368
368
|
import * as THREE2 from "three";
|
|
369
369
|
var VIDEO_EXTENSIONS = /\.(mp4|webm|mov|ogg)(\?|$)/i;
|
|
370
|
+
var VIDEO_LOAD_TIMEOUT_MS = 15e3;
|
|
370
371
|
function isVideoSource(layer) {
|
|
371
372
|
if (layer.type) return layer.type === "video";
|
|
372
373
|
return VIDEO_EXTENSIONS.test(layer.src);
|
|
@@ -457,24 +458,71 @@ function loadVideoTexture(layer, onError) {
|
|
|
457
458
|
video.style.zIndex = "-9999";
|
|
458
459
|
document.body.appendChild(video);
|
|
459
460
|
pendingVideos.add(video);
|
|
461
|
+
let settled = false;
|
|
462
|
+
const timeoutId = globalThis.setTimeout(() => {
|
|
463
|
+
if (settled) return;
|
|
464
|
+
settled = true;
|
|
465
|
+
pendingVideos.delete(video);
|
|
466
|
+
video.src = "";
|
|
467
|
+
video.remove();
|
|
468
|
+
onError?.(new Error(`Timed out loading video texture: ${layer.src}`));
|
|
469
|
+
resolve({ texture: getTransparentTexture() });
|
|
470
|
+
}, VIDEO_LOAD_TIMEOUT_MS);
|
|
460
471
|
video.addEventListener("error", () => {
|
|
472
|
+
if (settled) return;
|
|
473
|
+
settled = true;
|
|
474
|
+
globalThis.clearTimeout(timeoutId);
|
|
461
475
|
pendingVideos.delete(video);
|
|
462
476
|
const msg = video.error?.message ?? "Unknown video error";
|
|
463
477
|
onError?.(new Error(`Failed to load video texture: ${layer.src} (${msg})`));
|
|
464
478
|
video.remove();
|
|
465
479
|
resolve({ texture: getTransparentTexture() });
|
|
466
480
|
}, { once: true });
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
}).catch(() => {
|
|
481
|
+
const resolveWithFrame = () => {
|
|
482
|
+
if (settled) return;
|
|
483
|
+
settled = true;
|
|
484
|
+
globalThis.clearTimeout(timeoutId);
|
|
485
|
+
video.pause();
|
|
473
486
|
pendingVideos.delete(video);
|
|
474
|
-
|
|
475
|
-
video.
|
|
476
|
-
|
|
477
|
-
|
|
487
|
+
const canvas = document.createElement("canvas");
|
|
488
|
+
canvas.width = video.videoWidth || 1;
|
|
489
|
+
canvas.height = video.videoHeight || 1;
|
|
490
|
+
const ctx = canvas.getContext("2d");
|
|
491
|
+
let posterTexture = getTransparentTexture();
|
|
492
|
+
if (ctx) {
|
|
493
|
+
try {
|
|
494
|
+
ctx.drawImage(video, 0, 0);
|
|
495
|
+
posterTexture = new THREE2.CanvasTexture(canvas);
|
|
496
|
+
posterTexture.needsUpdate = true;
|
|
497
|
+
} catch (error) {
|
|
498
|
+
debugLog("video poster drawImage failed", {
|
|
499
|
+
src: layer.src,
|
|
500
|
+
error: error instanceof Error ? error.message : String(error)
|
|
501
|
+
});
|
|
502
|
+
onError?.(error instanceof Error ? error : new Error(String(error)));
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
const liveTexture = new THREE2.VideoTexture(video);
|
|
506
|
+
resolve({
|
|
507
|
+
texture: posterTexture,
|
|
508
|
+
liveTexture,
|
|
509
|
+
videoEl: video,
|
|
510
|
+
play: () => {
|
|
511
|
+
video.play().catch(() => {
|
|
512
|
+
onError?.(new Error(`Video autoplay blocked: ${layer.src}`));
|
|
513
|
+
});
|
|
514
|
+
}
|
|
515
|
+
});
|
|
516
|
+
};
|
|
517
|
+
video.addEventListener("loadeddata", () => {
|
|
518
|
+
if ("requestVideoFrameCallback" in video) {
|
|
519
|
+
video.requestVideoFrameCallback(() => resolveWithFrame());
|
|
520
|
+
return;
|
|
521
|
+
}
|
|
522
|
+
window.setTimeout(resolveWithFrame, 0);
|
|
523
|
+
}, { once: true });
|
|
524
|
+
video.src = layer.src;
|
|
525
|
+
video.load();
|
|
478
526
|
});
|
|
479
527
|
}
|
|
480
528
|
function loadFrozenVideoTexture(layer, freezeTime, onError) {
|
|
@@ -641,6 +689,9 @@ function disposeLoadedLayer(loaded) {
|
|
|
641
689
|
if (loaded.texture !== getTransparentTexture()) {
|
|
642
690
|
loaded.texture.dispose();
|
|
643
691
|
}
|
|
692
|
+
if (loaded.liveTexture && loaded.liveTexture !== loaded.texture && loaded.liveTexture !== getTransparentTexture()) {
|
|
693
|
+
loaded.liveTexture.dispose();
|
|
694
|
+
}
|
|
644
695
|
}
|
|
645
696
|
function cleanupPendingVideos() {
|
|
646
697
|
for (const video of pendingVideos) {
|
|
@@ -2046,6 +2097,15 @@ var FaceRenderer = class {
|
|
|
2046
2097
|
updateResolution(width, height) {
|
|
2047
2098
|
this.material.uniforms.uResolution.value.set(width, height);
|
|
2048
2099
|
}
|
|
2100
|
+
playLoadedVideos() {
|
|
2101
|
+
for (let i = 0; i < this.loadedLayers.length; i++) {
|
|
2102
|
+
const loaded = this.loadedLayers[i];
|
|
2103
|
+
if (loaded.liveTexture) {
|
|
2104
|
+
this.material.uniforms[`uLayer${i}`].value = loaded.liveTexture;
|
|
2105
|
+
}
|
|
2106
|
+
loaded.play?.();
|
|
2107
|
+
}
|
|
2108
|
+
}
|
|
2049
2109
|
updateShader(fragmentShader) {
|
|
2050
2110
|
this.material.fragmentShader = resolveIncludes(fragmentShader);
|
|
2051
2111
|
this.material.needsUpdate = true;
|
|
@@ -2082,11 +2142,12 @@ function toThreeUniformValue(value) {
|
|
|
2082
2142
|
|
|
2083
2143
|
// src/renderer/index.ts
|
|
2084
2144
|
var Renderer = class {
|
|
2085
|
-
constructor(container, options, onError, onReady) {
|
|
2145
|
+
constructor(container, options, onError, onReady, onFirstFrame) {
|
|
2086
2146
|
this.container = container;
|
|
2087
2147
|
this.options = options;
|
|
2088
2148
|
this.onError = onError;
|
|
2089
2149
|
this.onReady = onReady;
|
|
2150
|
+
this.onFirstFrame = onFirstFrame;
|
|
2090
2151
|
this.backFace = null;
|
|
2091
2152
|
this.frontRenderer = null;
|
|
2092
2153
|
this.backRenderer = null;
|
|
@@ -2097,6 +2158,9 @@ var Renderer = class {
|
|
|
2097
2158
|
this.intersectionObserver = null;
|
|
2098
2159
|
this.visible = false;
|
|
2099
2160
|
this.destroyed = false;
|
|
2161
|
+
this.layersReady = false;
|
|
2162
|
+
this.firstFrameEmitted = false;
|
|
2163
|
+
this.firstFrameScheduled = false;
|
|
2100
2164
|
if (!options.front?.layers?.length) {
|
|
2101
2165
|
console.warn("[pocato] front.layers must have at least 1 element");
|
|
2102
2166
|
}
|
|
@@ -2253,11 +2317,23 @@ var Renderer = class {
|
|
|
2253
2317
|
}
|
|
2254
2318
|
await Promise.all(promises);
|
|
2255
2319
|
if (this.destroyed) return;
|
|
2320
|
+
this.layersReady = true;
|
|
2321
|
+
this.onReady?.();
|
|
2322
|
+
}
|
|
2323
|
+
scheduleFirstFrame() {
|
|
2324
|
+
if (!this.layersReady || this.firstFrameEmitted || this.firstFrameScheduled) return;
|
|
2325
|
+
this.firstFrameScheduled = true;
|
|
2256
2326
|
requestAnimationFrame(() => {
|
|
2257
|
-
if (this.destroyed) return;
|
|
2327
|
+
if (this.destroyed || this.firstFrameEmitted) return;
|
|
2328
|
+
this.firstFrameEmitted = true;
|
|
2258
2329
|
this.cardEl.classList.remove("pocato-loading");
|
|
2259
2330
|
this.cardEl.classList.add("pocato-ready");
|
|
2260
|
-
this.
|
|
2331
|
+
this.onFirstFrame?.();
|
|
2332
|
+
requestAnimationFrame(() => {
|
|
2333
|
+
if (this.destroyed) return;
|
|
2334
|
+
this.frontFace.playLoadedVideos();
|
|
2335
|
+
this.backFace?.playLoadedVideos();
|
|
2336
|
+
});
|
|
2261
2337
|
});
|
|
2262
2338
|
}
|
|
2263
2339
|
injectStyles() {
|
|
@@ -2429,6 +2505,7 @@ var Renderer = class {
|
|
|
2429
2505
|
this.backFace.material.uniforms.uTime.value += delta;
|
|
2430
2506
|
this.backRenderer.render(this.backFace.scene, this.backFace.camera);
|
|
2431
2507
|
}
|
|
2508
|
+
this.scheduleFirstFrame();
|
|
2432
2509
|
};
|
|
2433
2510
|
this.rafId = requestAnimationFrame(animate);
|
|
2434
2511
|
}
|
|
@@ -3117,7 +3194,8 @@ var PocaCard = class extends EventEmitter {
|
|
|
3117
3194
|
preventTouchScroll: options.preventTouchScroll
|
|
3118
3195
|
},
|
|
3119
3196
|
(error) => this.emit("error", error),
|
|
3120
|
-
() => this.emit("ready")
|
|
3197
|
+
() => this.emit("ready"),
|
|
3198
|
+
() => this.emit("firstFrame")
|
|
3121
3199
|
);
|
|
3122
3200
|
this.interaction = new InteractionHandler(
|
|
3123
3201
|
this.renderer.getRotatorEl(),
|