feat(voice): fork @discordjs/voice with video receive support
Fork @discordjs/voice 0.19.2 into vendor/discord-voice-fork and patch the voice gateway handshake so Discord sends camera/screen-share RTP video: - Identify payload now declares video:true + streams:[] (derived from Discord-RE/Discord-video-stream) - this is what makes Discord deliver H264 (PT 96-127) to the voice socket. Previously the client never declared video capability, so Discord omitted all video SSRCs/packets. - op-12 Speaking handler maps streams[].ssrc -> videoSSRC alongside the audio SSRC, so ssrcMap carries camera/screen-share stream IDs. - SSRCMap.get() now also resolves video SSRCs (video RTP arrives on a different SSRC than audio), enabling attribution for videoReceiver.ts. - Both dist/index.js (CJS) and dist/index.mjs (ESM) patched; 3 isolated hunks vs upstream, verified by diff. - package.json points @discordjs/voice -> file:vendor/discord-voice-fork (pnpm lockfile updated, CI --frozen-lockfile compatible). - .gitignore: replace global dist/ with per-service explicit patterns so the vendored fork's dist/ is committed while build outputs stay ignored. - tests: +3 SSRCMap videoSSRC cases (190 total, all pass). The receive-side pipeline (H264Depacketizer -> .h264 -> muxToMp4 -> MP4) already exists in videoReceiver.ts; this unblocks it by making Discord actually deliver video packets.
This commit is contained in:
@@ -4,6 +4,7 @@ import { access, stat } from "node:fs/promises";
|
||||
import { tmpdir } from "node:os";
|
||||
import path from "node:path";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { SSRCMap } from "@discordjs/voice";
|
||||
import {
|
||||
H264Depacketizer,
|
||||
muxToMp4,
|
||||
@@ -122,3 +123,44 @@ describe("muxToMp4", () => {
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// ─── Fork @discordjs/voice patch tests (video receive support) ─────────────
|
||||
// These assert the behavior added in vendor/discord-voice-fork:
|
||||
// 1. SSRCMap.update() accepts videoSSRC and get() resolves video SSRCs
|
||||
// 2. The op-12 Speaking handler maps streams[].ssrc → videoSSRC
|
||||
describe("discord-voice-fork videoSSRC", () => {
|
||||
it("registers a videoSSRC and get() resolves it from the video SSRC", () => {
|
||||
const map = new SSRCMap();
|
||||
// Simulates what the fork's onWsPacket(op 12) does:
|
||||
// ssrcMap.update({ userId, audioSSRC, videoSSRC })
|
||||
map.update({ userId: "user-1", audioSSRC: 1001, videoSSRC: 2001 });
|
||||
|
||||
// Audio path unchanged: get by audioSSRC
|
||||
expect(map.get(1001)?.userId).toBe("user-1");
|
||||
// Video path (fork): get by videoSSRC resolves to the same user
|
||||
expect(map.get(2001)?.userId).toBe("user-1");
|
||||
expect(map.get(2001)?.videoSSRC).toBe(2001);
|
||||
});
|
||||
|
||||
it("emits create event with videoSSRC for videoReceiver attribution", () => {
|
||||
const map = new SSRCMap();
|
||||
const seen: Array<{ userId: string; videoSSRC?: number }> = [];
|
||||
map.on("create", (data) => seen.push(data));
|
||||
|
||||
map.update({ userId: "user-2", audioSSRC: 1002, videoSSRC: 2002 });
|
||||
|
||||
expect(seen).toHaveLength(1);
|
||||
expect(seen[0].videoSSRC).toBe(2002);
|
||||
// videoReceiver.ts hooks this event to build videoSsrcToUser
|
||||
expect(seen[0].userId).toBe("user-2");
|
||||
});
|
||||
|
||||
it("keeps audio-only behavior when no videoSSRC present", () => {
|
||||
const map = new SSRCMap();
|
||||
map.update({ userId: "user-3", audioSSRC: 1003 });
|
||||
|
||||
expect(map.get(1003)?.userId).toBe("user-3");
|
||||
// No videoSSRC → get by a random SSRC returns undefined (no false match)
|
||||
expect(map.get(2003)).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user