WebRTC开发基础(WebRTC入门系列2:RTCPeerConnection)

课程地址:零声学院 WebRTC入门与提高 https://ke.qq.com/course/435382?tuin=137bb271

技术支持QQ群:782508536

webrtc时序图.png

版权声明:关于作者:王昕(QQ:475660) 十多年开发经验,在Delphi、ASP.net、Cobol、WPF、C#、SharePoint、Java、XMPP Server/Client、EPortal、BPM等领域较有经验,对开源技术有广泛兴趣,尤其关注门户平台和即时通讯技术,在广州工作生活30余年。 https://blog.csdn.net/starcrm/article/details/52576388

RTCPeerConnection的作用是在浏览器之间建立数据的“点对点”(peer to peer)通信.


使用WebRTC的编解码器和协议做了大量的工作,方便了开发者,使实时通信成为可能,甚至在不可靠的网络,

比如这些如果在voip体系下开发工作量将非常大,而用webRTC的js开发者则不用考虑这些,举几个例子:

  • 丢包隐藏
  • 回声抵消
  • 带宽自适应
  • 动态抖动缓冲
  • 自动增益控制
  • 噪声抑制与抑制
  • 图像清洗

不同客户端之间的音频/视频传递,是不用通过服务器的。但是,两个客户端之间建立信令联系,需要通过服务器。这个和XMPP的Jingle会话很类似。

服务器主要转递两种数据:

  • 通信内容的元数据:打开/关闭对话(session)的命令、媒体文件的元数据(编码格式、媒体类型和带宽)等。
  • 网络通信的元数据:IP地址、NAT网络地址翻译和防火墙等。

WebRTC协议没有规定与服务器的信令通信方式,因此可以采用各种方式,比如WebSocket。通过服务器,两个客户端按照Session Description Protocol(SDP协议)交换双方的元数据

本地和远端通讯的过程有些像电话,比如张三正在试着打电话给李四,详细机制:

  1. 张三创造了一个RTCPeerConnection 对象。
  2. 张三通过RTCPeerConnection createOffer()方法创造了一个offer(SDP会话描述) 。
  3. 张三通过他创建的offer调用setLocalDescription(),保存本地会话描述。
  4. 张三发送信令给李四。
  5. 李四接通带有李四offer的电话,调用setRemoteDescription() ,李四的RTCPeerConnection知道张三的设置(张三的本地描述到了李四这里,就成了李四的远程会话描述)。
  6. 李四调用createAnswer(),将李四的本地会话描述(local session description)成功回调。
  7. 李四调用setLocalDescription()设置他自己的本地局部描述。
  8. 李四发送应答信令answer给张三。
  9. 张三将李四的应答answer用setRemoteDescription()保存为远程会话描述(李四的remote session description)。

SDP详解

bitbucket的一步一步实验,动手代码

WebSocket简介(用于信令通信)
https://dzone.com/refcardz/html5-websocket
http://www.infoq.com/articles/Web-Sockets-Proxy-Servers

信令服务或开源webRTC框架
https://easyrtc.com/products/easyrtc-opensource
https://github.com/priologic/easyrtc
https://github.com/andyet/signalmaster

如果您一行代码都不想写,可以看看 vLine, OpenTok and Asterisk.
这里有一个单页应用程序。本地和远程的视频在一个网页,RTCPeerConnection objects 直接交换数据和消息。

https://webrtc.github.io/samples/src/content/peerconnection/pc1/

RTCPeerConnection

HTML代码:

<!DOCTYPE html>
<html>
<head>
......
</head>

<body>

  <div id="container">

    <h1><a href="//webrtc.github.io/samples/" title="WebRTC samples homepage">WebRTC samples</a> <span>Peer connection</span></h1>

    <video id="localVideo" autoplay muted></video>
    <video id="remoteVideo" autoplay></video>

    <div>
      <button id="startButton">Start</button>
      <button id="callButton">Call</button>
      <button id="hangupButton">Hang Up</button>
    </div>

  </div>

  <script src="../../../js/adapter.js"></script>
  <script src="../../../js/common.js"></script>
  <script src="js/main.js"></script>

  <script src="../../../js/lib/ga.js"></script>
</body>
</html>

这里是主要视图:

<video id="localVideo" autoplay muted></video>
<video id="remoteVideo" autoplay></video>

<div>
<button id="startButton">Start</button>
<button id="callButton">Call</button>
<button id="hangupButton">Hang Up</button>
</div>

适配器js代码:

/*
 *  Copyright (c) 2015 The WebRTC project authors. All Rights Reserved.
 *
 *  Use of this source code is governed by a BSD-style license
 *  that can be found in the LICENSE file in the root of the source
 *  tree.
 */

'use strict';

const startButton = document.getElementById('startButton');
const callButton = document.getElementById('callButton');
const hangupButton = document.getElementById('hangupButton');
callButton.disabled = true;
hangupButton.disabled = true;
startButton.addEventListener('click', start);
callButton.addEventListener('click', call);
hangupButton.addEventListener('click', hangup);

let startTime;
const localVideo = document.getElementById('localVideo');
const remoteVideo = document.getElementById('remoteVideo');

localVideo.addEventListener('loadedmetadata', function() {
  console.log(`Local video videoWidth: ${this.videoWidth}px,  videoHeight: ${this.videoHeight}px`);
});

remoteVideo.addEventListener('loadedmetadata', function() {
  console.log(`Remote video videoWidth: ${this.videoWidth}px,  videoHeight: ${this.videoHeight}px`);
});

remoteVideo.addEventListener('resize', () => {
  console.log(`Remote video size changed to ${remoteVideo.videoWidth}x${remoteVideo.videoHeight}`);
  // We'll use the first onsize callback as an indication that video has started
  // playing out.
  if (startTime) {
    const elapsedTime = window.performance.now() - startTime;
    console.log('Setup time: ' + elapsedTime.toFixed(3) + 'ms');
    startTime = null;
  }
});

let localStream;
let pc1;
let pc2;
const offerOptions = {
  offerToReceiveAudio: 1,
  offerToReceiveVideo: 1
};

function getName(pc) {
  return (pc === pc1) ? 'pc1' : 'pc2';
}

function getOtherPc(pc) {
  return (pc === pc1) ? pc2 : pc1;
}

async function start() {
  console.log('Requesting local stream');
  startButton.disabled = true;
  try {
    const stream = await navigator.mediaDevices.getUserMedia({audio: true, video: true});
    console.log('Received local stream');
    localVideo.srcObject = stream;
    localStream = stream;
    callButton.disabled = false;
  } catch (e) {
    alert(`getUserMedia() error: ${e.name}`);
  }
}

function getSelectedSdpSemantics() {
  const sdpSemanticsSelect = document.querySelector('#sdpSemantics');
  const option = sdpSemanticsSelect.options[sdpSemanticsSelect.selectedIndex];
  return option.value === '' ? {} : {sdpSemantics: option.value};
}

async function call() {
  callButton.disabled = true;
  hangupButton.disabled = false;
  console.log('Starting call');
  startTime = window.performance.now();
  const videoTracks = localStream.getVideoTracks();
  const audioTracks = localStream.getAudioTracks();
  if (videoTracks.length > 0) {
    console.log(`Using video device: ${videoTracks[0].label}`);
  }
  if (audioTracks.length > 0) {
    console.log(`Using audio device: ${audioTracks[0].label}`);
  }
  const configuration = getSelectedSdpSemantics();
  console.log('RTCPeerConnection configuration:', configuration);
  pc1 = new RTCPeerConnection(configuration);
  console.log('Created local peer connection object pc1');
  pc1.addEventListener('icecandidate', e => onIceCandidate(pc1, e));
  pc2 = new RTCPeerConnection(configuration);
  console.log('Created remote peer connection object pc2');
  pc2.addEventListener('icecandidate', e => onIceCandidate(pc2, e));
  pc1.addEventListener('iceconnectionstatechange', e => onIceStateChange(pc1, e));
  pc2.addEventListener('iceconnectionstatechange', e => onIceStateChange(pc2, e));
  pc2.addEventListener('track', gotRemoteStream);

  localStream.getTracks().forEach(track => pc1.addTrack(track, localStream));
  console.log('Added local stream to pc1');

  try {
    console.log('pc1 createOffer start');
    const offer = await pc1.createOffer(offerOptions);
    await onCreateOfferSuccess(offer);
  } catch (e) {
    onCreateSessionDescriptionError(e);
  }
}

function onCreateSessionDescriptionError(error) {
  console.log(`Failed to create session description: ${error.toString()}`);
}

async function onCreateOfferSuccess(desc) {
  console.log(`Offer from pc1\n${desc.sdp}`);
  console.log('pc1 setLocalDescription start');
  try {
    await pc1.setLocalDescription(desc);
    onSetLocalSuccess(pc1);
  } catch (e) {
    onSetSessionDescriptionError();
  }

  console.log('pc2 setRemoteDescription start');
  try {
    await pc2.setRemoteDescription(desc);
    onSetRemoteSuccess(pc2);
  } catch (e) {
    onSetSessionDescriptionError();
  }

  console.log('pc2 createAnswer start');
  // Since the 'remote' side has no media stream we need
  // to pass in the right constraints in order for it to
  // accept the incoming offer of audio and video.
  try {
    const answer = await pc2.createAnswer();
    await onCreateAnswerSuccess(answer);
  } catch (e) {
    onCreateSessionDescriptionError(e);
  }
}

function onSetLocalSuccess(pc) {
  console.log(`${getName(pc)} setLocalDescription complete`);
}

function onSetRemoteSuccess(pc) {
  console.log(`${getName(pc)} setRemoteDescription complete`);
}

function onSetSessionDescriptionError(error) {
  console.log(`Failed to set session description: ${error.toString()}`);
}

function gotRemoteStream(e) {
  if (remoteVideo.srcObject !== e.streams[0]) {
    remoteVideo.srcObject = e.streams[0];
    console.log('pc2 received remote stream');
  }
}

async function onCreateAnswerSuccess(desc) {
  console.log(`Answer from pc2:\n${desc.sdp}`);
  console.log('pc2 setLocalDescription start');
  try {
    await pc2.setLocalDescription(desc);
    onSetLocalSuccess(pc2);
  } catch (e) {
    onSetSessionDescriptionError(e);
  }
  console.log('pc1 setRemoteDescription start');
  try {
    await pc1.setRemoteDescription(desc);
    onSetRemoteSuccess(pc1);
  } catch (e) {
    onSetSessionDescriptionError(e);
  }
}

async function onIceCandidate(pc, event) {
  try {
    await (getOtherPc(pc).addIceCandidate(event.candidate));
    onAddIceCandidateSuccess(pc);
  } catch (e) {
    onAddIceCandidateError(pc, e);
  }
  console.log(`${getName(pc)} ICE candidate:\n${event.candidate ? event.candidate.candidate : '(null)'}`);
}

function onAddIceCandidateSuccess(pc) {
  console.log(`${getName(pc)} addIceCandidate success`);
}

function onAddIceCandidateError(pc, error) {
  console.log(`${getName(pc)} failed to add ICE Candidate: ${error.toString()}`);
}

function onIceStateChange(pc, event) {
  if (pc) {
    console.log(`${getName(pc)} ICE state: ${pc.iceConnectionState}`);
    console.log('ICE state change event: ', event);
  }
}

function hangup() {
  console.log('Ending call');
  pc1.close();
  pc2.close();
  pc1 = null;
  pc2 = null;
  hangupButton.disabled = true;
  callButton.disabled = false;
}

本地,呼叫者

// servers是配置文件(TURN and STUN配置)
pc1 = new webkitRTCPeerConnection(servers);
// ...
pc1.addStream(localStream); 

远端,被叫着
创建一个offer ,将其设定为PC1的局部描述(local description),PC2远程描述(remote description)。
这样可以不使用信令通讯,因为主叫和被叫都在同一网页上。

pc1.createOffer(gotDescription1);
//...
function gotDescription1(desc){
  pc1.setLocalDescription(desc);
  trace("Offer from pc1 \n" + desc.sdp);
  pc2.setRemoteDescription(desc);
  pc2.createAnswer(gotDescription2);
}

创建pc2,当pc1产生视频流,则显示在remoteVideo视频控件(video element):

pc2 = new webkitRTCPeerConnection(servers);
pc2.onaddstream = gotRemoteStream;
//...
function gotRemoteStream(e){
  remoteVideo.src = URL.createObjectURL(e.stream);
 trace('pc2 received remote stream');
}

运行结果:

Navigated to https://webrtc.github.io/samples/src/content/peerconnection/pc1/
adapter.js:32 This appears to be Chrome
common.js:8 12.639: Requesting local stream
adapter.js:32 chrome: {"audio":true,"video":true}
common.js:8 12.653: Received local stream
common.js:8 14.038: Local video videoWidth: 640px,  videoHeight: 480px
common.js:8 15.183: Starting call
common.js:8 15.183: Using video device: Integrated Camera (04f2:b39a)
common.js:8 15.183: Using audio device: 默认
common.js:8 15.185: Created local peer connection object pc1
common.js:8 15.186: Created remote peer connection object pc2
common.js:8 15.186: Added local stream to pc1
common.js:8 15.187: pc1 createOffer start
common.js:8 15.190: Offer from pc1
v=0
o=- 5740173043645401541 2 IN IP4 127.0.0.1
s=-
t=0 0
a=group:BUNDLE audio video
a=msid-semantic: WMS ZWvBmXl2Dax58ugXR3BYDITTKIIV1TYPqViT
m=audio 9 UDP/TLS/RTP/SAVPF 111 103 104 9 0 8 106 105 13 126
c=IN IP4 0.0.0.0
a=rtcp:9 IN IP4 0.0.0.0
a=ice-ufrag:MOApAbo/PL8Jl3m9
a=ice-pwd:dxcuXVAFcyVqbgwi0QdQNh0S
a=fingerprint:sha-256 5F:CB:FF:EF:73:09:BC:0A:6F:18:0C:DB:11:A5:AE:AF:37:49:37:71:D0:FE:BA:39:EC:53:6B:10:8C:8A:95:9E
a=setup:actpass
a=mid:audio
a=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level
a=extmap:3 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time
a=sendrecv
a=rtcp-mux
a=rtpmap:111 opus/48000/2
a=fmtp:111 minptime=10; useinbandfec=1
a=rtpmap:103 ISAC/16000
a=rtpmap:104 ISAC/32000
a=rtpmap:9 G722/8000
a=rtpmap:0 PCMU/8000
a=rtpmap:8 PCMA/8000
a=rtpmap:106 CN/32000
a=rtpmap:105 CN/16000
a=rtpmap:13 CN/8000
a=rtpmap:126 telephone-event/8000
a=maxptime:60
a=ssrc:32244674 cname:anS0gTF+aWAKlwYj
a=ssrc:32244674 msid:ZWvBmXl2Dax58ugXR3BYDITTKIIV1TYPqViT 8ae8dd85-bd5c-49ff-a9bd-f4b88f2663c7
a=ssrc:32244674 mslabel:ZWvBmXl2Dax58ugXR3BYDITTKIIV1TYPqViT
a=ssrc:32244674 label:8ae8dd85-bd5c-49ff-a9bd-f4b88f2663c7
m=video 9 UDP/TLS/RTP/SAVPF 100 116 117 96
c=IN IP4 0.0.0.0
a=rtcp:9 IN IP4 0.0.0.0
a=ice-ufrag:MOApAbo/PL8Jl3m9
a=ice-pwd:dxcuXVAFcyVqbgwi0QdQNh0S
a=fingerprint:sha-256 5F:CB:FF:EF:73:09:BC:0A:6F:18:0C:DB:11:A5:AE:AF:37:49:37:71:D0:FE:BA:39:EC:53:6B:10:8C:8A:95:9E
a=setup:actpass
a=mid:video
a=extmap:2 urn:ietf:params:rtp-hdrext:toffset
a=extmap:3 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time
a=extmap:4 urn:3gpp:video-orientation
a=sendrecv
a=rtcp-mux
a=rtpmap:100 VP8/90000
a=rtcp-fb:100 ccm fir
a=rtcp-fb:100 nack
a=rtcp-fb:100 nack pli
a=rtcp-fb:100 goog-remb
a=rtpmap:116 red/90000
a=rtpmap:117 ulpfec/90000
a=rtpmap:96 rtx/90000
a=fmtp:96 apt=100
a=ssrc-group:FID 1099776253 671187929
a=ssrc:1099776253 cname:anS0gTF+aWAKlwYj
a=ssrc:1099776253 msid:ZWvBmXl2Dax58ugXR3BYDITTKIIV1TYPqViT eda73070-3562-4daf-ae0d-143694f294d5
a=ssrc:1099776253 mslabel:ZWvBmXl2Dax58ugXR3BYDITTKIIV1TYPqViT
a=ssrc:1099776253 label:eda73070-3562-4daf-ae0d-143694f294d5
a=ssrc:671187929 cname:anS0gTF+aWAKlwYj
a=ssrc:671187929 msid:ZWvBmXl2Dax58ugXR3BYDITTKIIV1TYPqViT eda73070-3562-4daf-ae0d-143694f294d5
a=ssrc:671187929 mslabel:ZWvBmXl2Dax58ugXR3BYDITTKIIV1TYPqViT
a=ssrc:671187929 label:eda73070-3562-4daf-ae0d-143694f294d5
common.js:8 15.190: pc1 setLocalDescription start
common.js:8 15.191: pc2 setRemoteDescription start
common.js:8 15.192: pc2 createAnswer start
common.js:8 15.202: pc1 setLocalDescription complete
common.js:8 15.203: pc1 ICE candidate: 
candidate:2999745851 1 udp 2122260223 192.168.56.1 64106 typ host generation 0
common.js:8 15.204: pc1 ICE candidate: 
candidate:1425577752 1 udp 2122194687 172.17.22.106 64107 typ host generation 0
common.js:8 15.204: pc1 ICE candidate: 
candidate:2733511545 1 udp 2122129151 192.168.127.1 64108 typ host generation 0
common.js:8 15.204: pc1 ICE candidate: 
candidate:1030387485 1 udp 2122063615 192.168.204.1 64109 typ host generation 0
common.js:8 15.205: pc1 ICE candidate: 
candidate:3003979406 1 udp 2121998079 172.17.26.47 64110 typ host generation 0
common.js:8 15.206: pc2 setRemoteDescription complete
common.js:8 15.206: Answer from pc2:
v=0
o=- 3554329696104028001 2 IN IP4 127.0.0.1
s=-
t=0 0
a=group:BUNDLE audio video
a=msid-semantic: WMS
m=audio 9 UDP/TLS/RTP/SAVPF 111 103 104 9 0 8 106 105 13 126
c=IN IP4 0.0.0.0
a=rtcp:9 IN IP4 0.0.0.0
a=ice-ufrag:raDEDz+tkFTWXMn8
a=ice-pwd:RQ8bf7mtOXHIDQ0/vF25IRMf
a=fingerprint:sha-256 5F:CB:FF:EF:73:09:BC:0A:6F:18:0C:DB:11:A5:AE:AF:37:49:37:71:D0:FE:BA:39:EC:53:6B:10:8C:8A:95:9E
a=setup:active
a=mid:audio
a=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level
a=extmap:3 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time
a=recvonly
a=rtcp-mux
a=rtpmap:111 opus/48000/2
a=fmtp:111 minptime=10; useinbandfec=1
a=rtpmap:103 ISAC/16000
a=rtpmap:104 ISAC/32000
a=rtpmap:9 G722/8000
a=rtpmap:0 PCMU/8000
a=rtpmap:8 PCMA/8000
a=rtpmap:106 CN/32000
a=rtpmap:105 CN/16000
a=rtpmap:13 CN/8000
a=rtpmap:126 telephone-event/8000
a=maxptime:60
m=video 9 UDP/TLS/RTP/SAVPF 100 116 117 96
c=IN IP4 0.0.0.0
a=rtcp:9 IN IP4 0.0.0.0
a=ice-ufrag:raDEDz+tkFTWXMn8
a=ice-pwd:RQ8bf7mtOXHIDQ0/vF25IRMf
a=fingerprint:sha-256 5F:CB:FF:EF:73:09:BC:0A:6F:18:0C:DB:11:A5:AE:AF:37:49:37:71:D0:FE:BA:39:EC:53:6B:10:8C:8A:95:9E
a=setup:active
a=mid:video
a=extmap:2 urn:ietf:params:rtp-hdrext:toffset
a=extmap:3 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time
a=extmap:4 urn:3gpp:video-orientation
a=recvonly
a=rtcp-mux
a=rtpmap:100 VP8/90000
a=rtcp-fb:100 ccm fir
a=rtcp-fb:100 nack
a=rtcp-fb:100 nack pli
a=rtcp-fb:100 goog-remb
a=rtpmap:116 red/90000
a=rtpmap:117 ulpfec/90000
a=rtpmap:96 rtx/90000
a=fmtp:96 apt=100
common.js:8 15.207: pc2 setLocalDescription start
common.js:8 15.207: pc1 setRemoteDescription start
common.js:8 15.208: pc2 received remote stream
2common.js:8 15.209: pc1 addIceCandidate success
3common.js:8 15.210: pc1 addIceCandidate success
common.js:8 15.224: pc1 ICE candidate: 
candidate:2999745851 2 udp 2122260222 192.168.56.1 64111 typ host generation 0
common.js:8 15.224: pc1 ICE candidate: 
candidate:1425577752 2 udp 2122194686 172.17.22.106 64112 typ host generation 0
common.js:8 15.225: pc1 ICE candidate: 
candidate:2733511545 2 udp 2122129150 192.168.127.1 64113 typ host generation 0
common.js:8 15.225: pc1 ICE candidate: 
candidate:1030387485 2 udp 2122063614 192.168.204.1 64114 typ host generation 0
common.js:8 15.226: pc1 ICE candidate: 
candidate:3003979406 2 udp 2121998078 172.17.26.47 64115 typ host generation 0
common.js:8 15.226: pc1 ICE candidate: 
candidate:2999745851 1 udp 2122260223 192.168.56.1 64116 typ host generation 0
common.js:8 15.227: pc1 ICE candidate: 
candidate:1425577752 1 udp 2122194687 172.17.22.106 64117 typ host generation 0
common.js:8 15.227: pc1 ICE candidate: 
candidate:2733511545 1 udp 2122129151 192.168.127.1 64118 typ host generation 0
common.js:8 15.227: pc1 ICE candidate: 
candidate:1030387485 1 udp 2122063615 192.168.204.1 64119 typ host generation 0
common.js:8 15.228: pc1 ICE candidate: 
candidate:3003979406 1 udp 2121998079 172.17.26.47 64120 typ host generation 0
common.js:8 15.228: pc1 ICE candidate: 
candidate:2999745851 2 udp 2122260222 192.168.56.1 64121 typ host generation 0
common.js:8 15.228: pc1 ICE candidate: 
candidate:1425577752 2 udp 2122194686 172.17.22.106 64122 typ host generation 0
common.js:8 15.229: pc1 ICE candidate: 
candidate:2733511545 2 udp 2122129150 192.168.127.1 64123 typ host generation 0
common.js:8 15.229: pc1 ICE candidate: 
candidate:1030387485 2 udp 2122063614 192.168.204.1 64124 typ host generation 0
common.js:8 15.230: pc1 ICE candidate: 
candidate:3003979406 2 udp 2121998078 172.17.26.47 64125 typ host generation 0
common.js:8 15.231: pc1 addIceCandidate success
common.js:8 15.231: pc2 setLocalDescription complete
common.js:8 15.231: pc1 setRemoteDescription complete
common.js:8 15.231: pc1 addIceCandidate success
8common.js:8 15.232: pc1 addIceCandidate success
5common.js:8 15.233: pc1 addIceCandidate success
common.js:8 15.233: pc2 ICE state: checking
main.js:197 ICE state change event:  Event {isTrusted: true}
common.js:8 15.243: pc2 ICE candidate: 
candidate:2999745851 1 udp 2122260223 192.168.56.1 64126 typ host generation 0
common.js:8 15.246: pc2 ICE candidate: 
candidate:1425577752 1 udp 2122194687 172.17.22.106 64127 typ host generation 0
common.js:8 15.247: pc2 ICE candidate: 
candidate:2733511545 1 udp 2122129151 192.168.127.1 64128 typ host generation 0
common.js:8 15.248: pc2 ICE candidate: 
candidate:1030387485 1 udp 2122063615 192.168.204.1 64129 typ host generation 0
common.js:8 15.249: pc2 ICE candidate: 
candidate:3003979406 1 udp 2121998079 172.17.26.47 64130 typ host generation 0
common.js:8 15.250: pc1 ICE state: checking
main.js:197 ICE state change event:  Event {isTrusted: true}
5common.js:8 15.251: pc2 addIceCandidate success
common.js:8 16.271: pc1 ICE state: connected
main.js:197 ICE state change event:  Event {isTrusted: true}
common.js:8 16.272: pc2 ICE state: connected
main.js:197 ICE state change event:  Event {isTrusted: true}
common.js:8 16.326: Remote video size changed to 640x480
common.js:8 16.326: Setup time: 1142.795ms
common.js:8 16.326: Remote video videoWidth: 640px,  videoHeight: 480px
common.js:8 16.326: Remote video size changed to 640x480
common.js:8 18.447: Ending call

但在真实世界,不可能不通过服务器传送信令,WebRTC 两端必须通过服务器交换信令。

  • 用户相互发现对方和交换“真实世界”的信息,如姓名。
  • WebRTC客户端应用程序交换网络信息。
  • 视频格式和分辨率等交换数据。
  • 客户端应用穿越NAT网关和防火墙。

所以,你的服务器端需要实现的功能:

  • 用户发现和通信。
  • 信令通信。
  • NAT和防火墙的穿越。
  • 在点对点通信失败后的中继服务(补救服务)。

STUN协议和它的扩展TURN使用ICE framework。

ICE先试图在节点直接连接,通过最低的延迟,通过UDP协议。在这个过程中:STUN服务器启用NAT后面找到它的公共地址和端口。


STUN, TURN and signaling 介绍:
http://www.html5rocks.com/en/tutorials/webrtc/infrastructure/

许多现有的WebRTC应用只是Web浏览器之间的通信,但网关服务器可以使WebRTC应用在浏览器与设备如电话互动(PSTN和VoIP系统)。
2012五月,Doubango开源了sipml5 SIP客户端,sipml5是通过WebRTC和WebSocket,使视频和语音通话在浏览器和应用程序(iOS或Android)之间进行。

网址:https://www.doubango.org/sipml5/

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,634评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,951评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,427评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,770评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,835评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,799评论 1 294
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,768评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,544评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,979评论 1 308
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,271评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,427评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,121评论 5 340
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,756评论 3 324
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,375评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,579评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,410评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,315评论 2 352

推荐阅读更多精彩内容