fix: stop playback stutter and save failures via async yt-dlp, per-download OPFS workers, lazy playback blobs, GPU composite trims, and landscape pane scrolling
This commit is contained in:
@@ -71,7 +71,11 @@
|
||||
const [handle, name] = found;
|
||||
const file = await handle.getFile();
|
||||
const ext = name.slice(name.lastIndexOf('.') + 1);
|
||||
const blob = new Blob([await file.arrayBuffer()], { type: extToMime(ext) });
|
||||
// Wrap the File (a lazy disk-backed Blob) instead of buffering it:
|
||||
// `await file.arrayBuffer()` here pulled the ENTIRE video into main-
|
||||
// thread memory just to retype it, which froze the UI and stuttered
|
||||
// audio on phones. Blob parts reference the File without reading it.
|
||||
const blob = new Blob([file], { type: extToMime(ext) });
|
||||
const url = URL.createObjectURL(blob);
|
||||
_blobUrls.add(url);
|
||||
return url;
|
||||
@@ -88,9 +92,42 @@
|
||||
}
|
||||
},
|
||||
|
||||
// Download a video entirely inside a dedicated Web Worker — the fetch and
|
||||
// the OPFS writes both happen off the main thread, so saves can never
|
||||
// jank the UI. One worker per download; concurrent saves get concurrent
|
||||
// workers. Resolves { ok:true } on success, { ok:false, error } on a real
|
||||
// failure, or { ok:false, fallback:true } when the worker path is
|
||||
// unavailable and the caller should use writeFromResponse instead.
|
||||
downloadVideo(videoId, url) {
|
||||
return new Promise((resolve) => {
|
||||
let worker;
|
||||
try {
|
||||
worker = new Worker('/opfs-worker.js');
|
||||
} catch {
|
||||
resolve({ ok: false, fallback: true });
|
||||
return;
|
||||
}
|
||||
const finish = (result) => {
|
||||
worker.terminate();
|
||||
resolve(result);
|
||||
};
|
||||
worker.onmessage = (e) => {
|
||||
const m = e.data || {};
|
||||
if (m.type === 'done') finish({ ok: true });
|
||||
else if (m.type === 'unsupported') finish({ ok: false, fallback: true });
|
||||
else if (m.type === 'error') finish({ ok: false, error: m.error });
|
||||
// 'progress' messages are informational; ignored here
|
||||
};
|
||||
worker.onerror = () => finish({ ok: false, fallback: true });
|
||||
worker.postMessage({ videoId, url });
|
||||
});
|
||||
},
|
||||
|
||||
// Stream a fetch Response body into OPFS. Uses a writable stream so only
|
||||
// a small chunk lives in memory at a time (no full-file buffering).
|
||||
// Falls back to ArrayBuffer if WritableStream is unavailable.
|
||||
// Main-thread fallback for downloadVideo — used when Workers or
|
||||
// createSyncAccessHandle are unavailable.
|
||||
async writeFromResponse(videoId, ext, response) {
|
||||
const dir = await getRoot();
|
||||
const filename = videoId + '.' + (ext || 'mp4');
|
||||
@@ -124,12 +161,22 @@
|
||||
await writable.close();
|
||||
}
|
||||
|
||||
// Rename tmp → final. OPFS doesn't have rename, so: read + write + delete.
|
||||
const finalHandle = await dir.getFileHandle(filename, { create: true });
|
||||
const finalWritable = await finalHandle.createWritable();
|
||||
const tmpFile = await tmpHandle.getFile();
|
||||
await finalWritable.write(await tmpFile.arrayBuffer());
|
||||
await finalWritable.close();
|
||||
// Rename tmp → final. Prefer the native rename; else stream-copy so
|
||||
// the whole file is never buffered in main-thread memory at once.
|
||||
if (typeof tmpHandle.move === 'function') {
|
||||
await tmpHandle.move(filename);
|
||||
} else {
|
||||
const finalHandle = await dir.getFileHandle(filename, { create: true });
|
||||
const finalWritable = await finalHandle.createWritable();
|
||||
const tmpFile = await tmpHandle.getFile();
|
||||
const tmpStream = typeof tmpFile.stream === 'function' ? tmpFile.stream() : null;
|
||||
if (tmpStream && typeof tmpStream.pipeTo === 'function') {
|
||||
await tmpStream.pipeTo(finalWritable); // pipeTo closes the writable
|
||||
} else {
|
||||
await finalWritable.write(await tmpFile.arrayBuffer());
|
||||
await finalWritable.close();
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
// Remove .part file regardless
|
||||
try { await dir.removeEntry(tmpName); } catch { /* already gone */ }
|
||||
|
||||
Reference in New Issue
Block a user