Add optional JamesDSP audio effects
This commit is contained in:
@@ -644,6 +644,7 @@ function toAssetUrl(path) {
|
||||
// ---------- State ----------
|
||||
const DEFAULT_SETTINGS = {
|
||||
quality: 'auto', volume: 1, audioOnly: false, autoPreload: true,
|
||||
jamesDsp: { enabled: false, preset: 'Flat', params: {} },
|
||||
p2pShare: true, // P2P (docs/p2p-architecture.md): share my saved videos with other devices — ON by default
|
||||
p2pReceive: true, // fetch from other devices when YouTube and the server can't — ON by default
|
||||
saveBeforePlay: false, // download (server-muxed single file) before playing instead of streaming
|
||||
@@ -2658,6 +2659,7 @@ const Player = {
|
||||
|
||||
play() {
|
||||
this._wantsPlaying = true;
|
||||
if (data.settings.jamesDsp?.enabled) JamesDSP.activate(this.soundEl).catch((e) => JamesDSP.fail(e));
|
||||
audioContinuity.play(this.master);
|
||||
audioContinuity.resume(this.secondary, this.master);
|
||||
},
|
||||
@@ -6817,6 +6819,36 @@ const PiP = (() => {
|
||||
return { toggle, render };
|
||||
})();
|
||||
|
||||
// Optional JamesDSP stage. The module and WASM are fetched only after opt-in.
|
||||
const JamesDSP = (() => {
|
||||
const state = { controller: null, importing: null };
|
||||
async function activate(element = Player.soundEl) {
|
||||
if (!data.settings.jamesDsp?.enabled || !element) return;
|
||||
try {
|
||||
if (!state.importing) state.importing = import('./vendor/jamesdsp/controller.js');
|
||||
const mod = await state.importing;
|
||||
if (!state.controller) { state.controller = new mod.JamesDSPController(); state.controller.onError = fail; }
|
||||
await state.controller.enable(element, data.settings.jamesDsp);
|
||||
} catch (error) { fail(error); throw error; }
|
||||
}
|
||||
function fail(error) {
|
||||
console.warn('[JamesDSP] Disabled:', error);
|
||||
data.settings.jamesDsp.enabled = false;
|
||||
state.controller?.disable();
|
||||
persist();
|
||||
const checkbox = $('setJamesDsp'); if (checkbox) checkbox.checked = false;
|
||||
const note = $('jamesDspStatus'); if (note) note.textContent = `Audio effects unavailable: ${error?.message || error}`;
|
||||
toast(`Audio effects disabled: ${error?.message || error}`);
|
||||
}
|
||||
async function toggle(on) {
|
||||
data.settings.jamesDsp.enabled = !!on; persist();
|
||||
if (!on) { await state.controller?.disable(); return; }
|
||||
await activate(Player.soundEl);
|
||||
}
|
||||
async function apply() { if (data.settings.jamesDsp?.enabled) await activate(Player.soundEl); }
|
||||
return { activate, fail, toggle, apply, get controller() { return state.controller; } };
|
||||
})();
|
||||
|
||||
// ============================================================================
|
||||
// Equalizer — 10-band graphic EQ (Web Audio)
|
||||
// ============================================================================
|
||||
@@ -9643,6 +9675,17 @@ async function renderSettings() {
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="set-group" id="jamesDspSettings">
|
||||
<div class="set-group-title">Audio effects (JamesDSP)</div>
|
||||
<label class="set-row"><span>Enable JamesDSP<small>Loads the audio processor only when enabled. iOS background playback may be interrupted.</small></span><input id="setJamesDsp" type="checkbox" ${data.settings.jamesDsp?.enabled ? 'checked' : ''} /></label>
|
||||
<label class="set-row"><span>Preset</span>${sel('setJamesDspPreset', data.settings.jamesDsp?.preset || 'Flat', [['Flat','Flat'],['Bass boost','Bass boost'],['Vocal','Vocal'],['Headphone crossfeed','Headphone crossfeed'],['Loudness','Loudness']])}</label>
|
||||
<label class="set-row"><span>EQ low band<small>25 Hz · −12 to +12 dB</small></span><input type="range" min="-12" max="12" step="1" value="${data.settings.jamesDsp?.params?.['eq.band0'] ?? 0}" id="setDspEq" /></label>
|
||||
<label class="set-row"><span>Bass boost<small>0 to 12 dB</small></span><input type="range" min="0" max="12" step="1" value="${data.settings.jamesDsp?.params?.['bass.boost'] ?? 6}" id="setDspBass" /></label>
|
||||
<label class="set-row"><span>Headphone crossfeed</span><input id="setDspCrossfeed" type="checkbox" ${data.settings.jamesDsp?.params?.['crossfeed.enabled'] ? 'checked' : ''} /></label>
|
||||
<label class="set-row"><span>Limiter threshold<small>−24 to 0 dB</small></span><input type="range" min="-24" max="0" step="1" value="${data.settings.jamesDsp?.params?.['limiter.threshold'] ?? -0.1}" id="setDspLimiter" /></label>
|
||||
<small id="jamesDspStatus" class="sp-note">Supported sample rates: 44.1 and 48 kHz.</small>
|
||||
</div>
|
||||
|
||||
<div class="set-group">
|
||||
<div class="set-group-title">Sharing (peer-to-peer)</div>
|
||||
<label class="set-row">
|
||||
@@ -9836,6 +9879,7 @@ async function renderSettings() {
|
||||
const definitions = [
|
||||
['appearance', 'Appearance', 'Listening', () => `${data.settings.theme || 'dark'} · ${data.settings.fontScale || 'normal'} text`, '<circle cx="12" cy="12" r="8"/><path d="M12 4v16"/>'],
|
||||
['playback', 'Playback & sound', 'Listening', () => `${data.settings.quality || 'auto'} quality · ${Level.enabled() ? 'Levelled volume' : 'Original volume'}`, '<path d="m9 5 10 7-10 7zM4 5v14"/>'],
|
||||
['jamesdsp', 'Audio effects (JamesDSP)', 'Listening', () => data.settings.jamesDsp?.enabled ? `On · ${data.settings.jamesDsp.preset || 'Flat'}` : 'Off', '<path d="M4 12h3l2-7 4 14 2-7h5"/>'],
|
||||
['sharing', 'Sharing', 'Library & storage', () => data.settings.p2pShare !== false ? 'Sharing saved videos' : 'Sharing off', '<circle cx="6" cy="12" r="2"/><circle cx="18" cy="5" r="2"/><circle cx="18" cy="19" r="2"/><path d="m8 11 8-5M8 13l8 5"/>'],
|
||||
['offline', 'Offline storage', 'Library & storage', () => `${SaveSlots.limit ? SaveSlots.limit() : Number(data.settings.parallelSaves) || 4} at once · ${$('cacheTotal')?.textContent || 'Checking storage'}`, '<path d="M12 3v12m-4-4 4 4 4-4M4 16v5h16v-5"/>'],
|
||||
['searches', 'Saved searches', 'Library & storage', () => `${SearchLibrary.list().length} saved searches`, '<circle cx="10" cy="10" r="6"/><path d="m15 15 6 6"/>'],
|
||||
@@ -9914,6 +9958,12 @@ async function renderSettings() {
|
||||
refresh();
|
||||
}
|
||||
|
||||
$('setJamesDsp').addEventListener('change', (e) => { JamesDSP.toggle(e.target.checked).catch(() => {}); });
|
||||
$('setJamesDspPreset').addEventListener('change', async (e) => { data.settings.jamesDsp ||= { enabled: false, params: {} }; data.settings.jamesDsp.preset = e.target.value; persist(); try { await JamesDSP.apply(); } catch {} });
|
||||
const dspParam = (id, name, parse = Number) => $(id).addEventListener('input', async (e) => { data.settings.jamesDsp ||= { enabled: false, preset: 'Flat', params: {} }; const value = parse(e.target.value); data.settings.jamesDsp.params ||= {}; data.settings.jamesDsp.params[name] = value; persist(); try { if (JamesDSP.controller) await JamesDSP.controller.update(name, value, data.settings.jamesDsp); } catch (error) { JamesDSP.fail(error); } });
|
||||
dspParam('setDspEq', 'eq.band0'); dspParam('setDspBass', 'bass.boost'); dspParam('setDspLimiter', 'limiter.threshold');
|
||||
$('setDspCrossfeed').addEventListener('change', async (e) => { const s = data.settings.jamesDsp ||= { enabled: false, preset: 'Flat', params: {} }; s.params ||= {}; s.params['crossfeed.enabled'] = Number(e.target.checked); persist(); try { if (JamesDSP.controller) await JamesDSP.controller.update('crossfeed.enabled', Number(e.target.checked), s); } catch (error) { JamesDSP.fail(error); } });
|
||||
|
||||
// ---- Wire playback controls ----
|
||||
$('setQuality').addEventListener('change', (e) => {
|
||||
data.settings.quality = e.target.value;
|
||||
|
||||
Reference in New Issue
Block a user