@sangwonl/pocato-core 0.4.9 → 0.4.11

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 CHANGED
@@ -2022,11 +2022,15 @@ var FRAG_SHADERS = {
2022
2022
  lenticular: lenticular_frag_default
2023
2023
  };
2024
2024
  var MAX_LAYERS = 8;
2025
+ var VIDEO_FRAME_WAIT_TIMEOUT_MS = 750;
2025
2026
  var FaceRenderer = class {
2026
2027
  constructor(shader, width, height, effectUniforms) {
2027
2028
  this.loadedLayers = [];
2028
2029
  this.layerLoadVersion = 0;
2029
2030
  this.layerLoadAbort = null;
2031
+ this.videoActivationVersion = 0;
2032
+ this.videoActivationAbort = null;
2033
+ this.destroyed = false;
2030
2034
  bootstrapShaders();
2031
2035
  this.scene = new THREE3.Scene();
2032
2036
  this.camera = new THREE3.Camera();
@@ -2077,6 +2081,9 @@ var FaceRenderer = class {
2077
2081
  }
2078
2082
  async loadLayers(layers, onError) {
2079
2083
  const version = ++this.layerLoadVersion;
2084
+ this.videoActivationVersion++;
2085
+ this.videoActivationAbort?.abort();
2086
+ this.videoActivationAbort = null;
2080
2087
  this.layerLoadAbort?.abort();
2081
2088
  const abort = new AbortController();
2082
2089
  this.layerLoadAbort = abort;
@@ -2115,13 +2122,25 @@ var FaceRenderer = class {
2115
2122
  updateResolution(width, height) {
2116
2123
  this.material.uniforms.uResolution.value.set(width, height);
2117
2124
  }
2118
- playLoadedVideos() {
2119
- for (let i = 0; i < this.loadedLayers.length; i++) {
2120
- const loaded = this.loadedLayers[i];
2121
- if (loaded.liveTexture) {
2122
- this.material.uniforms[`uLayer${i}`].value = loaded.liveTexture;
2125
+ async playLoadedVideos() {
2126
+ const activationVersion = ++this.videoActivationVersion;
2127
+ this.videoActivationAbort?.abort();
2128
+ const activationAbort = new AbortController();
2129
+ this.videoActivationAbort = activationAbort;
2130
+ await Promise.all(this.loadedLayers.map(async (loaded, i) => {
2131
+ if (this.destroyed || activationAbort.signal.aborted || activationVersion !== this.videoActivationVersion) return;
2132
+ if (!loaded.liveTexture) {
2133
+ loaded.play?.();
2134
+ return;
2123
2135
  }
2124
2136
  loaded.play?.();
2137
+ const hasFrame = await waitForVideoFrame(loaded.videoEl, activationAbort.signal);
2138
+ if (hasFrame && !this.destroyed && !activationAbort.signal.aborted && activationVersion === this.videoActivationVersion) {
2139
+ this.material.uniforms[`uLayer${i}`].value = loaded.liveTexture;
2140
+ }
2141
+ }));
2142
+ if (this.videoActivationAbort === activationAbort) {
2143
+ this.videoActivationAbort = null;
2125
2144
  }
2126
2145
  }
2127
2146
  updateShader(fragmentShader) {
@@ -2140,7 +2159,11 @@ var FaceRenderer = class {
2140
2159
  this.material.uniforms.uLayerCount.value = 0;
2141
2160
  }
2142
2161
  destroy() {
2162
+ this.destroyed = true;
2143
2163
  this.layerLoadVersion++;
2164
+ this.videoActivationVersion++;
2165
+ this.videoActivationAbort?.abort();
2166
+ this.videoActivationAbort = null;
2144
2167
  this.layerLoadAbort?.abort();
2145
2168
  this.layerLoadAbort = null;
2146
2169
  this.disposeLayers();
@@ -2148,6 +2171,39 @@ var FaceRenderer = class {
2148
2171
  this.material.dispose();
2149
2172
  }
2150
2173
  };
2174
+ function waitForVideoFrame(video, signal) {
2175
+ return new Promise((resolve) => {
2176
+ if (signal?.aborted) {
2177
+ resolve(false);
2178
+ return;
2179
+ }
2180
+ let settled = false;
2181
+ let timeoutId = null;
2182
+ let videoFrameId = null;
2183
+ const finish = (hasFrame) => {
2184
+ if (settled) return;
2185
+ settled = true;
2186
+ if (timeoutId != null) globalThis.clearTimeout(timeoutId);
2187
+ if (video && videoFrameId != null && "cancelVideoFrameCallback" in video) {
2188
+ video.cancelVideoFrameCallback(videoFrameId);
2189
+ }
2190
+ signal?.removeEventListener("abort", abort);
2191
+ resolve(hasFrame);
2192
+ };
2193
+ const abort = () => finish(false);
2194
+ if (video && "requestVideoFrameCallback" in video) {
2195
+ timeoutId = globalThis.setTimeout(() => finish(false), VIDEO_FRAME_WAIT_TIMEOUT_MS);
2196
+ signal?.addEventListener("abort", abort, { once: true });
2197
+ videoFrameId = video.requestVideoFrameCallback(() => finish(true));
2198
+ return;
2199
+ }
2200
+ const rafId2 = requestAnimationFrame(() => finish(true));
2201
+ signal?.addEventListener("abort", () => {
2202
+ cancelAnimationFrame(rafId2);
2203
+ finish(false);
2204
+ }, { once: true });
2205
+ });
2206
+ }
2151
2207
  function toThreeUniformValue(value) {
2152
2208
  if (typeof value === "string") {
2153
2209
  return value.startsWith("#") ? new THREE3.Color(value) : value;
@@ -2182,6 +2238,7 @@ var Renderer = class {
2182
2238
  this.firstFrameEmitted = false;
2183
2239
  this.firstFrameScheduled = false;
2184
2240
  this.mediaActivated = false;
2241
+ this.mediaActivationPromise = null;
2185
2242
  if (!options.front?.layers?.length) {
2186
2243
  console.warn("[pocato] front.layers must have at least 1 element");
2187
2244
  }
@@ -2345,10 +2402,15 @@ var Renderer = class {
2345
2402
  if (!this.layersReady || this.firstFrameEmitted || this.firstFrameScheduled) return;
2346
2403
  if (!this.mediaActivated) {
2347
2404
  this.mediaActivated = true;
2348
- this.frontFace.playLoadedVideos();
2349
- this.backFace?.playLoadedVideos();
2405
+ this.mediaActivationPromise = Promise.all([
2406
+ this.frontFace.playLoadedVideos(),
2407
+ this.backFace?.playLoadedVideos() ?? Promise.resolve()
2408
+ ]).then(() => {
2409
+ this.mediaActivationPromise = null;
2410
+ });
2350
2411
  return;
2351
2412
  }
2413
+ if (this.mediaActivationPromise) return;
2352
2414
  requestAnimationFrame(() => {
2353
2415
  if (this.destroyed || this.firstFrameEmitted) return;
2354
2416
  this.firstFrameEmitted = true;