perf(canvas): 画布媒体 - 优化媒体加载、播放器与节点交互 (#357)

Merge origin/main at fb089b2 and retain canvas media performance, player layout, and interaction updates.

Co-authored-by: ddCat <2695092045@qq.com>
This commit is contained in:
wurendao007
2026-08-31 09:00:37 +08:00
committed by GitHub
parent 21facff923
commit 8ebceecbb6
58 changed files with 2662 additions and 207 deletions

View File

@@ -1,6 +1,8 @@
import { describe, expect, test } from "bun:test";
import { buildNodeMentionReferences, canvasResourceMentionToken, collectUpstreamVideoNodes } from "../src/lib/canvas/canvas-resource-references";
import { buildAssetMentionReferences, buildNodeMentionReferences, buildOrderedCanvasResourceReferences, canvasResourceMentionToken, collectUpstreamVideoNodes } from "../src/lib/canvas/canvas-resource-references";
import { canvasNodeToAsset } from "../src/lib/canvas/canvas-node-asset";
import { buildNodeGenerationInputs } from "../src/components/canvas/canvas-node-generation";
import { CanvasNodeType, type CanvasConnection, type CanvasNodeData } from "../src/types/canvas";
function videoNode(id: string): CanvasNodeData {
@@ -76,6 +78,89 @@ describe("collectUpstreamVideoNodes", () => {
});
describe("canvas resource mention slots", () => {
test("素材库视频优先使用封面,没有封面时保留首帧视频回退源", () => {
const poster = buildAssetMentionReferences([{
id: "video-with-poster",
kind: "video",
title: "带封面视频",
coverUrl: "https://cdn.example.com/poster.jpg",
tags: [],
createdAt: "2026-08-31T00:00:00.000Z",
updatedAt: "2026-08-31T00:00:00.000Z",
data: { url: "https://cdn.example.com/video.mp4", storageKey: "resource:video", width: 1280, height: 720, bytes: 1, mimeType: "video/mp4" },
}])[0];
expect(poster?.previewUrl).toBe("https://cdn.example.com/poster.jpg");
expect(poster?.mediaUrl).toBeUndefined();
const legacy = buildAssetMentionReferences([{
id: "legacy-video",
kind: "video",
title: "旧视频",
coverUrl: "https://cdn.example.com/video.mp4",
tags: [],
createdAt: "2026-08-31T00:00:00.000Z",
updatedAt: "2026-08-31T00:00:00.000Z",
data: { url: "https://cdn.example.com/video.mp4", storageKey: "resource:legacy-video", width: 1280, height: 720, bytes: 1, mimeType: "video/mp4" },
}])[0];
expect(legacy?.previewUrl).toBe("");
expect(legacy?.mediaUrl).toBe("https://cdn.example.com/video.mp4");
});
test("保存画布视频资产时保留节点已有的静态首帧", () => {
const node = videoNode("video-with-poster");
node.metadata = {
content: "https://cdn.example.com/video.mp4",
videoPreview: { content: "https://cdn.example.com/poster.jpg", storageKey: "resource:poster" },
};
const asset = canvasNodeToAsset(node, { canvasId: "canvas", source: "canvas-upload" });
expect(asset?.kind).toBe("video");
expect(asset && "coverUrl" in asset ? asset.coverUrl : "").toBe("https://cdn.example.com/poster.jpg");
});
test("上传视频作为生成设置引用时携带首帧预览,而不是播放器地址", () => {
const source = videoNode("uploaded-video");
source.metadata = {
content: "https://cdn.example.com/video.mp4",
storageKey: "video:user:uploaded",
videoPreview: { content: "blob:poster", storageKey: "image:user:poster", width: 400, height: 225 },
};
const config: CanvasNodeData = {
id: "config",
type: CanvasNodeType.Config,
title: "生成设置",
position: { x: 0, y: 0 },
width: 320,
height: 180,
metadata: {},
};
const inputs = buildNodeGenerationInputs(config.id, [source, config], [connection(source.id, config.id)]);
expect(inputs).toHaveLength(1);
expect(inputs[0]?.type).toBe("video");
expect(inputs[0]?.previewUrl).toBe("blob:poster");
expect(inputs[0]?.previewUrl).not.toBe(source.metadata.content);
});
test("视频引用只暴露静态首帧,不把原视频当缩略图", () => {
const genericVideo = videoNode("generic-video");
genericVideo.metadata = { content: "https://example.com/video.mp4" };
const libtvVideo = videoNode("libtv-video");
libtvVideo.metadata = { content: "https://libtv-res.liblib.art/path/video.mp4" };
const references = buildOrderedCanvasResourceReferences([genericVideo, libtvVideo]);
expect(references[0]?.previewUrl).toBe("");
expect(references[1]?.previewUrl).toContain("video%2Fsnapshot");
const uploaded = videoNode("uploaded-video");
uploaded.metadata = {
content: "https://cdn.example.com/video.mp4",
storageKey: "video:user:source",
videoPreview: { content: "https://cdn.example.com/poster.jpg", storageKey: "image:user:poster" },
};
const [uploadedReference] = buildOrderedCanvasResourceReferences([uploaded]);
expect(uploadedReference?.previewStorageKey).toBe("image:user:poster");
expect(uploadedReference?.storageKey).toBe("video:user:source");
});
test("画布节点引用只保存类型位置,不保存节点 ID", () => {
const target = videoNode("target");
const image = imageNode("image-a");