perf(golive/spike): binding addTrack + TS port of @dank074 media stack
Phase 1 spike: replace @dank074/discord-video-stream + node-datachannel +
node-av (1.3GB) with minimal libdatachannel N-API binding + native RTP
packetizers (H264 FU-A, RTCP SR/NACK, pacer) + pure-TS GoLive stack.
Binding v0.4: addTrack (m=audio/video SDP), TrackWrap w/ setPacketizer +
sendFrame (raw RTP to transport) + addTimestamp — verified by two-peer
handshake emitting SDP with audio(opus 120)+video(H264 101) and 8-frame
RTP roundtrip.
TS layer (src/goLive/, 21 files): CodecPayloadType, VoiceOpCodes,
GatewayOpCodes, utils, BaseMediaConnection (voice WS + DAVE + heartbeat),
VoiceConnection, StreamConnection, Streamer, WebRtcWrapper (SDP mungling,
DAVE encrypt, packetizer chain), BaseMediaStream (pacing/sync), VideoStream,
AudioStream, Demuxer (ffmpeg-spawn NUT/AnnexB, no node-av 114M binary),
Encoders, prepareStream/playStream.
Integration: screenShareController.ts now imports from ../../goLive/index.js —
prepareStream(prepared, ...) + playStream(prepared, streamer, {...}).
Tests: tests/goLive-port.test.ts (8/8 pass). tsc --noEmit clean. biome clean.
This commit is contained in:
@@ -0,0 +1,94 @@
|
||||
/**
|
||||
* goLive port smoke tests — verify the TS layer (no native binding needed
|
||||
* for these; native is covered by the C++/node test-packetizer.js).
|
||||
*/
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { H264Helpers } from "../src/goLive/AnnexBHelper.js";
|
||||
import { AVCodecID } from "../src/goLive/Demuxer.js";
|
||||
import {
|
||||
BaseMediaStream,
|
||||
CodecPayloadType,
|
||||
Encoders,
|
||||
normalizeVideoCodec,
|
||||
} from "../src/goLive/index.js";
|
||||
import { rewriteSPSVUI } from "../src/goLive/SPSVUIRewriter.js";
|
||||
|
||||
describe("goLive port: codec + encoders", () => {
|
||||
it("normalizeVideoCodec maps aliases to canonical names", () => {
|
||||
expect(normalizeVideoCodec("H.264")).toBe("H264");
|
||||
expect(normalizeVideoCodec("AVC")).toBe("H264");
|
||||
expect(normalizeVideoCodec("h265")).toBe("H265");
|
||||
expect(normalizeVideoCodec("vp8")).toBe("VP8");
|
||||
expect(normalizeVideoCodec("av1")).toBe("AV1");
|
||||
});
|
||||
|
||||
it("software encoder exposes x264 libx264 superfast film", () => {
|
||||
const enc = Encoders.software()();
|
||||
expect(enc.H264.name).toBe("libx264");
|
||||
expect(enc.H264.options).toContain("-preset superfast");
|
||||
expect(enc.H264.options).toContain("-tune film");
|
||||
});
|
||||
|
||||
it("CodecPayloadType has opus + H264 entries", () => {
|
||||
expect(CodecPayloadType.opus).toBeDefined();
|
||||
expect(CodecPayloadType.H264).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe("goLive port: annexb + sps rewriter", () => {
|
||||
it("H264Helpers detects NAL unit types", () => {
|
||||
const nal = Buffer.from([0x67, 0x42, 0x00, 0x1e]); // SPS
|
||||
expect(H264Helpers.getUnitType(nal)).toBe(7); // SPS type
|
||||
expect(H264Helpers.getUnitType(Buffer.from([0x65, 0x88]))).toBe(5); // IDR
|
||||
});
|
||||
|
||||
it("rewriteSPSVUI returns a buffer for valid SPS", () => {
|
||||
const sps = Buffer.from([
|
||||
0x67, 0x42, 0x00, 0x1e, 0x96, 0x54, 0x05, 0x01, 0xec, 0x80,
|
||||
]);
|
||||
expect(() => rewriteSPSVUI(sps)).not.toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
describe("goLive port: streams", () => {
|
||||
it("BaseMediaStream accepts plain frame objects", () => {
|
||||
// BaseMediaStream is abstract — use a concrete subclass that no-ops the
|
||||
// packetizer hook.
|
||||
class TestStream extends BaseMediaStream {
|
||||
async _sendFrame(_frame: Buffer, _frametime: number): Promise<void> {
|
||||
/* no-op */
|
||||
}
|
||||
}
|
||||
const stream = new TestStream("video");
|
||||
const frame = {
|
||||
data: Buffer.from([1, 2, 3]),
|
||||
pts: 0,
|
||||
duration: 40,
|
||||
timeBase: { num: 1, den: 48000 },
|
||||
flags: 0,
|
||||
streamIndex: 0,
|
||||
free: () => {},
|
||||
};
|
||||
expect(() => stream.write(frame)).not.toThrow();
|
||||
stream.end();
|
||||
});
|
||||
});
|
||||
|
||||
describe("goLive port: demuxer codec ids", () => {
|
||||
it("maps H264/HEVC/opus AVCodecID values", () => {
|
||||
expect(AVCodecID.AV_CODEC_ID_H264).toBe(27);
|
||||
expect(AVCodecID.AV_CODEC_ID_HEVC).toBe(173);
|
||||
expect(AVCodecID.AV_CODEC_ID_OPUS).toBe(86019);
|
||||
});
|
||||
});
|
||||
|
||||
describe("goLive port: prepareStream option merge", () => {
|
||||
it("merges default options into the descriptor (no ffmpeg spawn)", () => {
|
||||
// Import the merge logic directly via the module; prepareStream spawns
|
||||
// ffmpeg so we verify the descriptors it would build by checking the
|
||||
// encoder + option functions that prepareStream uses.
|
||||
const enc = Encoders.software()();
|
||||
expect(enc.H264.options).toContain("-forced-idr 1");
|
||||
expect(normalizeVideoCodec("H264")).toBe("H264");
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user