fix(play): bound preloadImage decode by the timeout; clarify proxy env docs
Addresses two GitHub Copilot review comments on PR #24: - preloadImage cleared the 20s timeout in onload, before awaiting img.decode(), leaving the decode phase unguarded — a hung decode could keep the promise pending forever and stall the play loop. Move clearTimeout into a single idempotent done() so the timeout stays armed through decode() too, matching the stated "timeouts resolve quietly" intent. - .env.example said to leave BOTH proxy vars blank, but shipped NEXT_PUBLIC_IMAGE_PROXY_ALLOWED_HOSTS=im.runware.ai. Only NEXT_PUBLIC_IMAGE_PROXY_URL gates the feature; the allowlist is inert until the URL is set. Corrected the wording, kept the self-documenting default value. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
+4
-3
@@ -57,9 +57,10 @@ TTS_SPEECH_MODEL=mimo-v2.5-tts
|
|||||||
MOCK_IMAGE=false
|
MOCK_IMAGE=false
|
||||||
|
|
||||||
# ---- 5b. Image proxy (Cloudflare Worker, OPTIONAL) -----------------
|
# ---- 5b. Image proxy (Cloudflare Worker, OPTIONAL) -----------------
|
||||||
# Leave BOTH blank (the default) and the browser fetches images directly
|
# Leave NEXT_PUBLIC_IMAGE_PROXY_URL blank (the default) and the browser
|
||||||
# from the provider — exactly as the app worked before this proxy existed.
|
# fetches images directly from the provider — exactly as the app worked
|
||||||
# You are completely unaffected; skip this whole section.
|
# before this proxy existed. The ALLOWED_HOSTS value below is inert until
|
||||||
|
# a proxy URL is set, so you're completely unaffected; skip this section.
|
||||||
#
|
#
|
||||||
# Why you might want it: Chrome's direct fetch of im.runware.ai is unreliable
|
# Why you might want it: Chrome's direct fetch of im.runware.ai is unreliable
|
||||||
# on some networks (ERR_QUIC_PROTOCOL_ERROR mid-stream → partial bytes →
|
# on some networks (ERR_QUIC_PROTOCOL_ERROR mid-stream → partial bytes →
|
||||||
|
|||||||
+11
-7
@@ -73,18 +73,22 @@ const IMAGE_PRELOAD_TIMEOUT_MS = 20000;
|
|||||||
function preloadImage(url: string): Promise<void> {
|
function preloadImage(url: string): Promise<void> {
|
||||||
return new Promise<void>((resolve) => {
|
return new Promise<void>((resolve) => {
|
||||||
const img = new Image();
|
const img = new Image();
|
||||||
const done = () => resolve();
|
let timer: ReturnType<typeof setTimeout>;
|
||||||
const timer = setTimeout(done, IMAGE_PRELOAD_TIMEOUT_MS);
|
// Single exit: clear the timeout and resolve. resolve() is idempotent, so
|
||||||
img.onload = () => {
|
// whichever path fires first (load+decode, error, timeout) wins.
|
||||||
|
const done = () => {
|
||||||
clearTimeout(timer);
|
clearTimeout(timer);
|
||||||
|
resolve();
|
||||||
|
};
|
||||||
|
// Armed across BOTH network load and decode, so a hung decode still
|
||||||
|
// resolves quietly — better a broken <img> than a stuck play loop.
|
||||||
|
timer = setTimeout(done, IMAGE_PRELOAD_TIMEOUT_MS);
|
||||||
|
img.onload = () => {
|
||||||
// .decode() forces the bitmap to be fully decoded before we proceed —
|
// .decode() forces the bitmap to be fully decoded before we proceed —
|
||||||
// without it, a slow decode could still cause a flash on first paint.
|
// without it, a slow decode could still cause a flash on first paint.
|
||||||
img.decode().then(done, done);
|
img.decode().then(done, done);
|
||||||
};
|
};
|
||||||
img.onerror = () => {
|
img.onerror = done;
|
||||||
clearTimeout(timer);
|
|
||||||
done();
|
|
||||||
};
|
|
||||||
img.src = url;
|
img.src = url;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user