129 lines
4.4 KiB
TypeScript

import JSZip from 'jszip'
import type { AnimationRule } from './types';
import { extractFrameFromVideo } from './vFrameExtractor';
export async function loadFromResSmartly({ url, rules, onprogress }: {
url: { zip?: string, video?: string },
rules: AnimationRule[],
onprogress?: (progress: { download: number, decode: number }) => void
}): Promise<CanvasImageSource[]> {
const isChromium = navigator.userAgent.includes('Chrome') || navigator.userAgent.includes('Chromium');
const isWindows = navigator.userAgent.includes('Windows');
const useWebCodec = isChromium && 'VideoDecoder' in window && !isWindows;
const finalType =
useWebCodec && url.video ? 'video' :
(url.zip ? 'zip' : 'none');
if (finalType == 'none') throw new Error('No valid resource type provided. Use "zip" or "video".');
return loadFromRes({
url: finalType === 'zip' ? url.zip! : url.video!,
type: finalType,
rules,
onprogress
});
}
export async function loadFromRes({ url, type, rules, onprogress }: {
url: string,
type: 'zip' | 'video',
rules: AnimationRule[],
onprogress?: (progress: { download: number, decode: number }) => void
}): Promise<CanvasImageSource[]> {
if (type === 'zip') {
const urls = await loadzip(url, p => (onprogress?.({ download: p, decode: 0 })));
const images = await loadAllImages(urls);
onprogress?.({ download: 1, decode: 1 });
return images
} else if (type === 'video') {
return extractFrameFromVideo(url, rules?.reduce((acc, rule) => acc + rule.frame, 0), onprogress);
} else {
throw new Error('Unsupported type. Use "zip" or "video".');
}
}
export async function loadzip(url: string, onprogress?: (progress: number) => void): Promise<string[]> {
const zipBlob = await downloadRes(url, (progress) => {
onprogress?.(progress * 0.8);
});
console.log(`加载压缩包: ${url}, 大小: ${(zipBlob.byteLength / 1024).toFixed(2)} KB`);
const zip = new JSZip()
const zipData = await zip.loadAsync(zipBlob)
console.log(`压缩包加载完成,包含文件: ${Object.keys(zipData.files).length} 个文件`);
onprogress?.(60);
const webpFiles = Object.keys(zipData.files)
.filter(name => !zipData.files[name].dir && (name.endsWith('.webp') || name.endsWith('.png') || name.endsWith('.jpg')))
.sort((a, b) => {
const numA = parseInt(a.match(/(\d+)\.\w+$/)?.[1] || '0')
const numB = parseInt(b.match(/(\d+)\.\w+$/)?.[1] || '0')
return numA - numB
})
if (webpFiles.length === 0) {
throw new Error('压缩包中没有找到支持的图片文件 (webp, png, jpg)')
}
const imageUrls: string[] = []
for (let i = 0; i < webpFiles.length; i++) {
const fileName = webpFiles[i]
const file = zipData.files[fileName]
const imageBlob = await file.async('blob')
const blobUrl = URL.createObjectURL(imageBlob)
imageUrls.push(blobUrl)
}
onprogress?.(100);
return imageUrls
}
export async function loadAllImages(urls: string[]) {
const images: HTMLImageElement[] = []
for (const url of urls) {
const img = new Image()
img.src = url
await new Promise((resolve, reject) => {
img.onload = () => resolve(undefined)
img.onerror = () => resolve(undefined)
})
images.push(img)
}
return images
}
// 下载视频文件
export async function downloadRes(url: string, onProgress: (progress: number) => void): Promise<ArrayBuffer> {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest()
xhr.open('GET', url, true)
xhr.responseType = 'arraybuffer'
xhr.onprogress = (event) => {
if (event.lengthComputable) {
const percentComplete = event.loaded / event.total
onProgress(percentComplete)
}
}
xhr.onload = () => {
if (xhr.status !== 200) {
reject(new Error(`Failed to load video: ${xhr.statusText}`))
return
}
if (!xhr.response) {
reject(new Error('Failed to load video data'))
return
}
resolve(xhr.response)
}
xhr.onerror = () => reject(new Error('Network error occurred'))
xhr.send()
})
}