Skip to content

Commit 32b7d47

Browse files
committed
fix: fix: OOM when add many clips to AVCanvas
1 parent 5b9a790 commit 32b7d47

6 files changed

Lines changed: 60 additions & 13 deletions

File tree

.changeset/full-banks-hope.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
'@webav/av-canvas': patch
3+
'@webav/av-cliper': patch
4+
---
5+
6+
fix: OOM when add many clips to AVCanvas

packages/av-canvas/demo/video-editor.tsx

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,18 @@ function App() {
183183
// addSprite2Track('4-text', spr, '文字');
184184
// })();
185185

186+
// 模拟添加 60个素材,验证内存占用
187+
// (async () => {
188+
// const f = file('/test-1.mp4');
189+
// await write(f, (await fetch('./video/incorrect-frame-type.mp4')).body!);
190+
// for (let i = 0; i < 60; i++) {
191+
// const spr = new VisibleSprite(new MP4Clip(f));
192+
// await spr.ready;
193+
// await cvs.addSprite(spr);
194+
// addSprite2Track('1-video', spr, '视频');
195+
// }
196+
// })();
197+
186198
return () => {
187199
cvs.destroy();
188200
};

packages/av-canvas/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
"@xzdarcy/react-timeline-editor": "^0.1.9",
4747
"autoprefixer": "^10.4.19",
4848
"esbuild-plugin-react-virtualized": "^1.0.5",
49+
"opfs-tools": "^0.7.2",
4950
"postcss": "^8.4.38",
5051
"react": "^18.3.1",
5152
"react-dom": "^18.3.1",

packages/av-canvas/src/av-canvas.ts

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import {
77
Rect,
88
VisibleSprite,
99
} from '@webav/av-cliper';
10-
import { EventTool, workerTimer } from '@webav/internal-utils';
10+
import { EventTool, throttle, workerTimer } from '@webav/internal-utils';
1111
import { renderCtrls } from './sprites/render-ctrl';
1212
import { ESpriteManagerEvt, SpriteManager } from './sprites/sprite-manager';
1313
import { activeSprite, draggabelSprite } from './sprites/sprite-op';
@@ -163,6 +163,7 @@ export class AVCanvas {
163163
#updateRenderTime(time: number) {
164164
this.#renderTime = time;
165165
this.#spriteManager.updateRenderTime(time);
166+
this.#autoPreFrame.updateTime(time);
166167
}
167168

168169
#pause() {
@@ -177,6 +178,7 @@ export class AVCanvas {
177178
asn.disconnect();
178179
}
179180
this.#playingAudioCache.clear();
181+
this.#autoPreFrame.reset();
180182
}
181183

182184
#audioCtx = new AudioContext();
@@ -259,11 +261,7 @@ export class AVCanvas {
259261
}
260262

261263
this.#updateRenderTime(opts.start);
262-
this.#spriteManager.getSprites({ time: false }).forEach((vs) => {
263-
const { offset, duration } = vs.time;
264-
const selfOffset = this.#renderTime - offset;
265-
vs.preFrame(selfOffset > 0 && selfOffset < duration ? selfOffset : 0);
266-
});
264+
this.#autoPreFrame.reset();
267265

268266
this.#playState.start = opts.start;
269267
this.#playState.end = end;
@@ -276,6 +274,27 @@ export class AVCanvas {
276274
Log.info('AVCanvs play by:', this.#playState);
277275
}
278276

277+
#autoPreFrame = (() => {
278+
const readyVS = new Set<VisibleSprite>();
279+
return {
280+
reset() {
281+
readyVS.clear();
282+
},
283+
updateTime: throttle((curTime: number) => {
284+
const sprs = this.#spriteManager.getSprites({ time: false });
285+
// 匹配接下来 300ms 内即将要播放的 Sprite
286+
const matchPreSprs = sprs.filter((vs) => {
287+
const { offset } = vs.time;
288+
return offset > curTime && offset - 300e3 <= curTime;
289+
});
290+
for (const vs of matchPreSprs) {
291+
if (!readyVS.has(vs)) vs.preFrame(0);
292+
readyVS.add(vs);
293+
}
294+
}, 300),
295+
};
296+
})();
297+
279298
/**
280299
* 暂停播放,画布内容不再更新
281300
*/
@@ -288,6 +307,9 @@ export class AVCanvas {
288307
*/
289308
previewFrame(time: number) {
290309
this.#updateRenderTime(time);
310+
this.#spriteManager.getSprites().forEach((vs) => {
311+
vs.preFrame(time - vs.time.offset);
312+
});
291313
this.#pause();
292314
}
293315

@@ -330,7 +352,6 @@ export class AVCanvas {
330352
this.#sprMapAudioNode.set(vs, audioNode);
331353
}
332354
await this.#spriteManager.addSprite(vs);
333-
vs.preFrame(0);
334355
};
335356
/**
336357
* 删除 {@link VisibleSprite}

packages/av-cliper/src/sprite/visible-sprite.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
import { BaseSprite } from './base-sprite';
2-
import { IClip } from '../clips';
31
import { Log } from '@webav/internal-utils';
42
import { changePCMPlaybackRate } from '../av-utils';
3+
import { IClip } from '../clips';
4+
import { BaseSprite } from './base-sprite';
55

66
/**
77
* 包装 {@link IClip} 给素材扩展坐标、层级、透明度等信息,用于 {@link [AVCanvas](../../av-canvas/classes/AVCanvas.html)} 响应用户交互
@@ -48,6 +48,10 @@ export class VisibleSprite extends BaseSprite {
4848
#update(time: number) {
4949
if (this.#ticking) return;
5050
this.#ticking = true;
51+
52+
this.#lastVf?.close();
53+
this.#lastVf = null;
54+
this.#lastAudio = [];
5155
this.#clip
5256
.tick(time * this.time.playbackRate)
5357
.then(({ video, audio }) => {

pnpm-lock.yaml

Lines changed: 7 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)