perf(app): load voice background tiles only near the viewport (#1971)

This commit is contained in:
Hampus authored and GitHub committed 2026-08-24 12:55:32 +02:00
1 parent 52282bfccf
commit 61bf8f6ad3
2 files changed
+348 -290

No files matched your search

@@ -8,12 +8,30 @@ export interface NativeBackgroundMediaSource {
mediaKind: VoiceBackgroundMediaKind;
}
interface BackgroundImageRead {
cancelled: boolean;
promise: Promise<string | null>;
}
const resolvedUrls = new Map<string, string>();
const pendingReads = new Map<string, BackgroundImageRead>();
async function readBackgroundImageObjectURL(id: string): Promise<string | null> {
const readVoiceBackgroundMedia = getElectronAPI()?.readVoiceBackgroundMedia;
if (!readVoiceBackgroundMedia) return null;
const media = await readVoiceBackgroundMedia(id);
if (!media?.dataUrl) return null;
const response = await fetch(media.dataUrl);
return URL.createObjectURL(await response.blob());
}
export async function saveBackgroundImage(id: string, blob: Blob): Promise<NativeBackgroundMediaSource> {
const cacheVoiceBackgroundMedia = getElectronAPI()?.cacheVoiceBackgroundMedia;
if (!cacheVoiceBackgroundMedia) {
throw new Error('Native background media cache unavailable');
}
const fileName = blob instanceof File ? blob.name : undefined;
releaseBackgroundImageURL(id);
return cacheVoiceBackgroundMedia({
id,
mimeType: blob.type,
@@ -27,29 +45,48 @@ export async function deleteBackgroundImage(id: string): Promise<void> {
if (!deleteVoiceBackgroundMedia) {
throw new Error('Native background media cache unavailable');
}
releaseBackgroundImageURL(id);
await deleteVoiceBackgroundMedia(id);
}
export function dataUrlToObjectUrl(dataUrl: string): string | null {
const match = dataUrl.match(/^data:([^;,]+)?(;base64)?,([\s\S]*)$/);
if (!match) return null;
const mimeType = match[1] || 'application/octet-stream';
const isBase64 = match[2] === ';base64';
const payload = match[3] ?? '';
try {
const bytes = isBase64
? Uint8Array.from(atob(payload), (char) => char.charCodeAt(0))
: new TextEncoder().encode(decodeURIComponent(payload));
return URL.createObjectURL(new Blob([bytes], {type: mimeType}));
} catch {
return null;
export function getCachedBackgroundImageURL(id: string): string | null {
return resolvedUrls.get(id) ?? null;
}
export function releaseBackgroundImageURL(id: string): void {
const resolved = resolvedUrls.get(id);
if (resolved != null) {
URL.revokeObjectURL(resolved);
resolvedUrls.delete(id);
}
const pending = pendingReads.get(id);
if (pending != null) {
pending.cancelled = true;
pendingReads.delete(id);
}
}
export async function getBackgroundImageURL(id: string): Promise<string | null> {
const readVoiceBackgroundMedia = getElectronAPI()?.readVoiceBackgroundMedia;
if (!readVoiceBackgroundMedia) return null;
const media = await readVoiceBackgroundMedia(id);
if (!media?.dataUrl) return null;
return dataUrlToObjectUrl(media.dataUrl);
const resolved = resolvedUrls.get(id);
if (resolved != null) return resolved;
const inFlight = pendingReads.get(id);
if (inFlight != null) return inFlight.promise;
const read: BackgroundImageRead = {cancelled: false, promise: Promise.resolve(null)};
read.promise = readBackgroundImageObjectURL(id).then(
(url) => {
if (read.cancelled) {
if (url != null) URL.revokeObjectURL(url);
return null;
}
pendingReads.delete(id);
if (url != null) resolvedUrls.set(id, url);
return url;
},
(error: unknown) => {
if (!read.cancelled) pendingReads.delete(id);
throw error;
},
);
pendingReads.set(id, read);
return read.promise;
}