@twick/2d 0.13.0 → 0.14.0
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/LICENSE +21 -0
- package/editor/editor/tsconfig.build.tsbuildinfo +1 -1
- package/lib/components/Audio.d.ts.map +1 -1
- package/lib/components/Audio.js +3 -33
- package/lib/components/CodeBlock.d.ts +1 -1
- package/lib/components/Media.d.ts +0 -6
- package/lib/components/Media.d.ts.map +1 -1
- package/lib/components/Media.js +39 -255
- package/lib/components/Node.d.ts +1 -1
- package/lib/components/Path.d.ts +1 -1
- package/lib/components/SVG.d.ts +1 -1
- package/lib/components/Shape.d.ts +1 -1
- package/lib/components/Video.d.ts +1 -0
- package/lib/components/Video.d.ts.map +1 -1
- package/lib/components/Video.js +65 -70
- package/lib/components/utils/waitUntil.d.ts +7 -0
- package/lib/components/utils/waitUntil.d.ts.map +1 -0
- package/lib/components/utils/waitUntil.js +15 -0
- package/lib/tsconfig.build.tsbuildinfo +1 -1
- package/lib/utils/waitUntil.d.ts +7 -0
- package/lib/utils/waitUntil.d.ts.map +1 -0
- package/lib/utils/waitUntil.js +15 -0
- package/package.json +5 -4
- package/src/lib/components/Audio.ts +2 -39
- package/src/lib/components/Media.ts +45 -275
- package/src/lib/components/Video.ts +78 -77
- package/src/lib/utils/waitUntil.ts +18 -0
|
@@ -8,7 +8,8 @@ import {ImageCommunication} from '../utils/video/ffmpeg-client';
|
|
|
8
8
|
import {dropExtractor, getFrame} from '../utils/video/mp4-parser-manager';
|
|
9
9
|
import type {MediaProps} from './Media';
|
|
10
10
|
import {Media} from './Media';
|
|
11
|
-
|
|
11
|
+
import {waitUntil} from '../utils/waitUntil';
|
|
12
|
+
|
|
12
13
|
export interface VideoProps extends MediaProps {
|
|
13
14
|
/**
|
|
14
15
|
* {@inheritDoc Video.alpha}
|
|
@@ -107,77 +108,74 @@ export class Video extends Media {
|
|
|
107
108
|
@computed()
|
|
108
109
|
private video(): HTMLVideoElement {
|
|
109
110
|
const src = this.src();
|
|
110
|
-
|
|
111
|
-
// Use a temporary key for undefined src to avoid conflicts
|
|
112
|
-
const key = `${this.key}/${src || 'pending'}`;
|
|
113
|
-
|
|
111
|
+
const key = `${this.key}/${src}`;
|
|
114
112
|
let video = Video.pool[key];
|
|
115
113
|
if (!video) {
|
|
116
114
|
video = document.createElement('video');
|
|
117
115
|
video.crossOrigin = 'anonymous';
|
|
116
|
+
video.preload = 'metadata';
|
|
117
|
+
video.playsInline = true;
|
|
118
|
+
video.setAttribute('webkit-playsinline', 'true');
|
|
119
|
+
video.setAttribute('playsinline', 'true');
|
|
120
|
+
|
|
121
|
+
// Set initial volume
|
|
122
|
+
video.volume = this.getVolume();
|
|
123
|
+
|
|
124
|
+
const parsedSrc = new URL(src, window.location.origin);
|
|
125
|
+
if (parsedSrc.pathname.endsWith('.m3u8')) {
|
|
126
|
+
const hls = new Hls();
|
|
127
|
+
hls.loadSource(src);
|
|
128
|
+
hls.attachMedia(video);
|
|
129
|
+
} else {
|
|
130
|
+
video.src = src;
|
|
131
|
+
}
|
|
118
132
|
|
|
119
|
-
//
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
if (parsedSrc.pathname.endsWith('.m3u8')) {
|
|
125
|
-
const hls = new Hls();
|
|
126
|
-
hls.loadSource(src);
|
|
127
|
-
hls.attachMedia(video);
|
|
128
|
-
} else {
|
|
129
|
-
video.src = src;
|
|
130
|
-
}
|
|
131
|
-
} catch (error) {
|
|
132
|
-
// Fallback to direct assignment
|
|
133
|
-
video.src = src;
|
|
133
|
+
// Add metadata event listeners
|
|
134
|
+
video.addEventListener('loadedmetadata', () => {
|
|
135
|
+
if (video.duration === Infinity || video.duration === 0) {
|
|
136
|
+
// For iOS, we need to seek to the end to get the duration
|
|
137
|
+
video.currentTime = 24 * 60 * 60; // 24 hours
|
|
134
138
|
}
|
|
135
|
-
}
|
|
139
|
+
});
|
|
136
140
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
const parsedSrc = new URL(src, window.location.origin);
|
|
142
|
-
|
|
143
|
-
if (parsedSrc.pathname.endsWith('.m3u8')) {
|
|
144
|
-
const hls = new Hls();
|
|
145
|
-
hls.loadSource(src);
|
|
146
|
-
hls.attachMedia(video);
|
|
147
|
-
} else {
|
|
148
|
-
video.src = src;
|
|
141
|
+
video.addEventListener('seeked', () => {
|
|
142
|
+
if (video.duration === Infinity || video.duration === 0) {
|
|
143
|
+
// If we still don't have duration, try a different approach
|
|
144
|
+
video.currentTime = 0;
|
|
149
145
|
}
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
// Add durationchange event listener
|
|
149
|
+
video.addEventListener('durationchange', () => {
|
|
150
|
+
if (video.duration === Infinity || video.duration === 0) {
|
|
151
|
+
// Try to force duration calculation
|
|
152
|
+
video.currentTime = 0.1;
|
|
153
|
+
}
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
// Add loadeddata event listener
|
|
157
|
+
video.addEventListener('loadeddata', () => {
|
|
158
|
+
if (video.duration === Infinity || video.duration === 0) {
|
|
159
|
+
// Try to force duration calculation
|
|
160
|
+
video.currentTime = 0.1;
|
|
161
|
+
}
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
// Add canplay event listener
|
|
165
|
+
video.addEventListener('canplay', () => {
|
|
166
|
+
if (video.duration === Infinity || video.duration === 0) {
|
|
167
|
+
// Try to force duration calculation
|
|
168
|
+
video.currentTime = 0.1;
|
|
169
|
+
}
|
|
170
|
+
});
|
|
160
171
|
|
|
161
|
-
|
|
162
|
-
if (!src || src === 'undefined') {
|
|
163
|
-
DependencyContext.collectPromise(
|
|
164
|
-
new Promise<void>(resolve => {
|
|
165
|
-
// Check periodically for valid src
|
|
166
|
-
const checkSrc = () => {
|
|
167
|
-
const currentSrc = this.src();
|
|
168
|
-
if (currentSrc && currentSrc !== 'undefined') {
|
|
169
|
-
resolve();
|
|
170
|
-
} else {
|
|
171
|
-
setTimeout(checkSrc, 10);
|
|
172
|
-
}
|
|
173
|
-
};
|
|
174
|
-
checkSrc();
|
|
175
|
-
}),
|
|
176
|
-
);
|
|
172
|
+
Video.pool[key] = video;
|
|
177
173
|
}
|
|
178
174
|
|
|
175
|
+
// Update volume whenever video is accessed
|
|
176
|
+
video.volume = this.getVolume();
|
|
177
|
+
|
|
179
178
|
const weNeedToWait = this.waitForCanPlayNecessary(video);
|
|
180
|
-
|
|
181
179
|
if (!weNeedToWait) {
|
|
182
180
|
return video;
|
|
183
181
|
}
|
|
@@ -224,7 +222,6 @@ export class Video extends Media {
|
|
|
224
222
|
|
|
225
223
|
const playing =
|
|
226
224
|
this.playing() && time < video.duration && video.playbackRate > 0;
|
|
227
|
-
|
|
228
225
|
if (playing) {
|
|
229
226
|
if (video.paused) {
|
|
230
227
|
DependencyContext.collectPromise(video.play());
|
|
@@ -336,9 +333,6 @@ export class Video extends Media {
|
|
|
336
333
|
}
|
|
337
334
|
|
|
338
335
|
protected override async draw(context: CanvasRenderingContext2D) {
|
|
339
|
-
// Auto-start playback if Revideo is playing but media isn't
|
|
340
|
-
this.autoPlayBasedOnRevideo();
|
|
341
|
-
|
|
342
336
|
this.drawShape(context);
|
|
343
337
|
const alpha = this.alpha();
|
|
344
338
|
if (alpha > 0) {
|
|
@@ -364,23 +358,15 @@ export class Video extends Media {
|
|
|
364
358
|
|
|
365
359
|
protected override applyFlex() {
|
|
366
360
|
super.applyFlex();
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
this.element.style.aspectRatio = (
|
|
372
|
-
this.ratio() ?? video.videoWidth / video.videoHeight
|
|
373
|
-
).toString();
|
|
374
|
-
}
|
|
375
|
-
} catch (error) {
|
|
376
|
-
// If video element is not ready yet, skip setting aspect ratio
|
|
377
|
-
// It will be set later when the video becomes available
|
|
378
|
-
}
|
|
361
|
+
const video = this.video();
|
|
362
|
+
this.element.style.aspectRatio = (
|
|
363
|
+
this.ratio() ?? video.videoWidth / video.videoHeight
|
|
364
|
+
).toString();
|
|
379
365
|
}
|
|
380
366
|
|
|
381
367
|
public override remove() {
|
|
382
368
|
super.remove();
|
|
383
|
-
dropExtractor(this.key, this.
|
|
369
|
+
dropExtractor(this.key, this.video().src);
|
|
384
370
|
return this;
|
|
385
371
|
}
|
|
386
372
|
|
|
@@ -458,4 +444,19 @@ export class Video extends Media {
|
|
|
458
444
|
})(),
|
|
459
445
|
);
|
|
460
446
|
}
|
|
447
|
+
|
|
448
|
+
public *waitForMetadata() {
|
|
449
|
+
const video = this.video();
|
|
450
|
+
|
|
451
|
+
// If duration is already available and valid, return immediately
|
|
452
|
+
if (video.duration > 0 && video.duration !== Infinity) {
|
|
453
|
+
return;
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
// Try to force duration calculation
|
|
457
|
+
video.currentTime = 0.1;
|
|
458
|
+
|
|
459
|
+
// Wait for metadata to be loaded with a valid duration
|
|
460
|
+
yield* waitUntil(() => video.duration > 0 && video.duration !== Infinity);
|
|
461
|
+
}
|
|
461
462
|
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Waits until a condition is met.
|
|
3
|
+
* @param condition - Function that returns true when the condition is met
|
|
4
|
+
* @param timeout - Optional timeout in milliseconds
|
|
5
|
+
*/
|
|
6
|
+
export function* waitUntil(
|
|
7
|
+
condition: () => boolean,
|
|
8
|
+
timeout: number = 10000,
|
|
9
|
+
): Generator<void, void, unknown> {
|
|
10
|
+
const startTime = Date.now();
|
|
11
|
+
|
|
12
|
+
while (!condition()) {
|
|
13
|
+
if (Date.now() - startTime > timeout) {
|
|
14
|
+
throw new Error('Timeout waiting for condition');
|
|
15
|
+
}
|
|
16
|
+
yield;
|
|
17
|
+
}
|
|
18
|
+
}
|