Files
OpenCut/apps/web/src/services/renderer/nodes/video-node.ts
T

83 lines
1.8 KiB
TypeScript
Raw Normal View History

2026-01-31 00:20:04 +01:00
import type { CanvasRenderer } from "../canvas-renderer";
import { BaseNode } from "./base-node";
2026-01-31 22:54:33 +01:00
import { videoCache } from "@/services/video-cache/service";
2026-01-31 00:20:04 +01:00
const VIDEO_EPSILON = 1 / 1000;
export interface VideoNodeParams {
url: string;
2026-01-31 00:20:04 +01:00
file: File;
mediaId: string;
2026-01-31 00:20:04 +01:00
duration: number;
timeOffset: number;
trimStart: number;
trimEnd: number;
x?: number;
y?: number;
width?: number;
height?: number;
opacity?: number;
}
export class VideoNode extends BaseNode<VideoNodeParams> {
private getVideoTime(time: number) {
return time - this.params.timeOffset + this.params.trimStart;
}
private isInRange(time: number) {
const videoTime = this.getVideoTime(time);
return (
videoTime >= this.params.trimStart - VIDEO_EPSILON &&
videoTime < this.params.trimStart + this.params.duration
);
}
async render({ renderer, time }: { renderer: CanvasRenderer; time: number }) {
await super.render({ renderer, time });
if (!this.isInRange(time)) {
return;
}
const videoTime = this.getVideoTime(time);
const frame = await videoCache.getFrameAt({
mediaId: this.params.mediaId,
file: this.params.file,
time: videoTime,
});
if (frame) {
renderer.context.save();
if (this.params.opacity !== undefined) {
renderer.context.globalAlpha = this.params.opacity;
}
if (
this.params.x !== undefined &&
this.params.y !== undefined &&
this.params.width !== undefined &&
this.params.height !== undefined
) {
renderer.context.drawImage(
frame.canvas,
this.params.x,
this.params.y,
this.params.width,
this.params.height,
);
} else {
renderer.context.drawImage(
frame.canvas,
0,
0,
renderer.width,
renderer.height,
);
}
renderer.context.restore();
}
}
}