WebRTC - RTCPeerConnection API
RTCPeerConnection API 是每个浏览器之间点对点连接的核心。要创建 RTCPeerConnection 对象,只需编写
var pc = RTCPeerConnection(config);
其中配置参数至少包含一个键,iceServers。它是一个 URL 对象数组,包含有关 STUN 和 TURN 服务器的信息,在查找 ICE 候选者期间使用。您可以在code.google.com上找到可用公共 STUN 服务器的列表
根据您是调用者还是被调用者,RTCPeerConnection 对象在连接两端的使用方式略有不同。
这是用户流程的示例 -
注册onicecandidate处理程序。它会在收到任何 ICE 候选者后将其发送给其他对等方。
注册onaddstream处理程序。一旦从远程对等点接收到视频流,它就会处理视频流的显示。
注册消息处理程序。您的信令服务器还应该有一个处理程序,用于处理从其他对等方收到的消息。如果消息包含RTCSessionDescription对象,则应使用setRemoteDescription()方法将其添加到RTCPeerConnection对象。如果消息包含RTCIceCandidate对象,则应使用addIceCandidate()方法将其添加到RTCPeerConnection对象。
利用getUserMedia()设置本地媒体流并使用addStream()方法将其添加到RTCPeerConnection对象。
开始报价/答复谈判流程。这是调用者流程与被调用者流程不同的唯一步骤。调用者使用createOffer()方法开始协商,并注册接收RTCSessionDescription对象的回调。然后,此回调应使用setLocalDescription()将此RTCSessionDescription对象添加到您的RTCPeerConnection对象。最后,调用者应使用信令服务器将此RTCSessionDescription发送到远程对等点。另一方面,被调用者注册相同的回调,但在createAnswer()方法中。请注意,只有在收到呼叫者的报价后,才会启动被呼叫者流程。
RTCPeerConnection API
特性
RTCPeerConnection.iceConnectionState(只读) - 返回描述连接状态的 RTCIceConnectionState 枚举。当该值更改时,将触发iceconnectionstatechange 事件。可能的值 -
新- ICE 代理正在等待远程候选人或收集地址
检查- ICE 代理有远程候选者,但尚未找到连接
已连接- ICE 代理已找到可用的连接,但仍在检查更远程的候选连接以获得更好的连接。
已完成- ICE 代理已找到可用的连接并停止测试远程候选者。
失败- ICE 代理已检查所有远程候选者,但未找到至少一个组件的匹配项。
断开连接- 至少一个组件不再存在。
关闭- ICE 代理关闭。
RTCPeerConnection.iceGatheringState(只读) - 返回一个 RTCIceGatheringState 枚举,描述连接的 ICE 收集状态 -
new - 对象刚刚创建。
收集- ICE 代理人正在收集候选人
完成ICE 代理已完成收集。
RTCPeerConnection.localDescription(只读) - 返回描述本地会话的 RTCSessionDescription。如果尚未设置,则可以为 null。
RTCPeerConnection.peerIdentity(只读) - 返回 RTCIdentityAssertion。它由 idp(域名)和代表远程对等点身份的名称组成。
RTCPeerConnection.remoteDescription(只读) - 返回描述远程会话的 RTCSessionDescription。如果尚未设置,则可以为 null。
RTCPeerConnection.signalingState(只读) - 返回描述本地连接的信令状态的 RTCSignalingState 枚举。该状态描述了 SDP 提议。当该值更改时,将触发signalingstatechange 事件。可能的值 -
稳定- 初始状态。没有正在进行的 SDP 提议/应答交换。
have-local-offer - 连接的本地端已在本地应用 SDP Offer。
have-remote-offer - 连接的远程端已在本地应用 SDP Offer。
have-local-pranswer - 已应用远程 SDP Offer,并在本地应用 SDP pranswer。
have-remote-pranswer - 已应用本地 SDP,并远程应用 SDP pranswer。
已关闭- 连接已关闭。
事件处理程序
| 编号 | 事件处理程序和描述 | 
|---|---|
| 1 | RTCPeerConnection.onaddstream 当 addstream 事件被触发时,这个处理程序被调用。当远程对等方将 MediaStream 添加到此连接时,将发送此事件。  | 
| 2 | RTCPeerConnection.ondatachannel 当 datachannel 事件被触发时,这个处理程序被调用。当 RTCDataChannel 添加到此连接时发送此事件。  | 
| 3 | RTCPeerConnection.onicecandidate 当icecandidate事件被触发时,这个处理程序被调用。当 RTCIceCandidate 对象添加到脚本时发送此事件。  | 
| 4 | RTCPeerConnection.oniceconnectionstatechange 当iceconnectionstatechange事件被触发时,这个处理程序被调用。当iceConnectionState的值改变时发送该事件。  | 
| 5 | RTCPeerConnection.onidentityresult 当 IdentityResult 事件被触发时,将调用此处理程序。在通过 getIdentityAssertion() 创建报价或应答期间生成身份断言时,会发送此事件。  | 
| 6 | RTCPeerConnection.onidpassertionerror 当 idpassertionerror 事件被触发时,将调用此处理程序。当 IdP(身份提供商)在生成身份断言时发现错误时发送此事件。  | 
| 7 | RTCPeerConnection.onidpvalidation 当 idpvalidationerror 事件被触发时,将调用此处理程序。当 IdP(身份提供商)在验证身份断言时发现错误时发送此事件。  | 
| 8 | RTCPeerConnection.onnegotiationneeded 当 Negotiationneeded 事件被触发时,将调用此处理程序。该事件由浏览器发送,通知将来某个时刻需要进行协商。  | 
| 9 | RTCPeerConnection.onpeeridentity 当peeridentity事件被触发时,这个处理程序被调用。当在此连接上设置并验证对等身份时发送此事件。  | 
| 10 | RTCPeerConnection.onremovestream 当signalingstatechange事件被触发时,这个处理程序被调用。当signalingState的值改变时发送该事件。  | 
| 11 | RTCPeerConnection.onsignalingstatechange 当removestream 事件被触发时,这个处理程序被调用。当 MediaStream 从此连接中删除时发送此事件。  | 
方法
| 编号 | 方法与说明 | 
|---|---|
| 1 | RTCPeerConnection() 返回一个新的 RTCPeerConnection 对象。  | 
| 2 | RTCPeerConnection.createOffer() 创建一个要约(请求)以查找远程对等点。该方法的前两个参数是成功回调和错误回调。可选的第三个参数是选项,例如启用音频或视频流。  | 
| 3 | RTCPeerConnection.createAnswer() 在要约/应答协商过程中创建对远程对等方收到的要约的应答。该方法的前两个参数是成功回调和错误回调。可选的第三个参数是要创建的答案的选项。  | 
| 4 | RTCPeerConnection.setLocalDescription() 更改本地连接描述。该描述定义了连接的属性。连接必须能够支持新旧描述。该方法采用三个参数,RTCSessionDescription 对象,如果描述更改成功则回调,如果描述更改失败则回调。  | 
| 5 | RTCPeerConnection.setRemoteDescription() 更改远程连接描述。该描述定义了连接的属性。连接必须能够支持新旧描述。该方法采用三个参数,RTCSessionDescription 对象,如果描述更改成功则回调,如果描述更改失败则回调。  | 
| 6 | RTCPeerConnection.updateIce() 更新 ICE 代理流程,以 ping 远程候选人并收集本地候选人。  | 
| 7 | RTCPeerConnection.addIceCandidate() 为 ICE 代理提供远程候选人。  | 
| 8 | RTCPeerConnection.getConfiguration() 返回 RTCConfiguration 对象。它表示 RTCPeerConnection 对象的配置。  | 
| 9 | RTCPeerConnection.getLocalStreams() 返回本地 MediaStream 连接的数组。  | 
| 10 | RTCPeerConnection.getRemoteStreams() 返回远程 MediaStream 连接的数组。  | 
| 11 | RTCPeerConnection.getStreamById() 按给定 ID 返回本地或远程 MediaStream。  | 
| 12 | RTCPeerConnection.addStream() 添加 MediaStream 作为本地视频或音频源。  | 
| 13 | RTCPeerConnection.removeStream() 删除作为本地视频或音频源的 MediaStream。  | 
| 14 | RTCPeerConnection.close() 关闭连接。  | 
| 15 | RTCPeerConnection.createDataChannel() 创建一个新的 RTCDataChannel。  | 
| 16 | RTCPeerConnection.createDTMFSender() 创建一个与特定 MediaStreamTrack 关联的新 RTCDTMFSender。允许通过连接发送 DTMF(双音多频)电话信令。  | 
| 17 号 | RTCPeerConnection.getStats() 创建一个新的 RTCStatsReport,其中包含有关连接的统计信息。  | 
| 18 | RTCPeerConnection.setIdentityProvider() 设置 IdP。采用三个参数 - 名称、用于通信的协议和可选的用户名。  | 
| 19 | RTCPeerConnection.getIdentityAssertion() 收集身份断言。预计应用程序中不会处理此方法。因此,您可以明确地调用它,只是为了预测需要。  | 
建立连接
现在让我们创建一个示例应用程序。首先,通过“节点服务器”运行我们在“信令服务器”教程中创建的信令服务器。
页面上将有两个文本输入,一个用于登录,一个用于我们要连接的用户名。创建一个index.html文件并添加以下代码 -
<html lang = "en"> 
   <head> 
      <meta charset = "utf-8" /> 
   </head>
	
   <body> 
	
      <div> 
         <input type = "text" id = "loginInput" /> 
         <button id = "loginBtn">Login</button> 
      </div> 
	
      <div> 
         <input type = "text" id = "otherUsernameInput" />
         <button id = "connectToOtherUsernameBtn">Establish connection</button> 
      </div> 
		
      <script src = "client2.js"></script>
		
   </body>
	
</html>
您可以看到我们添加了登录的文本输入、登录按钮、其他对等用户名的文本输入以及连接到他的按钮。现在创建一个client.js文件并添加以下代码 -
var connection = new WebSocket('ws://localhost:9090'); 
var name = ""; 
 
var loginInput = document.querySelector('#loginInput'); 
var loginBtn = document.querySelector('#loginBtn'); 
var otherUsernameInput = document.querySelector('#otherUsernameInput'); 
var connectToOtherUsernameBtn = document.querySelector('#connectToOtherUsernameBtn'); 
var connectedUser, myConnection;
  
//when a user clicks the login button 
loginBtn.addEventListener("click", function(event){ 
   name = loginInput.value; 
	
   if(name.length > 0){ 
      send({ 
         type: "login", 
         name: name 
      }); 
   } 
	
});
  
//handle messages from the server 
connection.onmessage = function (message) { 
   console.log("Got message", message.data);
   var data = JSON.parse(message.data); 
	
   switch(data.type) { 
      case "login": 
         onLogin(data.success); 
         break; 
      case "offer": 
         onOffer(data.offer, data.name); 
         break; 
      case "answer": 
         onAnswer(data.answer); 
         break; 
      case "candidate": 
         onCandidate(data.candidate); 
         break; 
      default: 
         break; 
   } 
};
  
//when a user logs in 
function onLogin(success) { 
   if (success === false) { 
      alert("oops...try a different username"); 
   } else { 
      //creating our RTCPeerConnection object 
		
      var configuration = { 
         "iceServers": [{ "url": "stun:stun.1.google.com:19302" }] 
      }; 
		
      myConnection = new webkitRTCPeerConnection(configuration); 
      console.log("RTCPeerConnection object was created"); 
      console.log(myConnection); 
  
      //setup ice handling
      //when the browser finds an ice candidate we send it to another peer 
      myConnection.onicecandidate = function (event) { 
		
         if (event.candidate) { 
            send({ 
               type: "candidate", 
               candidate: event.candidate 
            }); 
         } 
      }; 
   } 
};
  
connection.onopen = function () { 
   console.log("Connected"); 
};
  
connection.onerror = function (err) { 
   console.log("Got error", err); 
};
  
// Alias for sending messages in JSON format 
function send(message) { 
   if (connectedUser) { 
      message.name = connectedUser; 
   } 
	
   connection.send(JSON.stringify(message)); 
};
您可以看到我们与信令服务器建立了套接字连接。当用户单击登录按钮时,应用程序将其用户名发送到服务器。如果登录成功,应用程序将创建 RTCPeerConnection 对象并设置 onicecandidate 处理程序,该处理程序将所有找到的icecandidate 发送到其他对等方。现在打开页面并尝试登录。您应该看到以下控制台输出 -
下一步是向其他同行创建报价。将以下代码添加到您的client.js文件中 -
//setup a peer connection with another user 
connectToOtherUsernameBtn.addEventListener("click", function () { 
 
   var otherUsername = otherUsernameInput.value; 
   connectedUser = otherUsername;
	
   if (otherUsername.length > 0) { 
      //make an offer 
      myConnection.createOffer(function (offer) { 
         console.log(); 
         send({ 
            type: "offer", 
            offer: offer 
         });
			
         myConnection.setLocalDescription(offer); 
      }, function (error) { 
         alert("An error has occurred."); 
      }); 
   } 
}); 
 
//when somebody wants to call us 
function onOffer(offer, name) { 
   connectedUser = name; 
   myConnection.setRemoteDescription(new RTCSessionDescription(offer)); 
	
   myConnection.createAnswer(function (answer) { 
      myConnection.setLocalDescription(answer); 
		
      send({ 
         type: "answer", 
         answer: answer 
      }); 
		
   }, function (error) { 
      alert("oops...error"); 
   }); 
}
  
//when another user answers to our offer 
function onAnswer(answer) { 
   myConnection.setRemoteDescription(new RTCSessionDescription(answer)); 
} 
 
//when we got ice candidate from another user 
function onCandidate(candidate) { 
   myConnection.addIceCandidate(new RTCIceCandidate(candidate)); 
}	
您可以看到,当用户单击“建立连接”按钮时,应用程序向其他对等点发出 SDP 提议。我们还设置了onAnswer和onCandidate处理程序。重新加载您的页面,在两个选项卡中打开它,使用两个用户登录并尝试在他们之间建立连接。您应该看到以下控制台输出 -
现在点对点连接已建立。在接下来的教程中,我们将添加视频和音频流以及文本聊天支持。