[TOC] ## 视频传输 调用`new RTCPeerConnection(configuration)`会创建一个`RTCPeerConnection`对象,该对象是两个用户/浏览器之间通信通道的抽象,可以为特定的`MediaStream`输入或输出 ![](https://a-wing.github.io/webrtc-book-cn/assets/img/rcwr_0301.d47be7a7.png) 将`MediaStream`添加到`PeerConnection` ### 示例 <details> <summary>本地模拟远程 peerConnect index.html</summary> ``` <!doctype html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>Document</title> </head> <body> <table border="1" width="100%"> <tr> <th>Local video</th> <th>'Remote' video</th> </tr> <tr> <td><video id="localVideo" autoplay></video></td> <td><video id="remoteVideo" autoplay></video></td> </tr> <tr> <td align="center"> <div> <button id="startButton">Start</button> <button id="callButton">Call</button> <button id="hangupButton">Hang Up</button> </div> </td> <td><!-- void --></td> </tr> </table> <script lang="javascript"> var localStream, localPeerConnection, remotePeerConnection; var localVideo = document.getElementById("localVideo"); var remoteVideo = document.getElementById("remoteVideo"); // 按钮 DOM var startButton = document.getElementById("startButton"); var callButton = document.getElementById("callButton"); var hangupButton = document.getElementById("hangupButton"); startButton.disabled = false; callButton.disabled = true; hangupButton.disabled = true; startButton.onclick = start; callButton.onclick = call; hangupButton.onclick = hangup; function log(text) { console.log("At time: " + (performance.now() / 1000).toFixed(3) + " --> " + text); } function successCallback(stream) { log("Received local stream"); if (window.URL) { // localVideo.src = URL.createObjectURL(stream); localVideo.srcObject=stream; } else { localVideo.src = stream; } localStream = stream; callButton.disabled = false; } // 获取本地流 function start() { log("Requesting local stream"); startButton.disabled = true; navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia; let conf = {audio: false, video: true}; navigator.getUserMedia(conf, successCallback, function(error) { log("navigator.getUserMedia error: ", error); }); } // 发送给远程 steam function call() { callButton.disabled = true; hangupButton.disabled = false; log("Starting call"); if (navigator.webkitGetUserMedia) { if (localStream.getVideoTracks().length > 0) { log('Using video device: ' + localStream.getVideoTracks()[0].label); } if (localStream.getAudioTracks().length > 0) { log('Using audio device: ' + localStream.getAudioTracks()[0].label); } } // Chrome if (navigator.webkitGetUserMedia) { RTCPeerConnection = webkitRTCPeerConnection; // Firefox } else if(navigator.mozGetUserMedia) { RTCPeerConnection = mozRTCPeerConnection; RTCSessionDescription = mozRTCSessionDescription; RTCIceCandidate = mozRTCIceCandidate; } log("RTCPeerConnection object: " + RTCPeerConnection); // NAT遍历设置 var servers = {asd:"asd"}; // 创建 localPeerConnect localPeerConnection = new RTCPeerConnection(servers); log("Created local peer connection object localPeerConnection"); // 添加 ICE 协议处理 localPeerConnection.onicecandidate = gotLocalIceCandidate; // 创建 remotePeerConnect remotePeerConnection = new RTCPeerConnection(servers); log("Created remote peer connection object remotePeerConnection"); // 添加 ICE 协议处理 remotePeerConnection.onicecandidate = gotRemoteIceCandidate; // 处理添加remote steam的处理 remotePeerConnection.onaddstream = gotRemoteStream; // 添加本地流 localPeerConnection.addStream(localStream); log("Added localStream to localPeerConnection"); localPeerConnection.createOffer(gotLocalDescription, onSignalingError); } function onSignalingError(error) { console.log('Failed to create signaling message : ' + error.name); } function gotLocalDescription(description) { localPeerConnection.setLocalDescription(description); log("Offer from localPeerConnection: \n" + description.sdp); remotePeerConnection.setRemoteDescription(description); remotePeerConnection.createAnswer(gotRemoteDescription, onSignalingError); } function gotRemoteDescription(description){ remotePeerConnection.setLocalDescription(description); log("Answer from remotePeerConnection: \n" + description.sdp); localPeerConnection.setRemoteDescription(description); } // 挂断 function hangup() { log("Ending call"); localPeerConnection.close(); remotePeerConnection.close(); localPeerConnection = null; remotePeerConnection = null; hangupButton.disabled = true; callButton.disabled = false; } function gotRemoteStream(event){ if (window.URL) { // Chrome; // remoteVideo.src = window.URL.createObjectURL(event.stream); remoteVideo.srcObject=event.stream; } else { // Firefox; remoteVideo.src = event.stream; } log("Received remote stream"); } function gotLocalIceCandidate(event){ if (event.candidate) { remotePeerConnection.addIceCandidate(new RTCIceCandidate(event.candidate)); log("Local ICE candidate: \n" + event.candidate.candidate); } } function gotRemoteIceCandidate(event){ if (event.candidate) { localPeerConnection.addIceCandidate(new RTCIceCandidate(event.candidate)); log("Remote ICE candidate: \n " + event.candidate.candidate); } } </script> </body> </html> ``` </details> <br /> ## 将数据通道添加到本地 PeerConnection <details> <summary>index.html</summary> ``` <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <title>DataChannel simple example</title> </head> <body> <textarea rows="5" cols="50" id="dataChannelSend" disabled placeholder="1: Press Start; 2: Enter text; 3: Press Send."></textarea> <textarea rows="5" cols="50" id="dataChannelReceive" disabled></textarea> <div id="buttons"> <button id="startButton">Start</button> <button id="sendButton">Send</button> <button id="closeButton">Stop</button> </div> <script lang="javascript"> //JavaScript variables associated with send and receive channels var sendChannel, receiveChannel,localPeerConnection; //JavaScript variables associated with demo buttons var startButton = document.getElementById("startButton"); var sendButton = document.getElementById("sendButton"); var closeButton = document.getElementById("closeButton"); //On startup, just the Start button must be enabled startButton.disabled = false; sendButton.disabled = true; closeButton.disabled = true; //Associate handlers with buttons startButton.onclick = createConnection; sendButton.onclick = sendData; closeButton.onclick = closeDataChannels; //Utility function for logging information to the JavaScript console function log(text) { console.log("At time: " + (performance.now() / 1000).toFixed(3) +" --> " + text); } function createConnection() { // Chrome if (navigator.webkitGetUserMedia) { RTCPeerConnection = webkitRTCPeerConnection; // Firefox } else if(navigator.mozGetUserMedia) { RTCPeerConnection = mozRTCPeerConnection; RTCSessionDescription = mozRTCSessionDescription; RTCIceCandidate = mozRTCIceCandidate; } log("RTCPeerConnection object: " + RTCPeerConnection); // This is an optional configuration string // associated with NAT traversal setup var servers = null; // JavaScript variable associated with proper // configuration of an RTCPeerConnection object: // use DTLS/SRTP var pc_constraints = { 'optional': [{ 'DtlsSrtpKeyAgreement': true}]}; // Create the local PeerConnection object... // ...with data channels localPeerConnection = new RTCPeerConnection(servers,pc_constraints); log("Created local peer connection object, with Data Channel"); try { // Note: SCTP-based reliable DataChannels supported // in Chrome 29+ ! // use {reliable: false} if you have an older version of Chrome sendChannel = localPeerConnection.createDataChannel( "sendDataChannel",{reliable: true}); log('Created reliable send data channel'); } catch (e) { alert('Failed to create data channel!'); log('createDataChannel() failed with following message: ' + e.message); } // Associate handlers with peer connection ICE events localPeerConnection.onicecandidate = gotLocalCandidate; // 本地 打开关闭时间 sendChannel.onopen = handleSendChannelStateChange; sendChannel.onclose = handleSendChannelStateChange; // Mimic a remote peer connection window.remotePeerConnection = new RTCPeerConnection(servers, pc_constraints); log('Created remote peer connection object, with DataChannel'); // Associate handlers with peer connection ICE events... remotePeerConnection.onicecandidate = gotRemoteIceCandidate; // 远程打开时间 remotePeerConnection.ondatachannel = gotReceiveChannel; // We're all set! Let's start negotiating a session... localPeerConnection.createOffer(gotLocalDescription,onSignalingError); // Disable Start button and enable Close button startButton.disabled = true; closeButton.disabled = false; } function onSignalingError(error) { console.log('Failed to create signaling message : ' + error.name); } // Handler for sending data to the remote peer function sendData() { var data = document.getElementById("dataChannelSend").value; sendChannel.send(data); log('Sent data: ' + data); } // Close button handler function closeDataChannels() { // Close channels... log('Closing data channels'); sendChannel.close(); log('Closed data channel with label: ' + sendChannel.label); receiveChannel.close(); log('Closed data channel with label: ' + receiveChannel.label); // Close peer connections localPeerConnection.close(); remotePeerConnection.close(); // Reset local variables localPeerConnection = null; remotePeerConnection = null; log('Closed peer connections'); // Rollback to the initial setup of the HTML5 page startButton.disabled = false; sendButton.disabled = true; closeButton.disabled = true; dataChannelSend.value = ""; dataChannelReceive.value = ""; dataChannelSend.disabled = true; dataChannelSend.placeholder = "1: Press Start; 2: Enter text; 3: Press Send."; } // Handler to be called as soon as the local SDP is made available to // the application function gotLocalDescription(desc) { // Set local SDP as the right (local/remote) description for both local // and remote parties localPeerConnection.setLocalDescription(desc); log('localPeerConnection\'s SDP: \n' + desc.sdp); remotePeerConnection.setRemoteDescription(desc); // Create answer from the remote party, based on the local SDP remotePeerConnection.createAnswer(gotRemoteDescription,onSignalingError); } // Handler to be called as soon as the remote SDP is made available to // the application function gotRemoteDescription(desc) { // Set remote SDP as the right (remote/local) description for both local // and remote parties remotePeerConnection.setLocalDescription(desc); log('Answer from remotePeerConnection\'s SDP: \n' + desc.sdp); localPeerConnection.setRemoteDescription(desc); } // Handler to be called whenever a new local ICE candidate becomes available function gotLocalCandidate(event) { log('local ice callback'); if (event.candidate) { remotePeerConnection.addIceCandidate(event.candidate); log('Local ICE candidate: \n' + event.candidate.candidate); } } // Handler to be called whenever a new remote ICE candidate becomes available function gotRemoteIceCandidate(event) { log('remote ice callback'); if (event.candidate) { localPeerConnection.addIceCandidate(event.candidate); log('Remote ICE candidate: \n ' + event.candidate.candidate); } } // Handler associated with the management of remote peer connection's // data channel events function gotReceiveChannel(event) { log('Receive Channel Callback: event --> ' + event); // Retrieve channel information receiveChannel = event.channel; // Set handlers for the following events: // (i) open; (ii) message; (iii) close receiveChannel.onopen = handleReceiveChannelStateChange; receiveChannel.onmessage = handleMessage; receiveChannel.onclose = handleReceiveChannelStateChange; } // Message event handler function handleMessage(event) { log('Received message: ' + event.data); // Show message in the HTML5 page document.getElementById("dataChannelReceive").value = event.data; // Clean 'Send' text area in the HTML page document.getElementById("dataChannelSend").value = ''; } // Handler for either 'open' or 'close' events on sender's data channel function handleSendChannelStateChange() { var readyState = sendChannel.readyState; log('Send channel state is: ' + readyState); if (readyState == "open") { // Enable 'Send' text area and set focus on it dataChannelSend.disabled = false; dataChannelSend.focus(); dataChannelSend.placeholder = ""; // Enable both Send and Close buttons sendButton.disabled = false; closeButton.disabled = false; } else { // event MUST be 'close', if we are here... // Disable 'Send' text area dataChannelSend.disabled = true; // Disable both Send and Close buttons sendButton.disabled = true; closeButton.disabled = true; } } // Handler for either 'open' or 'close' events on receiver's data channel function handleReceiveChannelStateChange() { var readyState = receiveChannel.readyState; log('Receive channel state is: ' + readyState); } </script> </body> </html> ``` </details> <br />