Keep window crops aligned as windows change

This commit is contained in:
young
2026-09-04 20:06:05 +10:00
parent ed32f05f52
commit a5bbfa8e43
10 changed files with 150 additions and 42 deletions
@@ -21,6 +21,7 @@ import {
} from "@/lib/mediaTiming";
import {
destroyPixiApplication,
destroyPixiContainer,
initializePixiApplicationWithTimeout,
} from "@/lib/pixiApplicationLifecycle";
import {
@@ -1997,16 +1998,12 @@ const VideoPlayback = forwardRef<VideoPlaybackRef, VideoPlaybackProps>(
video.removeEventListener("seeking", handleSeeking);
dispose();
if (videoSprite) {
videoContainer.removeChild(videoSprite);
videoSprite.destroy();
}
cameraContainer.removeChild(maskGraphics);
maskGraphics.destroy();
videoEffectsContainer.mask = null;
videoContainer.mask = null;
destroyPixiContainer(videoSprite);
destroyPixiContainer(maskGraphics);
maskGraphicsRef.current = null;
videoTexture.destroy(false);
if (!videoTexture.destroyed) videoTexture.destroy(false);
videoSpriteRef.current = null;
};
+8 -6
View File
@@ -34,7 +34,10 @@ import {
PixiCursorOverlay,
preloadCursorAssets,
} from "@/components/video-editor/videoPlayback/cursorRenderer";
import { computePaddedLayout } from "@/components/video-editor/videoPlayback/layoutUtils";
import {
computePaddedLayout,
scalePreviewBorderRadius,
} from "@/components/video-editor/videoPlayback/layoutUtils";
import {
createSpringState,
getZoomSpringConfig,
@@ -1622,13 +1625,12 @@ export class FrameRenderer {
this.videoContainer.position.set(0, 0);
const canvasScaleFactor = Math.min(
width / BASE_PREVIEW_WIDTH,
height / BASE_PREVIEW_HEIGHT,
const scaledBorderRadius = scalePreviewBorderRadius(
layout.croppedDisplayWidth,
layout.croppedDisplayHeight,
borderRadius,
);
const scaledBorderRadius = borderRadius * canvasScaleFactor;
this.maskGraphics.clear();
drawSquircleOnGraphics(this.maskGraphics, {
x: layout.centerOffsetX,
+25
View File
@@ -2,10 +2,22 @@ import type { Application } from "pixi.js";
import { describe, expect, it, vi } from "vitest";
import {
destroyPixiApplication,
destroyPixiContainer,
initializePixiApplication,
initializePixiApplicationWithTimeout,
} from "./pixiApplicationLifecycle";
function createContainer(destroyed = false) {
const container = {
destroyed,
destroy: vi.fn(() => {
container.destroyed = true;
}),
parent: { removeChild: vi.fn() },
};
return container;
}
function createApplication(init: () => Promise<void> = async () => undefined) {
return {
init: vi.fn(init),
@@ -16,6 +28,19 @@ function createApplication(init: () => Promise<void> = async () => undefined) {
}
describe("Pixi application lifecycle", () => {
it("safely ignores display objects already destroyed by their application", () => {
const liveContainer = createContainer();
destroyPixiContainer(liveContainer as never);
destroyPixiContainer(liveContainer as never);
expect(liveContainer.parent.removeChild).toHaveBeenCalledTimes(1);
expect(liveContainer.destroy).toHaveBeenCalledTimes(1);
const destroyedContainer = createContainer(true);
destroyPixiContainer(destroyedContainer as never);
expect(destroyedContainer.parent.removeChild).not.toHaveBeenCalled();
expect(destroyedContainer.destroy).not.toHaveBeenCalled();
});
it("cleans a failed initialization without running uninitialized plugins", async () => {
const initializationError = new Error("No available renderer");
const app = createApplication(async () => {
+7 -1
View File
@@ -1,4 +1,4 @@
import type { Application } from "pixi.js";
import type { Application, Container } from "pixi.js";
type PixiInitializationState = "initializing" | "initialized" | "failed";
type PixiInitOptions = Parameters<Application["init"]>[0];
@@ -108,3 +108,9 @@ export function destroyPixiApplication(app: Application | null, context: string)
destroyContexts.set(app, context);
if (initializationStates.get(app) !== "initializing") completeDestroy(app);
}
export function destroyPixiContainer(container: Container | null): void {
if (!container || container.destroyed) return;
container.parent?.removeChild(container);
container.destroy();
}