@revideo/2d 0.5.7-alpha.1084 → 0.5.8-alpha.1082

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.
@@ -1,23 +1,14 @@
1
1
  export class ImageCommunication {
2
2
  constructor() {
3
- this.nextFrameHandlers = [];
4
3
  if (!import.meta.hot) {
5
4
  throw new Error('FfmpegVideoFrame can only be used with HMR.');
6
5
  }
7
- import.meta.hot.on('revideo:ffmpeg-decoder:video-frame-res', this.handler.bind(this));
8
- }
9
- handler(event) {
10
- const handlers = this.nextFrameHandlers;
11
- this.nextFrameHandlers = [];
12
- for (const handler of handlers) {
13
- handler(event);
14
- }
15
6
  }
16
7
  async getFrame(id, src, time, duration, fps) {
17
8
  const response = await fetch('/revideo-ffmpeg-decoder/video-frame', {
18
9
  method: 'POST',
19
10
  headers: {
20
- // eslint-disable-next-line @typescript-eslint/naming-convention
11
+ // eslint-disable-next-line
21
12
  'Content-Type': 'application/json',
22
13
  },
23
14
  body: JSON.stringify({
@@ -31,29 +22,12 @@ export class ImageCommunication {
31
22
  if (!response.ok) {
32
23
  throw new Error(`HTTP error! status: ${response.status}`);
33
24
  }
25
+ const width = parseInt(response.headers.get('X-Frame-Width') || '1080', 10);
26
+ const height = parseInt(response.headers.get('X-Frame-Height') || '1920', 10);
34
27
  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)
52
- }
53
- imageData = new ImageData(blackFrame, width, height);
54
- }
28
+ const imageData = new ImageData(new Uint8ClampedArray(arrayBuffer), width, height);
55
29
  const imageBitmap = await createImageBitmap(imageData);
56
30
  return imageBitmap;
57
31
  }
58
32
  }
59
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZmZtcGVnLWNsaWVudC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uL3NyYy9saWIvdXRpbHMvdmlkZW8vZmZtcGVnLWNsaWVudC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxNQUFNLE9BQU8sa0JBQWtCO0lBQzdCO1FBV1Esc0JBQWlCLEdBQXNDLEVBQUUsQ0FBQztRQVZoRSxJQUFJLENBQUMsTUFBTSxDQUFDLElBQUksQ0FBQyxHQUFHLEVBQUUsQ0FBQztZQUNyQixNQUFNLElBQUksS0FBSyxDQUFDLDZDQUE2QyxDQUFDLENBQUM7UUFDakUsQ0FBQztRQUVELE1BQU0sQ0FBQyxJQUFJLENBQUMsR0FBRyxDQUFDLEVBQUUsQ0FDaEIsd0NBQXdDLEVBQ3hDLElBQUksQ0FBQyxPQUFPLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxDQUN4QixDQUFDO0lBQ0osQ0FBQztJQUlPLE9BQU8sQ0FBQyxLQUFtQjtRQUNqQyxNQUFNLFFBQVEsR0FBRyxJQUFJLENBQUMsaUJBQWlCLENBQUM7UUFDeEMsSUFBSSxDQUFDLGlCQUFpQixHQUFHLEVBQUUsQ0FBQztRQUU1QixLQUFLLE1BQU0sT0FBTyxJQUFJLFFBQVEsRUFBRSxDQUFDO1lBQy9CLE9BQU8sQ0FBQyxLQUFLLENBQUMsQ0FBQztRQUNqQixDQUFDO0lBQ0gsQ0FBQztJQUVNLEtBQUssQ0FBQyxRQUFRLENBQ25CLEVBQVUsRUFDVixHQUFXLEVBQ1gsSUFBWSxFQUNaLFFBQWdCLEVBQ2hCLEdBQVc7UUFFWCxNQUFNLFFBQVEsR0FBRyxNQUFNLEtBQUssQ0FBQyxxQ0FBcUMsRUFBRTtZQUNsRSxNQUFNLEVBQUUsTUFBTTtZQUNkLE9BQU8sRUFBRTtnQkFDUCxnRUFBZ0U7Z0JBQ2hFLGNBQWMsRUFBRSxrQkFBa0I7YUFDbkM7WUFDRCxJQUFJLEVBQUUsSUFBSSxDQUFDLFNBQVMsQ0FBQztnQkFDbkIsRUFBRTtnQkFDRixRQUFRLEVBQUUsR0FBRztnQkFDYixTQUFTLEVBQUUsSUFBSTtnQkFDZixRQUFRO2dCQUNSLEdBQUc7YUFDSixDQUFDO1NBQ0gsQ0FBQyxDQUFDO1FBRUgsSUFBSSxDQUFDLFFBQVEsQ0FBQyxFQUFFLEVBQUUsQ0FBQztZQUNqQixNQUFNLElBQUksS0FBSyxDQUFDLHVCQUF1QixRQUFRLENBQUMsTUFBTSxFQUFFLENBQUMsQ0FBQztRQUM1RCxDQUFDO1FBRUQsTUFBTSxXQUFXLEdBQUcsTUFBTSxRQUFRLENBQUMsV0FBVyxFQUFFLENBQUM7UUFFakQsb0NBQW9DO1FBQ3BDLE1BQU0sS0FBSyxHQUFHLElBQUksQ0FBQyxDQUFDLGtDQUFrQztRQUN0RCxNQUFNLE1BQU0sR0FBRyxJQUFJLENBQUMsQ0FBQyxtQ0FBbUM7UUFFeEQsSUFBSSxTQUFvQixDQUFDO1FBRXpCLElBQUksQ0FBQztZQUNILFNBQVMsR0FBRyxJQUFJLFNBQVMsQ0FDdkIsSUFBSSxpQkFBaUIsQ0FBQyxXQUFXLENBQUMsRUFDbEMsS0FBSyxFQUNMLE1BQU0sQ0FDUCxDQUFDO1FBQ0osQ0FBQztRQUFDLE1BQU0sQ0FBQztZQUNQLE1BQU0sS0FBSyxHQUFHLElBQUksQ0FBQyxDQUFDLHFCQUFxQjtZQUN6QyxNQUFNLE1BQU0sR0FBRyxJQUFJLENBQUMsQ0FBQyxzQkFBc0I7WUFFM0MsdUJBQXVCO1lBQ3ZCLE1BQU0sVUFBVSxHQUFHLElBQUksaUJBQWlCLENBQUMsS0FBSyxHQUFHLE1BQU0sR0FBRyxDQUFDLENBQUMsQ0FBQyxDQUFDLHlCQUF5QjtZQUN2RixLQUFLLElBQUksQ0FBQyxHQUFHLENBQUMsRUFBRSxDQUFDLEdBQUcsVUFBVSxDQUFDLE1BQU0sRUFBRSxDQUFDLElBQUksQ0FBQyxFQUFFLENBQUM7Z0JBQzlDLFVBQVUsQ0FBQyxDQUFDLENBQUMsR0FBRyxDQUFDLENBQUMsQ0FBQyxJQUFJO2dCQUN2QixVQUFVLENBQUMsQ0FBQyxHQUFHLENBQUMsQ0FBQyxHQUFHLENBQUMsQ0FBQyxDQUFDLElBQUk7Z0JBQzNCLFVBQVUsQ0FBQyxDQUFDLEdBQUcsQ0FBQyxDQUFDLEdBQUcsQ0FBQyxDQUFDLENBQUMsSUFBSTtnQkFDM0IsVUFBVSxDQUFDLENBQUMsR0FBRyxDQUFDLENBQUMsR0FBRyxHQUFHLENBQUMsQ0FBQyxtQkFBbUI7WUFDOUMsQ0FBQztZQUVELFNBQVMsR0FBRyxJQUFJLFNBQVMsQ0FBQyxVQUFVLEVBQUUsS0FBSyxFQUFFLE1BQU0sQ0FBQyxDQUFDO1FBQ3ZELENBQUM7UUFDRCxNQUFNLFdBQVcsR0FBRyxNQUFNLGlCQUFpQixDQUFDLFNBQVMsQ0FBQyxDQUFDO1FBQ3ZELE9BQU8sV0FBVyxDQUFDO0lBQ3JCLENBQUM7Q0FDRiJ9
33
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZmZtcGVnLWNsaWVudC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uL3NyYy9saWIvdXRpbHMvdmlkZW8vZmZtcGVnLWNsaWVudC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxNQUFNLE9BQU8sa0JBQWtCO0lBQzdCO1FBQ0UsSUFBSSxDQUFDLE1BQU0sQ0FBQyxJQUFJLENBQUMsR0FBRyxFQUFFLENBQUM7WUFDckIsTUFBTSxJQUFJLEtBQUssQ0FBQyw2Q0FBNkMsQ0FBQyxDQUFDO1FBQ2pFLENBQUM7SUFDSCxDQUFDO0lBRU0sS0FBSyxDQUFDLFFBQVEsQ0FDbkIsRUFBVSxFQUNWLEdBQVcsRUFDWCxJQUFZLEVBQ1osUUFBZ0IsRUFDaEIsR0FBVztRQUVYLE1BQU0sUUFBUSxHQUFHLE1BQU0sS0FBSyxDQUFDLHFDQUFxQyxFQUFFO1lBQ2xFLE1BQU0sRUFBRSxNQUFNO1lBQ2QsT0FBTyxFQUFFO2dCQUNQLDJCQUEyQjtnQkFDM0IsY0FBYyxFQUFFLGtCQUFrQjthQUNuQztZQUNELElBQUksRUFBRSxJQUFJLENBQUMsU0FBUyxDQUFDO2dCQUNuQixFQUFFO2dCQUNGLFFBQVEsRUFBRSxHQUFHO2dCQUNiLFNBQVMsRUFBRSxJQUFJO2dCQUNmLFFBQVE7Z0JBQ1IsR0FBRzthQUNKLENBQUM7U0FDSCxDQUFDLENBQUM7UUFFSCxJQUFJLENBQUMsUUFBUSxDQUFDLEVBQUUsRUFBRSxDQUFDO1lBQ2pCLE1BQU0sSUFBSSxLQUFLLENBQUMsdUJBQXVCLFFBQVEsQ0FBQyxNQUFNLEVBQUUsQ0FBQyxDQUFDO1FBQzVELENBQUM7UUFFRCxNQUFNLEtBQUssR0FBRyxRQUFRLENBQUMsUUFBUSxDQUFDLE9BQU8sQ0FBQyxHQUFHLENBQUMsZUFBZSxDQUFDLElBQUksTUFBTSxFQUFFLEVBQUUsQ0FBQyxDQUFDO1FBQzVFLE1BQU0sTUFBTSxHQUFHLFFBQVEsQ0FDckIsUUFBUSxDQUFDLE9BQU8sQ0FBQyxHQUFHLENBQUMsZ0JBQWdCLENBQUMsSUFBSSxNQUFNLEVBQ2hELEVBQUUsQ0FDSCxDQUFDO1FBRUYsTUFBTSxXQUFXLEdBQUcsTUFBTSxRQUFRLENBQUMsV0FBVyxFQUFFLENBQUM7UUFFakQsTUFBTSxTQUFTLEdBQUcsSUFBSSxTQUFTLENBQzdCLElBQUksaUJBQWlCLENBQUMsV0FBVyxDQUFDLEVBQ2xDLEtBQUssRUFDTCxNQUFNLENBQ1AsQ0FBQztRQUVGLE1BQU0sV0FBVyxHQUFHLE1BQU0saUJBQWlCLENBQUMsU0FBUyxDQUFDLENBQUM7UUFDdkQsT0FBTyxXQUFXLENBQUM7SUFDckIsQ0FBQztDQUNGIn0=
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@revideo/2d",
3
- "version": "0.5.7-alpha.1084+4580e7ac",
3
+ "version": "0.5.8-alpha.1082+e524d024",
4
4
  "description": "A 2D renderer for revideo",
5
5
  "author": "revideo",
6
6
  "homepage": "https://re.video/",
@@ -30,7 +30,7 @@
30
30
  "devDependencies": {
31
31
  "@preact/signals": "^1.2.1",
32
32
  "@revideo/internal": "^0.0.0",
33
- "@revideo/ui": "^0.5.7-alpha.1084+4580e7ac",
33
+ "@revideo/ui": "^0.5.8-alpha.1082+e524d024",
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": "^0.5.7-alpha.1084+4580e7ac",
43
+ "@revideo/core": "^0.5.8-alpha.1082+e524d024",
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": "4580e7ac3430f58ef720a31e7e46a99c22c9f261"
51
+ "gitHead": "e524d024fbe1f3ad82b309989146cb537dfa04ab"
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) {
@@ -78,6 +95,10 @@ export abstract class Media extends Asset {
78
95
  return this.volume;
79
96
  }
80
97
 
98
+ public getUrl(): string {
99
+ return this.mediaElement().src;
100
+ }
101
+
81
102
  public override dispose() {
82
103
  this.pause();
83
104
  this.remove();
@@ -124,12 +145,49 @@ export abstract class Media extends Asset {
124
145
  `volumes cannot be negative - the value will be clamped to 0.`,
125
146
  );
126
147
  }
148
+ const media = this.mediaElement();
149
+ media.volume = Math.min(Math.max(volume, 0), 1);
150
+
127
151
  if (volume > 1) {
152
+ if (this.allowVolumeAmplificationInPreview()) {
153
+ this.amplify(media, volume);
154
+ return;
155
+ }
128
156
  console.warn(
129
- `the browser does not natively support volumes higher than 1 - your preview will have a volume of 1, but your exported video will have a volume of ${volume}`,
157
+ `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
158
  );
131
159
  }
132
- this.mediaElement().volume = Math.min(Math.max(volume, 0), 1);
160
+ }
161
+
162
+ @computed()
163
+ protected amplify(node: HTMLMediaElement, volume: number) {
164
+ const key = `${viaProxy(this.fullSource())}/${this.key}`;
165
+
166
+ if (Media.amplificationPool[key]) {
167
+ Media.amplificationPool[key].gainNode.gain.value = volume;
168
+ return;
169
+ }
170
+
171
+ const audioContext = new AudioContext();
172
+ const sourceNode = audioContext.createMediaElementSource(node);
173
+ const gainNode = audioContext.createGain();
174
+
175
+ gainNode.gain.value = volume;
176
+ sourceNode.connect(gainNode);
177
+ gainNode.connect(audioContext.destination);
178
+
179
+ Media.amplificationPool[key] = {audioContext, sourceNode, gainNode};
180
+
181
+ if (typeof window === 'undefined' || audioContext.state !== 'suspended') {
182
+ return;
183
+ }
184
+
185
+ // Start audio context after user interation, neccessary due to browser autoplay policies
186
+ const handleInteraction = () => {
187
+ Media.amplificationPool[key].audioContext.resume();
188
+ window.removeEventListener('click', handleInteraction);
189
+ };
190
+ window.addEventListener('click', handleInteraction);
133
191
  }
134
192
 
135
193
  protected setPlaybackRate(playbackRate: number) {
@@ -197,7 +197,7 @@ export class Video extends Media {
197
197
  return video;
198
198
  }
199
199
 
200
- protected lastFrame: any | null = null;
200
+ protected lastFrame: ImageBitmap | null = null;
201
201
 
202
202
  protected async webcodecSeekedVideo(): Promise<CanvasImageSource> {
203
203
  const video = this.video();
@@ -170,7 +170,8 @@ export class Scene2D extends GeneratorScene<View2D> implements Inspectable {
170
170
  ...playingVideos.map(vid => ({
171
171
  key: vid.key,
172
172
  type: 'video' as const,
173
- src: vid.fullSource(),
173
+ src: vid.getUrl(),
174
+ decoder: vid.decoder(),
174
175
  playbackRate:
175
176
  typeof vid.playbackRate === 'function'
176
177
  ? vid.playbackRate()
@@ -3,22 +3,6 @@ export class ImageCommunication {
3
3
  if (!import.meta.hot) {
4
4
  throw new Error('FfmpegVideoFrame can only be used with HMR.');
5
5
  }
6
-
7
- import.meta.hot.on(
8
- 'revideo:ffmpeg-decoder:video-frame-res',
9
- this.handler.bind(this),
10
- );
11
- }
12
-
13
- private nextFrameHandlers: ((event: MessageEvent) => void)[] = [];
14
-
15
- private handler(event: MessageEvent) {
16
- const handlers = this.nextFrameHandlers;
17
- this.nextFrameHandlers = [];
18
-
19
- for (const handler of handlers) {
20
- handler(event);
21
- }
22
6
  }
23
7
 
24
8
  public async getFrame(
@@ -31,7 +15,7 @@ export class ImageCommunication {
31
15
  const response = await fetch('/revideo-ffmpeg-decoder/video-frame', {
32
16
  method: 'POST',
33
17
  headers: {
34
- // eslint-disable-next-line @typescript-eslint/naming-convention
18
+ // eslint-disable-next-line
35
19
  'Content-Type': 'application/json',
36
20
  },
37
21
  body: JSON.stringify({
@@ -47,35 +31,20 @@ export class ImageCommunication {
47
31
  throw new Error(`HTTP error! status: ${response.status}`);
48
32
  }
49
33
 
50
- const arrayBuffer = await response.arrayBuffer();
51
-
52
- // Assuming fixed dimensions for now
53
- const width = 1080; // or whatever your fixed width is
54
- const height = 1920; // or whatever your fixed height is
55
-
56
- let imageData: ImageData;
34
+ const width = parseInt(response.headers.get('X-Frame-Width') || '1080', 10);
35
+ const height = parseInt(
36
+ response.headers.get('X-Frame-Height') || '1920',
37
+ 10,
38
+ );
57
39
 
58
- try {
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
40
+ const arrayBuffer = await response.arrayBuffer();
67
41
 
68
- // Create a black frame
69
- const blackFrame = new Uint8ClampedArray(width * height * 4); // 4 channels: R, G, B, A
70
- for (let i = 0; i < blackFrame.length; i += 4) {
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)
75
- }
42
+ const imageData = new ImageData(
43
+ new Uint8ClampedArray(arrayBuffer),
44
+ width,
45
+ height,
46
+ );
76
47
 
77
- imageData = new ImageData(blackFrame, width, height);
78
- }
79
48
  const imageBitmap = await createImageBitmap(imageData);
80
49
  return imageBitmap;
81
50
  }