fix(voice): DAVE video decrypt fallback chain (VIDEO→AUDIO→passthrough)

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.
This commit is contained in:
asepharyana
2026-09-01 23:54:07 +07:00
parent 5fdd6ed80c
commit 7c21629404
@@ -484,16 +484,36 @@ function decryptVideoPacket(
} }
// DAVE layer — decrypt as VIDEO (audio wrapper hardcodes MediaType.AUDIO). // 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 { try {
const decrypted = daveSession.session.decrypt( const decrypted = daveSession.session.decrypt(
userId, userId,
MediaType.VIDEO, MediaType.VIDEO,
packet, packet,
); );
return decrypted && decrypted.length > 0 ? decrypted : null; if (decrypted && decrypted.length > 0) return decrypted;
} catch { } 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( function handleUdpMessage(