Files
HexTowerDefense3/src/app/assetPreloaderService.ts

98 lines
2.7 KiB
TypeScript

import { Injectable } from '@angular/core';
import { BehaviorSubject } from 'rxjs';
interface AssetEntry {
path: string;
type: 'image' | 'audio';
size: number;
}
@Injectable({ providedIn: 'root' })
export class AssetPreloaderService {
private totalBytes = 0;
private loadedBytes = 0;
private _progress = new BehaviorSubject<number>(0);
progress$ = this._progress.asObservable();
private images = new Map<string, HTMLImageElement>();
private audio = new Map<string, HTMLAudioElement>();
async preload(): Promise<void> {
const manifest: AssetEntry[] = await fetch(
'/assets/manifest.json'
).then((res) => res.json());
this.totalBytes = manifest.reduce((sum, asset) => sum + asset.size, 0);
const loadTasks = manifest.map((entry) =>
this.loadAsset(entry).then((blob) => {
const url = URL.createObjectURL(blob);
if (entry.type === 'image') {
const img = new Image();
const loadedPromise = new Promise<void>((resolve, reject) => {
img.onload = () => {
URL.revokeObjectURL(url);
resolve();
};
img.onerror = (e) => {
console.error(`Failed to load image: ${entry.path}`, e);
URL.revokeObjectURL(url);
reject(e);
};
});
img.src = url;
return loadedPromise.then(() => {
this.images.set(entry.path, img);
});
} else if (entry.type === 'audio') {
const audio = new Audio();
const loadedPromise = new Promise<void>((resolve, reject) => {
audio.oncanplaythrough = () => resolve();
audio.onerror = (e) => {
console.error(`Failed to load audio: ${entry.path}`, e);
resolve(); // Don't reject to avoid blocking all
};
});
audio.src = url;
return loadedPromise.then(() => {
this.audio.set(entry.path, audio);
});
}
return Promise.resolve();
})
);
await Promise.all(loadTasks);
}
private async loadAsset(entry: AssetEntry): Promise<Blob> {
const response = await fetch(`/assets/${entry.path}`);
if (!response.ok) {
throw new Error(`Failed to fetch asset: ${entry.path}`);
}
const contentType = response.headers.get('Content-Type') || undefined;
const reader = response.body?.getReader();
let loaded = 0;
const chunks = [];
while (true) {
const { done, value } = await reader!.read();
if (done) break;
chunks.push(value);
loaded += value.length;
this.loadedBytes += value.length;
this._progress.next(this.loadedBytes / this.totalBytes);
}
return new Blob(chunks, contentType ? { type: contentType } : undefined);
}
getImage(name: string): HTMLImageElement {
return this.images.get(name)!;
}
getAudio(name: string): HTMLAudioElement {
return this.audio.get(name)!;
}
}