@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 +46 -80
- 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,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 ||
|
|
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.
|
|
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();
|