本章讲解端对端传输如何进行媒体能力的协商,以及其处理的基本流程。这部分知识非常重要,了解了这部分知识基本上就撑握了WebRTC的一半,它们是实现真正的 1V1实时互动直播的基础。
10-1 【必备原理】媒体能力协商过程一
Bob提前准备好没有音视频流的Answer,只是建立连接。
10-2 【必备原理】媒体能力协商过程二
10-3 【必备原理】1:1连接的基本流程
上面的
媒体协商部分Offer-Answer
候选者的连接与检测部分Candidate
媒体数据流的通信部分 Stream
10-4-5 【来点实战】本机内的1:1音视频互通
1)效果
2)代码
index.html
<html>
<head>
<title>RTCPeerConnection</title>
<link rel="stylesheet" href="css/main.css"/>
</head>
<body>
<div>
<video id="localVideo" autoplay playsinline></video>
<video id="remoteVideo" autoplay playsinline></video>
<div>
<button id="start">start</button>
<button id="call">call</button>
<button id="hangup">hang up</button>
</div>
</div>
<script src="https://webrtc.github.io/adapter/adapter-latest.js"></script>
<script src="./js/main.js"></script>
</body>
</html>
main.js
'use strict'
var localVideo = document.querySelector('video#localVideo');
var remoteVideo = document.querySelector('video#remoteVideo');
var btnStart = document.querySelector('button#start');
var btnCall = document.querySelector('button#call');
var btnHangUp= document.querySelector('button#hangup');
var localStream;
var pc1;
var pc2;
function gotMediaStream(stream){
localVideo.srcObject = stream;
localStream = stream;
}
function handleError(err){
console.log("Failed to call getUserMedia", err);
}
//开始建立连接
function start(){
var constraints = {
video: true,
audio: false
}
if(!navigator.mediaDevices ||
!navigator.mediaDevices.getUserMedia){
return;
}else {
navigator.mediaDevices.getUserMedia(constraints)
.then(gotMediaStream)
.catch(handleError);
}
}
function gotAnswerDescription(desc){
pc2.setLocalDescription(desc);
//send sdp to caller
//recieve sdp from callee
pc1.setRemoteDescription(desc);
}
function gotLocalDescription(desc){
pc1.setLocalDescription(desc);
//send sdp to callee
//receive sdp from caller
pc2.setRemoteDescription(desc);
pc2.createAnswer().then(gotAnswerDescription)
.catch(handleError);
}
function gotRemoteStream(e){
if(remoteVideo.srcObject !== e.streams[0]){
remoteVideo.srcObject = e.streams[0];
}
}
//呼叫函数
function call(){
if(!localStream)
{
console.log('no start');
return;
}
var offerOptions = {
offerToReceiveAudio: 0,
offerToReceiveVideo: 1
}
if(!pc1)
pc1 = new RTCPeerConnection();
pc1.onicecandidate = (e) => {
// send candidate to peer
// receive candidate from peer
pc2.addIceCandidate(e.candidate)
.catch(handleError);
console.log('pc1 ICE candidate:', e.candidate);
}
pc1.iceconnectionstatechange = (e) => {
console.log(`pc1 ICE state: ${pc.iceConnectionState}`);
console.log('ICE state change event: ', e);
}
if(!pc2)
pc2 = new RTCPeerConnection();
pc2.onicecandidate = (e)=> {
// send candidate to peer
// receive candidate from peer
pc1.addIceCandidate(e.candidate)
.catch(handleError);
console.log('pc2 ICE candidate:', e.candidate);
}
pc2.iceconnectionstatechange = (e) => {
console.log(`pc2 ICE state: ${pc.iceConnectionState}`);
console.log('ICE state change event: ', e);
}
pc2.ontrack = gotRemoteStream;
//add Stream to caller
localStream.getTracks().forEach((track)=>{
pc1.addTrack(track, localStream);
});
pc1.createOffer(offerOptions)
.then(gotLocalDescription)
.catch(handleError);
}
function hangup(){
if(pc1)
pc1.close();
if(pc2)
pc2.close();
pc1 = null;
pc2 = null;
}
btnStart.onclick = start;
btnCall.onclick = call;
btnHangUp.onclick = hangup;
server.js
使用第八章的server.js
'use strict'
var log4js = require('log4js');
var http = require('http');
var https = require('https');
var fs = require('fs');
var socketIo = require('socket.io');
var express = require('express');
var serveIndex = require('serve-index');
var USERCOUNT = 3;
log4js.configure({
appenders: {
file: {
type: 'file',
filename: 'app.log',
layout: {
type: 'pattern',
pattern: '%r %p - %m',
}
}
},
categories: {
default: {
appenders: ['file'],
level: 'debug'
}
}
});
var logger = log4js.getLogger();
var app = express();
app.use(serveIndex('./public'));
app.use(express.static('./public'));
//http server
var http_server = http.createServer(app);
http_server.listen(80, '0.0.0.0');
var options = {
key : fs.readFileSync('./cert/1557605_www.learningrtc.cn.key'),
cert: fs.readFileSync('./cert/1557605_www.learningrtc.cn.pem')
}
//https server
var https_server = https.createServer(options, app);
var io = socketIo.listen(https_server);
io.sockets.on('connection', (socket)=> {
socket.on('message', (room, data)=>{
socket.to(room).emit('message',room, data);
});
socket.on('join', (room)=>{
socket.join(room);
var myRoom = io.sockets.adapter.rooms[room];
var users = (myRoom)? Object.keys(myRoom.sockets).length : 0;
logger.debug('the user number of room is: ' + users);
if(users < USERCOUNT){
socket.emit('joined', room, socket.id); //发给除自己之外的房间内的所有人
if(users > 1){
socket.to(room).emit('otherjoin', room, socket.id);
}
}else{
socket.leave(room);
socket.emit('full', room, socket.id);
}
//socket.emit('joined', room, socket.id); //发给自己
//socket.broadcast.emit('joined', room, socket.id); //发给除自己之外的这个节点上的所有人
//io.in(room).emit('joined', room, socket.id); //发给房间内的所有人
});
socket.on('leave', (room)=>{
var myRoom = io.sockets.adapter.rooms[room];
var users = (myRoom)? Object.keys(myRoom.sockets).length : 0;
logger.debug('the user number of room is: ' + (users-1));
//socket.emit('leaved', room, socket.id);
//socket.broadcast.emit('leaved', room, socket.id);
socket.to(room).emit('bye', room, socket.id);
socket.emit('leaved', room, socket.id);
//io.in(room).emit('leaved', room, socket.id);
});
});
https_server.listen(443, '0.0.0.0');
10-6 【来点实战】获取 offer/answer 创建的 SDP
1)效果
2)代码
index.html
<html>
<head>
<title>WebRTC PeerConnection</title>
<link href="./css/main.css" rel="stylesheet" />
</head>
<body>
<div>
<div>
<button id="start">Start</button>
<button id="call" disabled>Call</button>
<button id="hangup" disabled>HangUp</button>
</div>
<div id="preview">
<div >
<h2>Local:</h2>
<video id="localvideo" autoplay playsinline></video>
<h2>Local SDP:</h2>
<textarea id="offer"></textarea>
</div>
<div>
<h2>Remote:</h2>
<video id="remotevideo" autoplay playsinline></video>
<h2>Remote SDP:</h2>
<textarea id="answer"></textarea>
</div>
</div>
</div>
<script src="https://webrtc.github.io/adapter/adapter-latest.js"></script>
<script src="js/main.js"></script>
</body>
</html>
main.js
'use strict'
var localVideo = document.querySelector('video#localvideo');
var remoteVideo = document.querySelector('video#remotevideo');
var btnStart = document.querySelector('button#start');
var btnCall = document.querySelector('button#call');
var btnHangup = document.querySelector('button#hangup');
var offerSdpTextarea = document.querySelector('textarea#offer');
var answerSdpTextarea = document.querySelector('textarea#answer');
var localStream;
var pc1;
var pc2;
function getMediaStream(stream){
localVideo.srcObject = stream;
localStream = stream;
}
function handleError(err){
console.error('Failed to get Media Stream!', err);
}
//开始建立连接
function start(){
if(!navigator.mediaDevices ||
!navigator.mediaDevices.getUserMedia){
console.error('the getUserMedia is not supported!');
return;
}else {
var constraints = {
video: true,
audio: false
}
navigator.mediaDevices.getUserMedia(constraints)
.then(getMediaStream)
.catch(handleError);
btnStart.disabled = true;
btnCall.disabled = false;
btnHangup.disabled = true;
}
}
function getRemoteStream(e){
remoteVideo.srcObject = e.streams[0];
}
function handleOfferError(err){
console.error('Failed to create offer:', err);
}
function handleAnswerError(err){
console.error('Failed to create answer:', err);
}
function getAnswer(desc){
pc2.setLocalDescription(desc);
answerSdpTextarea.value = desc.sdp
//send desc to signal
//receive desc from signal
pc1.setRemoteDescription(desc);
}
function getOffer(desc){
pc1.setLocalDescription(desc);
offerSdpTextarea.value = desc.sdp
//send desc to signal
//receive desc from signal
pc2.setRemoteDescription(desc);
pc2.createAnswer()
.then(getAnswer)
.catch(handleAnswerError);
}
function call(){
if(!localStream)
{
console.log('no start');
return;
}
if(!pc1)
pc1 = new RTCPeerConnection();
if(!pc2)
pc2 = new RTCPeerConnection();
pc1.onicecandidate = (e)=>{
pc2.addIceCandidate(e.candidate);
}
pc2.onicecandidate = (e)=>{
pc1.addIceCandidate(e.candidate);
}
pc2.ontrack = getRemoteStream;
localStream.getTracks().forEach((track)=>{
pc1.addTrack(track, localStream);
});
var offerOptions = {
offerToRecieveAudio: 0,
offerToRecieveVideo: 1
}
pc1.createOffer(offerOptions)
.then(getOffer)
.catch(handleOfferError);
btnCall.disabled = true;
btnHangup.disabled = false;
}
function hangup(){
if(pc1)
pc1.close();
if(pc2)
pc2.close();
pc1 = null;
pc2 = null;
btnCall.disabled = false;
btnHangup.disabled = true;
}
btnStart.onclick = start;
btnCall.onclick = call;
btnHangup.onclick = hangup;
server.js和上一个的一样。
10-7 WebRTC媒体协商的过程是怎样的?
思路:
A用户先要向B用户发送Offer消息;然后B用户给A用户回Answer。
点拨:
能信的双方要先与信令服力器建立连接,然后开始交换 Offer/Answer消息,最终实现媒体协商。