@sangwonl/pocato-core 0.4.7 → 0.4.9
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 +81 -56
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -381,7 +381,6 @@ function getTransparentTexture() {
|
|
|
381
381
|
}
|
|
382
382
|
return transparentTexture;
|
|
383
383
|
}
|
|
384
|
-
var pendingVideos = /* @__PURE__ */ new Set();
|
|
385
384
|
function debugLog(message, data) {
|
|
386
385
|
try {
|
|
387
386
|
if (globalThis.localStorage?.getItem("pocatoDebug") === "1") {
|
|
@@ -408,14 +407,14 @@ function getFreezeTargetTime(freezeTime, duration, seed) {
|
|
|
408
407
|
function isAtTargetTime(video, targetTime) {
|
|
409
408
|
return Math.abs(video.currentTime - targetTime) < 0.05;
|
|
410
409
|
}
|
|
411
|
-
function loadLayerTexture(layer,
|
|
410
|
+
function loadLayerTexture(layer, options = {}) {
|
|
412
411
|
if (isVideoSource(layer)) {
|
|
413
412
|
if (layer.freeze != null) {
|
|
414
|
-
return loadFrozenVideoTexture(layer, layer.freeze === "random" ? -1 : layer.freeze,
|
|
413
|
+
return loadFrozenVideoTexture(layer, layer.freeze === "random" ? -1 : layer.freeze, options);
|
|
415
414
|
}
|
|
416
|
-
return loadVideoTexture(layer,
|
|
415
|
+
return loadVideoTexture(layer, options);
|
|
417
416
|
}
|
|
418
|
-
return loadImageTexture(layer.src, onError);
|
|
417
|
+
return loadImageTexture(layer.src, options.onError);
|
|
419
418
|
}
|
|
420
419
|
var inflight = /* @__PURE__ */ new Map();
|
|
421
420
|
function loadImageTexture(src, onError) {
|
|
@@ -441,8 +440,13 @@ function loadImageTexture(src, onError) {
|
|
|
441
440
|
}
|
|
442
441
|
return pending.then((texture) => ({ texture }));
|
|
443
442
|
}
|
|
444
|
-
function loadVideoTexture(layer,
|
|
443
|
+
function loadVideoTexture(layer, options) {
|
|
445
444
|
return new Promise((resolve) => {
|
|
445
|
+
const { onError, signal } = options;
|
|
446
|
+
if (signal?.aborted) {
|
|
447
|
+
resolve({ texture: getTransparentTexture() });
|
|
448
|
+
return;
|
|
449
|
+
}
|
|
446
450
|
const video = document.createElement("video");
|
|
447
451
|
video.crossOrigin = "anonymous";
|
|
448
452
|
video.muted = true;
|
|
@@ -457,33 +461,40 @@ function loadVideoTexture(layer, onError) {
|
|
|
457
461
|
video.style.pointerEvents = "none";
|
|
458
462
|
video.style.zIndex = "-9999";
|
|
459
463
|
document.body.appendChild(video);
|
|
460
|
-
pendingVideos.add(video);
|
|
461
464
|
let settled = false;
|
|
462
|
-
const
|
|
463
|
-
|
|
464
|
-
settled = true;
|
|
465
|
-
pendingVideos.delete(video);
|
|
465
|
+
const cleanupVideo = () => {
|
|
466
|
+
video.pause();
|
|
466
467
|
video.src = "";
|
|
467
468
|
video.remove();
|
|
469
|
+
};
|
|
470
|
+
const finish = (loaded) => {
|
|
471
|
+
if (settled) return;
|
|
472
|
+
settled = true;
|
|
473
|
+
globalThis.clearTimeout(timeoutId);
|
|
474
|
+
signal?.removeEventListener("abort", abort);
|
|
475
|
+
resolve(loaded);
|
|
476
|
+
};
|
|
477
|
+
const abort = () => {
|
|
478
|
+
cleanupVideo();
|
|
479
|
+
finish({ texture: getTransparentTexture() });
|
|
480
|
+
};
|
|
481
|
+
const timeoutId = globalThis.setTimeout(() => {
|
|
482
|
+
if (settled) return;
|
|
483
|
+
cleanupVideo();
|
|
468
484
|
onError?.(new Error(`Timed out loading video texture: ${layer.src}`));
|
|
469
|
-
|
|
485
|
+
finish({ texture: getTransparentTexture() });
|
|
470
486
|
}, VIDEO_LOAD_TIMEOUT_MS);
|
|
487
|
+
signal?.addEventListener("abort", abort, { once: true });
|
|
471
488
|
video.addEventListener("error", () => {
|
|
472
489
|
if (settled) return;
|
|
473
|
-
settled = true;
|
|
474
|
-
globalThis.clearTimeout(timeoutId);
|
|
475
|
-
pendingVideos.delete(video);
|
|
476
490
|
const msg = video.error?.message ?? "Unknown video error";
|
|
477
491
|
onError?.(new Error(`Failed to load video texture: ${layer.src} (${msg})`));
|
|
478
|
-
|
|
479
|
-
|
|
492
|
+
cleanupVideo();
|
|
493
|
+
finish({ texture: getTransparentTexture() });
|
|
480
494
|
}, { once: true });
|
|
481
495
|
const resolveWithFrame = () => {
|
|
482
496
|
if (settled) return;
|
|
483
|
-
settled = true;
|
|
484
|
-
globalThis.clearTimeout(timeoutId);
|
|
485
497
|
video.pause();
|
|
486
|
-
pendingVideos.delete(video);
|
|
487
498
|
const canvas = document.createElement("canvas");
|
|
488
499
|
canvas.width = video.videoWidth || 1;
|
|
489
500
|
canvas.height = video.videoHeight || 1;
|
|
@@ -503,7 +514,7 @@ function loadVideoTexture(layer, onError) {
|
|
|
503
514
|
}
|
|
504
515
|
}
|
|
505
516
|
const liveTexture = new THREE2.VideoTexture(video);
|
|
506
|
-
|
|
517
|
+
finish({
|
|
507
518
|
texture: posterTexture,
|
|
508
519
|
liveTexture,
|
|
509
520
|
videoEl: video,
|
|
@@ -521,8 +532,13 @@ function loadVideoTexture(layer, onError) {
|
|
|
521
532
|
video.load();
|
|
522
533
|
});
|
|
523
534
|
}
|
|
524
|
-
function loadFrozenVideoTexture(layer, freezeTime,
|
|
535
|
+
function loadFrozenVideoTexture(layer, freezeTime, options) {
|
|
525
536
|
return new Promise((resolve) => {
|
|
537
|
+
const { onError, signal } = options;
|
|
538
|
+
if (signal?.aborted) {
|
|
539
|
+
resolve({ texture: getTransparentTexture() });
|
|
540
|
+
return;
|
|
541
|
+
}
|
|
526
542
|
debugLog("freeze load start", {
|
|
527
543
|
src: layer.src,
|
|
528
544
|
freezeTime,
|
|
@@ -541,13 +557,27 @@ function loadFrozenVideoTexture(layer, freezeTime, onError) {
|
|
|
541
557
|
video.style.pointerEvents = "none";
|
|
542
558
|
video.style.zIndex = "-9999";
|
|
543
559
|
document.body.appendChild(video);
|
|
544
|
-
pendingVideos.add(video);
|
|
545
560
|
let settled = false;
|
|
546
561
|
let targetTime = 0;
|
|
547
562
|
let retrySeekRegistered = false;
|
|
548
|
-
const
|
|
563
|
+
const cleanupVideo = () => {
|
|
564
|
+
video.pause();
|
|
565
|
+
video.src = "";
|
|
566
|
+
video.remove();
|
|
567
|
+
};
|
|
568
|
+
const finish = (loaded) => {
|
|
549
569
|
if (settled) return;
|
|
550
570
|
settled = true;
|
|
571
|
+
signal?.removeEventListener("abort", abort);
|
|
572
|
+
resolve(loaded);
|
|
573
|
+
};
|
|
574
|
+
const abort = () => {
|
|
575
|
+
cleanupVideo();
|
|
576
|
+
finish({ texture: getTransparentTexture() });
|
|
577
|
+
};
|
|
578
|
+
signal?.addEventListener("abort", abort, { once: true });
|
|
579
|
+
const captureFrame = () => {
|
|
580
|
+
if (settled) return;
|
|
551
581
|
video.pause();
|
|
552
582
|
debugLog("freeze capture frame", {
|
|
553
583
|
src: layer.src,
|
|
@@ -562,10 +592,8 @@ function loadFrozenVideoTexture(layer, freezeTime, onError) {
|
|
|
562
592
|
canvas.height = video.videoHeight || 1;
|
|
563
593
|
const ctx = canvas.getContext("2d");
|
|
564
594
|
if (!ctx) {
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
video.remove();
|
|
568
|
-
resolve({ texture: getTransparentTexture() });
|
|
595
|
+
cleanupVideo();
|
|
596
|
+
finish({ texture: getTransparentTexture() });
|
|
569
597
|
return;
|
|
570
598
|
}
|
|
571
599
|
try {
|
|
@@ -575,11 +603,9 @@ function loadFrozenVideoTexture(layer, freezeTime, onError) {
|
|
|
575
603
|
src: layer.src,
|
|
576
604
|
error: error instanceof Error ? error.message : String(error)
|
|
577
605
|
});
|
|
578
|
-
|
|
579
|
-
video.src = "";
|
|
580
|
-
video.remove();
|
|
606
|
+
cleanupVideo();
|
|
581
607
|
onError?.(error instanceof Error ? error : new Error(String(error)));
|
|
582
|
-
|
|
608
|
+
finish({ texture: getTransparentTexture() });
|
|
583
609
|
return;
|
|
584
610
|
}
|
|
585
611
|
const texture = new THREE2.CanvasTexture(canvas);
|
|
@@ -589,10 +615,8 @@ function loadFrozenVideoTexture(layer, freezeTime, onError) {
|
|
|
589
615
|
width: canvas.width,
|
|
590
616
|
height: canvas.height
|
|
591
617
|
});
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
video.remove();
|
|
595
|
-
resolve({ texture });
|
|
618
|
+
cleanupVideo();
|
|
619
|
+
finish({ texture });
|
|
596
620
|
};
|
|
597
621
|
const captureReadyFrame = () => {
|
|
598
622
|
debugLog("freeze wait decoded frame", {
|
|
@@ -633,11 +657,10 @@ function loadFrozenVideoTexture(layer, freezeTime, onError) {
|
|
|
633
657
|
video.addEventListener("loadeddata", retry, { once: true });
|
|
634
658
|
};
|
|
635
659
|
video.addEventListener("error", () => {
|
|
636
|
-
pendingVideos.delete(video);
|
|
637
660
|
const msg = video.error?.message ?? "Unknown video error";
|
|
638
661
|
onError?.(new Error(`Failed to load video texture: ${layer.src} (${msg})`));
|
|
639
|
-
|
|
640
|
-
|
|
662
|
+
cleanupVideo();
|
|
663
|
+
finish({ texture: getTransparentTexture() });
|
|
641
664
|
}, { once: true });
|
|
642
665
|
video.addEventListener("loadedmetadata", () => {
|
|
643
666
|
const time = getFreezeTargetTime(freezeTime, video.duration, layer.freezeSeed);
|
|
@@ -689,14 +712,6 @@ function disposeLoadedLayer(loaded) {
|
|
|
689
712
|
loaded.liveTexture.dispose();
|
|
690
713
|
}
|
|
691
714
|
}
|
|
692
|
-
function cleanupPendingVideos() {
|
|
693
|
-
for (const video of pendingVideos) {
|
|
694
|
-
video.pause();
|
|
695
|
-
video.src = "";
|
|
696
|
-
video.remove();
|
|
697
|
-
}
|
|
698
|
-
pendingVideos.clear();
|
|
699
|
-
}
|
|
700
715
|
|
|
701
716
|
// src/shaders/common.vert.ts
|
|
702
717
|
var common_vert_default = glsl`
|
|
@@ -2011,6 +2026,7 @@ var FaceRenderer = class {
|
|
|
2011
2026
|
constructor(shader, width, height, effectUniforms) {
|
|
2012
2027
|
this.loadedLayers = [];
|
|
2013
2028
|
this.layerLoadVersion = 0;
|
|
2029
|
+
this.layerLoadAbort = null;
|
|
2014
2030
|
bootstrapShaders();
|
|
2015
2031
|
this.scene = new THREE3.Scene();
|
|
2016
2032
|
this.camera = new THREE3.Camera();
|
|
@@ -2061,19 +2077,25 @@ var FaceRenderer = class {
|
|
|
2061
2077
|
}
|
|
2062
2078
|
async loadLayers(layers, onError) {
|
|
2063
2079
|
const version = ++this.layerLoadVersion;
|
|
2080
|
+
this.layerLoadAbort?.abort();
|
|
2081
|
+
const abort = new AbortController();
|
|
2082
|
+
this.layerLoadAbort = abort;
|
|
2064
2083
|
this.disposeLayers();
|
|
2065
2084
|
const count = Math.min(layers.length, MAX_LAYERS);
|
|
2066
2085
|
this.material.uniforms.uLayerCount.value = count;
|
|
2067
2086
|
const promises = layers.slice(0, MAX_LAYERS).map(
|
|
2068
|
-
(layer) => loadLayerTexture(layer, onError)
|
|
2087
|
+
(layer) => loadLayerTexture(layer, { onError, signal: abort.signal })
|
|
2069
2088
|
);
|
|
2070
2089
|
const results = await Promise.all(promises);
|
|
2071
|
-
if (version !== this.layerLoadVersion) {
|
|
2090
|
+
if (version !== this.layerLoadVersion || abort.signal.aborted) {
|
|
2072
2091
|
for (const loaded of results) {
|
|
2073
2092
|
disposeLoadedLayer(loaded);
|
|
2074
2093
|
}
|
|
2075
2094
|
return;
|
|
2076
2095
|
}
|
|
2096
|
+
if (this.layerLoadAbort === abort) {
|
|
2097
|
+
this.layerLoadAbort = null;
|
|
2098
|
+
}
|
|
2077
2099
|
this.loadedLayers = results;
|
|
2078
2100
|
for (let i = 0; i < results.length; i++) {
|
|
2079
2101
|
this.material.uniforms[`uLayer${i}`].value = results[i].texture;
|
|
@@ -2119,6 +2141,8 @@ var FaceRenderer = class {
|
|
|
2119
2141
|
}
|
|
2120
2142
|
destroy() {
|
|
2121
2143
|
this.layerLoadVersion++;
|
|
2144
|
+
this.layerLoadAbort?.abort();
|
|
2145
|
+
this.layerLoadAbort = null;
|
|
2122
2146
|
this.disposeLayers();
|
|
2123
2147
|
this.mesh.geometry.dispose();
|
|
2124
2148
|
this.material.dispose();
|
|
@@ -2157,6 +2181,7 @@ var Renderer = class {
|
|
|
2157
2181
|
this.layersReady = false;
|
|
2158
2182
|
this.firstFrameEmitted = false;
|
|
2159
2183
|
this.firstFrameScheduled = false;
|
|
2184
|
+
this.mediaActivated = false;
|
|
2160
2185
|
if (!options.front?.layers?.length) {
|
|
2161
2186
|
console.warn("[pocato] front.layers must have at least 1 element");
|
|
2162
2187
|
}
|
|
@@ -2318,19 +2343,20 @@ var Renderer = class {
|
|
|
2318
2343
|
}
|
|
2319
2344
|
scheduleFirstFrame() {
|
|
2320
2345
|
if (!this.layersReady || this.firstFrameEmitted || this.firstFrameScheduled) return;
|
|
2321
|
-
this.
|
|
2346
|
+
if (!this.mediaActivated) {
|
|
2347
|
+
this.mediaActivated = true;
|
|
2348
|
+
this.frontFace.playLoadedVideos();
|
|
2349
|
+
this.backFace?.playLoadedVideos();
|
|
2350
|
+
return;
|
|
2351
|
+
}
|
|
2322
2352
|
requestAnimationFrame(() => {
|
|
2323
2353
|
if (this.destroyed || this.firstFrameEmitted) return;
|
|
2324
2354
|
this.firstFrameEmitted = true;
|
|
2325
2355
|
this.cardEl.classList.remove("pocato-loading");
|
|
2326
2356
|
this.cardEl.classList.add("pocato-ready");
|
|
2327
2357
|
this.onFirstFrame?.();
|
|
2328
|
-
requestAnimationFrame(() => {
|
|
2329
|
-
if (this.destroyed) return;
|
|
2330
|
-
this.frontFace.playLoadedVideos();
|
|
2331
|
-
this.backFace?.playLoadedVideos();
|
|
2332
|
-
});
|
|
2333
2358
|
});
|
|
2359
|
+
this.firstFrameScheduled = true;
|
|
2334
2360
|
}
|
|
2335
2361
|
injectStyles() {
|
|
2336
2362
|
const id = "pocato-styles";
|
|
@@ -2593,7 +2619,6 @@ var Renderer = class {
|
|
|
2593
2619
|
this.stopRenderLoop();
|
|
2594
2620
|
this.resizeObserver?.disconnect();
|
|
2595
2621
|
this.intersectionObserver?.disconnect();
|
|
2596
|
-
cleanupPendingVideos();
|
|
2597
2622
|
this.frontFace.destroy();
|
|
2598
2623
|
if (this.frontRenderer) {
|
|
2599
2624
|
this.disposeWebGLRenderer(this.frontRenderer);
|