@sangwonl/pocato-core 0.4.10 → 0.4.12
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 +83 -10
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -368,6 +368,7 @@ 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;
|
|
371
372
|
function isVideoSource(layer) {
|
|
372
373
|
if (layer.type) return layer.type === "video";
|
|
373
374
|
return VIDEO_EXTENSIONS.test(layer.src);
|
|
@@ -438,7 +439,15 @@ function loadImageTexture(src, onError) {
|
|
|
438
439
|
});
|
|
439
440
|
inflight.set(src, pending);
|
|
440
441
|
}
|
|
441
|
-
return pending.then((texture) => ({
|
|
442
|
+
return pending.then((texture) => ({
|
|
443
|
+
texture: cloneDisposableTexture(texture)
|
|
444
|
+
}));
|
|
445
|
+
}
|
|
446
|
+
function cloneDisposableTexture(texture) {
|
|
447
|
+
if (texture === getTransparentTexture()) return texture;
|
|
448
|
+
const clone = texture.clone();
|
|
449
|
+
clone.needsUpdate = true;
|
|
450
|
+
return clone;
|
|
442
451
|
}
|
|
443
452
|
function loadVideoTexture(layer, options) {
|
|
444
453
|
return new Promise((resolve) => {
|
|
@@ -526,12 +535,37 @@ function loadVideoTexture(layer, options) {
|
|
|
526
535
|
});
|
|
527
536
|
};
|
|
528
537
|
video.addEventListener("loadeddata", () => {
|
|
529
|
-
|
|
538
|
+
waitForDecodedVideoFrame(video, signal).then(() => {
|
|
539
|
+
resolveWithFrame();
|
|
540
|
+
});
|
|
530
541
|
}, { once: true });
|
|
531
542
|
video.src = layer.src;
|
|
532
543
|
video.load();
|
|
533
544
|
});
|
|
534
545
|
}
|
|
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
|
+
}
|
|
535
569
|
function loadFrozenVideoTexture(layer, freezeTime, options) {
|
|
536
570
|
return new Promise((resolve) => {
|
|
537
571
|
const { onError, signal } = options;
|
|
@@ -2028,6 +2062,9 @@ var FaceRenderer = class {
|
|
|
2028
2062
|
this.loadedLayers = [];
|
|
2029
2063
|
this.layerLoadVersion = 0;
|
|
2030
2064
|
this.layerLoadAbort = null;
|
|
2065
|
+
this.videoActivationVersion = 0;
|
|
2066
|
+
this.videoActivationAbort = null;
|
|
2067
|
+
this.destroyed = false;
|
|
2031
2068
|
bootstrapShaders();
|
|
2032
2069
|
this.scene = new THREE3.Scene();
|
|
2033
2070
|
this.camera = new THREE3.Camera();
|
|
@@ -2078,6 +2115,9 @@ var FaceRenderer = class {
|
|
|
2078
2115
|
}
|
|
2079
2116
|
async loadLayers(layers, onError) {
|
|
2080
2117
|
const version = ++this.layerLoadVersion;
|
|
2118
|
+
this.videoActivationVersion++;
|
|
2119
|
+
this.videoActivationAbort?.abort();
|
|
2120
|
+
this.videoActivationAbort = null;
|
|
2081
2121
|
this.layerLoadAbort?.abort();
|
|
2082
2122
|
const abort = new AbortController();
|
|
2083
2123
|
this.layerLoadAbort = abort;
|
|
@@ -2117,16 +2157,25 @@ var FaceRenderer = class {
|
|
|
2117
2157
|
this.material.uniforms.uResolution.value.set(width, height);
|
|
2118
2158
|
}
|
|
2119
2159
|
async playLoadedVideos() {
|
|
2160
|
+
const activationVersion = ++this.videoActivationVersion;
|
|
2161
|
+
this.videoActivationAbort?.abort();
|
|
2162
|
+
const activationAbort = new AbortController();
|
|
2163
|
+
this.videoActivationAbort = activationAbort;
|
|
2120
2164
|
await Promise.all(this.loadedLayers.map(async (loaded, i) => {
|
|
2165
|
+
if (this.destroyed || activationAbort.signal.aborted || activationVersion !== this.videoActivationVersion) return;
|
|
2121
2166
|
if (!loaded.liveTexture) {
|
|
2122
2167
|
loaded.play?.();
|
|
2123
2168
|
return;
|
|
2124
2169
|
}
|
|
2125
2170
|
loaded.play?.();
|
|
2126
|
-
|
|
2171
|
+
const hasFrame = await waitForVideoFrame(loaded.videoEl, activationAbort.signal);
|
|
2172
|
+
if (hasFrame && !this.destroyed && !activationAbort.signal.aborted && activationVersion === this.videoActivationVersion) {
|
|
2127
2173
|
this.material.uniforms[`uLayer${i}`].value = loaded.liveTexture;
|
|
2128
2174
|
}
|
|
2129
2175
|
}));
|
|
2176
|
+
if (this.videoActivationAbort === activationAbort) {
|
|
2177
|
+
this.videoActivationAbort = null;
|
|
2178
|
+
}
|
|
2130
2179
|
}
|
|
2131
2180
|
updateShader(fragmentShader) {
|
|
2132
2181
|
this.material.fragmentShader = resolveIncludes(fragmentShader);
|
|
@@ -2144,7 +2193,11 @@ var FaceRenderer = class {
|
|
|
2144
2193
|
this.material.uniforms.uLayerCount.value = 0;
|
|
2145
2194
|
}
|
|
2146
2195
|
destroy() {
|
|
2196
|
+
this.destroyed = true;
|
|
2147
2197
|
this.layerLoadVersion++;
|
|
2198
|
+
this.videoActivationVersion++;
|
|
2199
|
+
this.videoActivationAbort?.abort();
|
|
2200
|
+
this.videoActivationAbort = null;
|
|
2148
2201
|
this.layerLoadAbort?.abort();
|
|
2149
2202
|
this.layerLoadAbort = null;
|
|
2150
2203
|
this.disposeLayers();
|
|
@@ -2152,17 +2205,37 @@ var FaceRenderer = class {
|
|
|
2152
2205
|
this.material.dispose();
|
|
2153
2206
|
}
|
|
2154
2207
|
};
|
|
2155
|
-
function waitForVideoFrame(video) {
|
|
2208
|
+
function waitForVideoFrame(video, signal) {
|
|
2156
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);
|
|
2157
2228
|
if (video && "requestVideoFrameCallback" in video) {
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
resolve(true);
|
|
2162
|
-
});
|
|
2229
|
+
timeoutId = globalThis.setTimeout(() => finish(false), VIDEO_FRAME_WAIT_TIMEOUT_MS);
|
|
2230
|
+
signal?.addEventListener("abort", abort, { once: true });
|
|
2231
|
+
videoFrameId = video.requestVideoFrameCallback(() => finish(true));
|
|
2163
2232
|
return;
|
|
2164
2233
|
}
|
|
2165
|
-
requestAnimationFrame(() =>
|
|
2234
|
+
const rafId2 = requestAnimationFrame(() => finish(true));
|
|
2235
|
+
signal?.addEventListener("abort", () => {
|
|
2236
|
+
cancelAnimationFrame(rafId2);
|
|
2237
|
+
finish(false);
|
|
2238
|
+
}, { once: true });
|
|
2166
2239
|
});
|
|
2167
2240
|
}
|
|
2168
2241
|
function toThreeUniformValue(value) {
|