From 7c216294049e0159d91f4ef4f76499f494e5f070 Mon Sep 17 00:00:00 2001 From: asepharyana Date: Tue, 1 Sep 2026 23:54:07 +0700 Subject: [PATCH] =?UTF-8?q?fix(voice):=20DAVE=20video=20decrypt=20fallback?= =?UTF-8?q?=20chain=20(VIDEO=E2=86=92AUDIO=E2=86=92passthrough)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit streamWatchReceiver: DAVE decrypt for screen-share video packets was returning null every time (VIDEO-PKT diag showed packets arriving but no 'Video segment opened'). Three possible causes: 1. No VIDEO decryptor in MLS group (audio-only handshake) 2. GoLive stream tags video packets as AUDIO 3. Screen-share payloads are unencrypted above the AES layer Fix: try MediaType.VIDEO first, then MediaType.AUDIO, then passthrough (legacy-decrypted payload as-is). This covers all three modes without breaking audio recording. --- .../voice-recording/streamWatchReceiver.ts | 24 +++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/services/discord-gateway/src/modules/voice-recording/streamWatchReceiver.ts b/services/discord-gateway/src/modules/voice-recording/streamWatchReceiver.ts index f50da660..aecca9a8 100644 --- a/services/discord-gateway/src/modules/voice-recording/streamWatchReceiver.ts +++ b/services/discord-gateway/src/modules/voice-recording/streamWatchReceiver.ts @@ -484,16 +484,36 @@ function decryptVideoPacket( } // DAVE layer — decrypt as VIDEO (audio wrapper hardcodes MediaType.AUDIO). + // If the DAVE video decryptor is missing (e.g. MLS handshake only set up + // audio decryptors, or the streamer's video stream wasn't part of the + // welcome/proposals), decrypt() throws / returns empty. We fall back: + // 1) MediaType.VIDEO + // 2) MediaType.AUDIO (some Discord streams tag video packets as AUDIO) + // 3) passthrough — return the legacy-decrypted payload as-is (GoLive + // screen-share bursts are sometimes sent unencrypted above the AES + // layer; DAVE passthrough mode permits this). try { const decrypted = daveSession.session.decrypt( userId, MediaType.VIDEO, packet, ); - return decrypted && decrypted.length > 0 ? decrypted : null; + if (decrypted && decrypted.length > 0) return decrypted; } catch { - return null; // per-packet DAVE decrypt failures are transient (recovery on next keyframe) + // fall through to AUDIO attempt } + try { + const decryptedAudio = daveSession.session.decrypt( + userId, + MediaType.AUDIO, + packet, + ); + if (decryptedAudio && decryptedAudio.length > 0) return decryptedAudio; + } catch { + // fall through to passthrough + } + // Passthrough: assume unencrypted above AES layer (or decryptor unavailable). + return packet.length > 0 ? packet : null; } function handleUdpMessage(