Files
multiverse/jumpa-chat/lib/xcu-quantum-decoder.ts
T

520 lines
16 KiB
TypeScript

// [TSM.ID].[11031972] -- All Rights Reserved. Proprietary & Confidential.
export class XCUQuantumMatrix {
private transport: WebTransport | null = null;
private stream: WebTransportBidirectionalStream | null = null;
private streamWriter: WritableStreamDefaultWriter | null = null;
private streamReader: ReadableStreamDefaultReader | null = null;
private videoDecoders: Map<number, VideoDecoder> = new Map();
private canvasCtxMap: Map<number, CanvasRenderingContext2D> = new Map();
private videoEncoder: VideoEncoder | null = null;
private isRunning: boolean = false;
private mediaStream: MediaStream | null = null;
public participantId: number = 0;
public participantRole: "PANELIST" | "AUDIENCE" = "PANELIST";
// Callbacks
public onParticipantJoined: ((id: number) => void) | null = null;
public onParticipantLeft: ((id: number) => void) | null = null;
public onQuantumDataReceived:
| ((senderId: number, payload: string) => void)
| null = null;
public onLocalStream: ((stream: MediaStream) => void) | null = null;
public onActiveSpeakerChanged: ((speakerId: number) => void) | null = null;
public onAudioLevel: ((level: number) => void) | null = null;
private vadAudioCtx: AudioContext | null = null;
public registerCanvas(participantId: number, canvasId: string) {
const canvas = document.getElementById(canvasId) as HTMLCanvasElement;
if (canvas) {
const ctx = canvas.getContext("2d");
if (ctx) this.canvasCtxMap.set(participantId, ctx);
console.log(
`[QUANTUM MATRIX] Canvas didaftarkan untuk Partisipan ${participantId}`,
);
}
}
public async ignite() {
this.isRunning = true;
console.log(
"[QUANTUM MATRIX] Menginisialisasi Pipa WebTransport & WebCodecs...",
);
// 2. Setup WebTransport ke Rust Backend
try {
// Menggunakan port Standar Kuantum 4433 (UDP) dengan Sertifikat Valid Let's Encrypt
this.transport = new WebTransport(
typeof window !== 'undefined' ? `${window.location.protocol}//${window.location.hostname}:8443` : "/xcu-engine",
);
await this.transport.ready;
console.log("[QUANTUM MATRIX] Terhubung ke Server Zero-Copy QUIC!");
// Setup awal dihapus, decoder akan dibuat per-partisipan
this.startReceiver();
} catch (e: unknown) {
console.error("[QUANTUM MATRIX] Gagal menembus matriks QUIC:", e);
throw e;
}
}
public toggleMic(enabled: boolean) {
if (this.mediaStream) {
this.mediaStream.getAudioTracks().forEach((track) => {
track.enabled = enabled;
});
console.log(`[QUANTUM UPLINK] Mikrofon ${enabled ? "Aktif" : "Mati"}`);
}
}
public resumeAudioContext() {
if (this.vadAudioCtx && this.vadAudioCtx.state === "suspended") {
this.vadAudioCtx
.resume()
.catch((e) => console.error("Force resume failed", e));
}
}
private createDecoderForParticipant(senderId: number) {
const decoder = new VideoDecoder({
output: (frame: unknown) => {
const ctx = this.canvasCtxMap.get(senderId);
if (ctx) {
ctx.drawImage(
frame as unknown as CanvasImageSource,
0,
0,
ctx.canvas.width,
ctx.canvas.height,
);
}
(frame as { close(): void }).close();
},
error: (e: unknown) =>
console.error(`Decoder Error untuk ${senderId}:`, e),
});
decoder.configure({
codec: "avc1.42E01E", // H.264 Baseline
codedWidth: 1280,
codedHeight: 720,
});
this.videoDecoders.set(senderId, decoder);
// Notifikasi React untuk merender tile
if (this.onParticipantJoined) {
this.onParticipantJoined(senderId);
}
}
public async activateUplink(source: "camera" | "screen" = "camera") {
if (this.mediaStream) {
console.warn(
"[QUANTUM UPLINK] Kamera/Layar sudah aktif! Mengabaikan perintah ganda untuk mencegah kebocoran memori.",
);
return;
}
console.log(
`[QUANTUM UPLINK] Memulai Injeksi ${source === "screen" ? "Layar" : "Kamera"} ke WebCodecs...`,
);
try {
if (source === "screen") {
this.mediaStream = await navigator.mediaDevices.getDisplayMedia({
video: { width: 1280, height: 720 },
audio: false,
});
} else {
this.mediaStream = await navigator.mediaDevices.getUserMedia({
video: { width: 1280, height: 720 },
audio: true,
});
this.startVadLoop(this.mediaStream); // Phase 37: Auto-Start VAD
}
if (this.onLocalStream) {
this.onLocalStream(this.mediaStream);
}
// Jika Klien adalah AUDIENCE, abaikan penyalaan WebCodecs untuk menghemat RAM 100%
if (this.participantRole === "AUDIENCE") {
console.log(
"[QUANTUM MATRIX] Tersambung sebagai AUDIENCE. Menutup jalur Uplink Video.",
);
await this.sendRoleSignal();
return;
}
const videoTrack = this.mediaStream.getVideoTracks()[0];
const processor = new MediaStreamTrackProcessor({
track: videoTrack,
});
const reader = processor.readable.getReader();
// Setup Hardware Encoder
this.videoEncoder = new VideoEncoder({
output: async (chunk: unknown) => {
if (this.streamWriter && this.isRunning) {
// Quantum Protocol v3: 8-Byte Header
// Byte 0: Type (0=Delta, 1=Key, 2=Audio, 3=Control)
// Byte 1: Quality (0=Low, 1=High, 2=VAD Active)
// Byte 2-3: Participant ID (u16)
// Byte 4-7: Length (4 bytes)
const packet = new Uint8Array(
8 + (chunk as { byteLength: number }).byteLength,
);
packet[0] = (chunk as { type: string }).type === "key" ? 1 : 0;
packet[1] = 1; // High Quality
const view = new DataView(packet.buffer);
view.setUint16(2, this.participantId, true);
view.setUint32(
4,
(chunk as { byteLength: number }).byteLength,
true,
); // Little-endian length
(chunk as { copyTo(buf: ArrayBuffer): void }).copyTo(
packet.buffer.slice(8),
);
// Tembakkan via QUIC Stream (Otomatis Fragmentasi MTU)
await this.streamWriter.write(packet);
}
},
error: (e: unknown) => console.error("Encoder Error:", e),
});
this.videoEncoder.configure({
codec: "avc1.42E01E", // H.264
width: 1280,
height: 720,
bitrate: 2_000_000, // 2 Mbps
framerate: 30,
latencyMode: "realtime",
});
// Loop pengiriman Frame ke GPU Encoder
this.encodeLoop(reader);
} catch (e: unknown) {
console.error("[QUANTUM UPLINK] Gagal mengaktifkan kamera:", e);
}
}
public async deactivateUplink() {
console.log("[QUANTUM UPLINK] Mematikan Kamera (Downlink tetap aktif)...");
if (this.mediaStream) {
this.mediaStream.getTracks().forEach((t) => {
t.stop();
});
this.mediaStream = null;
}
if (this.videoEncoder && this.videoEncoder.state !== "closed") {
this.videoEncoder.close();
this.videoEncoder = null;
}
}
private async encodeLoop(reader: ReadableStreamDefaultReader) {
while (this.isRunning) {
try {
const { done, value: frame } = await reader.read();
if (done || !frame) break;
if (this.videoEncoder && this.videoEncoder.state === "configured") {
this.videoEncoder.encode(frame, { keyFrame: false });
}
if (frame) frame.close();
} catch {
// Stream terputus atau kamera dimatikan
break;
}
}
}
private async startReceiver() {
if (!this.transport) return;
try {
console.log("[QUANTUM MATRIX] Menciptakan QUIC Bidirectional Stream...");
// Klien yang berinisiatif membuka Bi-Directional Stream pertama kali
this.stream = await this.transport.createBidirectionalStream();
this.streamWriter = this.stream.writable.getWriter();
this.streamReader = this.stream.readable.getReader();
let buffer = new Uint8Array(0);
while (this.isRunning) {
const { value, done } = await this.streamReader.read();
if (done) {
console.log("QUIC Stream Ditutup oleh Server.");
break;
}
if (value) {
// Gabungkan chunk yang baru datang ke dalam buffer
const newBuffer = new Uint8Array(buffer.length + value.length);
newBuffer.set(buffer);
newBuffer.set(value, buffer.length);
buffer = newBuffer;
// Ekstrak Frame berdasarkan 8-byte header
while (buffer.length >= 8) {
const frameType = buffer[0];
const quality = buffer[1];
const view = new DataView(
buffer.buffer,
buffer.byteOffset,
buffer.byteLength,
);
const senderId = view.getUint16(2, true);
const frameLength = view.getUint32(4, true);
if (buffer.length >= 8 + frameLength) {
const payloadData = buffer.slice(8, 8 + frameLength);
buffer = buffer.slice(8 + frameLength);
// [FASE 40] Eksekusi Quantum Ledger (Telepati Data)
if (frameType === 6) {
if (this.onQuantumDataReceived) {
const textDecoder = new TextDecoder();
const payloadStr = textDecoder.decode(payloadData);
this.onQuantumDataReceived(senderId, payloadStr);
}
continue; // Lanjut ke paket berikutnya, ini bukan Video
}
// [FASE 37] Tangkap Sinyal Active Speaker Broadcast dari Core
if (frameType === 3 && quality === 2) {
if (this.onActiveSpeakerChanged) {
this.onActiveSpeakerChanged(senderId);
}
continue;
}
// Injeksi Frame Video langsung ke Hardware Decoder Klien
if (frameType === 0 || frameType === 1) {
if (!this.videoDecoders.has(senderId)) {
this.createDecoderForParticipant(senderId);
}
const decoder = this.videoDecoders.get(senderId);
if (decoder && decoder.state === "configured") {
const chunk = new EncodedVideoChunk({
type: frameType === 1 ? "key" : "delta",
timestamp: performance.now() * 1000,
data: payloadData,
});
decoder.decode(chunk);
}
}
} else {
// Butuh lebih banyak byte untuk membentuk frame utuh
break;
}
}
}
}
} catch (e: unknown) {
console.error("[QUANTUM MATRIX] Kegagalan Aliran (Stream Failure):", e);
}
}
// Fase 37: Voice Activity Detection (VAD) Loop
private startVadLoop(stream: MediaStream) {
try {
this.vadAudioCtx = new (
window.AudioContext ||
(window as unknown as { webkitAudioContext: typeof AudioContext })
.webkitAudioContext
)();
if (this.vadAudioCtx.state === "suspended") {
this.vadAudioCtx
.resume()
.catch((e) => console.warn("[VAD] Cannot resume AudioContext", e));
}
const source = this.vadAudioCtx.createMediaStreamSource(stream);
const analyser = this.vadAudioCtx.createAnalyser();
analyser.fftSize = 512;
// Trik khusus untuk browser modern agar benar-benar memproses FFT stream
const dummyGain = this.vadAudioCtx.createGain();
dummyGain.gain.value = 0; // Bisukan (Mute) agar tidak terjadi echo
source.connect(analyser);
analyser.connect(dummyGain);
dummyGain.connect(this.vadAudioCtx.destination);
const dataArray = new Uint8Array(analyser.frequencyBinCount);
let speakingFrames = 0;
setInterval(() => {
analyser.getByteFrequencyData(dataArray);
const sum = dataArray.reduce((a, b) => a + b, 0);
const avg = sum / dataArray.length;
if (this.onAudioLevel) {
this.onAudioLevel(avg);
}
if (avg > 5) {
// Ambang batas diturunkan drastis ke 5
speakingFrames++;
if (speakingFrames === 3) {
// Hanya kirim jika uplink siap
if (this.streamWriter && this.isRunning) {
this.sendDirectorSignal(2);
}
}
} else {
speakingFrames = 0;
}
}, 100);
} catch (e: unknown) {
console.warn(
"[VAD] Audio Context tidak didukung atau dicekal browser.",
e,
);
}
}
private async sendDirectorSignal(actionQuality: number) {
if (!this.streamWriter) return;
const packet = new Uint8Array(8);
packet[0] = 3; // Control
packet[1] = actionQuality;
const view = new DataView(packet.buffer);
view.setUint16(2, this.participantId, true);
view.setUint32(4, 0, true);
await this.streamWriter.write(packet);
}
// Fase 34: Zero-CPU Omniscient Recording
public async triggerQuantumRecording() {
if (!this.streamWriter) return;
const packet = new Uint8Array(8);
packet[0] = 3; // Control
packet[1] = 6; // Set Action: Record
const view = new DataView(packet.buffer);
view.setUint16(2, this.participantId, true);
view.setUint32(4, 0, true);
await this.streamWriter.write(packet);
console.log(
"[VAULT MATRIX] Sinyal Perekaman Kuantum Langsung Ke SSD Server Dikirim.",
);
}
// Fase 35: SVC Downgrade (Anti-Lag Manual/Otomatis)
public async activateAntiLagDowngrade() {
if (this.streamWriter) {
console.log("[SVC MATRIX] Mengirim sinyal Downgrade ke Server Rust...");
const controlPacket = new Uint8Array(8);
controlPacket[0] = 3; // Type: Control
controlPacket[1] = 0; // Quality: Low (Drop paket 1080p)
const view = new DataView(controlPacket.buffer);
view.setUint16(2, this.participantId, true);
view.setUint32(4, 0, true); // Payload Length: 0
await this.streamWriter.write(controlPacket);
}
}
// Fase 39: Webinar Asimetris (Audience)
public async joinAsAudience() {
this.participantRole = "AUDIENCE";
await this.sendRoleSignal();
console.log(
"[WEBINAR MATRIX] Beralih ke Mode AUDIENCE (Uplink Dimatikan).",
);
}
private async sendRoleSignal() {
if (!this.streamWriter) return;
const packet = new Uint8Array(8);
packet[0] = 3; // Control
packet[1] = 4; // Set Role
const view = new DataView(packet.buffer);
view.setUint16(2, this.participantId, true);
packet[4] = this.participantRole === "AUDIENCE" ? 1 : 0; // 1=Audience, 0=Panelist
await this.streamWriter.write(packet);
}
// Fase 39: Podcast Mode (Matikan Video di Routing Level)
public async setPodcastMode(isPodcast: boolean) {
if (!this.streamWriter) return;
const packet = new Uint8Array(8);
packet[0] = 3; // Control
packet[1] = 5; // Set Mode
const view = new DataView(packet.buffer);
view.setUint16(2, this.participantId, true);
packet[4] = isPodcast ? 1 : 0; // 1=Podcast, 0=Webinar
await this.streamWriter.write(packet);
console.log(
`[PODCAST MATRIX] Sinyal ${isPodcast ? "PODCAST" : "WEBINAR"} dikirim ke Core.`,
);
}
// Fase 40: The Quantum Ledger (Pengganti WebSocket Node.js)
// Menembakkan Chat, JSON, Emoji, Koordinat langsung via QUIC
public async sendQuantumData(payload: string) {
if (!this.streamWriter) return;
const textEncoder = new TextEncoder();
const payloadBytes = textEncoder.encode(payload);
const packet = new Uint8Array(8 + payloadBytes.byteLength);
packet[0] = 6; // Type: Ledger Data
packet[1] = 0; // Quality: N/A
const view = new DataView(packet.buffer);
view.setUint16(2, this.participantId, true);
view.setUint32(4, payloadBytes.byteLength, true);
packet.set(payloadBytes, 8);
await this.streamWriter.write(packet);
}
public disconnect() {
this.isRunning = false;
if (this.transport) {
this.transport.close();
}
this.videoDecoders.forEach((decoder) => {
if (decoder.state !== "closed") decoder.close();
});
this.videoDecoders.clear();
this.canvasCtxMap.clear();
if (this.videoEncoder && this.videoEncoder.state !== "closed") {
this.videoEncoder.close();
}
if (this.mediaStream) {
this.mediaStream.getTracks().forEach((t) => {
t.stop();
});
}
console.log("[QUANTUM MATRIX] Sistem Terputus.");
}
public shutdown() {
this.isRunning = false;
if (this.transport) {
this.transport.close();
}
this.videoDecoders.forEach((decoder) => {
if (decoder.state !== "closed") decoder.close();
});
this.videoDecoders.clear();
this.canvasCtxMap.clear();
if (this.videoEncoder && this.videoEncoder.state !== "closed") {
this.videoEncoder.close();
}
if (this.mediaStream) {
this.mediaStream.getTracks().forEach((t) => {
t.stop();
});
}
console.log("[QUANTUM MATRIX] Sistem Terputus.");
}
}