æ¬æä»ç»äº WebRTC å®ç¾ååï¼è¯´æäºå®çå·¥ä½åç以å为ä»ä¹å®æ¯å¯¹çæ¹ä¹é´åå WebRTC è¿æ¥çæ¨èæ¹å¼ï¼å¹¶æä¾äºç¤ºä¾ä»£ç æ¥æ¼ç¤ºè¯¥ææ¯ã
ç±äº WebRTC 并没æå¼ºå¶è¦æ±å¨å忰坹çè¿æ¥æé´ä½¿ç¨ç¹å®çä¼ è¾æºå¶æ¥åé信令ï¼å æ¤å®å ·æå¾é«ççµæ´»æ§ãä¸è¿ï¼å°½ç®¡ä¿¡ä»¤æ¶æ¯çä¼ è¾åéä¿¡å ·æè¿ç§çµæ´»æ§ï¼å¨å¯è½çæ åµä¸ï¼ä½ åºéµå¾ªä¸ç§æ¨èç设计模å¼ï¼å³å®ç¾ååã
å¨é¦æ¬¡é¨ç½²æ¯æ WebRTC çæµè§å¨åï¼äººä»¬æè¯å°ååè¿ç¨çæäºé¨åæ¯å ¸åç¨ä¾æéçæ´ä¸ºå¤æãè¿æ¯ç±äºåºç¨ç¨åºæ¥å£çä¸äºå°é®é¢åä¸äºéè¦é¢é²çæ½å¨ç«äºæ¡ä»¶é æçãè¿äºé®é¢åæ¥é½å¾å°äºè§£å³ï¼è¿è让æä»¬å¤§å¤§ç®åäº WebRTC ååè¿ç¨ãå®ç¾å忍¡å¼æ¯ WebRTC æ©ææ¹è¿ååæ¹å¼çä¸ä¸ªä¾åã
å®ç¾ååçæ¦å¿µå®ç¾ååå¯ä»¥ä½¿ååè¿ç¨ä¸åºç¨ç¨åºå ¶ä»é»è¾æ ç¼ä¸å®å ¨çå离ãååæ¬è´¨ä¸æ¯ä¸ç§ä¸å¯¹ç§°æä½ï¼ä¸æ¹éè¦å å½âè°ç¨è âï¼èå¦ä¸æ¹åæ¯â被è°ç¨è âãå®ç¾çå忍¡å¼éè¿å°è¿ç§å·®å¼å离å°ç¬ç«çååé»è¾ä¸æ¥æ¶é¤è¿ç§å·®å¼ï¼å æ¤ä½ çåºç¨ç¨åºæ éå ³å¿å®æ¯è¿æ¥çåªä¸ç«¯ãå°±åºç¨ç¨åºèè¨ï¼æ¯ååºå¼å«è¿æ¯æ¥æ¶å¼å«å¹¶æ åºå«ã
å®ç¾ååçæå¤§ä¼ç¹æ¯ï¼è°ç¨æ¹å被è°ç¨æ¹ä½¿ç¨ç¸åç代ç ï¼å æ¤æ éç¼åéå¤æå ¶ä»é¢å¤çåå代ç ã
å®ç¾ååçå·¥ä½åçæ¯ï¼å¨ååè¿ç¨ä¸ä¸ºä¸¤ä¸ªå¯¹çç½ç»ä¸çæ¯ä¸ä¸ªåé ä¸ä¸ªè§è²ï¼è¯¥è§è²ä¸ WebRTC è¿æ¥ç¶æå®å ¨å离ï¼
è¿æ ·ï¼å¦æå·²åéçé约ä¹é´åç碰æï¼åæ¹é½è½æ¸ æ¥å°ç¥éåºè¯¥å¦ä½å¤çã对é误æ¡ä»¶çååºä¹å徿´å¯é¢æµã
å¦ä½ç¡®å®åªä¸ªå¯¹çç¹æ¯ç¤¼è²çï¼åªä¸ªæ¯ä¸ç¤¼è²çï¼ä¸è¬ç±ä½ èªå·±å³å®ãå¯ä»¥ç®åå°å°ç¤¼è²è§è²åé ç»ç¬¬ä¸ä¸ªè¿æ¥å°ä¿¡ä»¤æå¡å¨ç对çç¹ï¼ä¹å¯ä»¥åä¸äºæ´å¤æçäºæ ï¼æ¯å¦è®©å¯¹çç¹äº¤æ¢éæºæ°åï¼ç¶åå°ç¤¼è²è§è²åé ç»è·èè ãæ è®ºå¦ä½ç¡®å®ï¼ä¸æ¦å°è¿äºè§è²åé ç»ä¸¤ä¸ªå¯¹çç¹ï¼å®ä»¬å°±å¯ä»¥å ±å管ç信令ï¼è¿æ ·å°±ä¸ä¼åºç°æ»éï¼ä¹ä¸éè¦å¾å¤é¢å¤çä»£ç æ¥ç®¡çã
éè¦ç¢è®°çéè¦ä¸ç¹æ¯ï¼å¨å®ç¾ååè¿ç¨ä¸ï¼å¼å«æ¹å被å¼å«æ¹çè§è²å¯ä»¥äºæ¢ã妿æç¤¼è²ç对çç¹æ¯ä¸»å«æ¹ï¼å®åéäºä¸ä¸ªé约ï¼ä½ä¸æ 礼ç对çç¹åçäºç¢°æï¼é£ä¹æç¤¼è²ç对çç¹å°±ä¼æ¾å¼å®çé约ï¼è½¬èåå¤å®ä»æ 礼ç对çç¹æ¶å°çé约ãè¿æ ·ï¼æç¤¼è²ç对çç¹å°±ä»å¼å«æ¹åæäºè¢«å¼å«æ¹ï¼
å®ç°å®ç¾åå让æä»¬æ¥çä¸ä¸ªå®ç°å®ç¾å忍¡å¼ç示ä¾ã代ç åå®äºä¸ä¸ªç¨äºä¸ä¿¡ä»¤æå¡å¨éä¿¡ç SignalingChannel
ç±»ãå½ç¶ï¼ä½ èªå·±ç代ç å¯ä»¥ä½¿ç¨ä»»ä½ä½ 忬¢çä¿¡ä»¤ææ¯ã
请注æï¼è¯¥ä»£ç 对åä¸è¿æ¥ç两个对çç¹é½æ¯ç¸åçã
å建信令å对çè¿æ¥é¦å
ï¼éè¦æå¼ä¿¡ä»¤ééå¹¶å建 RTCPeerConnection
ãè¿éååºç STUN æå¡å¨æ¾ç¶ä¸æ¯çæ£çæå¡å¨ï¼ä½ éè¦å° stun.myserver.tld
æ¿æ¢ä¸ºçæ£ç STUN æå¡å¨å°åã
const config = {
iceServers: [{ urls: "stun:stun.mystunserver.tld" }],
};
const signaler = new SignalingChannel();
const pc = new RTCPeerConnection(config);
è¿æ®µä»£ç è¿ä½¿ç¨âselfviewâåâremoteviewâç±»è·å <video>
å
ç´ ï¼è¿äºå
ç´ å°åå«å
嫿¬å°ç¨æ·çèªæè§å¾åæ¥èªè¿ç¨å¯¹çç¹çè¾å
¥æµè§å¾ã
const constraints = { audio: true, video: true };
const selfVideo = document.querySelector("video.selfview");
const remoteVideo = document.querySelector("video.remoteview");
async function start() {
try {
const stream = await navigator.mediaDevices.getUserMedia(constraints);
for (const track of stream.getTracks()) {
pc.addTrack(track, stream);
}
selfVideo.srcObject = stream;
} catch (err) {
console.error(err);
}
}
æ³è¦äºç¸å¯¹è¯ç两个端ç¹ä¸çä»»ä½ä¸ä¸ªé½å¯ä»¥è°ç¨ä¸é¢æ¾ç¤ºç start()
彿°ãè°å
è°ç¨å¹¶ä¸éè¦ï¼åªéè¿è¡ååå³å¯ã
è¿ä¸æ§ç WebRTC è¿æ¥å»ºç«ä»£ç æ²¡ææ¾èåºå«ãéè¿è°ç¨ getUserMedia()
è·åç¨æ·çæå头å麦å
é£ãç¶åï¼å°å¾å°çåªä½è½¨ééè¿ä¼ å
¥ addTrack()
æ·»å å° RTCPeerConnection
ä¸ãæåï¼å°ç± selfVideo
常éæç¤ºçèªè§å¾ <video>
å
ç´ çåªä½æºè®¾ç½®ä¸ºæå头å麦å
飿µï¼è¿æ ·æ¬å°ç¨æ·å°±è½çå°å¯¹æ¹çå°çå
容ã
æ¥ä¸æ¥ï¼æä»¬éè¦ä¸º track
äºä»¶è®¾ç½®ä¸ä¸ªå¤çå¨ï¼ä»¥å¤ç该对çè¿æ¥å忥æ¶çå
¥ç«è§é¢åé³é¢è½¨è¿¹ã为æ¤ï¼æä»¬å®ç°äº RTCPeerConnection
ç ontrack
äºä»¶å¤çå¨ã
pc.ontrack = ({ track, streams }) => {
track.onunmute = () => {
if (remoteVideo.srcObject) {
return;
}
remoteVideo.srcObject = streams[0];
};
};
å½åç track
äºä»¶æ¶ï¼å°æ§è¡è¯¥å¤çå¨ã使ç¨è§£æå¯ä»¥æå RTCTrackEvent
ç track
å streams
屿§ãåè
æ¯æ¥æ¶å°çè§é¢è½¨æé³é¢è½¨ãåè
æ¯ä¸ä¸ª MediaStream
对象æ°ç»ï¼æ¯ä¸ªå¯¹è±¡ä»£è¡¨ä¸ä¸ªå
å«è¯¥é³è½¨çæµï¼å¨æå°æ°æ
åµä¸ï¼ä¸ä¸ªé³è½¨å¯è½åæ¶å±äºå¤ä¸ªæµï¼ã卿们çä¾åä¸ï¼è¿å°å§ç»å
å«ä¸ä¸ªæµï¼ä½äº 0 å·ç´¢å¼ï¼å 为æä»¬ä¹åå¨ addTrack()
ä¸ä¼ éäºä¸ä¸ªæµã
æä»¬ä¸ºè½¨éæ·»å ä¸ä¸ªåæ¶éé³äºä»¶å¤çå¨ï¼å 为轨é䏿¦å¼å§æ¥æ¶æ°æ®å ï¼å°±ä¼åæ¶éé³ãæä»¬å°æ¥æ¶ä»£ç çå ¶ä½é¨åæ¾å¨è¿éã
妿æä»¬å·²ç»ä»è¿ç¨å¯¹çæ¹æ¥æ¶å°è§é¢ï¼æä»¬å¯ä»¥éè¿è¿ç¨è§å¾ç <video>
å
ç´ ç srcObject
屿§å·²ç»æå¼æ¥å¤æï¼ï¼æä»¬ä¸å任使ä½ãå¦åï¼æä»¬å° srcObject
设置为 streams
æ°ç»ä¸ç´¢å¼ 0 å¤çæµã
ç°å¨æä»¬è¿å ¥çæ£å®ç¾ååçé»è¾ï¼å®çåè½å®å ¨ç¬ç«äºåºç¨ç¨åºçå ¶ä»é¨åã
å¤çéè¦è°å¤çäºä»¶é¦å
ï¼æä»¬å®ç°äº RTCPeerConnection
äºä»¶å¤çå¨ onnegotiationneeded
æ¥è·åæ¬å°æè¿°ï¼å¹¶ä½¿ç¨ä¿¡ä»¤ééå°å
¶åéç»è¿ç¨å¯¹çç¹ã
let makingOffer = false;
pc.onnegotiationneeded = async () => {
try {
makingOffer = true;
await pc.setLocalDescription();
signaler.send({ description: pc.localDescription });
} catch (err) {
console.error(err);
} finally {
makingOffer = false;
}
};
请注æï¼ä¸å¸¦åæ°ç setLocalDescription()
伿 ¹æ®å½åç signalingState
èªå¨å建å设置éå½çæè¿°ãæè®¾ç½®çæè¿°æ¯å¯¹è¿ç¨å¯¹çæ¹ææ°é约çååºï¼ææ¯ä¸ä¸ªæ°å建çé约ï¼å¦ææ²¡ææ£å¨è¿è¡çååï¼ãå¨è¿éï¼å®å°å§ç»æ¯ä¸ä¸ª offer
ï¼å 为éè¦ååçäºä»¶åªå¨ stable
ç¶æä¸è§¦åã
æä»¬å°å¸å°åé makingOffer
设为 true
ï¼è¡¨ç¤ºæä»¬æ£å¨åå¤é约ã为äºé¿å
ç«ææ¡ä»¶ï¼æä»¬ç¨åå°ä½¿ç¨è¯¥å¼è䏿¯ä¿¡ä»¤ç¶ææ¥ç¡®å®æ¯å¦æ£å¨å¤çé约ï¼å 为 signalingState
ç弿¯å¼æ¥ååçï¼è¿å¼å
¥äºäº§çå¹²æ°ï¼glareï¼çæºä¼ã
䏿¦é约å建ã设置ååéå®æï¼æåçé误ï¼ï¼makingOffer
å°±ä¼è¢«è®¾å false
ã
æ¥ä¸æ¥ï¼æä»¬éè¦å¤ç RTCPeerConnection
äºä»¶ icecandidate
ï¼è¿æ¯æ¬å° ICE å±å¦ä½å°åéè
ä¼ éç»æä»¬ï¼ä»¥ä¾¿éè¿ä¿¡ä»¤ééä¼ éç»è¿ç¨å¯¹çæ¹çæ¹å¼ã
pc.onicecandidate = ({ candidate }) => signaler.send({ candidate });
å®ä¼è·å ICE äºä»¶ç candidate
æåï¼å¹¶å°å
¶ä¼ éç»ä¿¡ä»¤ééç send()
æ¹æ³ï¼ä»¥ä¾¿éè¿ä¿¡ä»¤æå¡å¨åéç»è¿ç¨å¯¹ç设å¤ã
æåä¸åæ¼å¾æ¯å¤çæ¥èªä¿¡ä»¤æå¡å¨çä¼ å
¥ä¿¡æ¯ç代ç ãå¨è¿éï¼å®æ¯ä½ä¸ºä¿¡ä»¤éé对象ä¸ç onmessage
äºä»¶å¤ç卿¥å®ç°çãæ¯æ¬¡ä¿¡ä»¤æå¡å¨åéæ¶æ¯æ¶ï¼é½ä¼è°ç¨è¯¥æ¹æ³ã
let ignoreOffer = false;
signaler.onmessage = async ({ data: { description, candidate } }) => {
try {
if (description) {
const offerCollision =
description.type === "offer" &&
(makingOffer || pc.signalingState !== "stable");
ignoreOffer = !polite && offerCollision;
if (ignoreOffer) {
return;
}
await pc.setRemoteDescription(description);
if (description.type === "offer") {
await pc.setLocalDescription();
signaler.send({ description: pc.localDescription });
}
} else if (candidate) {
try {
await pc.addIceCandidate(candidate);
} catch (err) {
if (!ignoreOffer) {
throw err;
}
}
}
} catch (err) {
console.error(err);
}
};
å¨éè¿ onmessage
äºä»¶å¤ç卿¥æ¶å°æ¥èª SignalingChannel
çä¼ å
¥æ¶æ¯æ¶ï¼ä¼å¯¹æ¥æ¶å°ç JSON 对象è¿è¡è§£æï¼ä»¥è·å¾å
¶ä¸ç description
æ candidate
ãå¦æä¼ å
¥çæ¶æ¯æ description
ï¼é£ä¹å®è¦ä¹æ¯å¯¹æ¹ååºçé约ï¼è¦ä¹æ¯å¯¹æ¹ååºççå¤ã
å¦ä¸æ¹é¢ï¼å¦ææ¶æ¯ä¸å
å«ä¸ä¸ª candidate
åæ®µï¼é£ä¹å®å°±æ¯ä½ä¸ºæ¸è¿å¼ ICE çä¸é¨åï¼ä»è¿ç¨å¯¹çæ¹æ¥æ¶å°ç ICE åéä¿¡æ¯ãè¿ä¸ªåé项å°éè¿è°ç¨ addIceCandidate()
æ¹æ³ä¼ éç»æ¬å°ç ICE å¤çå±ã
妿æä»¬æ¶å°äº description
ï¼æä»¬å°±ä¼åå¤å¯¹æ¶å°çé约æçå¤ååºååºãé¦å
ï¼æä»¬è¦æ£æ¥æ¯å¦å¤äºå¯ä»¥æ¥åé约çç¶æãå¦æè¿æ¥çä¿¡ä»¤ç¶æä¸æ¯ stable
ï¼æè
è¿æ¥çæä»¬è¿ä¸ç«¯å·²ç»å¼å§ååºèªå·±çé约ï¼é£ä¹æä»¬å°±éè¦æ³¨æé约å²çªã
å¦æææ¹æ¯æ 礼çå¯¹çæ¹ï¼å¹¶ä¸æ£å¨æ¥æ¶ä¸ä¸ªç¢°æéçº¦ï¼æä»¬å°ä¸è®¾ç½®æè¿°èè¿åï¼å¹¶å° ignoreOffer
设置为 true
ï¼ä»¥ç¡®ä¿æä»¬ä¹å¿½ç¥å¯¹æ¹å¯è½å¨å±äºè¯¥é约ç信令信éä¸åéç»æä»¬çææåéä¿¡æ¯ãè¿æ ·åå¯ä»¥é¿å
é误åªå£°ï¼å 为æä»¬ä»æªå°æ¤é约éç¥ææ¹ã
妿æä»¬æ¯æç¤¼è²ç对çç¹ï¼èæä»¬æ¶å°çæ¯ä¸ä¸ªç¢°æéçº¦ï¼æä»¬ä¸éè¦åä»»ä½ç¹å«çäºæ ï¼å 为æä»¬ç°æçé约ä¼å¨ä¸ä¸æ¥èªå¨åæ»ã
å¨ç¡®å®è¦æ¥åé约åï¼æä»¬å°éè¿è°ç¨ setRemoteDescription()
ä¸ºä¼ å
¥çé约设置è¿ç¨æè¿°ãè¿ä¼è®© WebRTC ç¥é对æ¹ç建议é
ç½®æ¯ä»ä¹ã妿æä»¬æ¯ç¤¼è²ç对çç¹ï¼å°±ä¼æ¾å¼æä»¬çéçº¦ï¼æ¥åæ°çé约ã
妿æ°è®¾ç½®çè¿ç¨æè¿°æ¯ä¸ä¸ªéçº¦ï¼æä»¬å°±ä¼è¦æ± WebRTC éè¿è°ç¨ RTCPeerConnection
æ¹æ³ setLocalDescription()
æ¥éæ©åéçæ¬å°é
ç½®ï¼èæ éåæ°ãè¿æ ·ï¼setLocalDescription()
å°±ä¼èªå¨çæéå½çåºçï¼ä»¥ååºæ¶å°çé约ãç¶åï¼æä»¬éè¿ä¿¡ä»¤ä¿¡éå°åºçåéå第ä¸ä¸ªå¯¹çç¹ã
å¦æä½ å¥½å¥ä»ä¹è®©å®ç¾çåå妿¤å®ç¾ï¼é£ä¹è¿ä¸é¨åå°±æ¯ä¸ºä½ åå¤çãå¨è¿éï¼æä»¬å°ä¼æ¥çæ¯ä¸ä¸ªå¯¹ WebRTC API æåçæ´æ¹ä»¥åæä½³å®è·µå»ºè®®ï¼ä»¥ä½¿å®ç¾çååæä¸ºå¯è½ã
æ å²çªç setLocalDescription()è¿å»ï¼negotiationneeded
äºä»¶ç»å¸¸ä»¥ä¸ç§å®¹æå¯¼è´å²çªçæ¹å¼å¤çââå³å®¹æäº§çå¹²æ°ï¼ä¹å°±æ¯è¯´ï¼å®¹æåçå²çªï¼å¯¼è´åæ¹å¯¹çç¹åæ¶å°è¯è¿è¡é约ï¼ä»è导è´å
¶ä¸ä¸æ¹æå¦ä¸æ¹åºç°éè¯¯å¹¶ä¸æ¢è¿æ¥å°è¯ã
èèè¿ä¸ª onnegotiationneeded
äºä»¶å¤çå¨ï¼
pc.onnegotiationneeded = async () => {
try {
await pc.setLocalDescription(await pc.createOffer());
signaler.send({ description: pc.localDescription });
} catch (err) {
console.error(err);
}
};
ç±äº createOffer()
æ¹æ³æ¯å¼æ¥çï¼å¹¶ä¸éè¦ä¸äºæ¶é´æ¥å®æï¼å æ¤å¨æ¤æé´è¿ç¨å¯¹çç¹å¯è½ä¼å°è¯åéèªå·±çé约ï¼å¯¼è´æä»¬ç¦»å¼ stable
ç¶æå¹¶è¿å
¥ have-remote-offer
ç¶æï¼è¿æå³çæä»¬ç°å¨æ£å¨çå¾
对é约çååºã使¯ä¸æ¦å®æ¥æ¶å°æä»¬åååéçé约ï¼è¿ç¨å¯¹çç¹ä¹æ¯å¦æ¤ãè¿å°ä½¿å¾åæ¹é½å¤äºè¿æ¥å°è¯æ æ³å®æçç¶æã
æ£å¦å¨å®ç°å®ç¾ååé¨åæç¤ºï¼æä»¬å¯ä»¥éè¿å¼å
¥ä¸ä¸ªåéï¼è¿é称为 makingOffer
ï¼ï¼ç¨äºæç¤ºæä»¬æ£å¨åéä¸ä¸ªé约çè¿ç¨ï¼å¹¶å©ç¨æ´æ°åç setLocalDescription()
æ¹æ³æ¥æ¶é¤è¿ä¸ªé®é¢ã
let makingOffer = false;
pc.onnegotiationneeded = async () => {
try {
makingOffer = true;
await pc.setLocalDescription();
signaler.send({ description: pc.localDescription });
} catch (err) {
console.error(err);
} finally {
makingOffer = false;
}
};
æä»¬å¨è°ç¨ setLocalDescription()
ä¹åç«å³è®¾ç½® makingOffer
ï¼ä»¥é²æ¢å¹²æ°å鿤é约ï¼å¹¶ä¸å¨å°é约åéå°ä¿¡ä»¤æå¡å¨åï¼æè
åçé误ï¼å¯¼è´æ æ³ååºéçº¦ï¼æå°å
¶æ¸
é¤ä¸º false
ãéè¿è¿ç§æ¹å¼ï¼æä»¬é¿å
äºé约åçå²çªçé£é©ã
å®ç°å®ç¾ååçå
³é®ç»ä»¶æ¯æç¤¼è²ç对çç¹çæ¦å¿µï¼å¦æå®å¨çå¾
对ä¸ä¸ªé约çç夿¶æ¶å°ä¸ä¸ªé约ï¼å®æ»æ¯ä¼èªå¨åæ»ã以åï¼è§¦ååæ»æ¶åæå¨æ£æ¥åæ»æ¡ä»¶å¹¶æå¨è§¦ååæ»ï¼æ¹æ³æ¯å°æ¬å°æè¿°è®¾ç½®ä¸ºç±»å为 rollback
çæè¿°ï¼å¦ä¸æç¤ºï¼
await pc.setLocalDescription({ type: "rollback" });
è¿æ ·åä¼å°æ¬å°å¯¹çç¹ä»å
åçä»»ä½ç¶æè¿åå° stable
signalingState
ãç±äºå¯¹çç¹åªè½å¨ stable
ç¶æä¸æ¥åé约ï¼å æ¤å¯¹çç¹å·²ç»æ¤åäºèªå·±çé约ï¼å¹¶å夿¥æ¶æ¥èªè¿ç¨ï¼ä¸ç¤¼è²çï¼å¯¹çç¹çé约ãç¶èï¼æ£å¦æä»¬å°å¨æ¥ä¸æ¥çå°çï¼è¿ç§æ¹æ³åå¨é®é¢ã
å¨å®ç¾ååè¿ç¨ä¸ï¼ä½¿ç¨å åç API æ¥å®ç°ä¼ å ¥çååæ¶æ¯å¯è½å¦ä¸æç¤ºï¼
signaler.onmessage = async ({ data: { description, candidate } }) => {
try {
if (description) {
if (description.type === "offer" && pc.signalingState !== "stable") {
if (!polite) {
return;
}
await Promise.all([
pc.setLocalDescription({ type: "rollback" }),
pc.setRemoteDescription(description),
]);
} else {
await pc.setRemoteDescription(description);
}
if (description.type === "offer") {
await pc.setLocalDescription(await pc.createAnswer());
signaler.send({ description: pc.localDescription });
}
} else if (candidate) {
try {
await pc.addIceCandidate(candidate);
} catch (err) {
if (!ignoreOffer) {
throw err;
}
}
}
} catch (err) {
console.error(err);
}
};
ç±äºåæ»éè¿æ¨è¿æ´æ¹ç´å°ä¸ä¸æ¬¡ååï¼å¨å½ååå宿åç«å³å¼å§ï¼æ¥å®ç°ï¼æä»¥æç¤¼è²ç对çç¹éè¦ç¥é使¶éè¦ä¸¢å¼æ¶å°çé约ï¼å¦æå®å½åæ£å¨çå¾ å¯¹å·²åéçé约çåå¤ã
ä»£ç æ£æ¥æ¶æ¯æ¯å¦æ¯ä¸ä¸ªé约ï¼å¹¶ä¸å¦ææ¯ï¼åæ£æ¥æ¬å°ä¿¡ä»¤ç¶ææ¯å¦ä¸æ¯ stable
ã妿䏿¯ç¨³å®çï¼å¹¶ä¸æ¬å°å¯¹çç¹æ¯æç¤¼è²çï¼é£ä¹æä»¬éè¦è§¦ååæ»ï¼ä»¥ä¾¿æä»¬å¯ä»¥ç¨æ°æ¶å°çéçº¦æ¿æ¢æ£å¨ä¼ åºçé约ãè¿ä¸¤æ¥é½å¿
é¡»å¨æä»¬ç»§ç»å¤çæ¶å°çé约ä¹å宿ã
ç±äºæ²¡æä¸ä¸ªåç¬çâåæ»å¹¶ä½¿ç¨æ¤é约âï¼å¨æç¤¼è²çå¯¹çæ¹ä¸æ§è¡æ¤æ´æ¹éè¦ä¸¤ä¸ªæ¥éª¤ï¼å¨ Promise.all()
çä¸ä¸æä¸æ§è¡ï¼è¯¥æ¹æ³ç¨äºç¡®ä¿ä¸¤ä¸ªè¯å¥å¨ç»§ç»å¤çæ¥æ¶å°çé约ä¹åå®å
¨æ§è¡ã第ä¸ä¸ªè¯å¥è§¦ååæ»ï¼ç¬¬äºä¸ªè¯å¥å°è¿ç¨æè¿°è®¾ç½®ä¸ºæ¥æ¶å°çæè¿°ï¼ä»èå®æç¨æ°æ¶å°çéçº¦æ¿æ¢å
ååéçé约çè¿ç¨ãæç¤¼è²çå¯¹çæ¹ç°å¨æä¸ºè¢«å«æ¹è䏿¯ä¸»å«æ¹ã
æ¥èªä¸ç¤¼è²å¯¹çæ¹çææå
¶ä»æè¿°é½å平叏䏿 ·è¢«å¤çï¼éè¿å°å®ä»¬ä¼ éç» setRemoteDescription()
ã
æåï¼æä»¬éè¿è°ç¨ setLocalDescription()
å¤çæ¶å°çé约ï¼å°æä»¬çæ¬å°æè¿°è®¾ç½®ä¸ºç± createAnswer()
è¿åçæè¿°ãç¶åï¼éè¿ä¿¡ä»¤ééå°å
¶åéå°æç¤¼è²çå¯¹çæ¹ã
å¦æä¼ å
¥æ¶æ¯æ¯ ICE åéè䏿¯ SDP æè¿°ï¼åéè¿å°å
¶ä¼ éç» RTCPeerConnection
æ¹æ³ addIceCandidate()
å°å
¶ä¼ éå° ICE å±ã妿è¿éåçé误ï¼å¹¶ä¸æä»¬æ²¡æå 为å¨ç¢°ææé´æ¯ä¸ç¤¼è²çå¯¹çæ¹èåå丢å¼äºä¸ä¸ªéçº¦ï¼æä»¬å° throw
åºé误ï¼ä»¥ä¾¿è°ç¨è
å¯ä»¥å¤çå®ãå¦åï¼æä»¬å°æ¾å¼é误ï¼å¿½ç¥å®ï¼å 为å¨è¿ç§æ
åµä¸å®å¹¶ä¸éè¦ã
æ´æ°åç代ç å©ç¨äºè¿æ ·ä¸ä¸ªäºå®ï¼å³ç°å¨ä½ å¯ä»¥è°ç¨ setLocalDescription()
èä¸å¸¦åæ°ï¼å æ¤å®ä¼ä¸ºä½ 宿æ£ç¡®çæä½ï¼ä»¥å setRemoteDescription()
èªå¨åæ»ï¼å¦æéè¦çè¯ï¼ãè¿ä½¿å¾æä»¬æè±äºä½¿ç¨ Promise
æ¥ä¿ææ¶é´é¡ºåºçéè¦ï¼å ä¸ºåæ»åæäº setRemoteDescription()
è°ç¨çåºæ¬ååé¨åã
let ignoreOffer = false;
signaler.onmessage = async ({ data: { description, candidate } }) => {
try {
if (description) {
const offerCollision =
description.type === "offer" &&
(makingOffer || pc.signalingState !== "stable");
ignoreOffer = !polite && offerCollision;
if (ignoreOffer) {
return;
}
await pc.setRemoteDescription(description);
if (description.type === "offer") {
await pc.setLocalDescription();
signaler.send({ description: pc.localDescription });
}
} else if (candidate) {
try {
await pc.addIceCandidate(candidate);
} catch (err) {
if (!ignoreOffer) {
throw err;
}
}
}
} catch (err) {
console.error(err);
}
};
尽管代ç 大å°çå·®å¼å¾å°ï¼èä¸å¤ææ§ä¹æ²¡æåå°å¤å°ï¼ä½ä»£ç çå¯é æ§å¤§å¤§æé«äºã让æä»¬æ·±å ¥ä»£ç ï¼ççç°å¨å®æ¯å¦ä½å·¥ä½çã
卿¥æ¶å°æè¿°æ¶å¨ä¿®æ¹åç代ç ä¸ï¼å¦ææ¥æ¶å°çæ¶æ¯æ¯ä¸ä¸ª SDP description
ï¼æä»¬ä¼æ£æ¥å®æ¯å¦å¨æä»¬è¯å¾ä¼ è¾ä¸ä¸ªæè®®æ¶å°è¾¾ãå¦ææ¥æ¶å°çæ¶æ¯æ¯ä¸ä¸ª offer
ï¼å¹¶ä¸æ¬å°å¯¹çæ¹æ¯ä¸ç¤¼è²çå¯¹çæ¹ï¼å¹¶ä¸åçäºç¢°æï¼é£ä¹æä»¬ä¼å¿½ç¥è¯¥æè®®ï¼å 为æä»¬å¸æç»§ç»å°è¯ä½¿ç¨å·²ç»å¨åéè¿ç¨ä¸çæè®®ãè¿å°±æ¯ä¸ç¤¼è²çå¯¹çæ¹çä½ç¨ã
å¨ä»»ä½å
¶ä»æ
åµä¸ï¼æä»¬å°å°è¯å¤çä¼ å
¥çæ¶æ¯ãè¿ä»å°è¿ç¨æè¿°è®¾ç½®ä¸ºæ¥æ¶å°ç description
å¼å§ï¼éè¿å°å
¶ä¼ éç» setRemoteDescription()
æ¥å®ç°ãè¿å°å¨éè¦æ¶èªå¨æ§è¡åæ»ï¼å æ¤æ 论æä»¬æ¯å¨å¤çæè®®è¿æ¯åå¤ï¼é½å¯ä»¥æ£å¸¸å·¥ä½ã
å¨è¿ä¸ç¹ä¸ï¼å¦ææ¥æ¶å°çæ¶æ¯æ¯ä¸ä¸ª offer
ï¼æä»¬å°ä½¿ç¨ setLocalDescription()
å建并设置éå½çæ¬å°æè¿°ï¼ç¶åéè¿ä¿¡ä»¤æå¡å¨å°å
¶åéå°è¿ç¨å¯¹çæ¹ã
å¦ä¸æ¹é¢ï¼å¦ææ¥æ¶å°çæ¶æ¯æ¯ä¸ä¸ª ICE åéè
ï¼å³ JSON 对象å
å«ä¸ä¸ª candidate
æåï¼æä»¬éè¿è°ç¨ RTCPeerConnection
æ¹æ³ addIceCandidate()
å°å
¶ä¼ éç»æ¬å° ICE å±ã妿æä»¬åå丢å¼äºä¸ä¸ªæè®®ï¼é£ä¹é误ä¼å以å䏿 ·è¢«å¿½ç¥ã
å
åç¨äºå¨å¤ç negotiationneeded
äºä»¶æ¶è§¦å ICE éæ°å¯å¨çææ¯å卿¾è缺é·ãè¿äºç¼ºé·ä½¿å¾å¨ååè¿ç¨ä¸å®å
¨å¯é å°è§¦åéæ°å¯å¨åå¾å°é¾ãå®ç¾çååæ¹è¿éè¿å RTCPeerConnection
æ·»å äºä¸ä¸ªæ°ç restartIce()
æ¹æ³æ¥ä¿®å¤äºè¿ä¸ªé®é¢ã
å¨è¿å»ï¼å¦æéå° ICE é误并éè¦éæ°å¯å¨ååï¼ä½ å¯è½ä¼åè¿æ ·åï¼
pc.onnegotiationneeded = async (options) => {
await pc.setLocalDescription(await pc.createOffer(options));
signaler.send({ description: pc.localDescription });
};
pc.oniceconnectionstatechange = () => {
if (pc.iceConnectionState === "failed") {
pc.onnegotiationneeded({ iceRestart: true });
}
};
è¿ç§æ¹æ³åå¨è®¸å¤å¯é æ§é®é¢åææ¾çéè¯¯ï¼æ¯å¦å½ä¿¡ä»¤ç¶æä¸æ¯ stable
æ¶ï¼iceconnectionstatechange
äºä»¶è§¦åæ¶ä¼å¤±è´¥ï¼ï¼ä½ä½ å®é
䏿²¡æå
¶ä»æ¹æ³å¯ä»¥è¯·æ± ICE éæ°å¯å¨ï¼é¤äºå建并åéä¸ä¸ªå¸¦æ iceRestart
é项设置为 true
çæè®®ãå æ¤ï¼åééæ°å¯å¨è¯·æ±éè¦ç´æ¥è°ç¨ negotiationneeded
äºä»¶çå¤çå¨ãè¦åå°æ£ç¡®æ¯ç¸å½å°é¾çï¼èä¸å¾å®¹æåºéï¼å æ¤é误å¾å¸¸è§ã
ç°å¨ï¼ä½ å¯ä»¥ä½¿ç¨ restartIce()
æ¥æ´æ¸
æ°å°æ§è¡æ¤æä½ï¼
let makingOffer = false;
pc.onnegotiationneeded = async () => {
try {
makingOffer = true;
await pc.setLocalDescription();
signaler.send({ description: pc.localDescription });
} catch (err) {
console.error(err);
} finally {
makingOffer = false;
}
};
pc.oniceconnectionstatechange = () => {
if (pc.iceConnectionState === "failed") {
pc.restartIce();
}
};
éè¿è¿ç§æ¹è¿çææ¯ï¼ä¸åç´æ¥è°ç¨å¸¦æé项ç onnegotiationneeded
æ¥è§¦å ICE éæ°å¯å¨ï¼èæ¯å¨ ICE è¿æ¥ç¶æä¸º failed
æ¶è°ç¨ restartIce()
ãrestartIce()
åè¯ ICE å±å¨ä¸ä¸ä¸ªåéç ICE æ¶æ¯ä¸èªå¨æ·»å iceRestart
æ å¿ãé®é¢è§£å³äºï¼
æå¼äººæ³¨ç®ç API ååä¹ä¸æ¯ï¼å½å¤äº have-remote-pranswer
æ have-local-pranswer
ç¶ææ¶ï¼ç°å¨ä¸è½åæ§è¡åæ»æä½ã幸è¿çæ¯ï¼å½ä½¿ç¨å®ç¾çååæ¶ï¼é常æ
åµä¸æ ¹æ¬ä¸éè¦è¿æ ·åï¼å 为å¨å¿
è¦æ¶ä¼å¨æ§è¡åæ»ä¹åæè·å¹¶é²æ¢åºç°è¿äºæ
åµã
å æ¤ï¼å°è¯å¨è¿ä¸¤ç§ pranswer
ç¶æä¹ä¸ä¸è§¦ååæ»æä½å°ä¼æåºä¸ä¸ª InvalidStateError
ã
RetroSearch is an open source project built by @garambo | Open a GitHub Issue
Search and Browse the WWW like it's 1997 | Search results from DuckDuckGo
HTML:
3.2
| Encoding:
UTF-8
| Version:
0.7.4