@sangwonl/pocato-core 0.4.12 → 0.4.14
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 +62 -82
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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
|
-
|
|
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,18 @@ var FRAG_SHADERS = {
|
|
|
2056
2038
|
lenticular: lenticular_frag_default
|
|
2057
2039
|
};
|
|
2058
2040
|
var MAX_LAYERS = 8;
|
|
2059
|
-
|
|
2041
|
+
function waitForDecodedFrame(video) {
|
|
2042
|
+
if (!video || !("requestVideoFrameCallback" in video)) {
|
|
2043
|
+
return Promise.resolve();
|
|
2044
|
+
}
|
|
2045
|
+
return new Promise((resolve) => {
|
|
2046
|
+
const timeout = globalThis.setTimeout(resolve, 750);
|
|
2047
|
+
video.requestVideoFrameCallback(() => {
|
|
2048
|
+
globalThis.clearTimeout(timeout);
|
|
2049
|
+
resolve();
|
|
2050
|
+
});
|
|
2051
|
+
});
|
|
2052
|
+
}
|
|
2060
2053
|
var FaceRenderer = class {
|
|
2061
2054
|
constructor(shader, width, height, effectUniforms) {
|
|
2062
2055
|
this.loadedLayers = [];
|
|
@@ -2158,24 +2151,17 @@ var FaceRenderer = class {
|
|
|
2158
2151
|
}
|
|
2159
2152
|
async playLoadedVideos() {
|
|
2160
2153
|
const activationVersion = ++this.videoActivationVersion;
|
|
2161
|
-
this.
|
|
2162
|
-
|
|
2163
|
-
this.videoActivationAbort = activationAbort;
|
|
2164
|
-
await Promise.all(this.loadedLayers.map(async (loaded, i) => {
|
|
2165
|
-
if (this.destroyed || activationAbort.signal.aborted || activationVersion !== this.videoActivationVersion) return;
|
|
2154
|
+
const tasks2 = this.loadedLayers.map(async (loaded, i) => {
|
|
2155
|
+
if (this.destroyed || activationVersion !== this.videoActivationVersion) return;
|
|
2166
2156
|
if (!loaded.liveTexture) {
|
|
2167
2157
|
loaded.play?.();
|
|
2168
2158
|
return;
|
|
2169
2159
|
}
|
|
2160
|
+
this.material.uniforms[`uLayer${i}`].value = loaded.liveTexture;
|
|
2170
2161
|
loaded.play?.();
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
}
|
|
2175
|
-
}));
|
|
2176
|
-
if (this.videoActivationAbort === activationAbort) {
|
|
2177
|
-
this.videoActivationAbort = null;
|
|
2178
|
-
}
|
|
2162
|
+
await waitForDecodedFrame(loaded.videoEl);
|
|
2163
|
+
});
|
|
2164
|
+
await Promise.all(tasks2);
|
|
2179
2165
|
}
|
|
2180
2166
|
updateShader(fragmentShader) {
|
|
2181
2167
|
this.material.fragmentShader = resolveIncludes(fragmentShader);
|
|
@@ -2205,39 +2191,6 @@ var FaceRenderer = class {
|
|
|
2205
2191
|
this.material.dispose();
|
|
2206
2192
|
}
|
|
2207
2193
|
};
|
|
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
2194
|
function toThreeUniformValue(value) {
|
|
2242
2195
|
if (typeof value === "string") {
|
|
2243
2196
|
return value.startsWith("#") ? new THREE3.Color(value) : value;
|
|
@@ -2251,6 +2204,7 @@ function toThreeUniformValue(value) {
|
|
|
2251
2204
|
}
|
|
2252
2205
|
|
|
2253
2206
|
// src/renderer/index.ts
|
|
2207
|
+
var READY_REVEAL_TRANSITION_MS = 500;
|
|
2254
2208
|
var Renderer = class {
|
|
2255
2209
|
constructor(container, options, onError, onReady, onFirstFrame) {
|
|
2256
2210
|
this.container = container;
|
|
@@ -2273,6 +2227,7 @@ var Renderer = class {
|
|
|
2273
2227
|
this.firstFrameScheduled = false;
|
|
2274
2228
|
this.mediaActivated = false;
|
|
2275
2229
|
this.mediaActivationPromise = null;
|
|
2230
|
+
this.readyRevealTimeoutId = null;
|
|
2276
2231
|
if (!options.front?.layers?.length) {
|
|
2277
2232
|
console.warn("[pocato] front.layers must have at least 1 element");
|
|
2278
2233
|
}
|
|
@@ -2432,6 +2387,19 @@ var Renderer = class {
|
|
|
2432
2387
|
this.layersReady = true;
|
|
2433
2388
|
this.onReady?.();
|
|
2434
2389
|
}
|
|
2390
|
+
resetReadyState() {
|
|
2391
|
+
this.layersReady = false;
|
|
2392
|
+
this.firstFrameEmitted = false;
|
|
2393
|
+
this.firstFrameScheduled = false;
|
|
2394
|
+
this.mediaActivated = false;
|
|
2395
|
+
this.mediaActivationPromise = null;
|
|
2396
|
+
if (this.readyRevealTimeoutId != null) {
|
|
2397
|
+
window.clearTimeout(this.readyRevealTimeoutId);
|
|
2398
|
+
this.readyRevealTimeoutId = null;
|
|
2399
|
+
}
|
|
2400
|
+
this.cardEl.classList.add("pocato-loading");
|
|
2401
|
+
this.cardEl.classList.remove("pocato-ready");
|
|
2402
|
+
}
|
|
2435
2403
|
scheduleFirstFrame() {
|
|
2436
2404
|
if (!this.layersReady || this.firstFrameEmitted || this.firstFrameScheduled) return;
|
|
2437
2405
|
if (!this.mediaActivated) {
|
|
@@ -2447,10 +2415,14 @@ var Renderer = class {
|
|
|
2447
2415
|
if (this.mediaActivationPromise) return;
|
|
2448
2416
|
requestAnimationFrame(() => {
|
|
2449
2417
|
if (this.destroyed || this.firstFrameEmitted) return;
|
|
2450
|
-
this.firstFrameEmitted = true;
|
|
2451
2418
|
this.cardEl.classList.remove("pocato-loading");
|
|
2452
2419
|
this.cardEl.classList.add("pocato-ready");
|
|
2453
|
-
this.
|
|
2420
|
+
this.readyRevealTimeoutId = window.setTimeout(() => {
|
|
2421
|
+
this.readyRevealTimeoutId = null;
|
|
2422
|
+
if (this.destroyed || this.firstFrameEmitted) return;
|
|
2423
|
+
this.firstFrameEmitted = true;
|
|
2424
|
+
this.onFirstFrame?.();
|
|
2425
|
+
}, READY_REVEAL_TRANSITION_MS);
|
|
2454
2426
|
});
|
|
2455
2427
|
this.firstFrameScheduled = true;
|
|
2456
2428
|
}
|
|
@@ -2692,6 +2664,7 @@ var Renderer = class {
|
|
|
2692
2664
|
face.updateResolution(canvas.width, canvas.height);
|
|
2693
2665
|
}
|
|
2694
2666
|
async updateLayers(frontLayers, backLayers) {
|
|
2667
|
+
this.resetReadyState();
|
|
2695
2668
|
const promises = [];
|
|
2696
2669
|
if (frontLayers) {
|
|
2697
2670
|
promises.push(this.frontFace.loadLayers(frontLayers, this.onError));
|
|
@@ -2700,6 +2673,9 @@ var Renderer = class {
|
|
|
2700
2673
|
promises.push(this.backFace.loadLayers(backLayers, this.onError));
|
|
2701
2674
|
}
|
|
2702
2675
|
await Promise.all(promises);
|
|
2676
|
+
if (this.destroyed) return;
|
|
2677
|
+
this.layersReady = true;
|
|
2678
|
+
this.onReady?.();
|
|
2703
2679
|
}
|
|
2704
2680
|
getRotatorEl() {
|
|
2705
2681
|
return this.rotatorEl;
|
|
@@ -2713,6 +2689,10 @@ var Renderer = class {
|
|
|
2713
2689
|
destroy() {
|
|
2714
2690
|
this.destroyed = true;
|
|
2715
2691
|
this.stopRenderLoop();
|
|
2692
|
+
if (this.readyRevealTimeoutId != null) {
|
|
2693
|
+
window.clearTimeout(this.readyRevealTimeoutId);
|
|
2694
|
+
this.readyRevealTimeoutId = null;
|
|
2695
|
+
}
|
|
2716
2696
|
this.resizeObserver?.disconnect();
|
|
2717
2697
|
this.intersectionObserver?.disconnect();
|
|
2718
2698
|
this.frontFace.destroy();
|