// ============================================================ // Volume Booster — Offscreen Document // Läuft in einem unsichtbaren DOM-Kontext. Nur hier sind // getUserMedia + AudioContext verfügbar. Hält pro Tab einen // GainNode und gibt den verstärkten Stream wieder aus. // ============================================================ // Map const active = new Map(); function teardown(tabId) { const entry = active.get(tabId); if (!entry) return; try { entry.source.disconnect(); entry.gain.disconnect(); entry.stream.getTracks().forEach((t) => t.stop()); entry.context.close(); } catch (e) { /* trotzdem aufräumen */ } active.delete(tabId); } async function applyBoost(tabId, streamId, value) { // Falls (z. B. nach Fehler) noch ein alter Eintrag existiert: erst sauber weg. if (active.has(tabId)) { teardown(tabId); } // Stream aus der ID erzeugen const stream = await navigator.mediaDevices.getUserMedia({ audio: { mandatory: { chromeMediaSource: "tab", chromeMediaSourceId: streamId, }, }, video: false, }); const context = new AudioContext(); const source = context.createMediaStreamSource(stream); const gain = context.createGain(); gain.gain.value = value; source.connect(gain); gain.connect(context.destination); // Audio bleibt für den Nutzer hörbar // Wenn der Stream endet (Tab neu geladen / geschlossen) const track = stream.getAudioTracks()[0]; if (track) { track.addEventListener("ended", () => teardown(tabId)); } active.set(tabId, { context, gain, source, stream }); return { ok: true, boosting: true, value }; } chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => { if (msg.target !== "offscreen") return false; (async () => { try { if (msg.type === "APPLY") { const res = await applyBoost(msg.tabId, msg.streamId, msg.value); sendResponse(res); } else if (msg.type === "GAIN") { // Nur die Lautstärke eines bereits aktiven Tabs ändern const entry = active.get(msg.tabId); if (entry) { entry.gain.gain.value = msg.value; sendResponse({ ok: true, boosting: true, value: msg.value }); } else { // Kein aktiver Stream (mehr) — Popup soll neu anwenden sendResponse({ ok: false, error: "no active stream", boosting: false }); } } else if (msg.type === "STOP") { teardown(msg.tabId); sendResponse({ ok: true, boosting: false }); } else if (msg.type === "STATE") { const entry = active.get(msg.tabId); sendResponse({ ok: true, boosting: !!entry, value: entry ? entry.gain.gain.value : 1, }); } } catch (err) { sendResponse({ ok: false, error: String((err && err.message) || err) }); } })(); return true; // async });