feat(desktop): port v0.11.4-v0.15.2 from Tauri to Electron + Discord-parity audio (v0.16.0)

Chronological port of every Tauri release commit (v0.11.4 -> v0.15.2)
into the Electron rebuild, plus Discord-style audio handling that goes
beyond the Tauri original.

Highlights:
  - All 17 Tauri release commits ported (audio fixes, custom notification
    sound, Discord-style chat UX, profile banner, changelog page,
    Discord-parity call UX, screen-share echo + re-watch UX, audio-loop
    fix).
  - Native napi-rs audio-loopback addon with WASAPI process-loopback:
      * EXCLUDE_TARGET_PROCESS_TREE for full-screen shares -> peers
        never hear themselves echoed back through the capture.
      * INCLUDE_TARGET_PROCESS_TREE for window shares -> only the
        picked window's audio is captured, not the whole OS mixer
        (Discord parity).
      * HWND -> PID resolution via Win32 GetWindowThreadProcessId.
  - Discord-style screen-source picker (thumbnail grid, screens vs
    apps tabs, live-refreshing thumbnails).
  - Hash routing fix for packaged builds (file:// can't resolve
    BrowserRouter paths).
  - Tauri sources removed (apps/desktop/src-tauri).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
byGalax
2026-05-06 23:35:01 +02:00
parent 72d02e385f
commit 825160ee46
504 changed files with 14217 additions and 14366 deletions
+24 -5
View File
@@ -39,6 +39,22 @@ export function createPipeline(
if (!AudioCtx) return null;
try {
const ctx = new AudioCtx();
// A fresh AudioContext under Chromium's autoplay policy starts in
// `suspended` state when no recent user gesture is in scope —
// attachTrack fires from a LiveKit event, not the call-start click,
// so we can't rely on the gesture crossing the async boundary. Kick
// resume() immediately and retry on any statechange so a later
// suspension (window backgrounding, device change) doesn't leave
// the remote peer silent permanently.
const tryResume = () => {
if (ctx.state === 'suspended') {
void ctx.resume().catch(() => {
/* ignore — will retry on next statechange */
});
}
};
tryResume();
ctx.addEventListener('statechange', tryResume);
const source = ctx.createMediaElementSource(audio);
const gain = ctx.createGain();
// Start silent; the caller (CallContext) applies the correct effective
@@ -46,11 +62,14 @@ export function createPipeline(
gain.gain.value = 0;
source.connect(gain);
gain.connect(ctx.destination);
// createMediaElementSource diverts the element's direct output through
// the audio graph. Muting the element is then a double-guard — if the
// diversion ever fails (older WebKit), the element stays silent instead
// of bypassing the gain chain entirely.
audio.muted = true;
// Do NOT set `audio.muted = true` here. Chromium gates the
// media-element's internal sample production behind the muted flag,
// and that gate sits *before* the MediaElementAudioSourceNode tap —
// a muted element feeds zero samples into the WebAudio graph, which
// silences the peer even though createMediaElementSource already
// diverts the element's direct playback path. The diversion itself
// is sufficient to stop the element from double-playing to the
// default output; explicit muting is the bug.
const pipeline: RemoteAudioPipeline = {
trackSid: info.trackSid,
participantId: info.participantId,