129 lines
4.4 KiB
TypeScript
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()
|
|
})
|
|
} |