Replace saved copies that fail to play, reject truncated saves, and re-check the device cache when the app returns to the foreground
This commit is contained in:
@@ -85,6 +85,29 @@
|
||||
},
|
||||
|
||||
// Revoke an object URL previously returned by getFileUrl().
|
||||
// 'hevc' | 'avc' | null for a stored mp4. Server copies are faststart, so
|
||||
// the sample description (hvc1/hev1 vs avc1) sits in the first megabyte.
|
||||
async sniffCodec(videoId) {
|
||||
try {
|
||||
const found = await findHandle(videoId);
|
||||
if (!found) return null;
|
||||
const file = await found[0].getFile();
|
||||
const head = new Uint8Array(await file.slice(0, 1024 * 1024).arrayBuffer());
|
||||
const has = (tag) => {
|
||||
const [a, b, c, d] = [...tag].map((ch) => ch.charCodeAt(0));
|
||||
for (let i = 0; i + 3 < head.length; i++) {
|
||||
if (head[i] === a && head[i + 1] === b && head[i + 2] === c && head[i + 3] === d) return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
if (has('hvc1') || has('hev1')) return 'hevc';
|
||||
if (has('avc1')) return 'avc';
|
||||
return null;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
},
|
||||
|
||||
revokeUrl(url) {
|
||||
if (url && _blobUrls.has(url)) {
|
||||
URL.revokeObjectURL(url);
|
||||
@@ -153,6 +176,13 @@
|
||||
await writable.abort();
|
||||
throw err;
|
||||
}
|
||||
// A connection that ends early can close the body cleanly, leaving
|
||||
// a truncated file that would be badged "cached" yet never play.
|
||||
const expected = Number(response.headers.get('content-length'));
|
||||
if (expected > 0) {
|
||||
const got = (await tmpHandle.getFile()).size;
|
||||
if (got !== expected) throw new Error(`download cut short (${got} of ${expected} bytes)`);
|
||||
}
|
||||
} else {
|
||||
// No createWritable on this browser — main-thread OPFS writes are
|
||||
// impossible (sync access handles are worker-only). Surface a real
|
||||
@@ -187,7 +217,9 @@
|
||||
},
|
||||
|
||||
// List all cached videos — returns [{ id, size, name }]
|
||||
async listVideos() {
|
||||
// strict: rethrow instead of returning a partial list, for callers that
|
||||
// treat a missing entry as "the file is gone".
|
||||
async listVideos({ strict = false } = {}) {
|
||||
const dir = await getRoot();
|
||||
const items = [];
|
||||
try {
|
||||
@@ -200,7 +232,7 @@
|
||||
const file = await handle.getFile();
|
||||
items.push({ id, name, size: file.size });
|
||||
}
|
||||
} catch { /* OPFS not available */ }
|
||||
} catch (err) { if (strict) throw err; /* OPFS not available */ }
|
||||
return items;
|
||||
},
|
||||
|
||||
|
||||
Reference in New Issue
Block a user