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:
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user