本章讲解端对端传输如何进行媒体能力的协商,以及其处理的基本流程。这部分知识非常重要,了解了这部分知识基本上就撑握了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 calleepc1.setRemoteDescription(desc);}function gotLocalDescription(desc){pc1.setLocalDescription(desc);//send sdp to callee//receive sdp from callerpc2.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 peerpc2.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 peerpc1.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 callerlocalStream.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 servervar 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 servervar 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 signalpc1.setRemoteDescription(desc);}function getOffer(desc){pc1.setLocalDescription(desc);offerSdpTextarea.value = desc.sdp//send desc to signal//receive desc from signalpc2.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消息,最终实现媒体协商。
