概念
-
WebRTC基础介绍
webRTC 的最直接用途是两个浏览器间的p2p,不过加上中转服务器后也可以用来做其他的。
webRTC 是基于UDP的,作为对比,webSocket 是基于TCP的。
-
- Mesh: p2p
- SFU: 中心节点只转发
- MCU:中心节点混流
-
- STUN 只处理地址信息
- TURN 提供转发功能
流程
除了getUserMedia之外,主要是两件事情:
- 两端间确定流媒体的格式(SDP)
- 两端间确定对方的网络地址(ICE), 由于浏览器极有可能是在内网中的,所以需要有一般的p2p内网穿透的过程
调试工具
chrome://webrtc-internals/
https://test.webrtc.org/
代码
getUserMedia
<!doctype html>
<html lang="zh-CN">
<head> <meta charset="UTF-8">
<title>GetUserMedia实例</title>
</head>
<body>
<video id="video" autoplay></video>
</body>
<script type="text/javascript" src="webRTC.js"></script>
</html>
// simple camera
var constraints = {
video: true
};
function successCallback(stream) {
var video = document.querySelector("video");
//window.URL.createObjectURL is deprecated, so just assign the stream directly
//video.src=window.URL.createObjectURL(stream);
video.srcObject = stream;
}
function errorCallback(error) {
console.log("navigator.getUserMedia error:", error);
}
navigator.getUserMedia(constraints, successCallback, errorCallback);
local
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>webrtc案例</title>
</head>
<body>
<div class="container">
<h1>单机版视频呼叫</h1>
<hr>
<div class="video_container" align="center">
<video id="local_video" autoplay playsinline muted></video>
<video id="remote_video" autoplay></video>
</div>
<hr>
<div class="button_container">
<button id="startButton">采集视频</button>
<button id="callButton">呼叫</button>
<button id="hangupButton">关闭</button>
</div>
<script src="main.js"></script>
</div>
</body>
</html>
var startButton = document.getElementById('startButton');
var callButton = document.getElementById('callButton');
var hangupButton = document.getElementById('hangupButton');
callButton.disabled = true;
hangupButton.disabled = true;
startButton.addEventListener('click', startAction);
callButton.addEventListener('click', callAction);
hangupButton.addEventListener('click', hangupAction);
var localVideo = document.getElementById('local_video');
var remoteVideo = document.getElementById('remote_video');
var localStream;
var pc1;
var pc2;
const offerOptions = {
offerToReceiveVideo: 1,
offerToReceiveAudio: 1
};
function startAction() {
//采集摄像头视频
navigator.mediaDevices.getUserMedia({
video: true,
audio: true
})
.then(function(mediaStream) {
localStream = mediaStream;
localVideo.srcObject = mediaStream;
startButton.disabled = true;
callButton.disabled = false;
}).catch(function(error) {
console.log(JSON.stringify(error));
});
}
function callAction() {
hangupButton.disabled = false;
callButton.disabled = true;
pc1 = new RTCPeerConnection();
pc1.addEventListener('icecandidate', function(event) {
console.log("p1 ice candidate:", event);
var iceCandidate = event.candidate;
if (iceCandidate) {
// 实际通信中,这里应为pc1 把iceCandidate发送给Signal服务器,Signal服务器(通过websocket?)把p1 candidate的信息告诉p2
pc2.addIceCandidate(iceCandidate);
}
});
localStream.getTracks().forEach(track => pc1.addTrack(track, localStream));
pc2 = new RTCPeerConnection();
pc2.addEventListener('addstream', function(event) {
console.log("p2 add stream", event)
remoteVideo.srcObject = event.stream;
});
pc1.createOffer(offerOptions).then(function(offer) {
console.log("p1 createOffer", offer);
pc1.setLocalDescription(offer);
// 实际通信中, p1 应该把offer 通过Signal服务器发给p2
pc2.setRemoteDescription(offer);
pc2.createAnswer().then(function(description) {
console.log("p2 createAnswer", description);
pc2.setLocalDescription(description);
// 实际通信中, p2 应该把 answer 通过Signal服务器发给p1
pc1.setRemoteDescription(description);
});
});
}
function hangupAction() {
localStream.getTracks().forEach(track => track.stop());
pc1.close();
pc2.close();
pc1 = null;
pc2 = null;
hangupButton.disabled = true;
callButton.disabled = true;
startButton.disabled = false;
}