@sangwonl/pocato-core 0.4.6 → 0.4.8
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 +74 -55
- 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,
|
|
@@ -515,18 +526,19 @@ function loadVideoTexture(layer, onError) {
|
|
|
515
526
|
});
|
|
516
527
|
};
|
|
517
528
|
video.addEventListener("loadeddata", () => {
|
|
518
|
-
|
|
519
|
-
video.requestVideoFrameCallback(() => resolveWithFrame());
|
|
520
|
-
return;
|
|
521
|
-
}
|
|
522
|
-
window.setTimeout(resolveWithFrame, 0);
|
|
529
|
+
resolveWithFrame();
|
|
523
530
|
}, { once: true });
|
|
524
531
|
video.src = layer.src;
|
|
525
532
|
video.load();
|
|
526
533
|
});
|
|
527
534
|
}
|
|
528
|
-
function loadFrozenVideoTexture(layer, freezeTime,
|
|
535
|
+
function loadFrozenVideoTexture(layer, freezeTime, options) {
|
|
529
536
|
return new Promise((resolve) => {
|
|
537
|
+
const { onError, signal } = options;
|
|
538
|
+
if (signal?.aborted) {
|
|
539
|
+
resolve({ texture: getTransparentTexture() });
|
|
540
|
+
return;
|
|
541
|
+
}
|
|
530
542
|
debugLog("freeze load start", {
|
|
531
543
|
src: layer.src,
|
|
532
544
|
freezeTime,
|
|
@@ -545,13 +557,27 @@ function loadFrozenVideoTexture(layer, freezeTime, onError) {
|
|
|
545
557
|
video.style.pointerEvents = "none";
|
|
546
558
|
video.style.zIndex = "-9999";
|
|
547
559
|
document.body.appendChild(video);
|
|
548
|
-
pendingVideos.add(video);
|
|
549
560
|
let settled = false;
|
|
550
561
|
let targetTime = 0;
|
|
551
562
|
let retrySeekRegistered = false;
|
|
552
|
-
const
|
|
563
|
+
const cleanupVideo = () => {
|
|
564
|
+
video.pause();
|
|
565
|
+
video.src = "";
|
|
566
|
+
video.remove();
|
|
567
|
+
};
|
|
568
|
+
const finish = (loaded) => {
|
|
553
569
|
if (settled) return;
|
|
554
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;
|
|
555
581
|
video.pause();
|
|
556
582
|
debugLog("freeze capture frame", {
|
|
557
583
|
src: layer.src,
|
|
@@ -566,10 +592,8 @@ function loadFrozenVideoTexture(layer, freezeTime, onError) {
|
|
|
566
592
|
canvas.height = video.videoHeight || 1;
|
|
567
593
|
const ctx = canvas.getContext("2d");
|
|
568
594
|
if (!ctx) {
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
video.remove();
|
|
572
|
-
resolve({ texture: getTransparentTexture() });
|
|
595
|
+
cleanupVideo();
|
|
596
|
+
finish({ texture: getTransparentTexture() });
|
|
573
597
|
return;
|
|
574
598
|
}
|
|
575
599
|
try {
|
|
@@ -579,11 +603,9 @@ function loadFrozenVideoTexture(layer, freezeTime, onError) {
|
|
|
579
603
|
src: layer.src,
|
|
580
604
|
error: error instanceof Error ? error.message : String(error)
|
|
581
605
|
});
|
|
582
|
-
|
|
583
|
-
video.src = "";
|
|
584
|
-
video.remove();
|
|
606
|
+
cleanupVideo();
|
|
585
607
|
onError?.(error instanceof Error ? error : new Error(String(error)));
|
|
586
|
-
|
|
608
|
+
finish({ texture: getTransparentTexture() });
|
|
587
609
|
return;
|
|
588
610
|
}
|
|
589
611
|
const texture = new THREE2.CanvasTexture(canvas);
|
|
@@ -593,10 +615,8 @@ function loadFrozenVideoTexture(layer, freezeTime, onError) {
|
|
|
593
615
|
width: canvas.width,
|
|
594
616
|
height: canvas.height
|
|
595
617
|
});
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
video.remove();
|
|
599
|
-
resolve({ texture });
|
|
618
|
+
cleanupVideo();
|
|
619
|
+
finish({ texture });
|
|
600
620
|
};
|
|
601
621
|
const captureReadyFrame = () => {
|
|
602
622
|
debugLog("freeze wait decoded frame", {
|
|
@@ -637,11 +657,10 @@ function loadFrozenVideoTexture(layer, freezeTime, onError) {
|
|
|
637
657
|
video.addEventListener("loadeddata", retry, { once: true });
|
|
638
658
|
};
|
|
639
659
|
video.addEventListener("error", () => {
|
|
640
|
-
pendingVideos.delete(video);
|
|
641
660
|
const msg = video.error?.message ?? "Unknown video error";
|
|
642
661
|
onError?.(new Error(`Failed to load video texture: ${layer.src} (${msg})`));
|
|
643
|
-
|
|
644
|
-
|
|
662
|
+
cleanupVideo();
|
|
663
|
+
finish({ texture: getTransparentTexture() });
|
|
645
664
|
}, { once: true });
|
|
646
665
|
video.addEventListener("loadedmetadata", () => {
|
|
647
666
|
const time = getFreezeTargetTime(freezeTime, video.duration, layer.freezeSeed);
|
|
@@ -693,14 +712,6 @@ function disposeLoadedLayer(loaded) {
|
|
|
693
712
|
loaded.liveTexture.dispose();
|
|
694
713
|
}
|
|
695
714
|
}
|
|
696
|
-
function cleanupPendingVideos() {
|
|
697
|
-
for (const video of pendingVideos) {
|
|
698
|
-
video.pause();
|
|
699
|
-
video.src = "";
|
|
700
|
-
video.remove();
|
|
701
|
-
}
|
|
702
|
-
pendingVideos.clear();
|
|
703
|
-
}
|
|
704
715
|
|
|
705
716
|
// src/shaders/common.vert.ts
|
|
706
717
|
var common_vert_default = glsl`
|
|
@@ -2015,6 +2026,7 @@ var FaceRenderer = class {
|
|
|
2015
2026
|
constructor(shader, width, height, effectUniforms) {
|
|
2016
2027
|
this.loadedLayers = [];
|
|
2017
2028
|
this.layerLoadVersion = 0;
|
|
2029
|
+
this.layerLoadAbort = null;
|
|
2018
2030
|
bootstrapShaders();
|
|
2019
2031
|
this.scene = new THREE3.Scene();
|
|
2020
2032
|
this.camera = new THREE3.Camera();
|
|
@@ -2065,19 +2077,25 @@ var FaceRenderer = class {
|
|
|
2065
2077
|
}
|
|
2066
2078
|
async loadLayers(layers, onError) {
|
|
2067
2079
|
const version = ++this.layerLoadVersion;
|
|
2080
|
+
this.layerLoadAbort?.abort();
|
|
2081
|
+
const abort = new AbortController();
|
|
2082
|
+
this.layerLoadAbort = abort;
|
|
2068
2083
|
this.disposeLayers();
|
|
2069
2084
|
const count = Math.min(layers.length, MAX_LAYERS);
|
|
2070
2085
|
this.material.uniforms.uLayerCount.value = count;
|
|
2071
2086
|
const promises = layers.slice(0, MAX_LAYERS).map(
|
|
2072
|
-
(layer) => loadLayerTexture(layer, onError)
|
|
2087
|
+
(layer) => loadLayerTexture(layer, { onError, signal: abort.signal })
|
|
2073
2088
|
);
|
|
2074
2089
|
const results = await Promise.all(promises);
|
|
2075
|
-
if (version !== this.layerLoadVersion) {
|
|
2090
|
+
if (version !== this.layerLoadVersion || abort.signal.aborted) {
|
|
2076
2091
|
for (const loaded of results) {
|
|
2077
2092
|
disposeLoadedLayer(loaded);
|
|
2078
2093
|
}
|
|
2079
2094
|
return;
|
|
2080
2095
|
}
|
|
2096
|
+
if (this.layerLoadAbort === abort) {
|
|
2097
|
+
this.layerLoadAbort = null;
|
|
2098
|
+
}
|
|
2081
2099
|
this.loadedLayers = results;
|
|
2082
2100
|
for (let i = 0; i < results.length; i++) {
|
|
2083
2101
|
this.material.uniforms[`uLayer${i}`].value = results[i].texture;
|
|
@@ -2123,6 +2141,8 @@ var FaceRenderer = class {
|
|
|
2123
2141
|
}
|
|
2124
2142
|
destroy() {
|
|
2125
2143
|
this.layerLoadVersion++;
|
|
2144
|
+
this.layerLoadAbort?.abort();
|
|
2145
|
+
this.layerLoadAbort = null;
|
|
2126
2146
|
this.disposeLayers();
|
|
2127
2147
|
this.mesh.geometry.dispose();
|
|
2128
2148
|
this.material.dispose();
|
|
@@ -2597,7 +2617,6 @@ var Renderer = class {
|
|
|
2597
2617
|
this.stopRenderLoop();
|
|
2598
2618
|
this.resizeObserver?.disconnect();
|
|
2599
2619
|
this.intersectionObserver?.disconnect();
|
|
2600
|
-
cleanupPendingVideos();
|
|
2601
2620
|
this.frontFace.destroy();
|
|
2602
2621
|
if (this.frontRenderer) {
|
|
2603
2622
|
this.disposeWebGLRenderer(this.frontRenderer);
|