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:
yuanzonghao
2026-06-04 11:04:16 +08:00
parent 4347e5bfdf
commit 4bc47d8210
2 changed files with 15 additions and 10 deletions
+4 -3
View File
@@ -57,9 +57,10 @@ TTS_SPEECH_MODEL=mimo-v2.5-tts
MOCK_IMAGE=false
# ---- 5b. Image proxy (Cloudflare Worker, OPTIONAL) -----------------
# Leave BOTH blank (the default) and the browser fetches images directly
# from the provider — exactly as the app worked before this proxy existed.
# You are completely unaffected; skip this whole section.
# Leave NEXT_PUBLIC_IMAGE_PROXY_URL blank (the default) and the browser
# fetches images directly from the provider — exactly as the app worked
# 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
# on some networks (ERR_QUIC_PROTOCOL_ERROR mid-stream → partial bytes →
+11 -7
View File
@@ -73,18 +73,22 @@ const IMAGE_PRELOAD_TIMEOUT_MS = 20000;
function preloadImage(url: string): Promise<void> {
return new Promise<void>((resolve) => {
const img = new Image();
const done = () => resolve();
const timer = setTimeout(done, IMAGE_PRELOAD_TIMEOUT_MS);
img.onload = () => {
let timer: ReturnType<typeof setTimeout>;
// Single exit: clear the timeout and resolve. resolve() is idempotent, so
// whichever path fires first (load+decode, error, timeout) wins.
const done = () => {
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 —
// without it, a slow decode could still cause a flash on first paint.
img.decode().then(done, done);
};
img.onerror = () => {
clearTimeout(timer);
done();
};
img.onerror = done;
img.src = url;
});
}