This commit is contained in:
legop3
2025-11-15 05:27:47 -05:00
parent eb2e2b15d8
commit c9d5b4e0f3
6 changed files with 149 additions and 59 deletions
+45 -17
View File
@@ -1,17 +1,25 @@
function buildAuthHeader(token) {
if (!token) return {};
const credential = `${token}:${token}`;
const encoded = typeof btoa === 'function' ? btoa(credential) : Buffer.from(credential).toString('base64');
const encoded =
typeof btoa === 'function' ? btoa(credential) : Buffer.from(credential).toString('base64');
return { Authorization: `Basic ${encoded}` };
}
export class WhepPlayer {
constructor({ url, token, video }) {
constructor({ url, token, video, onStatus }) {
this.url = url;
this.token = token;
this.video = video;
this.pc = null;
this.abortController = null;
this.onStatus = onStatus;
}
notify(status, detail) {
if (typeof this.onStatus === 'function') {
this.onStatus(status, detail);
}
}
async start() {
@@ -19,6 +27,7 @@ export class WhepPlayer {
throw new Error('Video target missing');
}
this.stop();
this.notify('connecting');
this.abortController = new AbortController();
const pc = new RTCPeerConnection();
this.pc = pc;
@@ -30,23 +39,41 @@ export class WhepPlayer {
pc.addTransceiver('video', { direction: 'recvonly' });
pc.addTransceiver('audio', { direction: 'recvonly' });
const offer = await pc.createOffer();
await pc.setLocalDescription(offer);
pc.onconnectionstatechange = () => {
const state = pc.connectionState;
this.notify(state);
};
pc.oniceconnectionstatechange = () => {
const state = pc.iceConnectionState;
if (state === 'failed' || state === 'disconnected') {
this.notify(state);
}
};
const response = await fetch(this.url, {
method: 'POST',
headers: {
'Content-Type': 'application/sdp',
...buildAuthHeader(this.token),
},
body: offer.sdp,
signal: this.abortController.signal,
});
if (!response.ok) {
throw new Error(`WHEP request failed: ${response.status}`);
try {
const offer = await pc.createOffer();
await pc.setLocalDescription(offer);
const response = await fetch(this.url, {
method: 'POST',
headers: {
'Content-Type': 'application/sdp',
...buildAuthHeader(this.token),
},
body: offer.sdp,
signal: this.abortController.signal,
});
if (!response.ok) {
throw new Error(`WHEP request failed: ${response.status}`);
}
const answerSdp = await response.text();
await pc.setRemoteDescription({ type: 'answer', sdp: answerSdp });
this.notify('playing');
} catch (err) {
this.notify('error', err.message);
this.stop();
throw err;
}
const answerSdp = await response.text();
await pc.setRemoteDescription({ type: 'answer', sdp: answerSdp });
}
stop() {
@@ -63,5 +90,6 @@ export class WhepPlayer {
if (this.video) {
this.video.srcObject = null;
}
this.notify('stopped');
}
}