@twick/2d 0.15.0 → 0.15.2

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.cjs CHANGED
@@ -5670,8 +5670,8 @@ var Media = class extends Rect {
5670
5670
  const onError = () => {
5671
5671
  const reason = this.getErrorReason(media.error?.code);
5672
5672
  const srcValue = this.src();
5673
- console.log(`ERROR: Error loading video: src="${srcValue}", ${reason}`);
5674
- console.log(`Media element src: "${media.src}"`);
5673
+ console.error(`Error loading video: src="${srcValue}", ${reason}`);
5674
+ console.error(`Media element src: "${media.src}"`);
5675
5675
  media.removeEventListener("error", onError);
5676
5676
  media.removeEventListener("canplay", onCanPlayWrapper);
5677
5677
  media.removeEventListener("canplaythrough", onCanPlayWrapper);
@@ -5690,7 +5690,6 @@ var Media = class extends Rect {
5690
5690
  return this.awaitCanPlay() || this.view().playbackState() === import_core32.PlaybackState.Rendering;
5691
5691
  }
5692
5692
  play() {
5693
- console.log("=== Media.play() called ===");
5694
5693
  this.playing(true);
5695
5694
  this.schedulePlay();
5696
5695
  }
@@ -5742,12 +5741,14 @@ var Media = class extends Rect {
5742
5741
  const playPromise = media.play();
5743
5742
  if (playPromise !== void 0) {
5744
5743
  playPromise.then(() => {
5745
- console.log("Simple play started successfully");
5746
5744
  }).catch((error) => {
5747
5745
  if (error.name !== "AbortError") {
5748
5746
  console.warn("Error in simple play:", error);
5749
5747
  }
5750
- this.playing(false);
5748
+ const playbackState = this.view().playbackState();
5749
+ if (playbackState !== import_core32.PlaybackState.Rendering) {
5750
+ this.playing(false);
5751
+ }
5751
5752
  });
5752
5753
  }
5753
5754
  }
@@ -5757,33 +5758,24 @@ var Media = class extends Rect {
5757
5758
  }, 10);
5758
5759
  }
5759
5760
  actuallyPlay(media, timeFunction) {
5760
- console.log("=== actuallyPlay called ===");
5761
- console.log("Media element:", media);
5762
- console.log("Media src:", media.src);
5763
- console.log("Media paused:", media.paused);
5764
- console.log("Media readyState:", media.readyState);
5765
5761
  if (!this.playing()) {
5766
- console.log("Playing state is false, aborting actuallyPlay");
5767
5762
  return;
5768
5763
  }
5769
5764
  media.playbackRate = this.playbackRate();
5770
5765
  if (media.paused) {
5771
- console.log("Media is paused, calling play()");
5772
5766
  const playPromise = media.play();
5773
5767
  if (playPromise !== void 0) {
5774
5768
  playPromise.then(() => {
5775
- console.log("Media play() promise resolved - should be playing now");
5776
- console.log("Post-play media paused:", media.paused);
5777
- console.log("Post-play media currentTime:", media.currentTime);
5778
5769
  }).catch((error) => {
5779
5770
  if (error.name !== "AbortError") {
5780
5771
  console.warn("Error playing media:", error);
5781
5772
  }
5782
- this.playing(false);
5773
+ const playbackState = this.view().playbackState();
5774
+ if (playbackState !== import_core32.PlaybackState.Rendering) {
5775
+ this.playing(false);
5776
+ }
5783
5777
  });
5784
5778
  }
5785
- } else {
5786
- console.log("Media is already playing");
5787
5779
  }
5788
5780
  const start = timeFunction();
5789
5781
  const offset = media.currentTime;
@@ -5817,11 +5809,13 @@ var Media = class extends Rect {
5817
5809
  }
5818
5810
  autoPlayBasedOnTwick() {
5819
5811
  const playbackState = this.view().playbackState();
5820
- if ((playbackState === import_core32.PlaybackState.Playing || playbackState === import_core32.PlaybackState.Presenting) && !this.playing()) {
5821
- console.log("Auto-starting media playback via play() method");
5812
+ const shouldBePlaying = playbackState === import_core32.PlaybackState.Playing || playbackState === import_core32.PlaybackState.Presenting || playbackState === import_core32.PlaybackState.Rendering;
5813
+ if (shouldBePlaying && !this.playing()) {
5814
+ if (playbackState === import_core32.PlaybackState.Rendering) {
5815
+ this.playing(true);
5816
+ }
5822
5817
  this.play();
5823
- } else if (playbackState === import_core32.PlaybackState.Paused && this.playing()) {
5824
- console.log("Auto-pausing media playback via pause() method");
5818
+ } else if (!shouldBePlaying && this.playing()) {
5825
5819
  this.pause();
5826
5820
  }
5827
5821
  }
@@ -10169,6 +10163,57 @@ var Video = class extends Media {
10169
10163
  this.fileTypeWasDetected = false;
10170
10164
  this.lastFrame = null;
10171
10165
  }
10166
+ /**
10167
+ * Creates a video element with CORS fallback handling.
10168
+ * First tries with crossOrigin='anonymous', and if that fails due to CORS,
10169
+ * falls back to creating a video without crossOrigin.
10170
+ */
10171
+ createVideoElement(src, key) {
10172
+ const video = document.createElement("video");
10173
+ video.crossOrigin = "anonymous";
10174
+ if (src && src !== "undefined") {
10175
+ try {
10176
+ const parsedSrc = new URL(src, window.location.origin);
10177
+ if (parsedSrc.pathname.endsWith(".m3u8")) {
10178
+ const hls = new import_hls.default();
10179
+ hls.loadSource(src);
10180
+ hls.attachMedia(video);
10181
+ } else {
10182
+ video.src = src;
10183
+ }
10184
+ } catch (error) {
10185
+ video.src = src;
10186
+ }
10187
+ const errorHandler = () => {
10188
+ const error = video.error;
10189
+ if (error && (error.code === 4 || error.code === 2)) {
10190
+ video.removeEventListener("error", errorHandler);
10191
+ const fallbackKey = `${key}_no_cors`;
10192
+ let fallbackVideo = Video.pool[fallbackKey];
10193
+ if (!fallbackVideo) {
10194
+ fallbackVideo = document.createElement("video");
10195
+ fallbackVideo.crossOrigin = null;
10196
+ try {
10197
+ const parsedSrc = new URL(src, window.location.origin);
10198
+ if (parsedSrc.pathname.endsWith(".m3u8")) {
10199
+ const hls = new import_hls.default();
10200
+ hls.loadSource(src);
10201
+ hls.attachMedia(fallbackVideo);
10202
+ } else {
10203
+ fallbackVideo.src = src;
10204
+ }
10205
+ } catch (err) {
10206
+ fallbackVideo.src = src;
10207
+ }
10208
+ Video.pool[fallbackKey] = fallbackVideo;
10209
+ }
10210
+ Video.pool[key] = fallbackVideo;
10211
+ }
10212
+ };
10213
+ video.addEventListener("error", errorHandler, { once: true });
10214
+ }
10215
+ return video;
10216
+ }
10172
10217
  desiredSize() {
10173
10218
  const custom = super.desiredSize();
10174
10219
  if (custom.x === null && custom.y === null) {
@@ -10189,44 +10234,55 @@ var Video = class extends Media {
10189
10234
  fastSeekedMedia() {
10190
10235
  return this.fastSeekedVideo();
10191
10236
  }
10237
+ /**
10238
+ * Generates a normalized cache key based on the video source URL.
10239
+ * This ensures that all Video elements with the same src share the same HTMLVideoElement.
10240
+ */
10241
+ getCacheKey(src) {
10242
+ if (!src || src === "undefined") {
10243
+ return `${this.key}/pending`;
10244
+ }
10245
+ try {
10246
+ const url = new URL(src, window.location.origin);
10247
+ return url.href.split("#")[0];
10248
+ } catch {
10249
+ return src;
10250
+ }
10251
+ }
10192
10252
  video() {
10193
10253
  const src = this.src();
10194
- const key = `${this.key}/${src || "pending"}`;
10254
+ const key = this.getCacheKey(src);
10195
10255
  let video = Video.pool[key];
10196
10256
  if (!video) {
10197
- video = document.createElement("video");
10198
- video.crossOrigin = "anonymous";
10257
+ video = this.createVideoElement(src, key);
10258
+ Video.pool[key] = video;
10259
+ } else {
10199
10260
  if (src && src !== "undefined") {
10200
- try {
10201
- const parsedSrc = new URL(src, window.location.origin);
10202
- if (parsedSrc.pathname.endsWith(".m3u8")) {
10203
- const hls = new import_hls.default();
10204
- hls.loadSource(src);
10205
- hls.attachMedia(video);
10206
- } else {
10261
+ const normalizeUrl = (url) => {
10262
+ try {
10263
+ const parsed = new URL(url, window.location.origin);
10264
+ return parsed.href.split("#")[0];
10265
+ } catch {
10266
+ return url;
10267
+ }
10268
+ };
10269
+ const normalizedSrc = normalizeUrl(src);
10270
+ const normalizedVideoSrc = video.src ? normalizeUrl(video.src) : "";
10271
+ if (normalizedVideoSrc !== normalizedSrc) {
10272
+ try {
10273
+ const parsedSrc = new URL(src, window.location.origin);
10274
+ if (parsedSrc.pathname.endsWith(".m3u8")) {
10275
+ const hls = new import_hls.default();
10276
+ hls.loadSource(src);
10277
+ hls.attachMedia(video);
10278
+ } else {
10279
+ video.src = src;
10280
+ }
10281
+ } catch (error) {
10207
10282
  video.src = src;
10208
10283
  }
10209
- } catch (error) {
10210
- video.src = src;
10211
10284
  }
10212
10285
  }
10213
- Video.pool[key] = video;
10214
- } else if (src && src !== "undefined" && video.src !== src) {
10215
- try {
10216
- const parsedSrc = new URL(src, window.location.origin);
10217
- if (parsedSrc.pathname.endsWith(".m3u8")) {
10218
- const hls = new import_hls.default();
10219
- hls.loadSource(src);
10220
- hls.attachMedia(video);
10221
- } else {
10222
- video.src = src;
10223
- }
10224
- } catch (error) {
10225
- video.src = src;
10226
- }
10227
- delete Video.pool[key];
10228
- const newKey = `${this.key}/${src}`;
10229
- Video.pool[newKey] = video;
10230
10286
  }
10231
10287
  if (!src || src === "undefined") {
10232
10288
  import_core59.DependencyContext.collectPromise(
@@ -10445,6 +10501,11 @@ var Video = class extends Media {
10445
10501
  );
10446
10502
  }
10447
10503
  };
10504
+ /**
10505
+ * Static pool of video elements cached by source URL.
10506
+ * Multiple Video components with the same src will share the same HTMLVideoElement
10507
+ * to avoid duplicate network requests and improve performance.
10508
+ */
10448
10509
  Video.pool = {};
10449
10510
  Video.imageCommunication = !import_meta3.hot ? null : new ImageCommunication();
10450
10511
  __decorateClass([
@@ -10607,8 +10668,26 @@ var Scene2D = class extends import_core60.GeneratorScene {
10607
10668
  }
10608
10669
  }
10609
10670
  getMediaAssets() {
10610
- const playingVideos = Array.from(this.registeredNodes.values()).filter((node) => node instanceof Video).filter((video) => video.isPlaying());
10611
- const playingAudios = Array.from(this.registeredNodes.values()).filter((node) => node instanceof Audio).filter((audio) => audio.isPlaying());
10671
+ const playbackState = this.playback.state;
10672
+ const isRendering = playbackState === import_core60.PlaybackState.Rendering;
10673
+ const allVideos = Array.from(this.registeredNodes.values()).filter((node) => node instanceof Video);
10674
+ const allAudios = Array.from(this.registeredNodes.values()).filter((node) => node instanceof Audio);
10675
+ if (isRendering) {
10676
+ allVideos.forEach((video) => {
10677
+ const src = video.src();
10678
+ if (src && src !== "undefined" && !video.isPlaying()) {
10679
+ video.playing(true);
10680
+ }
10681
+ });
10682
+ allAudios.forEach((audio) => {
10683
+ const src = audio.src();
10684
+ if (src && src !== "undefined" && !audio.isPlaying()) {
10685
+ audio.playing(true);
10686
+ }
10687
+ });
10688
+ }
10689
+ const playingVideos = allVideos.filter((video) => video.isPlaying());
10690
+ const playingAudios = allAudios.filter((audio) => audio.isPlaying());
10612
10691
  const returnObjects = [];
10613
10692
  returnObjects.push(
10614
10693
  ...playingVideos.map((vid) => ({