@revideo/2d 0.5.7-alpha.1084 → 0.5.7
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/Media.d.ts +8 -0
- package/lib/components/Media.d.ts.map +1 -1
- package/lib/components/Media.js +43 -6
- package/lib/components/Video.d.ts +2 -2
- package/lib/components/Video.d.ts.map +1 -1
- package/lib/components/Video.js +1 -1
- package/lib/tsconfig.build.tsbuildinfo +1 -1
- package/lib/utils/video/ffmpeg-client.d.ts +1 -1
- package/lib/utils/video/ffmpeg-client.d.ts.map +1 -1
- package/lib/utils/video/ffmpeg-client.js +26 -40
- package/package.json +5 -5
- package/src/lib/components/Media.ts +56 -2
- package/src/lib/components/Video.ts +2 -2
- package/src/lib/utils/video/ffmpeg-client.ts +26 -44
|
@@ -14,46 +14,32 @@ export class ImageCommunication {
|
|
|
14
14
|
}
|
|
15
15
|
}
|
|
16
16
|
async getFrame(id, src, time, duration, fps) {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
'Content-Type': 'application/json',
|
|
22
|
-
},
|
|
23
|
-
body: JSON.stringify({
|
|
24
|
-
id,
|
|
25
|
-
filePath: src,
|
|
26
|
-
startTime: time,
|
|
27
|
-
duration,
|
|
28
|
-
fps,
|
|
29
|
-
}),
|
|
30
|
-
});
|
|
31
|
-
if (!response.ok) {
|
|
32
|
-
throw new Error(`HTTP error! status: ${response.status}`);
|
|
33
|
-
}
|
|
34
|
-
const arrayBuffer = await response.arrayBuffer();
|
|
35
|
-
// Assuming fixed dimensions for now
|
|
36
|
-
const width = 1080; // or whatever your fixed width is
|
|
37
|
-
const height = 1920; // or whatever your fixed height is
|
|
38
|
-
let imageData;
|
|
39
|
-
try {
|
|
40
|
-
imageData = new ImageData(new Uint8ClampedArray(arrayBuffer), width, height);
|
|
41
|
-
}
|
|
42
|
-
catch {
|
|
43
|
-
const width = 1080; // your desired width
|
|
44
|
-
const height = 1920; // your desired height
|
|
45
|
-
// Create a black frame
|
|
46
|
-
const blackFrame = new Uint8ClampedArray(width * height * 4); // 4 channels: R, G, B, A
|
|
47
|
-
for (let i = 0; i < blackFrame.length; i += 4) {
|
|
48
|
-
blackFrame[i] = 0; // R
|
|
49
|
-
blackFrame[i + 1] = 0; // G
|
|
50
|
-
blackFrame[i + 2] = 0; // B
|
|
51
|
-
blackFrame[i + 3] = 255; // A (fully opaque)
|
|
17
|
+
return new Promise((resolve, reject) => {
|
|
18
|
+
if (!import.meta.hot) {
|
|
19
|
+
reject('FfmpegVideoFrame can only be used with HMR.');
|
|
20
|
+
return;
|
|
52
21
|
}
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
22
|
+
function handler(event) {
|
|
23
|
+
const image = new Image();
|
|
24
|
+
const uint8Array = new Uint8Array(event.data.frame.data);
|
|
25
|
+
const blob = new Blob([uint8Array], { type: 'image/png' });
|
|
26
|
+
const url = URL.createObjectURL(blob);
|
|
27
|
+
image.src = url;
|
|
28
|
+
image.onload = () => {
|
|
29
|
+
resolve(image);
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
this.nextFrameHandlers.push(handler);
|
|
33
|
+
import.meta.hot.send('revideo:ffmpeg-decoder:video-frame', {
|
|
34
|
+
data: {
|
|
35
|
+
id: id,
|
|
36
|
+
filePath: src,
|
|
37
|
+
startTime: time,
|
|
38
|
+
duration,
|
|
39
|
+
fps,
|
|
40
|
+
},
|
|
41
|
+
});
|
|
42
|
+
});
|
|
57
43
|
}
|
|
58
44
|
}
|
|
59
|
-
//# sourceMappingURL=data:application/json;base64,
|
|
45
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZmZtcGVnLWNsaWVudC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uL3NyYy9saWIvdXRpbHMvdmlkZW8vZmZtcGVnLWNsaWVudC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxNQUFNLE9BQU8sa0JBQWtCO0lBQzdCO1FBV1Esc0JBQWlCLEdBQXNDLEVBQUUsQ0FBQztRQVZoRSxJQUFJLENBQUMsTUFBTSxDQUFDLElBQUksQ0FBQyxHQUFHLEVBQUUsQ0FBQztZQUNyQixNQUFNLElBQUksS0FBSyxDQUFDLDZDQUE2QyxDQUFDLENBQUM7UUFDakUsQ0FBQztRQUVELE1BQU0sQ0FBQyxJQUFJLENBQUMsR0FBRyxDQUFDLEVBQUUsQ0FDaEIsd0NBQXdDLEVBQ3hDLElBQUksQ0FBQyxPQUFPLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxDQUN4QixDQUFDO0lBQ0osQ0FBQztJQUlPLE9BQU8sQ0FBQyxLQUFtQjtRQUNqQyxNQUFNLFFBQVEsR0FBRyxJQUFJLENBQUMsaUJBQWlCLENBQUM7UUFDeEMsSUFBSSxDQUFDLGlCQUFpQixHQUFHLEVBQUUsQ0FBQztRQUU1QixLQUFLLE1BQU0sT0FBTyxJQUFJLFFBQVEsRUFBRSxDQUFDO1lBQy9CLE9BQU8sQ0FBQyxLQUFLLENBQUMsQ0FBQztRQUNqQixDQUFDO0lBQ0gsQ0FBQztJQUVNLEtBQUssQ0FBQyxRQUFRLENBQ25CLEVBQVUsRUFDVixHQUFXLEVBQ1gsSUFBWSxFQUNaLFFBQWdCLEVBQ2hCLEdBQVc7UUFFWCxPQUFPLElBQUksT0FBTyxDQUFtQixDQUFDLE9BQU8sRUFBRSxNQUFNLEVBQUUsRUFBRTtZQUN2RCxJQUFJLENBQUMsTUFBTSxDQUFDLElBQUksQ0FBQyxHQUFHLEVBQUUsQ0FBQztnQkFDckIsTUFBTSxDQUFDLDZDQUE2QyxDQUFDLENBQUM7Z0JBQ3RELE9BQU87WUFDVCxDQUFDO1lBRUQsU0FBUyxPQUFPLENBQUMsS0FBbUI7Z0JBQ2xDLE1BQU0sS0FBSyxHQUFHLElBQUksS0FBSyxFQUFFLENBQUM7Z0JBRTFCLE1BQU0sVUFBVSxHQUFHLElBQUksVUFBVSxDQUFDLEtBQUssQ0FBQyxJQUFJLENBQUMsS0FBSyxDQUFDLElBQUksQ0FBQyxDQUFDO2dCQUN6RCxNQUFNLElBQUksR0FBRyxJQUFJLElBQUksQ0FBQyxDQUFDLFVBQVUsQ0FBQyxFQUFFLEVBQUMsSUFBSSxFQUFFLFdBQVcsRUFBQyxDQUFDLENBQUM7Z0JBQ3pELE1BQU0sR0FBRyxHQUFHLEdBQUcsQ0FBQyxlQUFlLENBQUMsSUFBSSxDQUFDLENBQUM7Z0JBRXRDLEtBQUssQ0FBQyxHQUFHLEdBQUcsR0FBRyxDQUFDO2dCQUVoQixLQUFLLENBQUMsTUFBTSxHQUFHLEdBQUcsRUFBRTtvQkFDbEIsT0FBTyxDQUFDLEtBQUssQ0FBQyxDQUFDO2dCQUNqQixDQUFDLENBQUM7WUFDSixDQUFDO1lBRUQsSUFBSSxDQUFDLGlCQUFpQixDQUFDLElBQUksQ0FBQyxPQUFPLENBQUMsQ0FBQztZQUVyQyxNQUFNLENBQUMsSUFBSSxDQUFDLEdBQUcsQ0FBQyxJQUFJLENBQUMsb0NBQW9DLEVBQUU7Z0JBQ3pELElBQUksRUFBRTtvQkFDSixFQUFFLEVBQUUsRUFBRTtvQkFDTixRQUFRLEVBQUUsR0FBRztvQkFDYixTQUFTLEVBQUUsSUFBSTtvQkFDZixRQUFRO29CQUNSLEdBQUc7aUJBQ0o7YUFDRixDQUFDLENBQUM7UUFDTCxDQUFDLENBQUMsQ0FBQztJQUNMLENBQUM7Q0FDRiJ9
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@revideo/2d",
|
|
3
|
-
"version": "0.5.7
|
|
3
|
+
"version": "0.5.7",
|
|
4
4
|
"description": "A 2D renderer for revideo",
|
|
5
5
|
"author": "revideo",
|
|
6
6
|
"homepage": "https://re.video/",
|
|
@@ -29,8 +29,8 @@
|
|
|
29
29
|
],
|
|
30
30
|
"devDependencies": {
|
|
31
31
|
"@preact/signals": "^1.2.1",
|
|
32
|
-
"@revideo/internal": "
|
|
33
|
-
"@revideo/ui": "
|
|
32
|
+
"@revideo/internal": "0.0.0",
|
|
33
|
+
"@revideo/ui": "0.5.7",
|
|
34
34
|
"clsx": "^2.0.0",
|
|
35
35
|
"jsdom": "^22.1.0",
|
|
36
36
|
"preact": "^10.19.2",
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
"@codemirror/language": "^6.10.1",
|
|
41
41
|
"@lezer/common": "^1.2.1",
|
|
42
42
|
"@lezer/highlight": "^1.2.0",
|
|
43
|
-
"@revideo/core": "
|
|
43
|
+
"@revideo/core": "0.5.7",
|
|
44
44
|
"@rive-app/canvas-advanced": "2.7.3",
|
|
45
45
|
"code-fns": "^0.8.2",
|
|
46
46
|
"hls.js": "^1.5.11",
|
|
@@ -48,5 +48,5 @@
|
|
|
48
48
|
"mp4box": "^0.5.2",
|
|
49
49
|
"parse-svg-path": "^0.1.2"
|
|
50
50
|
},
|
|
51
|
-
"gitHead": "
|
|
51
|
+
"gitHead": "08795b6daaa3e0dfd3c7f33c5593c61eed1b77d5"
|
|
52
52
|
}
|
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
isReactive,
|
|
8
8
|
useLogger,
|
|
9
9
|
useThread,
|
|
10
|
+
viaProxy,
|
|
10
11
|
} from '@revideo/core';
|
|
11
12
|
import {computed, initial, nodeName, signal} from '../decorators';
|
|
12
13
|
import {Asset} from './Asset';
|
|
@@ -21,6 +22,7 @@ export interface MediaProps extends RectProps {
|
|
|
21
22
|
time?: SignalValue<number>;
|
|
22
23
|
play?: boolean;
|
|
23
24
|
awaitCanPlay?: SignalValue<boolean>;
|
|
25
|
+
allowVolumeAmplificationInPreview?: SignalValue<boolean>;
|
|
24
26
|
}
|
|
25
27
|
|
|
26
28
|
@nodeName('Media')
|
|
@@ -45,8 +47,23 @@ export abstract class Media extends Asset {
|
|
|
45
47
|
@signal()
|
|
46
48
|
protected declare readonly awaitCanPlay: SimpleSignal<boolean, this>;
|
|
47
49
|
|
|
50
|
+
@initial(false)
|
|
51
|
+
@signal()
|
|
52
|
+
protected declare readonly allowVolumeAmplificationInPreview: SimpleSignal<
|
|
53
|
+
boolean,
|
|
54
|
+
this
|
|
55
|
+
>;
|
|
56
|
+
|
|
48
57
|
protected declare volume: number;
|
|
49
58
|
|
|
59
|
+
protected static readonly amplificationPool: Record<
|
|
60
|
+
string,
|
|
61
|
+
{
|
|
62
|
+
audioContext: AudioContext;
|
|
63
|
+
sourceNode: MediaElementAudioSourceNode;
|
|
64
|
+
gainNode: GainNode;
|
|
65
|
+
}
|
|
66
|
+
> = {};
|
|
50
67
|
protected lastTime = -1;
|
|
51
68
|
|
|
52
69
|
public constructor(props: MediaProps) {
|
|
@@ -124,12 +141,49 @@ export abstract class Media extends Asset {
|
|
|
124
141
|
`volumes cannot be negative - the value will be clamped to 0.`,
|
|
125
142
|
);
|
|
126
143
|
}
|
|
144
|
+
const media = this.mediaElement();
|
|
145
|
+
media.volume = Math.min(Math.max(volume, 0), 1);
|
|
146
|
+
|
|
127
147
|
if (volume > 1) {
|
|
148
|
+
if (this.allowVolumeAmplificationInPreview()) {
|
|
149
|
+
this.amplify(media, volume);
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
128
152
|
console.warn(
|
|
129
|
-
`the browser does not
|
|
153
|
+
`you have set the volume of node ${this.key} to ${volume} - your video will be exported with the correct volume, but the browser does not support volumes higher than 1 by default. To enable volume amplification in the preview, set the "allowVolumeAmplificationInPreview" of your <Video/> or <Audio/> tag to true. Note that amplification for previews will not work if you use autoplay within the player due to browser autoplay policies: https://developer.chrome.com/blog/autoplay/#webaudio.`,
|
|
130
154
|
);
|
|
131
155
|
}
|
|
132
|
-
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
@computed()
|
|
159
|
+
protected amplify(node: HTMLMediaElement, volume: number) {
|
|
160
|
+
const key = `${viaProxy(this.fullSource())}/${this.key}`;
|
|
161
|
+
|
|
162
|
+
if (Media.amplificationPool[key]) {
|
|
163
|
+
Media.amplificationPool[key].gainNode.gain.value = volume;
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const audioContext = new AudioContext();
|
|
168
|
+
const sourceNode = audioContext.createMediaElementSource(node);
|
|
169
|
+
const gainNode = audioContext.createGain();
|
|
170
|
+
|
|
171
|
+
gainNode.gain.value = volume;
|
|
172
|
+
sourceNode.connect(gainNode);
|
|
173
|
+
gainNode.connect(audioContext.destination);
|
|
174
|
+
|
|
175
|
+
Media.amplificationPool[key] = {audioContext, sourceNode, gainNode};
|
|
176
|
+
|
|
177
|
+
if (typeof window === 'undefined' || audioContext.state !== 'suspended') {
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// Start audio context after user interation, neccessary due to browser autoplay policies
|
|
182
|
+
const handleInteraction = () => {
|
|
183
|
+
Media.amplificationPool[key].audioContext.resume();
|
|
184
|
+
window.removeEventListener('click', handleInteraction);
|
|
185
|
+
};
|
|
186
|
+
window.addEventListener('click', handleInteraction);
|
|
133
187
|
}
|
|
134
188
|
|
|
135
189
|
protected setPlaybackRate(playbackRate: number) {
|
|
@@ -197,7 +197,7 @@ export class Video extends Media {
|
|
|
197
197
|
return video;
|
|
198
198
|
}
|
|
199
199
|
|
|
200
|
-
protected lastFrame:
|
|
200
|
+
protected lastFrame: HTMLImageElement | null = null;
|
|
201
201
|
|
|
202
202
|
protected async webcodecSeekedVideo(): Promise<CanvasImageSource> {
|
|
203
203
|
const video = this.video();
|
|
@@ -213,7 +213,7 @@ export class Video extends Media {
|
|
|
213
213
|
return getFrame(this.key, video.src, time, fps);
|
|
214
214
|
}
|
|
215
215
|
|
|
216
|
-
protected async ffmpegSeekedVideo(): Promise<
|
|
216
|
+
protected async ffmpegSeekedVideo(): Promise<HTMLImageElement> {
|
|
217
217
|
const video = this.video();
|
|
218
218
|
const time = this.clampTime(this.time());
|
|
219
219
|
const duration = this.getDuration();
|
|
@@ -28,55 +28,37 @@ export class ImageCommunication {
|
|
|
28
28
|
duration: number,
|
|
29
29
|
fps: number,
|
|
30
30
|
) {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
},
|
|
37
|
-
body: JSON.stringify({
|
|
38
|
-
id,
|
|
39
|
-
filePath: src,
|
|
40
|
-
startTime: time,
|
|
41
|
-
duration,
|
|
42
|
-
fps,
|
|
43
|
-
}),
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
if (!response.ok) {
|
|
47
|
-
throw new Error(`HTTP error! status: ${response.status}`);
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
const arrayBuffer = await response.arrayBuffer();
|
|
31
|
+
return new Promise<HTMLImageElement>((resolve, reject) => {
|
|
32
|
+
if (!import.meta.hot) {
|
|
33
|
+
reject('FfmpegVideoFrame can only be used with HMR.');
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
51
36
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
const height = 1920; // or whatever your fixed height is
|
|
37
|
+
function handler(event: MessageEvent) {
|
|
38
|
+
const image = new Image();
|
|
55
39
|
|
|
56
|
-
|
|
40
|
+
const uint8Array = new Uint8Array(event.data.frame.data);
|
|
41
|
+
const blob = new Blob([uint8Array], {type: 'image/png'});
|
|
42
|
+
const url = URL.createObjectURL(blob);
|
|
57
43
|
|
|
58
|
-
|
|
59
|
-
imageData = new ImageData(
|
|
60
|
-
new Uint8ClampedArray(arrayBuffer),
|
|
61
|
-
width,
|
|
62
|
-
height,
|
|
63
|
-
);
|
|
64
|
-
} catch {
|
|
65
|
-
const width = 1080; // your desired width
|
|
66
|
-
const height = 1920; // your desired height
|
|
44
|
+
image.src = url;
|
|
67
45
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
blackFrame[i] = 0; // R
|
|
72
|
-
blackFrame[i + 1] = 0; // G
|
|
73
|
-
blackFrame[i + 2] = 0; // B
|
|
74
|
-
blackFrame[i + 3] = 255; // A (fully opaque)
|
|
46
|
+
image.onload = () => {
|
|
47
|
+
resolve(image);
|
|
48
|
+
};
|
|
75
49
|
}
|
|
76
50
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
51
|
+
this.nextFrameHandlers.push(handler);
|
|
52
|
+
|
|
53
|
+
import.meta.hot.send('revideo:ffmpeg-decoder:video-frame', {
|
|
54
|
+
data: {
|
|
55
|
+
id: id,
|
|
56
|
+
filePath: src,
|
|
57
|
+
startTime: time,
|
|
58
|
+
duration,
|
|
59
|
+
fps,
|
|
60
|
+
},
|
|
61
|
+
});
|
|
62
|
+
});
|
|
81
63
|
}
|
|
82
64
|
}
|