@twick/2d 0.15.1 → 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 +132 -53
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +16 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.js +133 -53
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
package/dist/index.d.cts
CHANGED
|
@@ -4153,13 +4153,29 @@ declare class Video extends Media {
|
|
|
4153
4153
|
readonly decoder: SimpleSignal<'web' | 'ffmpeg' | 'slow' | null, this>;
|
|
4154
4154
|
detectedFileType: 'mp4' | 'webm' | 'hls' | 'mov' | 'unknown';
|
|
4155
4155
|
private fileTypeWasDetected;
|
|
4156
|
+
/**
|
|
4157
|
+
* Static pool of video elements cached by source URL.
|
|
4158
|
+
* Multiple Video components with the same src will share the same HTMLVideoElement
|
|
4159
|
+
* to avoid duplicate network requests and improve performance.
|
|
4160
|
+
*/
|
|
4156
4161
|
private static readonly pool;
|
|
4157
4162
|
private static readonly imageCommunication;
|
|
4158
4163
|
constructor(props: VideoProps);
|
|
4164
|
+
/**
|
|
4165
|
+
* Creates a video element with CORS fallback handling.
|
|
4166
|
+
* First tries with crossOrigin='anonymous', and if that fails due to CORS,
|
|
4167
|
+
* falls back to creating a video without crossOrigin.
|
|
4168
|
+
*/
|
|
4169
|
+
private createVideoElement;
|
|
4159
4170
|
protected desiredSize(): SerializedVector2<DesiredLength>;
|
|
4160
4171
|
protected mediaElement(): HTMLVideoElement;
|
|
4161
4172
|
protected seekedMedia(): HTMLVideoElement;
|
|
4162
4173
|
protected fastSeekedMedia(): HTMLVideoElement;
|
|
4174
|
+
/**
|
|
4175
|
+
* Generates a normalized cache key based on the video source URL.
|
|
4176
|
+
* This ensures that all Video elements with the same src share the same HTMLVideoElement.
|
|
4177
|
+
*/
|
|
4178
|
+
private getCacheKey;
|
|
4163
4179
|
private video;
|
|
4164
4180
|
protected seekedVideo(): HTMLVideoElement;
|
|
4165
4181
|
protected fastSeekedVideo(): HTMLVideoElement;
|
package/dist/index.d.ts
CHANGED
|
@@ -4153,13 +4153,29 @@ declare class Video extends Media {
|
|
|
4153
4153
|
readonly decoder: SimpleSignal<'web' | 'ffmpeg' | 'slow' | null, this>;
|
|
4154
4154
|
detectedFileType: 'mp4' | 'webm' | 'hls' | 'mov' | 'unknown';
|
|
4155
4155
|
private fileTypeWasDetected;
|
|
4156
|
+
/**
|
|
4157
|
+
* Static pool of video elements cached by source URL.
|
|
4158
|
+
* Multiple Video components with the same src will share the same HTMLVideoElement
|
|
4159
|
+
* to avoid duplicate network requests and improve performance.
|
|
4160
|
+
*/
|
|
4156
4161
|
private static readonly pool;
|
|
4157
4162
|
private static readonly imageCommunication;
|
|
4158
4163
|
constructor(props: VideoProps);
|
|
4164
|
+
/**
|
|
4165
|
+
* Creates a video element with CORS fallback handling.
|
|
4166
|
+
* First tries with crossOrigin='anonymous', and if that fails due to CORS,
|
|
4167
|
+
* falls back to creating a video without crossOrigin.
|
|
4168
|
+
*/
|
|
4169
|
+
private createVideoElement;
|
|
4159
4170
|
protected desiredSize(): SerializedVector2<DesiredLength>;
|
|
4160
4171
|
protected mediaElement(): HTMLVideoElement;
|
|
4161
4172
|
protected seekedMedia(): HTMLVideoElement;
|
|
4162
4173
|
protected fastSeekedMedia(): HTMLVideoElement;
|
|
4174
|
+
/**
|
|
4175
|
+
* Generates a normalized cache key based on the video source URL.
|
|
4176
|
+
* This ensures that all Video elements with the same src share the same HTMLVideoElement.
|
|
4177
|
+
*/
|
|
4178
|
+
private getCacheKey;
|
|
4163
4179
|
private video;
|
|
4164
4180
|
protected seekedVideo(): HTMLVideoElement;
|
|
4165
4181
|
protected fastSeekedVideo(): HTMLVideoElement;
|
package/dist/index.js
CHANGED
|
@@ -5574,8 +5574,8 @@ var Media = class extends Rect {
|
|
|
5574
5574
|
const onError = () => {
|
|
5575
5575
|
const reason = this.getErrorReason(media.error?.code);
|
|
5576
5576
|
const srcValue = this.src();
|
|
5577
|
-
console.
|
|
5578
|
-
console.
|
|
5577
|
+
console.error(`Error loading video: src="${srcValue}", ${reason}`);
|
|
5578
|
+
console.error(`Media element src: "${media.src}"`);
|
|
5579
5579
|
media.removeEventListener("error", onError);
|
|
5580
5580
|
media.removeEventListener("canplay", onCanPlayWrapper);
|
|
5581
5581
|
media.removeEventListener("canplaythrough", onCanPlayWrapper);
|
|
@@ -5594,7 +5594,6 @@ var Media = class extends Rect {
|
|
|
5594
5594
|
return this.awaitCanPlay() || this.view().playbackState() === PlaybackState.Rendering;
|
|
5595
5595
|
}
|
|
5596
5596
|
play() {
|
|
5597
|
-
console.log("=== Media.play() called ===");
|
|
5598
5597
|
this.playing(true);
|
|
5599
5598
|
this.schedulePlay();
|
|
5600
5599
|
}
|
|
@@ -5646,12 +5645,14 @@ var Media = class extends Rect {
|
|
|
5646
5645
|
const playPromise = media.play();
|
|
5647
5646
|
if (playPromise !== void 0) {
|
|
5648
5647
|
playPromise.then(() => {
|
|
5649
|
-
console.log("Simple play started successfully");
|
|
5650
5648
|
}).catch((error) => {
|
|
5651
5649
|
if (error.name !== "AbortError") {
|
|
5652
5650
|
console.warn("Error in simple play:", error);
|
|
5653
5651
|
}
|
|
5654
|
-
this.
|
|
5652
|
+
const playbackState = this.view().playbackState();
|
|
5653
|
+
if (playbackState !== PlaybackState.Rendering) {
|
|
5654
|
+
this.playing(false);
|
|
5655
|
+
}
|
|
5655
5656
|
});
|
|
5656
5657
|
}
|
|
5657
5658
|
}
|
|
@@ -5661,33 +5662,24 @@ var Media = class extends Rect {
|
|
|
5661
5662
|
}, 10);
|
|
5662
5663
|
}
|
|
5663
5664
|
actuallyPlay(media, timeFunction) {
|
|
5664
|
-
console.log("=== actuallyPlay called ===");
|
|
5665
|
-
console.log("Media element:", media);
|
|
5666
|
-
console.log("Media src:", media.src);
|
|
5667
|
-
console.log("Media paused:", media.paused);
|
|
5668
|
-
console.log("Media readyState:", media.readyState);
|
|
5669
5665
|
if (!this.playing()) {
|
|
5670
|
-
console.log("Playing state is false, aborting actuallyPlay");
|
|
5671
5666
|
return;
|
|
5672
5667
|
}
|
|
5673
5668
|
media.playbackRate = this.playbackRate();
|
|
5674
5669
|
if (media.paused) {
|
|
5675
|
-
console.log("Media is paused, calling play()");
|
|
5676
5670
|
const playPromise = media.play();
|
|
5677
5671
|
if (playPromise !== void 0) {
|
|
5678
5672
|
playPromise.then(() => {
|
|
5679
|
-
console.log("Media play() promise resolved - should be playing now");
|
|
5680
|
-
console.log("Post-play media paused:", media.paused);
|
|
5681
|
-
console.log("Post-play media currentTime:", media.currentTime);
|
|
5682
5673
|
}).catch((error) => {
|
|
5683
5674
|
if (error.name !== "AbortError") {
|
|
5684
5675
|
console.warn("Error playing media:", error);
|
|
5685
5676
|
}
|
|
5686
|
-
this.
|
|
5677
|
+
const playbackState = this.view().playbackState();
|
|
5678
|
+
if (playbackState !== PlaybackState.Rendering) {
|
|
5679
|
+
this.playing(false);
|
|
5680
|
+
}
|
|
5687
5681
|
});
|
|
5688
5682
|
}
|
|
5689
|
-
} else {
|
|
5690
|
-
console.log("Media is already playing");
|
|
5691
5683
|
}
|
|
5692
5684
|
const start = timeFunction();
|
|
5693
5685
|
const offset = media.currentTime;
|
|
@@ -5721,11 +5713,13 @@ var Media = class extends Rect {
|
|
|
5721
5713
|
}
|
|
5722
5714
|
autoPlayBasedOnTwick() {
|
|
5723
5715
|
const playbackState = this.view().playbackState();
|
|
5724
|
-
|
|
5725
|
-
|
|
5716
|
+
const shouldBePlaying = playbackState === PlaybackState.Playing || playbackState === PlaybackState.Presenting || playbackState === PlaybackState.Rendering;
|
|
5717
|
+
if (shouldBePlaying && !this.playing()) {
|
|
5718
|
+
if (playbackState === PlaybackState.Rendering) {
|
|
5719
|
+
this.playing(true);
|
|
5720
|
+
}
|
|
5726
5721
|
this.play();
|
|
5727
|
-
} else if (
|
|
5728
|
-
console.log("Auto-pausing media playback via pause() method");
|
|
5722
|
+
} else if (!shouldBePlaying && this.playing()) {
|
|
5729
5723
|
this.pause();
|
|
5730
5724
|
}
|
|
5731
5725
|
}
|
|
@@ -10127,6 +10121,57 @@ var Video = class extends Media {
|
|
|
10127
10121
|
this.fileTypeWasDetected = false;
|
|
10128
10122
|
this.lastFrame = null;
|
|
10129
10123
|
}
|
|
10124
|
+
/**
|
|
10125
|
+
* Creates a video element with CORS fallback handling.
|
|
10126
|
+
* First tries with crossOrigin='anonymous', and if that fails due to CORS,
|
|
10127
|
+
* falls back to creating a video without crossOrigin.
|
|
10128
|
+
*/
|
|
10129
|
+
createVideoElement(src, key) {
|
|
10130
|
+
const video = document.createElement("video");
|
|
10131
|
+
video.crossOrigin = "anonymous";
|
|
10132
|
+
if (src && src !== "undefined") {
|
|
10133
|
+
try {
|
|
10134
|
+
const parsedSrc = new URL(src, window.location.origin);
|
|
10135
|
+
if (parsedSrc.pathname.endsWith(".m3u8")) {
|
|
10136
|
+
const hls = new Hls();
|
|
10137
|
+
hls.loadSource(src);
|
|
10138
|
+
hls.attachMedia(video);
|
|
10139
|
+
} else {
|
|
10140
|
+
video.src = src;
|
|
10141
|
+
}
|
|
10142
|
+
} catch (error) {
|
|
10143
|
+
video.src = src;
|
|
10144
|
+
}
|
|
10145
|
+
const errorHandler = () => {
|
|
10146
|
+
const error = video.error;
|
|
10147
|
+
if (error && (error.code === 4 || error.code === 2)) {
|
|
10148
|
+
video.removeEventListener("error", errorHandler);
|
|
10149
|
+
const fallbackKey = `${key}_no_cors`;
|
|
10150
|
+
let fallbackVideo = Video.pool[fallbackKey];
|
|
10151
|
+
if (!fallbackVideo) {
|
|
10152
|
+
fallbackVideo = document.createElement("video");
|
|
10153
|
+
fallbackVideo.crossOrigin = null;
|
|
10154
|
+
try {
|
|
10155
|
+
const parsedSrc = new URL(src, window.location.origin);
|
|
10156
|
+
if (parsedSrc.pathname.endsWith(".m3u8")) {
|
|
10157
|
+
const hls = new Hls();
|
|
10158
|
+
hls.loadSource(src);
|
|
10159
|
+
hls.attachMedia(fallbackVideo);
|
|
10160
|
+
} else {
|
|
10161
|
+
fallbackVideo.src = src;
|
|
10162
|
+
}
|
|
10163
|
+
} catch (err) {
|
|
10164
|
+
fallbackVideo.src = src;
|
|
10165
|
+
}
|
|
10166
|
+
Video.pool[fallbackKey] = fallbackVideo;
|
|
10167
|
+
}
|
|
10168
|
+
Video.pool[key] = fallbackVideo;
|
|
10169
|
+
}
|
|
10170
|
+
};
|
|
10171
|
+
video.addEventListener("error", errorHandler, { once: true });
|
|
10172
|
+
}
|
|
10173
|
+
return video;
|
|
10174
|
+
}
|
|
10130
10175
|
desiredSize() {
|
|
10131
10176
|
const custom = super.desiredSize();
|
|
10132
10177
|
if (custom.x === null && custom.y === null) {
|
|
@@ -10147,44 +10192,55 @@ var Video = class extends Media {
|
|
|
10147
10192
|
fastSeekedMedia() {
|
|
10148
10193
|
return this.fastSeekedVideo();
|
|
10149
10194
|
}
|
|
10195
|
+
/**
|
|
10196
|
+
* Generates a normalized cache key based on the video source URL.
|
|
10197
|
+
* This ensures that all Video elements with the same src share the same HTMLVideoElement.
|
|
10198
|
+
*/
|
|
10199
|
+
getCacheKey(src) {
|
|
10200
|
+
if (!src || src === "undefined") {
|
|
10201
|
+
return `${this.key}/pending`;
|
|
10202
|
+
}
|
|
10203
|
+
try {
|
|
10204
|
+
const url = new URL(src, window.location.origin);
|
|
10205
|
+
return url.href.split("#")[0];
|
|
10206
|
+
} catch {
|
|
10207
|
+
return src;
|
|
10208
|
+
}
|
|
10209
|
+
}
|
|
10150
10210
|
video() {
|
|
10151
10211
|
const src = this.src();
|
|
10152
|
-
const key =
|
|
10212
|
+
const key = this.getCacheKey(src);
|
|
10153
10213
|
let video = Video.pool[key];
|
|
10154
10214
|
if (!video) {
|
|
10155
|
-
video =
|
|
10156
|
-
|
|
10215
|
+
video = this.createVideoElement(src, key);
|
|
10216
|
+
Video.pool[key] = video;
|
|
10217
|
+
} else {
|
|
10157
10218
|
if (src && src !== "undefined") {
|
|
10158
|
-
|
|
10159
|
-
|
|
10160
|
-
|
|
10161
|
-
|
|
10162
|
-
|
|
10163
|
-
|
|
10164
|
-
}
|
|
10219
|
+
const normalizeUrl = (url) => {
|
|
10220
|
+
try {
|
|
10221
|
+
const parsed = new URL(url, window.location.origin);
|
|
10222
|
+
return parsed.href.split("#")[0];
|
|
10223
|
+
} catch {
|
|
10224
|
+
return url;
|
|
10225
|
+
}
|
|
10226
|
+
};
|
|
10227
|
+
const normalizedSrc = normalizeUrl(src);
|
|
10228
|
+
const normalizedVideoSrc = video.src ? normalizeUrl(video.src) : "";
|
|
10229
|
+
if (normalizedVideoSrc !== normalizedSrc) {
|
|
10230
|
+
try {
|
|
10231
|
+
const parsedSrc = new URL(src, window.location.origin);
|
|
10232
|
+
if (parsedSrc.pathname.endsWith(".m3u8")) {
|
|
10233
|
+
const hls = new Hls();
|
|
10234
|
+
hls.loadSource(src);
|
|
10235
|
+
hls.attachMedia(video);
|
|
10236
|
+
} else {
|
|
10237
|
+
video.src = src;
|
|
10238
|
+
}
|
|
10239
|
+
} catch (error) {
|
|
10165
10240
|
video.src = src;
|
|
10166
10241
|
}
|
|
10167
|
-
} catch (error) {
|
|
10168
|
-
video.src = src;
|
|
10169
10242
|
}
|
|
10170
10243
|
}
|
|
10171
|
-
Video.pool[key] = video;
|
|
10172
|
-
} else if (src && src !== "undefined" && video.src !== src) {
|
|
10173
|
-
try {
|
|
10174
|
-
const parsedSrc = new URL(src, window.location.origin);
|
|
10175
|
-
if (parsedSrc.pathname.endsWith(".m3u8")) {
|
|
10176
|
-
const hls = new Hls();
|
|
10177
|
-
hls.loadSource(src);
|
|
10178
|
-
hls.attachMedia(video);
|
|
10179
|
-
} else {
|
|
10180
|
-
video.src = src;
|
|
10181
|
-
}
|
|
10182
|
-
} catch (error) {
|
|
10183
|
-
video.src = src;
|
|
10184
|
-
}
|
|
10185
|
-
delete Video.pool[key];
|
|
10186
|
-
const newKey = `${this.key}/${src}`;
|
|
10187
|
-
Video.pool[newKey] = video;
|
|
10188
10244
|
}
|
|
10189
10245
|
if (!src || src === "undefined") {
|
|
10190
10246
|
DependencyContext8.collectPromise(
|
|
@@ -10403,6 +10459,11 @@ var Video = class extends Media {
|
|
|
10403
10459
|
);
|
|
10404
10460
|
}
|
|
10405
10461
|
};
|
|
10462
|
+
/**
|
|
10463
|
+
* Static pool of video elements cached by source URL.
|
|
10464
|
+
* Multiple Video components with the same src will share the same HTMLVideoElement
|
|
10465
|
+
* to avoid duplicate network requests and improve performance.
|
|
10466
|
+
*/
|
|
10406
10467
|
Video.pool = {};
|
|
10407
10468
|
Video.imageCommunication = !import.meta.hot ? null : new ImageCommunication();
|
|
10408
10469
|
__decorateClass([
|
|
@@ -10453,6 +10514,7 @@ function jsx(type, config, key) {
|
|
|
10453
10514
|
// src/lib/scenes/Scene2D.ts
|
|
10454
10515
|
import {
|
|
10455
10516
|
GeneratorScene,
|
|
10517
|
+
PlaybackState as PlaybackState5,
|
|
10456
10518
|
SceneRenderEvent,
|
|
10457
10519
|
Vector2 as Vector224,
|
|
10458
10520
|
transformVectorAsPoint as transformVectorAsPoint10,
|
|
@@ -10570,8 +10632,26 @@ var Scene2D = class extends GeneratorScene {
|
|
|
10570
10632
|
}
|
|
10571
10633
|
}
|
|
10572
10634
|
getMediaAssets() {
|
|
10573
|
-
const
|
|
10574
|
-
const
|
|
10635
|
+
const playbackState = this.playback.state;
|
|
10636
|
+
const isRendering = playbackState === PlaybackState5.Rendering;
|
|
10637
|
+
const allVideos = Array.from(this.registeredNodes.values()).filter((node) => node instanceof Video);
|
|
10638
|
+
const allAudios = Array.from(this.registeredNodes.values()).filter((node) => node instanceof Audio);
|
|
10639
|
+
if (isRendering) {
|
|
10640
|
+
allVideos.forEach((video) => {
|
|
10641
|
+
const src = video.src();
|
|
10642
|
+
if (src && src !== "undefined" && !video.isPlaying()) {
|
|
10643
|
+
video.playing(true);
|
|
10644
|
+
}
|
|
10645
|
+
});
|
|
10646
|
+
allAudios.forEach((audio) => {
|
|
10647
|
+
const src = audio.src();
|
|
10648
|
+
if (src && src !== "undefined" && !audio.isPlaying()) {
|
|
10649
|
+
audio.playing(true);
|
|
10650
|
+
}
|
|
10651
|
+
});
|
|
10652
|
+
}
|
|
10653
|
+
const playingVideos = allVideos.filter((video) => video.isPlaying());
|
|
10654
|
+
const playingAudios = allAudios.filter((audio) => audio.isPlaying());
|
|
10575
10655
|
const returnObjects = [];
|
|
10576
10656
|
returnObjects.push(
|
|
10577
10657
|
...playingVideos.map((vid) => ({
|