Files
ytplayer/frontend/direct-recv-worker.js

41 lines
2.8 KiB
JavaScript

'use strict';
let dir, handle, access, hash, pos = 0, claim, name;
let chain = Promise.resolve();
self.onmessage = e => { chain = chain.then(() => run(e.data)); };
async function close() { if (access) { access.flush(); access.close(); access = null; } }
async function run(m) {
try {
if (!self.Sha256) { self.__ASSET_URLS__=m.assetUrls || {}; importScripts(self.__ASSET_URLS__.sha256 || '/sha256.js'); }
if (m.op === 'open') {
claim = m.file;
if (!/^[A-Za-z0-9_-]{1,128}$/.test(claim.id) || !/^[a-f0-9]{64}$/.test(claim.cid)) throw Error('Invalid file identity');
dir = await (await navigator.storage.getDirectory()).getDirectoryHandle('videos', { create: true });
name = `${claim.id}.${claim.cid}.direct.part`;
handle = await dir.getFileHandle(name, { create: true });
access = await handle.createSyncAccessHandle();
pos = access.getSize(); if (pos > claim.size) { access.truncate(0); pos = 0; }
hash = self.Sha256.create();
for (let at = 0; at < pos; at += 1024 * 1024) { const b = new Uint8Array(Math.min(1024 * 1024, pos - at)); access.read(b, { at }); hash.update(b); }
self.postMessage({ op: 'opened', offset: pos });
} else if (m.op === 'chunk') {
const b = new Uint8Array(m.buf);
if (!access || b.length > 65536 || pos + b.length > claim.size) throw Error('Invalid file chunk');
if (access.write(b, { at: pos }) !== b.length) throw Error('Incomplete OPFS write');
hash.update(b); pos += b.length; access.flush();
self.postMessage({ op: 'progress', received: pos });
} else if (m.op === 'abort') { await close(); self.postMessage({ op: 'closed' }); }
else if (m.op === 'finish') {
await close();
if (pos !== claim.size || hash.hex() !== claim.cid) { await dir.removeEntry(name); throw Error('File verification failed; retry starts from zero'); }
const extension = /^(mp4|m4a|webm|ogg|opus|mkv|mp3|wav|aac)$/.test(claim.extension) ? claim.extension : 'mp4';
const final = `${claim.id}.${extension}`;
// Copy only after verification; existing saved playback remains intact until then.
const out = await (await dir.getFileHandle(final, { create: true })).createSyncAccessHandle();
try { const f = await handle.getFile(); for (let at = 0; at < f.size; at += 1024 * 1024) out.write(new Uint8Array(await f.slice(at, at + 1024 * 1024).arrayBuffer()), { at }); out.truncate(f.size); out.flush(); } finally { out.close(); }
await dir.removeEntry(name);
for await (const [old, entry] of dir.entries()) { if (entry.kind === 'file' && old !== final && old.startsWith(claim.id + '.') && /\.(mp4|m4a|webm|ogg|opus|mkv|mp3|wav|aac)$/.test(old)) await dir.removeEntry(old); }
self.postMessage({ op: 'done', file: claim });
}
} catch (e) { await close(); self.postMessage({ op: 'error', error: e.message }); }
}