mirror of
https://github.com/got-feedBack/feedBack-desktop.git
synced 2026-09-10 22:04:11 +00:00
feat(audio): ASIO/exclusive — renderer-bus in streamer mix, loopback plumbing, cache clear (#98)
* feat(audio): renderer-bus in streamer mix + whole-app loopback plumbing Two tester-confirmed gaps under ASIO/exclusive output: 1. Streamer mix carried guitar only when the song rode the renderer bus: composeAndPushStreamMix mixed guitar + native backing, never the bus. The bus ring is single-consumer, so the consumer step is reworked from mixRendererBusInto (drain+add) to pullRendererBus (drain once into a fixed scratch); both output callbacks then share the pulled block between the device output and the stream submix (rides includeBacking — it IS song audio). 2. Previews/UI sounds bypass the per-surface feeder taps entirely and leak to the default WASAPI device (audible under ASIO, which doesn't silence that endpoint). New plumbing lets the static bundle capture ALL app audio: setDisplayMediaRequestHandler answers with this window's own frame as audio source (frame-scoped — no other apps' audio), plus audio:setPageMuted IPC + preload setPageMuted() as the local-silence fallback when suppressLocalAudioPlayback is unsupported. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(diag): engine health metrics on every [asio-diag] line Tester symptom: all audio dead after stopping a song with tones active. The snapshot showed routing state but not whether the engine was still producing. Append volatile fields (outside change-detection): in/out/ backing levels, bus fill, input overflows, output underflows, split-ring fill — outputLevel≈0 with running=true is the silent-engine signature. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(main): clear Chromium HTTP cache before first load Testers hop between portable builds sharing one userData dir; an older build's server sent no Cache-Control, so its cached /static/app.js outlived it and silently replaced the new build's renderer code (the fix14 'watcher never installed' log). One cheap clearCache() per launch makes stale-bundle states impossible even against old-server caches. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
6349ed4c5f
commit
c71aa7c82f
+41
-2
@@ -56,7 +56,7 @@ if (process.platform !== 'linux') {
|
||||
}
|
||||
// ──────────────────────────────────────────────────────────────────────────
|
||||
|
||||
import { app, BrowserWindow, ipcMain, dialog, shell, session, crashReporter, powerSaveBlocker, systemPreferences, screen } from 'electron';
|
||||
import { app, BrowserWindow, ipcMain, dialog, shell, session, crashReporter, powerSaveBlocker, systemPreferences, desktopCapturer, screen } from 'electron';
|
||||
import * as path from 'path';
|
||||
import * as fs from 'fs';
|
||||
import { execFileSync } from 'child_process';
|
||||
@@ -500,10 +500,49 @@ function createWindow(port: number): void {
|
||||
console.log(`${prefix} ${message}`);
|
||||
});
|
||||
|
||||
// Whole-app audio capture for exclusive-style outputs (ASIO / WASAPI
|
||||
// exclusive). The renderer-bus feeder in the static bundle calls
|
||||
// getDisplayMedia({audio, video}) to capture EVERY sound the app makes
|
||||
// (song, previews, UI) and push it into the engine's renderer bus —
|
||||
// per-surface taps can't cover plugin-private AudioContexts. Answer the
|
||||
// request with this window's own frame as the audio source (frame-scoped:
|
||||
// other applications' audio is NOT captured — a system 'loopback' would
|
||||
// leak Discord/etc. into the performance mix) and any screen as the
|
||||
// required-but-unused video track (the feeder stops it immediately).
|
||||
session.defaultSession.setDisplayMediaRequestHandler((_request, callback) => {
|
||||
desktopCapturer.getSources({ types: ['screen'] }).then((sources) => {
|
||||
if (!mainWindow || sources.length === 0) { callback({}); return; }
|
||||
callback({ video: sources[0], audio: mainWindow.webContents.mainFrame });
|
||||
}).catch(() => callback({}));
|
||||
});
|
||||
|
||||
// Local-mute companion for the capture above: when the feeder engages
|
||||
// loopback it must stop the page's audio from ALSO reaching the default
|
||||
// WASAPI device. Preferred path is the suppressLocalAudioPlayback track
|
||||
// constraint; this IPC is the fallback when the constraint is
|
||||
// unsupported. Chromium's capture pipeline taps frame audio before the
|
||||
// output mute, so a muted page still feeds the captured stream.
|
||||
ipcMain.handle('audio:setPageMuted', (_event, muted: unknown) => {
|
||||
if (!mainWindow) return false;
|
||||
mainWindow.webContents.setAudioMuted(muted === true);
|
||||
return mainWindow.webContents.isAudioMuted();
|
||||
});
|
||||
|
||||
const serverUrl = `http://127.0.0.1:${port}`;
|
||||
|
||||
// Clear the Chromium HTTP cache before the first load. The server
|
||||
// historically sent no Cache-Control on /static, so heuristic freshness
|
||||
// let a NEW build's window run the PREVIOUS build's app.js from disk
|
||||
// cache (2026-07-11 ASIO investigation: the whole exclusive-reroute chain
|
||||
// silently missing). The server now sends no-cache, but testers hop
|
||||
// between portable builds sharing one userData dir — one cheap clear per
|
||||
// launch makes stale-bundle states impossible regardless of what an
|
||||
// older build's server cached.
|
||||
const clearCachePromise = mainWindow.webContents.session.clearCache()
|
||||
.catch((e) => console.warn(`[main] clearCache failed (continuing): ${e.message}`));
|
||||
|
||||
// Small delay to ensure server is fully accepting connections, then load
|
||||
setTimeout(() => mainWindow?.loadURL(serverUrl), 500);
|
||||
setTimeout(() => { void clearCachePromise.then(() => mainWindow?.loadURL(serverUrl)); }, 500);
|
||||
|
||||
// Retry loading if the server wasn't reachable yet. Previously this
|
||||
// retried just once, which left the window stuck on Chromium's
|
||||
|
||||
Reference in New Issue
Block a user