function toArrayBuffer(src: Buffer | Uint8Array): ArrayBuffer { const u8 = src instanceof Uint8Array ? src : new Uint8Array(src); return u8.buffer.slice(u8.byteOffset, u8.byteOffset + u8.byteLength) as ArrayBuffer; } export async function enableAudioE2EE(pc: RTCPeerConnection, keyBuffer: Buffer | Uint8Array) { const raw = toArrayBuffer(keyBuffer); if (raw.byteLength !== 32) throw new Error("E2EE key must be 32 bytes"); const key = await crypto.subtle.importKey( "raw", raw, { name: "AES-CTR" }, false, ["encrypt", "decrypt"] ); const makeCounter = (ts: number, dir: number) => { const counter = new Uint8Array(16); const dv = new DataView(counter.buffer); dv.setUint32(0, dir, false); // разделяем направление dv.setBigUint64(8, BigInt(ts), false); // nonce из timestamp кадра return counter; }; const attachSender = (sender: RTCRtpSender) => { // @ts-ignore Chromium/Electron API if (!sender.createEncodedStreams) return; // @ts-ignore const { readable, writable } = sender.createEncodedStreams(); const enc = new TransformStream({ async transform(frame: any, controller) { const counter = makeCounter(frame.timestamp ?? 0, 1); const out = await crypto.subtle.encrypt( { name: "AES-CTR", counter, length: 64 }, key, frame.data ); frame.data = new Uint8Array(out); // тот же размер controller.enqueue(frame); } }); readable.pipeThrough(enc).pipeTo(writable).catch(() => {}); }; const attachReceiver = (receiver: RTCRtpReceiver) => { // @ts-ignore Chromium/Electron API if (!receiver.createEncodedStreams) return; // @ts-ignore const { readable, writable } = receiver.createEncodedStreams(); const dec = new TransformStream({ async transform(frame: any, controller) { const counter = makeCounter(frame.timestamp ?? 0, 1); const out = await crypto.subtle.decrypt( { name: "AES-CTR", counter, length: 64 }, key, frame.data ); frame.data = new Uint8Array(out); // тот же размер controller.enqueue(frame); } }); readable.pipeThrough(dec).pipeTo(writable).catch(() => {}); }; pc.getSenders().forEach((s) => s.track?.kind === "audio" && attachSender(s)); pc.getReceivers().forEach((r) => r.track?.kind === "audio" && attachReceiver(r)); pc.addEventListener("track", (e) => { if (e.track.kind === "audio") attachReceiver(e.receiver); }); }