ä¸ºäºæ´å
¨é¢å°æ¯æé³é¢/è§é¢ä¼è®®ï¼WebRTC æ¯æå¨ RTCPeerConnection
ä¸åè¿ç¨å¯¹çæ¹åéåé³å¤é¢ä¿¡å·ï¼DTMFï¼ãæ¬æç®è¦æ¦è¿°äº WebRTC ä¸ç DTMF æ¯å¦ä½å·¥ä½çï¼ç¶åæä¾äºä¸ä¸ªæå¯¼å¼å人åå¦ä½éè¿ RTCPeerConnection
åé DTMF çæåãDTMF ç³»ç»é常被称为â触æ¸é³è°âï¼è¿æ¯ä¸ç§æ§çåæ åç§°ã
WebRTC ä¸ä¼å° DTMF 代ç ä½ä¸ºé³é¢æ°æ®åéãç¸åï¼å®ä»¬ä½ä¸º RTP è½½è·å¨å¸¦å¤åéã使¯ï¼è¯·æ³¨æï¼å°½ç®¡å¯ä»¥ä½¿ç¨ WebRTC åé DTMFï¼ä½ç®åæ æ³æ£æµææ¥æ¶ä¼ å ¥ç DTMFãWebRTC ç®åä¼å¿½ç¥è¿äºè½½è·ï¼è¿æ¯å 为 WebRTC ç DTMF æ¯æä¸»è¦æ¯ä¸ºäºä¸ä¾èµ DTMF é³è°æ§è¡ä»»å¡çä¼ ç»çµè¯æå¡ä¸èµ·ä½¿ç¨ï¼ä¾å¦ï¼
夿³¨ï¼ è½ç¶ DTMF ä¸ä¼ä½ä¸ºé³é¢åéå°è¿ç¨å¯¹çæ¹ï¼ä½æµè§å¨å¯è½ä¼éæ©åæ¬å°ç¨æ·ææ¾ç¸åºçé³è°æ¥æåç¨æ·ä½éªï¼å ä¸ºç¨æ·éå¸¸ä¹ æ¯äºå¬å°çµè¯ææ¾çæç¤ºé³ã
å¨ RTCPeerConnection ä¸åé DTMFå¯ä»¥å¨ä¸ä¸ªç»å®ç RTCPeerConnection
ä¸åéææ¥æ¶å¤ä¸ªåªä½è½¨éãå½ä½ å¸æä¼ è¾ DTMF ä¿¡å·æ¶ï¼ä½ é¦å
éè¦å³å®è¦å°å®ä»¬åéå°åªä¸ªè½¨éï¼å 为 DTMF æ¯ä½ä¸º RTCRtpSender
ââè´è´£å°è¯¥è½¨éçæ°æ®ä¼ è¾å°å
¶ä»å¯¹çæ¹ââä¸çä¸ç³»å带å¤è½½è·åéçã
䏿¦éæ©äºè½¨éï¼ä½ å¯ä»¥ä»å
¶ RTCRtpSender
è·åä½ å°ç¨äºåé DTMF ç RTCDTMFSender
对象ãå¨é£éï¼ä½ å¯ä»¥è°ç¨ RTCDTMFSender.insertDTMF()
å° DTMF ä¿¡å·æå
¥éåï¼ä»¥ä¾¿éè¿è½¨éå°å
¶åéç»å
¶ä»å¯¹çæ¹ãç¶åï¼RTCRtpSender
å°é³è°ä½ä¸ºæ°æ®å
ä¸è½¨éçé³é¢æ°æ®ä¸èµ·åéå°å
¶ä»å¯¹çæ¹ã
æ¯æ¬¡åéé³è°æ¶ï¼RTCPeerConnection
é½ä¼æ¥æ¶å°ä¸ä¸ªå¸¦æ tone
屿§ç tonechange
äºä»¶ï¼è¯¥å±æ§æå®äºææ¾å®æçé³è°ï¼è¿æ¯å®æè¯¸å¦æ´æ°æ¥å£å
ç´ çä»»å¡çæºä¼ãå½é³è°ç¼å²åºä¸ºç©ºæ¶ï¼è¡¨ç¤ºææé³è°é½å·²åé宿ï¼è¿æ¥å¯¹è±¡å°æ¥æ¶å°ä¸ä¸ªå¸¦æå
¶ tone
屿§è®¾ç½®ä¸º ""
ï¼ç©ºå符串ï¼ç tonechange
äºä»¶ã
å¦æä½ æ³äºè§£æ´å¤å
³äºè¿æ¯å¦ä½å·¥ä½çä¿¡æ¯ï¼è¯·é
读 RFC 3550: RTPï¼å®æ¶åºç¨çä¼ è¾åè®®å RFC 4733: ç¨äº DTMF æ°åãçµè¯é³åçµè¯ä¿¡å·ç RTP è½½è·ãå
³äºå¦ä½å¨ RTP ä¸å¤ç DTMF è½½è·ç详ç»ä¿¡æ¯è¶
åºäºæ¬æçèå´ãç¸åï¼æä»¬å°éè¿ç ç©¶ä¸ä¸ªç¤ºä¾çå·¥ä½åçæ¥éç¹å
³æ³¨å¦ä½å¨ RTCPeerConnection
ä¸ä¸æä¸ä½¿ç¨ DTMFã
è¿ä¸ªç®åçç¤ºä¾æå»ºäºä¸¤ä¸ª RTCPeerConnection
ï¼å¨å®ä»¬ä¹é´å»ºç«äºè¿æ¥ï¼ç¶åçå¾
ç¨æ·ç¹å»âæ¨å·âæé®ãå½ç¨æ·ç¹å»æé®æ¶ï¼ä½¿ç¨ RTCDTMFSender.insertDTMF()
å¨è¿æ¥ä¸åéä¸ä¸ª DTMF å符串ã䏿¦é³è°ä¼ è¾å®æï¼è¿æ¥å°±ä¼å
³éã
夿³¨ï¼ è¿ä¸ªç¤ºä¾æ¾ç¶æäºçµå¼ºï¼å 为é常两个 RTCPeerConnection
对象ä¼åå¨äºä¸åç设å¤ä¸ï¼å¹¶ä¸ä¿¡ä»¤ä¼ è¾é常æ¯éè¿ç½ç»è¿è¡çï¼è䏿¯åè¿é䏿 ·å
¨é¨é½å¨å
é¨è¿æ¥ã
è¿ä¸ªç¤ºä¾ç HTML é常åºç¡ï¼åªæä¸ä¸ªéè¦çå ç´ ï¼
<audio>
å
ç´ ï¼ç¨äºææ¾ç±è¢«âå¼å«âç RTCPeerConnection
æ¥æ¶å°çé³é¢ã<button>
å
ç´ ï¼ç¨äºè§¦åå建åè¿æ¥ä¸¤ä¸ª RTCPeerConnection
对象ï¼ç¶ååé DTMF é³è°ã<div>
ï¼ç¨äºæ¥æ¶åæ¾ç¤ºæ¥å¿ææ¬ï¼ä»¥æ¾ç¤ºç¶æä¿¡æ¯ã<p>
è¿ä¸ªç¤ºä¾æ¼ç¤ºäºå¨ WebRTC ä¸ä½¿ç¨
DTMFã请注æï¼è¿ä¸ªç¤ºä¾æ¯âä½å¼âçï¼å®å¨ä¸ä¸ªä»£ç æµä¸çæä¸¤ä¸ªå¯¹ç端ï¼è䏿¯è®©æ¯ä¸ªå¯¹çç«¯é½æ¯ä¸ä¸ªçæ£ç¬ç«çå®ä½ã
</p>
<audio id="audio" autoplay controls></audio><br />
<button name="dial" id="dial">æ¨å·</button>
<div class="log"></div>
JavaScript
让æä»¬æ¥ä¸æ¥çä¸ä¸ JavaScript 代ç ã请注æï¼è¿é建ç«è¿æ¥çè¿ç¨æäºçµå¼ºï¼é常æ åµä¸ï¼ä½ ä¸ä¼å¨åä¸ä¸ªææ¡£ä¸æå»ºè¿æ¥ç两端ã
å ¨å±åéé¦å ï¼æä»¬åå»ºå ¨å±åéã
let dialString = "12024561111";
let callerPC = null;
let receiverPC = null;
let dtmfSender = null;
let hasAddTrack = false;
let mediaConstraints = {
audio: true,
video: false,
};
let offerOptions = {
offerToReceiveAudio: 1,
offerToReceiveVideo: 0,
};
let dialButton = null;
let logElement = null;
å®ä»¬ä¾æ¬¡æ¯ï¼
dialString
å½ç¨æ·ç¹å»âæ¨å·âæé®æ¶ï¼å¼å«æ¹å°åéç DTMF å符串ã
callerPC
å receiverPC
åå«è¡¨ç¤ºå¼å«æ¹åæ¥æ¶æ¹ç RTCPeerConnection
对象ãè¿äºå¯¹è±¡å°å¨å¼å«å¯å¨æ¶å¨æä»¬ç connectAndDial()
彿°ä¸åå§åï¼å¦ä¸é¢çå¯å¨è¿æ¥è¿ç¨æç¤ºã
dtmfSender
è¿æ¥ç RTCDTMFSender
对象ãè¿å°å¨è®¾ç½®è¿æ¥æ¶å¨ gotStream()
彿°ä¸è·åï¼å¦å°é³é¢æ·»å å°è¿æ¥é¨åæç¤ºã
hasAddTrack
ç±äºä¸äºæµè§å¨å°æªå®ç° RTCPeerConnection.addTrack()
ï¼å æ¤éè¦ä½¿ç¨å·²è¿æ¶ç addStream()
æ¹æ³ï¼æä»¬ä½¿ç¨æ¤å¸å°å¼æ¥ç¡®å®ç¨æ·ä»£çæ¯å¦æ¯æ addTrack()
ï¼å¦æä¸æ¯æï¼æä»¬å°éåå° addStream()
ãè¿å°å¨ connectAndDial()
ä¸ç¡®å®ï¼å¦ä¸é¢çå¯å¨è¿æ¥è¿ç¨æç¤ºã
mediaConstraints
æå®å¯å¨è¿æ¥æ¶è¦ä½¿ç¨ç约æç对象ãæä»¬åªæ³è¦é³é¢è¿æ¥ï¼æä»¥ video
æ¯ false
ï¼è audio
æ¯ true
ã
offerOptions
ç¨äºå¨è°ç¨ RTCPeerConnection.createOffer()
æ¶æå®é项ç对象ã卿¬ä¾ä¸ï¼æä»¬å£°ææä»¬æ³è¦æ¥æ¶é³é¢ä½ä¸è¦è§é¢ã
dialButton
å logElement
è¿äºåéå°ç¨äºåå¨å¯¹æ¨å·æé®åå°åå
¥æ¥å¿ä¿¡æ¯ç <div>
çå¼ç¨ãå®ä»¬å°å¨é¡µé¢é¦æ¬¡å è½½æ¶è®¾ç½®ãåè§ä¸é¢çåå§åã
页é¢å è½½æ¶ï¼æä»¬è¿è¡ä¸äºåºæ¬è®¾ç½®ï¼æä»¬è·å对æ¨å·æé®åæ¥å¿è¾åºæ¡å
ç´ çå¼ç¨ï¼å¹¶ä½¿ç¨ addEventListener()
为æ¨å·æé®æ·»å äºä»¶çå¬å¨ï¼ä»¥ä¾¿ç¹å»å®ä¼è°ç¨å¼å§è¿æ¥è¿ç¨ç connectAndDial()
彿°ã
window.addEventListener("load", () => {
logElement = document.querySelector(".log");
dialButton = document.querySelector("#dial");
dialButton.addEventListener("click", connectAndDial, false);
});
å¯å¨è¿æ¥è¿ç¨
å½ç¹å»æ¨å·æé®æ¶ï¼å°è°ç¨ connectAndDial()
ãè¿å°å¼å§æå»º WebRTC è¿æ¥ï¼ä¸ºåé DTMF 代ç ååå¤ã
function connectAndDial() {
callerPC = new RTCPeerConnection();
hasAddTrack = callerPC.addTrack !== undefined;
callerPC.onicecandidate = handleCallerIceEvent;
callerPC.onnegotiationneeded = handleCallerNegotiationNeeded;
callerPC.oniceconnectionstatechange = handleCallerIceConnectionStateChange;
callerPC.onsignalingstatechange = handleCallerSignalingStateChangeEvent;
callerPC.onicegatheringstatechange = handleCallerGatheringStateChangeEvent;
receiverPC = new RTCPeerConnection();
receiverPC.onicecandidate = handleReceiverIceEvent;
if (hasAddTrack) {
receiverPC.ontrack = handleReceiverTrackEvent;
} else {
receiverPC.onaddstream = handleReceiverAddStreamEvent;
}
navigator.mediaDevices
.getUserMedia(mediaConstraints)
.then(gotStream)
.catch((err) => log(err.message));
}
å¨ä¸ºå¼å«æ¹ï¼callerPC
ï¼å建 RTCPeerConnection
åï¼æä»¬æ¥ç宿¯å¦å
·æ addTrack()
æ¹æ³ã妿æï¼æä»¬å° hasAddTrack
设置为 true
ï¼å¦åï¼æä»¬å°å
¶è®¾ç½®ä¸º false
ãè¿ä¸ªåéå°è®©ç¤ºä¾å³ä½¿å¨å°æªå®ç°è¾æ°ç addTrack()
æ¹æ³çæµè§å¨ä¸ä¹è½è¿è¡ï¼æä»¬å°éè¿éåå°è¾æ§ç addStream()
æ¹æ³æ¥å®ç°ã
æ¥ä¸æ¥ï¼ä¸ºå¼å«æ¹å»ºç«äºäºä»¶å¤çå¨ãæä»¬ç¨åå°è¯¦ç»ä»ç»è¿äºã
ç¶åå建第äºä¸ªç¨äºä»£è¡¨å¼å«çæ¥æ¶ç«¯ç RTCPeerConnection
ï¼å¹¶å°å®åå¨å¨ receiverPC
ä¸ï¼åæ¶è®¾ç½®å®ç onicecandidate
äºä»¶å¤çå¨ã
å¦ææ¯æ addTrack()
ï¼æä»¬è®¾ç½®æ¥æ¶æ¹ç ontrack
äºä»¶å¤çå¨ï¼å¦åï¼æä»¬è®¾ç½® onaddstream
ãå½åªä½è¢«æ·»å å°è¿æ¥æ¶ï¼ä¼åé track
å addstream
äºä»¶ã
æåï¼æä»¬è°ç¨ getUserMedia()
æ¥è·å对å¼å«æ¹éº¦å
é£çè®¿é®æéã妿æåï¼å°è°ç¨å½æ° gotStream()
ï¼å¦åæä»¬è®°å½è°ç¨å¤±è´¥çé误ã
å¦ä¸æè¿°ï¼å½ä»éº¦å
é£è·åå°é³é¢è¾å
¥æ¶ï¼å°è°ç¨ gotStream()
ãå®ç工使¯æå»ºåéå°æ¥æ¶æ¹çæµï¼ä»èå¯ä»¥å¼å§å®é
çä¼ è¾è¿ç¨ãå®è¿ä¼è·åæä»¬å°å¨è¿æ¥ä¸ä½¿ç¨ç RTCDTMFSender
ã
function gotStream(stream) {
log("å·²è·å麦å
é£çè®¿é®æéã");
let audioTracks = stream.getAudioTracks();
if (hasAddTrack) {
if (audioTracks.length > 0) {
audioTracks.forEach((track) => callerPC.addTrack(track, stream));
}
} else {
log(
"ä½ çæµè§å¨ä¸æ¯æ RTCPeerConnection.addTrack()ãæ£å¨éåå°<strong>å·²å¼ç¨</strong>ç addStream() æ¹æ³â¦",
);
callerPC.addStream(stream);
}
if (callerPC.getSenders) {
dtmfSender = callerPC.getSenders()[0].dtmf;
} else {
log(
"ä½ çæµè§å¨ä¸æ¯æ RTCPeerConnection.getSenders()ï¼å æ¤æ£å¨éåå°ä½¿ç¨<strong>å·²å¼ç¨</strong>ç createDTMFSender()ã",
);
dtmfSender = callerPC.createDTMFSender(audioTracks[0]);
}
dtmfSender.ontonechange = handleToneChangeEvent;
}
å¨å° audioTracks
è®¾ç½®ä¸ºç¨æ·éº¦å
飿µä¸çé³é¢è½¨éå表åï¼æ¯æ¶åå°åªä½æ·»å å°å¼å«æ¹ç RTCPeerConnection
ä¸äºã妿 RTCPeerConnection
䏿 addTrack()
å¯ç¨ï¼æä»¬å°ä½¿ç¨ RTCPeerConnection.addTrack()
éä¸ªå°æµçæ¯ä¸ªé³é¢è½¨éæ·»å å°è¿æ¥ä¸ãå¦åï¼æä»¬è°ç¨ RTCPeerConnection.addStream()
å°æµä½ä¸ºå个åå
æ·»å å°å¼å«ä¸ã
æ¥ä¸æ¥ï¼æä»¬æ¥çæ¯å¦å®ç°äº RTCPeerConnection.getSenders()
æ¹æ³ã妿å®ç°äºï¼æä»¬å¨ callerPC
ä¸è°ç¨å®ï¼å¹¶è·åè¿åçåéå¨å表ä¸ç第ä¸ä¸ªæ¡ç®ï¼è¿æ¯è´è´£ä¼ è¾å¼å«ç第ä¸ä¸ªé³é¢è½¨éæ°æ®ç RTCRtpSender
ï¼è¿æ¯æä»¬å°åé DTMF ç轨éï¼ãç¶åï¼æä»¬è·å RTCRtpSender
ç dtmf
屿§ï¼å®æ¯ä¸ä¸ª RTCDTMFSender
对象ï¼å¯ä»¥å¨è¿æ¥ä¸ä»å¼å«æ¹åéå°æ¥æ¶æ¹åé DTMFã
妿 getSenders()
ä¸å¯ç¨ï¼æä»¬å°è°ç¨ RTCPeerConnection.createDTMFSender()
æ¥è·å RTCDTMFSender
对象ã尽管è¿ä¸ªæ¹æ³å·²ç»è¿æ¶ï¼ä½è¿ä¸ªç¤ºä¾æ¯æå®ä½ä¸ºä¸ä¸ªå¤ç¨æ¹æ³ï¼è®©æ§çæµè§å¨ï¼åå°æªæ´æ°ä»¥æ¯æå½å WebRTC DTMF API çæµè§å¨ï¼å¯ä»¥è¿è¡è¿ä¸ªç¤ºä¾ã
æåï¼æä»¬è®¾ç½® DTMF åéå¨ç ontonechange
äºä»¶å¤çå¨ï¼ä»¥ä¾¿æ¯å½ä¸ä¸ª DTMF é³è°å®æææ¾æ¶é½ä¼æ¶å°éç¥ã
ä½ å¯ä»¥å¨å½åææ¡£çåºé¨æ¾å°æ¥å¿å½æ°ã
å½é³è°ææ¾å®ææ¯å½ä¸ä¸ª DTMF é³è°ææ¾å®ææ¶ï¼tonechange
äºä»¶å°±ä¼è¢«ä¼ éç» callerPC
ãè¿äºäºä»¶çäºä»¶çå¬å¨è¢«å®ç°ä¸º handleToneChangeEvent()
彿°ã
function handleToneChangeEvent(event) {
if (event.tone !== "") {
log(`ææ¾é³è°ï¼${event.tone}`);
} else {
log("All tones have played. Disconnecting.");
callerPC.getLocalStreams().forEach((stream) => {
stream.getTracks().forEach((track) => {
track.stop();
});
});
receiverPC.getLocalStreams().forEach((stream) => {
stream.getTracks().forEach((track) => {
track.stop();
});
});
audio.pause();
audio.srcObject = null;
receiverPC.close();
callerPC.close();
}
}
tonechange
äºä»¶æ¢ç¨äºæç¤ºå个é³è°å·²ææ¾ï¼ä¹ç¨äºæç¤ºææé³è°å·²å®æææ¾ãäºä»¶ç tone
屿§æ¯ä¸ä¸ªæç¤ºååå®æææ¾çé³è°çå符串ã妿ææé³è°é½å·²å®æææ¾ï¼tone
å°æ¯ä¸ä¸ªç©ºå符串ï¼å¨è¿ç§æ
åµä¸ï¼RTCDTMFSender.toneBuffer
为空ã
å¨è¿ä¸ªç¤ºä¾ä¸ï¼æä»¬å°ååå®æææ¾çé³è°è®°å½å°å±å¹ä¸ã卿´é«çº§çåºç¨ç¨åºä¸ï¼ä½ å¯è½ä¼æ´æ°ç¨æ·çé¢ï¼ä¾å¦ï¼æç¤ºå½åæ£å¨ææ¾çé³ç¬¦ã
å¦ä¸æ¹é¢ï¼å¦æé³è°ç¼å²åºä¸ºç©ºï¼æä»¬ç示ä¾è¢«è®¾è®¡ä¸ºæå¼éè¯ãè¿æ¯éè¿è¿ä»£æ¯ä¸ª RTCPeerConnection
ç轨éå表ï¼ç±å
¶ getTracks()
æ¹æ³è¿åï¼å¹¶è°ç¨æ¯ä¸ªè½¨éç stop()
æ¹æ³æ¥å®æçã
䏿¦å¼å«æ¹åæ¥æ¶æ¹çææåªä½è½¨éé½åæ¢äºï¼æä»¬æå <audio>
å
ç´ ï¼å¹¶å°å
¶ srcObject
设置为 null
ãè¿ä¼å°é³é¢æµä» <audio>
å
ç´ ä¸åç¦»åºæ¥ã
æåï¼éè¿è°ç¨æ¯ä¸ª RTCPeerConnection
ç close()
æ¹æ³æ¥å
³éå®ã
å½å¼å«æ¹ç RTCPeerConnection
ICE 屿åºä¸ä¸ªæ°çåéè
æ¶ï¼å®ä¼å callerPC
ååºä¸ä¸ª icecandidate
äºä»¶ãicecandidate
äºä»¶å¤çå¨ç工使¯å°åéè
ä¼ è¾ç»æ¥æ¶æ¹ã卿们ç示ä¾ä¸ï¼æä»¬ç´æ¥æ§å¶å¼å«æ¹åæ¥æ¶æ¹ï¼æä»¥æä»¬å¯ä»¥ç´æ¥éè¿è°ç¨å
¶ addIceCandidate()
æ¹æ³å°åéè
æ·»å å°æ¥æ¶æ¹ãè¿ç± handleCallerIceEvent()
å¤çï¼
function handleCallerIceEvent(event) {
if (event.candidate) {
log(`æ£å¨åæ¥æ¶æ¹æ·»å åéè
ï¼${event.candidate.candidate}`);
receiverPC
.addIceCandidate(new RTCIceCandidate(event.candidate))
.catch((err) => log(`åæ¥æ¶æ¹æ·»å åéè
æ¶åºéï¼${err}`));
} else {
log("å¼å«æ¹æ²¡ææ´å¤çåéè
ã");
}
}
妿 icecandidate
äºä»¶å
·æé null
ç candidate
屿§ï¼æä»¬å°ä» event.candidate
å符串å建ä¸ä¸ªæ°ç RTCIceCandidate
对象ï¼å¹¶éè¿è°ç¨ receiverPC.addIceCandidate()
å°å
¶âä¼ è¾âå°æ¥æ¶æ¹ï¼æä¾æ°ç RTCIceCandidate
ä½ä¸ºå
¶è¾å
¥ã妿 addIceCandidate()
失败ï¼catch()
åå¥å°é误è¾åºå°æä»¬çæ¥å¿æ¡ä¸ã
妿 event.candidate
æ¯ null
ï¼è¿è¡¨ç¤ºæ²¡ææ´å¤çåéè
å¯ç¨ï¼æä»¬ä¼è®°å½è¿ä¸ä¿¡æ¯ã
æä»¬çè®¾è®¡è¦æ±å¨è¿æ¥å»ºç«åç«å³åé DTMF å符串ã为äºå®ç°è¿ä¸ç¹ï¼æä»¬ä¼çè§å¼å«æ¹æ¥æ¶å°ç iceconnectionstatechange
äºä»¶ãå½ ICE è¿æ¥è¿ç¨çç¶æåçååï¼å
æ¬æå建ç«è¿æ¥æ¶ï¼å°±ä¼åéè¿ä¸ªäºä»¶ã
function handleCallerIceConnectionStateChange() {
log(`å¼å«æ¹è¿æ¥ç¶æå·²æ´æ¹ä¸º ${callerPC.iceConnectionState}`);
if (callerPC.iceConnectionState === "connected") {
log(`åé DTMF: "${dialString}"`);
dtmfSender.insertDTMF(dialString, 400, 50);
}
}
å®é
ä¸ï¼iceconnectionstatechange
äºä»¶å¹¶æ²¡æå¨å
¶ä¸å
嫿°ç¶æï¼å æ¤æä»¬ä» callerPC
ç RTCPeerConnection.iceConnectionState
屿§ä¸è·åè¿æ¥è¿ç¨çå½åç¶æãå¨è®°å½æ°ç¶æåï¼æä»¬æ¥çç¶ææ¯å¦ä¸º "connected"
ã妿æ¯ï¼æä»¬è®°å½å³å°åé DTMF çäºå®ï¼ç¶åè°ç¨ dtmf.insertDTMF()
å¨ä¸æä»¬ä¹ååå¨å¨ dtmfSender
ä¸ç RTCDTMFSender
ç¸åç轨éä¸åé DTMFã
æä»¬è°ç¨ insertDTMF()
ä¸ä»
æå®è¦åéç DTMFï¼dialString
ï¼ï¼è¿æå®äºæ¯ä¸ªé³è°çé¿åº¦ï¼400 毫ç§ï¼åé³è°ä¹é´çæ¶é´é´éï¼50 毫ç§ï¼ã
å½å¼å«æ¹ RTCPeerConnection
å¼å§æ¥æ¶åªä½ï¼å¨å°éº¦å
é£çæµæ·»å å°å
¶ä¸åï¼ï¼ä¼åå¼å«æ¹ä¼ éä¸ä¸ª negotiationneeded
äºä»¶ï¼è®©å®ç¥éç°å¨æ¯æ¶åå¼å§ä¸æ¥æ¶æ¹ååè¿æ¥äºãå¦åæè¿°ï¼æä»¬ç示ä¾ç¨å¾®ç®åäºä¸äºï¼å 为æä»¬æ§å¶çå¼å«æ¹åæ¥æ¶æ¹ï¼æä»¥ handleCallerNegotiationNeeded()
è½å¤å¿«éå°ä¸ºå¼å«æ¹åæ¥æ¶æ¹æå»ºè¿æ¥ï¼å¦ä¸æç¤ºã
function handleCallerNegotiationNeeded() {
log("ååä¸â¦â¦");
callerPC
.createOffer(offerOptions)
.then((offer) => {
log(`设置å¼å«æ¹çæ¬å°æè¿°ï¼${offer.sdp}`);
return callerPC.setLocalDescription(offer);
})
.then(() => {
log("å°æ¥æ¶æ¹çè¿ç¨æè¿°è®¾ç½®ä¸ºä¸å¼å«æ¹çæ¬å°æè¿°ç¸å");
return receiverPC.setRemoteDescription(callerPC.localDescription);
})
.then(() => {
log("å建åºç");
return receiverPC.createAnswer();
})
.then((answer) => {
log(`å°æ¥æ¶æ¹çæ¬å°æè¿°è®¾ç½®ä¸º ${answer.sdp}`);
return receiverPC.setLocalDescription(answer);
})
.then(() => {
log("设置è¦å¹é
çå¼å«æ¹çè¿ç¨æè¿°");
return callerPC.setRemoteDescription(receiverPC.localDescription);
})
.catch((err) => log(`ååè¿ç¨ä¸åºéï¼${err.message}`));
}
ç±äºåä¸ååè¿æ¥çåç§æ¹æ³è¿åäº promise
ï¼æä»¬å¯ä»¥åè¿æ ·å°å®ä»¬é¾æ¥å¨ä¸èµ·ï¼
callerPC.createOffer()
è·åä¸ä¸ªæè®®ãcallerPC.setLocalDescription()
设置å¼å«æ¹çæ¬å°æè¿°æ¥å¹é
ãreceiverPC.setRemoteDescription()
å°æè®®âä¼ è¾âå°æ¥æ¶æ¹ãè¿æ ·é
ç½®æ¥æ¶æ¹ï¼ä½¿å
¶ç¥éå¼å«æ¹çé
ç½®ãreceiverPC.createAnswer()
å建ä¸ä¸ªçå¤ãreceiverPC.setLocalDescription()
å°å
¶æ¬å°æè¿°è®¾ç½®ä¸ºä¸æ°å建ççå¤å¹é
ãcallerPC.setRemoteDescription()
å°çå¤âä¼ è¾âç»å¼å«æ¹ãè¿æ ·è®©å¼å«æ¹ç¥éæ¥æ¶æ¹çé
ç½®ãcatch()
åå¥å°è¾åºéè¯¯æ¶æ¯å°æ¥å¿ä¸ãæä»¬è¿å¯ä»¥è§å¯ä¿¡ä»¤ç¶æçååï¼éè¿æ¥å signalingstatechange
äºä»¶ï¼å ICE æ¶éç¶æçååï¼éè¿æ¥å icegatheringstatechange
äºä»¶ï¼ãæä»¬æ²¡æä¸ºè¿äºäºä»¶å任使ä½ï¼æä»¥æä»¬åªæ¯å°å®ä»¬è®°å½å°æ¥å¿ä¸ãå®é
ä¸ï¼æä»¬å®å
¨å¯ä»¥ä¸è®¾ç½®è¿äºäºä»¶çå¬å¨ã
function handleCallerSignalingStateChangeEvent() {
log(`å¼å«æ¹çä¿¡ä»¤ç¶æå·²æ´æ¹ä¸º ${callerPC.signalingState}`);
}
function handleCallerGatheringStateChangeEvent() {
log(`å¼å«æ¹ç ICE æ¶éç¶æå·²æ´æ¹ä¸º ${callerPC.iceGatheringState}`);
}
åæ¥æ¶æ¹æ·»å åéè
彿¥æ¶æ¹ç RTCPeerConnection
ICE 屿åºä¸ä¸ªæ°çåéè
æ¶ï¼å®ä¼å receiverPC
ååºä¸ä¸ª icecandidate
äºä»¶ãicecandidate
äºä»¶å¤çå¨ç工使¯å°åéè
ä¼ è¾ç»å¼å«æ¹ã卿们ç示ä¾ä¸ï¼æä»¬ç´æ¥æ§å¶å¼å«æ¹åæ¥æ¶æ¹ï¼æä»¥æä»¬å¯ä»¥ç´æ¥éè¿è°ç¨å
¶ addIceCandidate()
æ¹æ³å°åéè
æ·»å å°å¼å«æ¹ãè¿ç± handleReceiverIceEvent()
å¤çã
è¿æ®µä»£ç 类似äºä¸é¢å°åéè
æ·»å å°å¼å«æ¹ä¸çå°çå¼å«æ¹ç icecandidate
äºä»¶å¤çå¨ã
function handleReceiverIceEvent(event) {
if (event.candidate) {
log(`Adding candidate to caller: ${event.candidate.candidate}`);
callerPC
.addIceCandidate(new RTCIceCandidate(event.candidate))
.catch((err) => log(`åå¼å«æ¹æ·»å åéæ¶åºéï¼${err}`));
} else {
log("æ¥æ¶æ¹å·²ç»æ²¡ææ´å¤çåéè
ã");
}
}
妿 icecandidate
äºä»¶å
·æé null
ç candidate
屿§ï¼æä»¬å°ä» event.candidate
å符串å建ä¸ä¸ªæ°ç RTCIceCandidate
对象ï¼å¹¶å°å
¶ä¼ éç»å¼å«æ¹ï¼éè¿å°å
¶ä¼ éç» callerPC.addIceCandidate()
ã妿 addIceCandidate()
失败ï¼catch()
åå¥å°é误è¾åºå°æä»¬çæ¥å¿æ¡ä¸ã
妿 event.candidate
æ¯ null
ï¼è¿è¡¨ç¤ºæ²¡ææ´å¤çåéè
å¯ç¨ï¼æä»¬ä¼è®°å½è¿ä¸ä¿¡æ¯ã
彿¥æ¶æ¹å¼å§æ¥æ¶åªä½æ¶ï¼ä¸ä¸ªäºä»¶ä¼ä¼ éå°æ¥æ¶æ¹ç RTCPeerConnection
ï¼å³ receiverPC
ãå¦å¼å§è¿æ¥è¿ç¨ä¸æè§£éçï¼å½åç WebRTC è§èä½¿ç¨ track
äºä»¶æ¥å¤çè¿ä¸ªæ
åµãç±äºä¸äºæµè§å¨å°æªæ´æ°ä»¥æ¯ææ¤åè½ï¼æä»¬è¿éè¦å¤ç addstream
äºä»¶ãä¸é¢ç handleReceiverTrackEvent()
å handleReceiverAddStreamEvent()
æ¹æ³æ¼ç¤ºäºè¿ä¸ç¹ã
function handleReceiverTrackEvent(event) {
audio.srcObject = event.streams[0];
}
function handleReceiverAddStreamEvent(event) {
audio.srcObject = event.stream;
}
track
äºä»¶å
å«ä¸ä¸ª streams
屿§ï¼å
¶ä¸å
å«è½¨éæå±çæµçæ°ç»ï¼ä¸ä¸ªè½¨éå¯ä»¥æ¯å¤ä¸ªæµçä¸é¨åï¼ãæä»¬å第ä¸ä¸ªæµå¹¶å°å
¶éå å° <audio>
å
ç´ ä¸ã
addstream
äºä»¶å
å«ä¸ä¸ªæå®æ·»å å°è½¨éçå个æµç屿§ stream
ãæä»¬å°å
¶éå å° <audio>
å
ç´ ä¸ã
代ç ä¸ä½¿ç¨äºä¸ä¸ªç®åç log()
彿°ï¼ç¨äºå° HTML éå å°ä¸ä¸ª <div>
çåä¸ï¼ä»¥åç¨æ·æ¾ç¤ºç¶æåé误ã
function log(msg) {
logElement.innerHTML += `${msg}<br/>`;
}
ç»æ
ä½ å¯ä»¥å¨è¿éå°è¯æ¤ç¤ºä¾ãå½ä½ ç¹å»âæ¨å·âæé®æ¶ï¼ä½ åºè¯¥ä¼çå°ä¸ç³»åçæ¥å¿æ¶æ¯è¾åºï¼ç¶åæ¨å·å°å¼å§ãå¦æä½ çæµè§å¨ä½ä¸ºå ¶ç¨æ·ä½éªçä¸é¨å以å¯å¬çæ¹å¼ææ¾é³è°ï¼åå¨å®ä»¬è¢«ä¼ è¾æ¶ä½ åºè¯¥ä¼å¬å°å®ä»¬ã
䏿¦é³è°ä¼ è¾å®æï¼è¿æ¥å°±ä¼å ³éãä½ å¯ä»¥å次ç¹å»âæ¨å·âä»¥éæ°è¿æ¥å¹¶åéé³è°ã
åè§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