host our own shit

This commit is contained in:
KingKDot
2026-07-17 16:01:48 -05:00
parent 9a274811e8
commit f404f22b29
16 changed files with 462 additions and 17 deletions
+22 -3
View File
@@ -23,7 +23,7 @@ export class P2PClient {
* @param {(msg: object) => void} opts.send Send a JSON-encodable msg over the viewer's WS.
* @param {(state: string) => void} [opts.onState]
* @param {(stats: object) => void} [opts.onStats]
* @param {string[]} [opts.iceServers] STUN URLs. Default: Google public STUN.
* @param {RTCIceServer[]} [opts.iceServers] Optional explicit ICE configuration.
*/
constructor(opts) {
this.videoEl = opts.videoEl || null;
@@ -31,7 +31,7 @@ export class P2PClient {
this.send = opts.send;
this.onState = opts.onState || (() => {});
this.onStats = opts.onStats || (() => {});
this.iceServers = opts.iceServers || ["stun:stun.l.google.com:19302"];
this.iceServers = Array.isArray(opts.iceServers) ? opts.iceServers : null;
this.pc = null;
this.pendingRemoteCandidates = [];
this.statsSampler = null;
@@ -40,8 +40,9 @@ export class P2PClient {
async start() {
if (this.pc) await this.stop();
const iceServers = await this.resolveIceServers();
const pc = new RTCPeerConnection({
iceServers: this.iceServers.map((u) => ({ urls: u })),
iceServers,
});
this.pc = pc;
this.statsSampler = new WebRTCStatsSampler(pc, this.onStats);
@@ -81,6 +82,24 @@ export class P2PClient {
this.send({ type: "webrtc_p2p_offer", sdp: pc.localDescription.sdp });
}
async resolveIceServers() {
if (this.iceServers) return this.iceServers;
const clientId = new URLSearchParams(window.location.search).get("clientId");
if (!clientId) return [];
try {
const response = await fetch(`/api/webrtc/ice-config?clientId=${encodeURIComponent(clientId)}`, {
credentials: "same-origin",
cache: "no-store",
});
if (!response.ok) throw new Error(`HTTP ${response.status}`);
const body = await response.json();
return Array.isArray(body?.iceServers) ? body.iceServers : [];
} catch (error) {
console.warn("p2p: failed to load self-hosted ICE configuration; using host candidates only", error);
return [];
}
}
/** Handle an SDP answer relayed from the agent. */
async onAnswer(sdp) {
if (!this.pc || !sdp) return;
+19 -1
View File
@@ -34,7 +34,8 @@ export class WhepClient {
async start() {
if (this.pc) await this.stop();
const pc = new RTCPeerConnection({});
const iceServers = await this.resolveIceServers();
const pc = new RTCPeerConnection({ iceServers });
this.pc = pc;
this.statsSampler = new WebRTCStatsSampler(pc, this.onStats);
if (this.videoEl) pc.addTransceiver("video", { direction: "recvonly" });
@@ -80,6 +81,23 @@ export class WhepClient {
await pc.setRemoteDescription({ type: "answer", sdp: answerSDP });
}
async resolveIceServers() {
const clientId = new URLSearchParams(window.location.search).get("clientId");
if (!clientId) return [];
try {
const response = await fetch(`/api/webrtc/ice-config?clientId=${encodeURIComponent(clientId)}`, {
credentials: "same-origin",
cache: "no-store",
});
if (!response.ok) throw new Error(`HTTP ${response.status}`);
const body = await response.json();
return Array.isArray(body?.iceServers) ? body.iceServers : [];
} catch (error) {
console.warn("whep: failed to load self-hosted ICE configuration; using host candidates only", error);
return [];
}
}
async stop() {
const pc = this.pc;
const url = this.resourceURL;