iPhone: EQ and loudness levelling for saved songs that keep playing when locked
iOS stops Web Audio when the screen locks, so live processing can't reach a locked phone. For saved songs, EqRender renders the same chain as the live EQ (5 bands -> level gain -> limiter) offline into a 32 kHz stereo WAV in OPFS (eq-renders/<id>.wav, written by the OPFS worker's new write op), one song at a time in the background. Playback then takes the picture from the saved video (muted) and the sound from that copy in dual mode — plain media playback, so it continues with the screen locked; live gains are zeroed so nothing is applied twice. Copies are re-rendered when the EQ curve or a song's loudness changes and removed when nothing needs applying. - Level volume is now on by default everywhere (on iPhone it reaches saved songs through these copies). - New iPhone-only Settings -> Playback -> Live sound processing (off by default): live Web Audio EQ/levelling for streamed songs, stops when locked. - The EQ dialog works on iPhone and explains where the curve applies.
This commit is contained in:
@@ -185,7 +185,29 @@ async function resumableSave(videoId, url, target) {
|
||||
self.postMessage({ type: 'done', ext: target.ext || 'mp4', sha256, expectedSha, size: target.size });
|
||||
}
|
||||
|
||||
// Write a whole file into an OPFS sub-directory (sync access handle: works on
|
||||
// iOS Safari, where main-thread createWritable is missing). Used for the
|
||||
// iPhone EQ/levelling renders (EqRender in app.js).
|
||||
async function writeFile(dirName, name, buffer) {
|
||||
const root = await navigator.storage.getDirectory();
|
||||
const dir = await root.getDirectoryHandle(dirName, { create: true });
|
||||
const h = await dir.getFileHandle(name, { create: true });
|
||||
const a = await h.createSyncAccessHandle();
|
||||
try {
|
||||
const bytes = new Uint8Array(buffer);
|
||||
a.truncate(0);
|
||||
a.write(bytes, { at: 0 });
|
||||
a.truncate(bytes.byteLength);
|
||||
a.flush();
|
||||
} finally { a.close(); }
|
||||
}
|
||||
|
||||
self.onmessage = async (e) => {
|
||||
if (e.data && e.data.op === 'write') {
|
||||
try { await writeFile(e.data.dir, e.data.name, e.data.buffer); self.postMessage({ type: 'written' }); }
|
||||
catch (err) { self.postMessage({ type: 'error', error: err && err.message ? err.message : String(err) }); }
|
||||
return;
|
||||
}
|
||||
const { videoId, url, resumable } = e.data || {};
|
||||
|
||||
if (
|
||||
|
||||
Reference in New Issue
Block a user