// ============================================================ // Volume Booster — Background Service Worker // In Manifest V3 kann der Service Worker selbst KEIN Audio // capturen (kein DOM, kein getUserMedia). Er holt nur eine // Stream-ID via chrome.tabCapture.getMediaStreamId und reicht // sie an ein Offscreen-Dokument weiter, das den GainNode hält. // ============================================================ let creating = null; // Promise-Lock, damit nicht zwei Offscreen-Docs entstehen async function ensureOffscreen() { const contexts = await chrome.runtime.getContexts({ contextTypes: ["OFFSCREEN_DOCUMENT"], }); if (contexts.length > 0) return; if (creating) { await creating; return; } creating = chrome.offscreen.createDocument({ url: "offscreen.html", reasons: ["USER_MEDIA"], justification: "Tab-Audio verstärken (Volume Booster)", }); await creating; creating = null; } // Nachrichten aus dem Popup chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => { // Nachrichten fürs Offscreen-Dokument hier nicht behandeln if (msg.target === "offscreen") return false; (async () => { try { if (msg.type === "SET_VOLUME") { const { tabId, value } = msg; // value: 0.0 – 6.0 await ensureOffscreen(); if (value === 1) { // Exakt 100 %: Boost beenden, kein Capture nötig const res = await chrome.runtime.sendMessage({ target: "offscreen", type: "STOP", tabId, }); sendResponse(res || { ok: true, boosting: false }); return; } // ERST prüfen, ob dieser Tab schon einen aktiven Stream hat. // Wenn ja, NUR den Gain ändern — keine neue Stream-ID holen! // (Eine getMediaStreamId-ID ist einmalig und der Tab kann nicht // ein zweites Mal gecaptured werden, solange der erste Stream läuft.) const state = await chrome.runtime.sendMessage({ target: "offscreen", type: "STATE", tabId, }); if (state && state.boosting) { // Tab läuft schon: nur Lautstärke anpassen const gainRes = await chrome.runtime.sendMessage({ target: "offscreen", type: "GAIN", tabId, value, }); if (gainRes && gainRes.ok) { sendResponse(gainRes); return; } // Falls der Stream zwischendurch verloren ging: unten neu capturen } // Tab noch nicht aktiv: jetzt eine frische Stream-ID holen const streamId = await chrome.tabCapture.getMediaStreamId({ targetTabId: tabId, }); const applyRes = await chrome.runtime.sendMessage({ target: "offscreen", type: "APPLY", tabId, streamId, value, }); sendResponse( applyRes || { ok: false, error: "Keine Antwort vom Offscreen-Dokument" } ); } else if (msg.type === "GET_STATE") { await ensureOffscreen(); const res = await chrome.runtime.sendMessage({ target: "offscreen", type: "STATE", tabId: msg.tabId, }); sendResponse(res || { ok: true, boosting: false, value: 1 }); } else if (msg.type === "RESET") { await ensureOffscreen(); const res = await chrome.runtime.sendMessage({ target: "offscreen", type: "STOP", tabId: msg.tabId, }); sendResponse(res || { ok: true }); } } catch (err) { sendResponse({ ok: false, error: String((err && err.message) || err) }); } })(); return true; // async sendResponse }); // Aufräumen, wenn ein Tab geschlossen wird chrome.tabs.onRemoved.addListener((tabId) => { chrome.runtime .sendMessage({ target: "offscreen", type: "STOP", tabId }) .catch(() => {}); });