fix(canvas): 远程媒体 - 统一资源地址并校验视频缓存

This commit is contained in:
ddCat
2026-09-19 22:29:09 +08:00
parent cb68476e36
commit 04a1e19def
10 changed files with 83 additions and 134 deletions

View File

@@ -1,5 +1,4 @@
import { cacheResourceObjectUrl } from "@/services/resource-blob-cache";
import { resourceIdFromStorageKey } from "@/services/api/resources";
import { resourceFileUrl, resourceIdFromStorageKey } from "@/services/api/resources";
export type CanvasAudioSource = {
nodeId: string;
@@ -255,9 +254,9 @@ function sameSource(left: CanvasAudioSource, right: CanvasAudioSource) {
}
async function resolveAudioSource(source: CanvasAudioSource) {
if (source.storageKey && resourceIdFromStorageKey(source.storageKey)) {
const cached = await cacheResourceObjectUrl(source.storageKey).catch(() => "");
return cached || source.content;
if (source.storageKey) {
const resourceId = resourceIdFromStorageKey(source.storageKey);
if (resourceId) return resourceFileUrl(resourceId);
}
return source.content;
}

View File

@@ -5,7 +5,7 @@ import { getActiveUserScope } from "@/lib/user-scope";
import { captureVideoPoster, detectVideoAudioTrackFromBlob } from "@/lib/video-poster";
import { resourceFileUrl, resourceIdFromStorageKey, resourceStorageKey, ResourceUploadError, uploadResourceFile } from "@/services/api/resources";
import { uploadImage, type UploadedImage } from "@/services/image-storage";
import { getCachedResourceBlob, getCachedResourceObjectUrl, primeResourceBlobCache } from "@/services/resource-blob-cache";
import { getCachedResourceBlob, primeResourceBlobCache } from "@/services/resource-blob-cache";
export type UploadedFile = {
url: string;
@@ -115,8 +115,8 @@ export async function resolveMediaUrl(storageKey?: string, fallback = "") {
if (!storageKey) return fallback;
const resourceId = resourceIdFromStorageKey(storageKey);
if (resourceId) {
const cached = await getCachedResourceObjectUrl(storageKey).catch(() => "");
return cached || resourceFileUrl(resourceId);
// 远程资源展示统一走稳定的云端/资源文件地址;Blob 缓存仅服务于字节读取和媒体处理。
return resourceFileUrl(resourceId);
}
const cached = objectUrls.get(storageKey);
if (cached) return cached;

View File

@@ -4,7 +4,7 @@ import { nanoid } from "nanoid";
import { readImageMeta } from "@/lib/image-utils";
import { getActiveUserScope } from "@/lib/user-scope";
import { importResourceFromUrl, isResourceUrl, resourceFileUrl, resourceIdFromStorageKey, resourceStorageKey, ResourceUploadError, uploadResourceFile } from "@/services/api/resources";
import { cacheResourceObjectUrl, getCachedResourceBlob, getCachedResourceObjectUrl, primeResourceBlobCache } from "@/services/resource-blob-cache";
import { getCachedResourceBlob, primeResourceBlobCache } from "@/services/resource-blob-cache";
export type UploadedImage = {
url: string;
@@ -81,12 +81,7 @@ export async function resolveImageUrl(storageKey?: string, fallback = "", option
if (!storageKey) return fallback;
const resourceId = resourceIdFromStorageKey(storageKey);
if (resourceId) {
const cached = await getCachedResourceObjectUrl(storageKey).catch(() => "");
if (cached) return cached;
if (options?.cacheMiss) {
const populated = await cacheResourceObjectUrl(storageKey).catch(() => "");
if (populated) return populated;
}
// 远程资源展示不返回 blob: URL,确保节点、素材库和弹窗都使用云端稳定地址。
return resourceFileUrl(resourceId);
}
const cached = objectUrls.get(storageKey);

View File

@@ -12,6 +12,7 @@ import { uploadGeneratedAssetToConfiguredSources } from "@/services/external-ass
import { getImageBlob, resolveImageUrl, setImageBlob } from "@/services/image-storage";
import { generationArtifactStorageKey, loadOrStoreGenerationArtifact } from "@/services/generation-artifact-sink";
import { createProviderNeutralGenerationTaskEffectStore } from "@/services/provider-neutral-generation-effects";
import { getCachedResourceBlob } from "@/services/resource-blob-cache";
import { saveRemoteUserDataNow } from "@/services/user-data-sync";
import { getActiveUserScope } from "@/lib/user-scope";
import { normalizeAssetCategory } from "@/lib/asset-category";
@@ -265,6 +266,26 @@ async function storedGenerationMedia(dataUrl: string, effectKey: string, mediaTy
};
}
async function cachedRemoteGenerationVideo(video: NonNullable<BackendGenerationResult["video"]>, signal?: AbortSignal) {
if (!video.storageKey) throw new Error("生成视频缺少云端资源标识");
throwIfAborted(signal);
const blob = await getCachedResourceBlob(video.storageKey);
throwIfAborted(signal);
if (!blob) throw new Error("生成视频资源缓存失败,未标记为成功");
const url = await resolveMediaUrl(video.storageKey, video.dataUrl);
throwIfAborted(signal);
if (!url) throw new Error("生成视频资源地址为空,未标记为成功");
return {
url,
storageKey: video.storageKey,
width: video.width || 0,
height: video.height || 0,
durationMs: video.durationMs,
bytes: video.bytes || blob.size,
mimeType: video.mimeType || blob.type || "video/mp4",
};
}
async function generationOutputAsset(input: Parameters<MaterializeGenerationTaskOutput>[0], scope: string): Promise<NewAsset> {
throwIfAborted(input.signal);
const result = generationTaskResult(input.task);
@@ -305,15 +326,7 @@ async function generationOutputAsset(input: Parameters<MaterializeGenerationTask
const video = result.video;
if (!video) throw new Error("生成任务缺少视频输出");
const stored = video.storageKey
? {
url: await resolveMediaUrl(video.storageKey, video.dataUrl),
storageKey: video.storageKey,
width: video.width || 0,
height: video.height || 0,
durationMs: video.durationMs,
bytes: video.bytes || 0,
mimeType: video.mimeType || "video/mp4",
}
? await cachedRemoteGenerationVideo(video, input.signal)
: await storedGenerationMedia(
video.dataUrl,
input.effectKey,