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 { 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 { 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 { 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 { 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() }) }