fix(backend): handle voice PCM as JSON instead of binary

- Add discord:voice:pcm to SUBSCRIPTIONS as regular JSON channel
- Remove BINARY_CHANNELS and handleBinaryMessage function
- Discord-gateway sends PCM as JSON with base64, not raw binary
- This fixes PCM data not reaching browser via WebSocket

The issue was backend expected binary format but gateway sends:
{"type":"voice_pcm_data","data":{"userId":"...","pcm":"base64..."}}

Now backend correctly subscribes and broadcasts this to WebSocket clients.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
MythEclipse
2026-06-08 21:18:11 +07:00
co-authored by Claude Opus 4.8
parent e8e95f635c
commit 1c7b7e6398
+1 -44
View File
@@ -26,11 +26,9 @@ const SUBSCRIPTIONS: ChannelMapping[] = [
eventType: "analysis_queue_status", eventType: "analysis_queue_status",
}, },
{ channel: "discord:voice:active_user", eventType: "voice_active_user" }, { channel: "discord:voice:active_user", eventType: "voice_active_user" },
{ channel: "discord:voice:pcm", eventType: "voice_pcm_data" },
]; ];
// Binary channels that need special handling (messageBuffer event)
const BINARY_CHANNELS = ["discord:voice:pcm"];
let subscriber: Redis | null = null; let subscriber: Redis | null = null;
function createSubscriber(): Redis { function createSubscriber(): Redis {
@@ -94,41 +92,6 @@ function handleSubscriptionMessage(channel: string, message: string): void {
broadcastRaw(mapping.eventType, data); broadcastRaw(mapping.eventType, data);
} }
/**
* Handle binary messages from Redis (e.g. voice PCM data).
* Expected format: 4-byte userId hash + PCM buffer
*/
function handleBinaryMessage(channel: Buffer, message: Buffer): void {
const channelStr = channel.toString();
if (channelStr === "discord:voice:pcm") {
if (message.length < 4) {
logger.warn(
{ channel: channelStr, size: message.length },
"Received PCM message too short to contain userId",
);
return;
}
// First 4 bytes = userId hash, rest = PCM data
const userIdHash = message.readUInt32LE(0);
const pcmData = message.subarray(4);
logger.debug(
{ channel: channelStr, userIdHash, pcmSize: pcmData.length },
"Broadcasting voice PCM data",
);
// Broadcast as binary: userId (4 bytes) + PCM data
broadcastRaw("voice_pcm", message);
} else {
logger.warn(
{ channel: channelStr },
"Received binary message for unmapped channel",
);
}
}
export async function startRedisBridge(): Promise<void> { export async function startRedisBridge(): Promise<void> {
if (!config.REDIS_URL && !config.REDIS_HOST) { if (!config.REDIS_URL && !config.REDIS_HOST) {
logger.info("Redis not configured, skipping Redis bridge"); logger.info("Redis not configured, skipping Redis bridge");
@@ -155,7 +118,6 @@ export async function startRedisBridge(): Promise<void> {
}); });
subscriber.on("message", handleSubscriptionMessage); subscriber.on("message", handleSubscriptionMessage);
subscriber.on("messageBuffer", handleBinaryMessage);
await subscriber.ping(); await subscriber.ping();
logger.info("Redis ping OK"); logger.info("Redis ping OK");
@@ -164,11 +126,6 @@ export async function startRedisBridge(): Promise<void> {
await subscriber.subscribe(...channels); await subscriber.subscribe(...channels);
logger.info({ channels }, "Subscribed to Redis channels"); logger.info({ channels }, "Subscribed to Redis channels");
if (BINARY_CHANNELS.length > 0) {
await subscriber.subscribe(...BINARY_CHANNELS);
logger.info({ channels: BINARY_CHANNELS }, "Subscribed to binary Redis channels");
}
logger.info("Redis bridge started"); logger.info("Redis bridge started");
} catch (err) { } catch (err) {
logger.error({ err }, "Failed to start Redis bridge"); logger.error({ err }, "Failed to start Redis bridge");