@twick/2d 0.12.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/editor/editor/tsconfig.build.tsbuildinfo +1 -1
- package/lib/components/CodeBlock.d.ts +1 -1
- package/lib/components/Media.d.ts +1 -2
- package/lib/components/Media.d.ts.map +1 -1
- package/lib/components/Media.js +12 -38
- 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 +55 -1
- 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 +6 -6
- package/src/lib/components/Media.ts +13 -46
- package/src/lib/components/Video.ts +66 -1
- package/src/lib/utils/waitUntil.ts +18 -0
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@twick/2d",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.14.0",
|
|
4
4
|
"description": "A 2D renderer for twick",
|
|
5
5
|
"author": "twick",
|
|
6
6
|
"homepage": "https://re.video/",
|
|
7
|
-
"bugs": "https://github.com/
|
|
7
|
+
"bugs": "https://github.com/ncounterspecialist/twick-base/issues",
|
|
8
8
|
"license": "MIT",
|
|
9
9
|
"main": "lib/index.js",
|
|
10
10
|
"types": "./lib/index.d.ts",
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
"sideEffects": false,
|
|
20
20
|
"repository": {
|
|
21
21
|
"type": "git",
|
|
22
|
-
"url": "git+https://github.com/
|
|
22
|
+
"url": "git+https://github.com/ncounterspecialist/twick-base.git"
|
|
23
23
|
},
|
|
24
24
|
"files": [
|
|
25
25
|
"lib",
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
"@preact/signals": "^1.2.1",
|
|
32
32
|
"@rollup/plugin-node-resolve": "^15.2.4",
|
|
33
33
|
"@rollup/plugin-typescript": "^12.1.0",
|
|
34
|
-
"@twick/ui": "^0.
|
|
34
|
+
"@twick/ui": "^0.14.0",
|
|
35
35
|
"clsx": "^2.0.0",
|
|
36
36
|
"jsdom": "^22.1.0",
|
|
37
37
|
"preact": "^10.19.2",
|
|
@@ -42,12 +42,12 @@
|
|
|
42
42
|
"@lezer/common": "^1.2.1",
|
|
43
43
|
"@lezer/highlight": "^1.2.0",
|
|
44
44
|
"@rive-app/canvas-advanced": "2.7.3",
|
|
45
|
-
"@twick/core": "^0.
|
|
45
|
+
"@twick/core": "^0.14.0",
|
|
46
46
|
"code-fns": "^0.8.2",
|
|
47
47
|
"hls.js": "^1.5.11",
|
|
48
48
|
"mathjax-full": "^3.2.2",
|
|
49
49
|
"mp4box": "^0.5.2",
|
|
50
50
|
"parse-svg-path": "^0.1.2"
|
|
51
51
|
},
|
|
52
|
-
"gitHead": "
|
|
52
|
+
"gitHead": "54bd58bf80ea6afec48c27c061e6d455c1e0a885"
|
|
53
53
|
}
|
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
DependencyContext,
|
|
4
4
|
PlaybackState,
|
|
5
5
|
clamp,
|
|
6
|
-
isReactive,
|
|
6
|
+
isReactive,
|
|
7
7
|
useLogger,
|
|
8
8
|
useThread,
|
|
9
9
|
} from '@twick/core';
|
|
@@ -112,10 +112,7 @@ export abstract class Media extends Rect {
|
|
|
112
112
|
}
|
|
113
113
|
|
|
114
114
|
public getDuration(): number {
|
|
115
|
-
|
|
116
|
-
const isVideo = (mElement instanceof HTMLVideoElement);
|
|
117
|
-
const isAudio = (mElement instanceof HTMLVideoElement);
|
|
118
|
-
return (this.isIOS() && (isVideo || isAudio)) ? 2 /** dummy duration for iOS */ : this.mediaElement().duration;
|
|
115
|
+
return this.mediaElement().duration;
|
|
119
116
|
}
|
|
120
117
|
|
|
121
118
|
public getVolume(): number {
|
|
@@ -273,7 +270,7 @@ export abstract class Media extends Rect {
|
|
|
273
270
|
|
|
274
271
|
const onError = () => {
|
|
275
272
|
const reason = this.getErrorReason(media.error?.code);
|
|
276
|
-
console.
|
|
273
|
+
console.log(`ERROR: Error loading video: ${this.src()}, ${reason}`);
|
|
277
274
|
media.removeEventListener('error', onError);
|
|
278
275
|
};
|
|
279
276
|
|
|
@@ -295,40 +292,19 @@ export abstract class Media extends Rect {
|
|
|
295
292
|
);
|
|
296
293
|
}
|
|
297
294
|
|
|
298
|
-
public pause() {
|
|
299
|
-
const media = this.mediaElement();
|
|
300
|
-
|
|
301
|
-
this.playing(false);
|
|
302
|
-
this.time.save();
|
|
303
|
-
media.pause();
|
|
304
|
-
}
|
|
305
|
-
|
|
306
295
|
public play() {
|
|
307
|
-
const media = this.mediaElement();
|
|
308
|
-
|
|
309
|
-
// Set playback rate on media element
|
|
310
|
-
media.playbackRate = this.playbackRate();
|
|
311
|
-
|
|
312
|
-
// Start playing the media element
|
|
313
|
-
const playPromise = media.play();
|
|
314
|
-
if (playPromise !== undefined) {
|
|
315
|
-
playPromise.catch(error => {
|
|
316
|
-
console.warn('Error playing media:', error);
|
|
317
|
-
this.playing(false);
|
|
318
|
-
});
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
this.playing(true);
|
|
322
|
-
|
|
323
|
-
// Update time based on thread time
|
|
324
296
|
const time = useThread().time;
|
|
325
297
|
const start = time();
|
|
326
|
-
const offset =
|
|
327
|
-
|
|
328
|
-
this.
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
298
|
+
const offset = this.time();
|
|
299
|
+
const playbackRate = this.playbackRate();
|
|
300
|
+
this.playing(true);
|
|
301
|
+
this.time(() => this.clampTime(offset + (time() - start) * playbackRate));
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
public pause() {
|
|
305
|
+
this.playing(false);
|
|
306
|
+
this.time.save();
|
|
307
|
+
this.mediaElement().pause();
|
|
332
308
|
}
|
|
333
309
|
|
|
334
310
|
public clampTime(time: number): number {
|
|
@@ -367,13 +343,4 @@ export abstract class Media extends Rect {
|
|
|
367
343
|
|
|
368
344
|
return reason;
|
|
369
345
|
}
|
|
370
|
-
|
|
371
|
-
// Helper method to check if running on iOS
|
|
372
|
-
protected isIOS(): boolean {
|
|
373
|
-
if (typeof navigator === 'undefined') return false;
|
|
374
|
-
const isIos = /iPad|iPhone|iPod/.test(navigator.userAgent) ||
|
|
375
|
-
(navigator.platform === 'MacIntel' && navigator.maxTouchPoints > 1);
|
|
376
|
-
|
|
377
|
-
return isIos;
|
|
378
|
-
}
|
|
379
346
|
}
|
|
@@ -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}
|
|
@@ -112,6 +113,13 @@ export class Video extends Media {
|
|
|
112
113
|
if (!video) {
|
|
113
114
|
video = document.createElement('video');
|
|
114
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();
|
|
115
123
|
|
|
116
124
|
const parsedSrc = new URL(src, window.location.origin);
|
|
117
125
|
if (parsedSrc.pathname.endsWith('.m3u8')) {
|
|
@@ -122,9 +130,51 @@ export class Video extends Media {
|
|
|
122
130
|
video.src = src;
|
|
123
131
|
}
|
|
124
132
|
|
|
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
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
|
|
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;
|
|
145
|
+
}
|
|
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
|
+
});
|
|
171
|
+
|
|
125
172
|
Video.pool[key] = video;
|
|
126
173
|
}
|
|
127
174
|
|
|
175
|
+
// Update volume whenever video is accessed
|
|
176
|
+
video.volume = this.getVolume();
|
|
177
|
+
|
|
128
178
|
const weNeedToWait = this.waitForCanPlayNecessary(video);
|
|
129
179
|
if (!weNeedToWait) {
|
|
130
180
|
return video;
|
|
@@ -394,4 +444,19 @@ export class Video extends Media {
|
|
|
394
444
|
})(),
|
|
395
445
|
);
|
|
396
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
|
+
}
|
|
397
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
|
+
}
|