原文html
RTCPeerConnectionhtml5
1.caller和callee互相發現彼此web
2.而且交換capabilities信息瀏覽器
3.初始化session服務器
4.開始實時交換數據session
名詞解釋:app
信令:在客戶端之間傳遞控制信息,經過控制信息處理客戶端之間的發現、鏈接創建、鏈接維護和鏈接關閉等任務的機制。socket
function initialize() { console.log("Initializing; room=99688636."); card = document.getElementById("card"); localVideo = document.getElementById("localVideo"); miniVideo = document.getElementById("miniVideo"); remoteVideo = document.getElementById("remoteVideo"); resetStatus(); openChannel('AHRlWrqvgCpvbd9B-Gl5vZ2F1BlpwFv0xBUwRgLF/* ...*/');/*room token 由Google App Engine app 提供*/ doGetUserMedia();//確認瀏覽器是否支持getUserMedia API 若是支持則調用onUserMediaSuccess } /* 創建通道過程 1.客戶端A生成一個惟一的ID 2.客戶端A把ID傳給App Engine app,請求得到Channel token 3.App Engine app 把ID傳給 Channel API,請求得到一個channel和token 4.App把token傳給客戶端A 5.客戶端A打開socket,監聽channel */ function openChannel(channelToken) { console.log("Opening channel."); var channel = new goog.appengine.Channel(channelToken); var handler = { 'onopen': onChannelOpened, 'onmessage': onChannelMessage, 'onerror': onChannelError, 'onclose': onChannelClosed }; socket = channel.open(handler); } /*Sending a message works like this: 1.Client B makes a POST request to the App Engine app with an update. 2.The App Engine app passes a request to the channel. 3.The channel carries a message to Client A. 4.Client A's onmessage callback is called. */ //若是瀏覽器支持getUserMedia,則函數被調用 function onUserMediaSuccess(stream) { console.log("User has granted access to local media."); // Call the polyfill wrapper to attach the media stream to this element. attachMediaStream(localVideo, stream);//localVideo.src = ... localViedo表明一個標籤 localVideo.style.opacity = 1; localStream = stream; // Caller creates PeerConnection. if (initiator) maybeStart();//initiator 已經被設置爲1,直到caller的session終止 因此這裏會調用maybeStart } //connection只會被創建一次 //創建前提1.第一次創建 2.localStream已經準備好了,即本地視頻 3.信令通道準備好了 function maybeStart() { if (!started && localStream && channelReady) { // ...調用func,使用STUN建立RTCPeerConnection(pc),設置各類事件監聽函數 createPeerConnection(); // ... pc.addStream(localStream); started = true; // Caller initiates offer to peer. if (initiator) doCall(); } } //被maybeStart調用 //主要目的是使用STUN服務器和回調函數onIceCandidata來創建connection //爲每個RTCPeerConnection事件創建handlers function createPeerConnection() { var pc_config = {"iceServers": [{"url": "stun:stun.l.google.com:19302"}]}; try { // Create an RTCPeerConnection via the polyfill (adapter.js). pc = new RTCPeerConnection(pc_config);//在adapter.js中被包裝過了 pc.onicecandidate = onIceCandidate; console.log("Created RTCPeerConnnection with config:\n" + " \"" + JSON.stringify(pc_config) + "\"."); } catch (e) { console.log("Failed to create PeerConnection, exception: " + e.message); alert("Cannot create RTCPeerConnection object; WebRTC is not supported by this browser."); return; } pc.onconnecting = onSessionConnecting;//log status messages做用 pc.onopen = onSessionOpened; //log status messages做用 pc.onaddstream = onRemoteStreamAdded; //log status messages做用 pc.onremovestream = onRemoteStreamRemoved;//爲remoteVideo標籤設置內容 } //handler function onRemoteStreamAdded(event) { // ... miniVideo.src = localVideo.src; attachMediaStream(remoteVideo, event.stream); remoteStream = event.stream; waitForRemoteVideo(); } //在maybeStart()調用createPeerConnection()以後, a call is intitiated by creating and offer and sending it to the callee function doCall() { console.log("Sending offer to peer."); pc.createOffer(setLocalAndSendMessage, null, mediaConstraints); } //建立offer的過程和非信令的例子(caller callee都在一個瀏覽器內)相似。 //不一樣點:message被髮送到遠端(remote peer),giving a serialized SessionDescription //不一樣點的功能有setLocalAndMessage()完成 //客戶端配置信息叫作Session Description function setLocalAndSendMessage(sessionDescription) { // Set Opus as the preferred codec in SDP if Opus is present. sessionDescription.sdp = preferOpus(sessionDescription.sdp); pc.setLocalDescription(sessionDescription); sendMessage(sessionDescription); } /*signaling with the Channel API*/ /*當createPeerConnection()成功建立RTCPeerConnetion後 回調函數onIceCandidate被調用: 發送收集來的candidates的信息 */ function onIceCandidate(event) { if (event.candidate) { //使用XHR請求,客戶端向服務器發送出站信息 sendMessage({type: 'candidate', label: event.candidate.sdpMLineIndex, id: event.candidate.sdpMid, candidate: event.candidate.candidate}); } else { console.log("End of candidates."); } } //使用XHR請求,從客戶端向服務器發送出站消息(Outbound messaging) function sendMessage(message) { var msgString = JSON.stringify(message); console.log('C->S: ' + msgString); path = '/message?r=99688636' + '&u=92246248'; var xhr = new XMLHttpRequest(); xhr.open('POST', path, true); xhr.send(msgString); } /* 客戶端->服務器發送信令消息:使用XHR 服務器->客戶端發送信令消息:使用Google App Engine Channel API */ //處理由App Engine server發送來的消息 function processSignalingMessage(message) { var msg = JSON.parse(message); if (msg.type === 'offer') { // Callee creates PeerConnection if (!initiator && !started)//initiator表明session是否建立 RTCPeerConnection是否被建立 maybeStart(); pc.setRemoteDescription(new RTCSessionDescription(msg)); doAnswer(); } else if (msg.type === 'answer' && started) { pc.setRemoteDescription(new RTCSessionDescription(msg)); } else if (msg.type === 'candidate' && started) { var candidate = new RTCIceCandidate({sdpMLineIndex:msg.label, candidate:msg.candidate}); pc.addIceCandidate(candidate);//?? } else if (msg.type === 'bye' && started) { onRemoteHangup(); } } function doAnswer() { console.log("Sending answer to peer."); pc.createAnswer(setLocalAndSendMessage, null, mediaConstraints); } //在哪裏設置msg.type?