From b5c12bfcb4aa216bf6922fb808cc04fbe7399c44 Mon Sep 17 00:00:00 2001 From: Mikkeli Matlock Date: Mon, 14 Sep 2026 02:44:20 +0900 Subject: [PATCH] Send stored audio as a Blob body so large files upload on HTTP/1.1 Files over 8 MB were sent as a ReadableStream with duplex: 'half'. Chrome only allows streaming request bodies over HTTP/2+, so every such upload failed with "Failed to fetch" on HTTP/1.1 (e.g. http://localhost). A Blob body is read lazily from OPFS by the browser, so it keeps the "never hold the whole master in memory" property without depending on the protocol. Verified in headless Chromium: a 33 MB WAV drops and analyses both at http://localhost:8050 and behind Cloudflare/Caddy at a /uj/ prefix. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_015UXA39Sh9coAx4FhRkd8R2 --- static/audio-store.js | 38 ++++++++++---------------------------- 1 file changed, 10 insertions(+), 28 deletions(-) diff --git a/static/audio-store.js b/static/audio-store.js index c2567ca..5fabd08 100644 --- a/static/audio-store.js +++ b/static/audio-store.js @@ -165,41 +165,23 @@ export async function sweepIncomplete() { } /** - * Stream stored audio to an endpoint as a raw body. + * Send stored audio to an endpoint as a raw body. * - * The body is sent as a stream (duplex: 'half') so a 200 MB file doesn't have to - * be materialised as a second in-memory copy just to be uploaded. + * The OPFS File is passed to fetch as-is: the browser reads a Blob body lazily + * from its backing store, so a 200 MB master is never materialised in memory. + * (A ReadableStream body with duplex: 'half' was used before, but Chrome only + * allows streaming uploads over HTTP/2+, so any file past the old 8 MB cutoff + * failed with "Failed to fetch" on HTTP/1.1 — e.g. http://localhost.) */ export async function send(fileId, url, {method = 'POST', onProgress} = {}) { const blob = await get(fileId); if (!blob) throw new Error('audio is no longer in browser storage — re-add the file'); - // Small files go as a plain body: some proxies mishandle streaming duplex - // requests, and below a few MB there is nothing to gain. - if (blob.size < 8 * 1024 * 1024) { - const buf = await blob.arrayBuffer(); - if (onProgress) onProgress(blob.size, blob.size); - return fetch(url, { - method, - headers: {'Content-Type': 'application/octet-stream'}, - body: buf, - }); - } - - let sent = 0; - const body = blob.stream().pipeThrough( - new TransformStream({ - transform(chunk, controller) { - sent += chunk.byteLength; - if (onProgress) onProgress(sent, blob.size); - controller.enqueue(chunk); - }, - }), - ); - return fetch(url, { + const res = await fetch(url, { method, headers: {'Content-Type': 'application/octet-stream'}, - body, - duplex: 'half', + body: blob, }); + if (onProgress) onProgress(blob.size, blob.size); + return res; }