/**
|
* Performs the actual SDP exchange.
|
*
|
* 1. Constructs the client's SDP offer
|
* 2. Sends the SDP offer to the server,
|
* 3. Awaits the server's offer.
|
*
|
* SDP describes what kind of media we can send and how the server and client communicate.
|
*
|
* https://developer.mozilla.org/en-US/docs/Glossary/SDP
|
* https://www.ietf.org/archive/id/draft-ietf-wish-whip-01.html#name-protocol-operation
|
*/
|
export default async function negotiateConnectionWithClientOffer(
|
peerConnection,
|
endpoint
|
) {
|
/** https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/createOffer */
|
const offer = await peerConnection.createOffer();
|
/** https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/setLocalDescription */
|
await peerConnection.setLocalDescription(offer);
|
/** Wait for ICE gathering to complete */
|
let ofr = await waitToCompleteICEGathering(peerConnection);
|
if (!ofr) {
|
throw Error("failed to gather ICE candidates for offer");
|
}
|
/**
|
* As long as the connection is open, attempt to...
|
*/
|
while (peerConnection.connectionState !== "closed") {
|
/**
|
* This response contains the server's SDP offer.
|
* This specifies how the client should communicate,
|
* and what kind of media client and server have negotiated to exchange.
|
*/
|
let response = await postSDPOffer(endpoint, ofr.sdp);
|
if (response.status === 201) {
|
let answerSDP = await response.text();
|
await peerConnection.setRemoteDescription(
|
new RTCSessionDescription({ type: "answer", sdp: answerSDP })
|
);
|
return response.headers.get("Location");
|
} else if (response.status === 405) {
|
console.error("Update the URL passed into the WHIP or WHEP client");
|
} else {
|
const errorMessage = await response.text();
|
console.error(errorMessage);
|
}
|
/** Limit reconnection attempts to at-most once every 5 seconds */
|
await new Promise((r) => setTimeout(r, 5000));
|
}
|
}
|
async function postSDPOffer(endpoint, data) {
|
return await fetch(endpoint, {
|
method: "POST",
|
mode: "cors",
|
headers: {
|
"content-type": "application/sdp",
|
},
|
body: data,
|
});
|
}
|
/**
|
* Receives an RTCPeerConnection and waits until
|
* the connection is initialized or a timeout passes.
|
*
|
* https://www.ietf.org/archive/id/draft-ietf-wish-whip-01.html#section-4.1
|
* https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/iceGatheringState
|
* https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/icegatheringstatechange_event
|
*/
|
async function waitToCompleteICEGathering(peerConnection) {
|
return new Promise((resolve) => {
|
/** Wait at most 1 second for ICE gathering. */
|
setTimeout(function () {
|
resolve(peerConnection.localDescription);
|
}, 1000);
|
peerConnection.onicegatheringstatechange = (ev) =>
|
peerConnection.iceGatheringState === "complete" &&
|
resolve(peerConnection.localDescription);
|
});
|
}
|