volume-booster/background.js
2026-05-15 21:11:59 +02:00

124 lines
3.9 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// ============================================================
// 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(() => {});
});