fix(media): 视频播放 - 资源视频改走 playback 变体,失败后刷新签名并重试

画布预览、生成回填和项目水合都解析浏览器可播地址;播放失败时绕过展示缓存,等待转码完成后强制刷新 playback 地址。
This commit is contained in:
ddcat666 committed 2026-10-08 10:54:40 +08:00
1 parent 0b1d5073e8
commit 9997422fcd
8 files changed
+285 -101

No files matched your search

+19 -4
View File
@@ -3,7 +3,7 @@ import { nanoid } from "nanoid";
import { getActiveUserScope } from "@/lib/user-scope";
import { captureVideoPoster, detectVideoAudioTrackFromBlob } from "@/lib/video-poster";
import { getResourceAccess, resolveResourceAccessURL, resourceFileUrl, resourceIdFromStorageKey, resourceStorageKey, ResourceUploadError, uploadResourceFile } from "@/services/api/resources";
import { getResourceAccess, resolveResourceAccessURL, resourceFileUrl, resourceIdFromStorageKey, resourceStorageKey, ResourceUploadError, uploadResourceFile, type ResourceAccessVariant } from "@/services/api/resources";
import { uploadImage, type UploadedImage } from "@/services/image-storage";
import { getCachedResourceBlob, primeResourceBlobCache } from "@/services/resource-blob-cache";
@@ -93,7 +93,17 @@ export async function uploadMediaFile(input: Blob, prefix = "file", onProgress?:
// 缓存只影响后续读取性能,服务端资源已经成功落盘,不得把缓存失败误报为上传失败。
console.warn("预热媒体缓存失败,服务端资源已保存", { resourceId: resource.id, error });
}
return { url: resource.publicUrl || resourceFileUrl(resource.id), storageKey: resourceStorageKey(resource.id), bytes: resource.size || blob.size, mimeType: resource.mimeType || blob.type || "application/octet-stream", width: resource.width || meta.width, height: resource.height || meta.height, durationMs: resource.durationMs || meta.durationMs, hasAudio: meta.hasAudio, preview: poster };
return {
url: resource.publicUrl || resourceFileUrl(resource.id),
storageKey: resourceStorageKey(resource.id),
bytes: resource.size || blob.size,
mimeType: resource.mimeType || blob.type || "application/octet-stream",
width: resource.width || meta.width,
height: resource.height || meta.height,
durationMs: resource.durationMs || meta.durationMs,
hasAudio: meta.hasAudio,
preview: poster,
};
} catch (error) {
// 与图片上传同一套判定:永久性失败必须当场暴露,不能混进“稍后自动同步”。
if (error instanceof ResourceUploadError && error.permanent) throw error;
@@ -111,12 +121,12 @@ export async function uploadMediaFile(input: Blob, prefix = "file", onProgress?:
}
}
export async function resolveMediaUrl(storageKey?: string, fallback = "") {
export async function resolveMediaUrl(storageKey?: string, fallback = "", variant: ResourceAccessVariant = "original") {
if (!storageKey) return fallback;
const resourceId = resourceIdFromStorageKey(storageKey);
if (resourceId) {
// 展示直接命中 OSS/CDN;平台资源文件接口只保留给私有源站代理或本地存储兜底。
return resolveResourceAccessURL((await getResourceAccess(storageKey, "display")).url);
return resolveResourceAccessURL((await getResourceAccess(storageKey, "display", variant)).url);
}
const cached = objectUrls.get(storageKey);
if (cached) return cached;
@@ -127,6 +137,11 @@ export async function resolveMediaUrl(storageKey?: string, fallback = "") {
return url;
}
/** Resolve a video through the browser-compatible playback variant when one exists. */
export function resolveVideoMediaUrl(storageKey?: string, fallback = "") {
return resolveMediaUrl(storageKey, fallback, "playback");
}
export async function getMediaBlob(storageKey: string) {
if (resourceIdFromStorageKey(storageKey)) return getCachedResourceBlob(storageKey);
return store.getItem<Blob>(storageKey);