本章讲解端对端传输如何进行媒体能力的协商,以及其处理的基本流程。这部分知识非常重要,了解了这部分知识基本上就撑握了WebRTC的一半,它们是实现真正的 1V1实时互动直播的基础。

10-1 【必备原理】媒体能力协商过程一

图片.png

图片.png
图片.png
Bob提前准备好没有音视频流的Answer,只是建立连接。
图片.png

10-2 【必备原理】媒体能力协商过程二

图片.png
图片.png
图片.png
图片.png
图片.png

图片.png
图片.png
图片.png
图片.png

10-3 【必备原理】1:1连接的基本流程

图片.png
上面的
媒体协商部分Offer-Answer
候选者的连接与检测部分Candidate
媒体数据流的通信部分 Stream

10-4-5 【来点实战】本机内的1:1音视频互通

1)效果

图片.png

2)代码

index.html

  1. <html>
  2. <head>
  3. <title>RTCPeerConnection</title>
  4. <link rel="stylesheet" href="css/main.css"/>
  5. </head>
  6. <body>
  7. <div>
  8. <video id="localVideo" autoplay playsinline></video>
  9. <video id="remoteVideo" autoplay playsinline></video>
  10. <div>
  11. <button id="start">start</button>
  12. <button id="call">call</button>
  13. <button id="hangup">hang up</button>
  14. </div>
  15. </div>
  16. <script src="https://webrtc.github.io/adapter/adapter-latest.js"></script>
  17. <script src="./js/main.js"></script>
  18. </body>
  19. </html>

main.js

  1. 'use strict'
  2. var localVideo = document.querySelector('video#localVideo');
  3. var remoteVideo = document.querySelector('video#remoteVideo');
  4. var btnStart = document.querySelector('button#start');
  5. var btnCall = document.querySelector('button#call');
  6. var btnHangUp= document.querySelector('button#hangup');
  7. var localStream;
  8. var pc1;
  9. var pc2;
  10. function gotMediaStream(stream){
  11. localVideo.srcObject = stream;
  12. localStream = stream;
  13. }
  14. function handleError(err){
  15. console.log("Failed to call getUserMedia", err);
  16. }
  17. //开始建立连接
  18. function start(){
  19. var constraints = {
  20. video: true,
  21. audio: false
  22. }
  23. if(!navigator.mediaDevices ||
  24. !navigator.mediaDevices.getUserMedia){
  25. return;
  26. }else {
  27. navigator.mediaDevices.getUserMedia(constraints)
  28. .then(gotMediaStream)
  29. .catch(handleError);
  30. }
  31. }
  32. function gotAnswerDescription(desc){
  33. pc2.setLocalDescription(desc);
  34. //send sdp to caller
  35. //recieve sdp from callee
  36. pc1.setRemoteDescription(desc);
  37. }
  38. function gotLocalDescription(desc){
  39. pc1.setLocalDescription(desc);
  40. //send sdp to callee
  41. //receive sdp from caller
  42. pc2.setRemoteDescription(desc);
  43. pc2.createAnswer().then(gotAnswerDescription)
  44. .catch(handleError);
  45. }
  46. function gotRemoteStream(e){
  47. if(remoteVideo.srcObject !== e.streams[0]){
  48. remoteVideo.srcObject = e.streams[0];
  49. }
  50. }
  51. //呼叫函数
  52. function call(){
  53. if(!localStream)
  54. {
  55. console.log('no start');
  56. return;
  57. }
  58. var offerOptions = {
  59. offerToReceiveAudio: 0,
  60. offerToReceiveVideo: 1
  61. }
  62. if(!pc1)
  63. pc1 = new RTCPeerConnection();
  64. pc1.onicecandidate = (e) => {
  65. // send candidate to peer
  66. // receive candidate from peer
  67. pc2.addIceCandidate(e.candidate)
  68. .catch(handleError);
  69. console.log('pc1 ICE candidate:', e.candidate);
  70. }
  71. pc1.iceconnectionstatechange = (e) => {
  72. console.log(`pc1 ICE state: ${pc.iceConnectionState}`);
  73. console.log('ICE state change event: ', e);
  74. }
  75. if(!pc2)
  76. pc2 = new RTCPeerConnection();
  77. pc2.onicecandidate = (e)=> {
  78. // send candidate to peer
  79. // receive candidate from peer
  80. pc1.addIceCandidate(e.candidate)
  81. .catch(handleError);
  82. console.log('pc2 ICE candidate:', e.candidate);
  83. }
  84. pc2.iceconnectionstatechange = (e) => {
  85. console.log(`pc2 ICE state: ${pc.iceConnectionState}`);
  86. console.log('ICE state change event: ', e);
  87. }
  88. pc2.ontrack = gotRemoteStream;
  89. //add Stream to caller
  90. localStream.getTracks().forEach((track)=>{
  91. pc1.addTrack(track, localStream);
  92. });
  93. pc1.createOffer(offerOptions)
  94. .then(gotLocalDescription)
  95. .catch(handleError);
  96. }
  97. function hangup(){
  98. if(pc1)
  99. pc1.close();
  100. if(pc2)
  101. pc2.close();
  102. pc1 = null;
  103. pc2 = null;
  104. }
  105. btnStart.onclick = start;
  106. btnCall.onclick = call;
  107. btnHangUp.onclick = hangup;

server.js

使用第八章的server.js

  1. 'use strict'
  2. var log4js = require('log4js');
  3. var http = require('http');
  4. var https = require('https');
  5. var fs = require('fs');
  6. var socketIo = require('socket.io');
  7. var express = require('express');
  8. var serveIndex = require('serve-index');
  9. var USERCOUNT = 3;
  10. log4js.configure({
  11. appenders: {
  12. file: {
  13. type: 'file',
  14. filename: 'app.log',
  15. layout: {
  16. type: 'pattern',
  17. pattern: '%r %p - %m',
  18. }
  19. }
  20. },
  21. categories: {
  22. default: {
  23. appenders: ['file'],
  24. level: 'debug'
  25. }
  26. }
  27. });
  28. var logger = log4js.getLogger();
  29. var app = express();
  30. app.use(serveIndex('./public'));
  31. app.use(express.static('./public'));
  32. //http server
  33. var http_server = http.createServer(app);
  34. http_server.listen(80, '0.0.0.0');
  35. var options = {
  36. key : fs.readFileSync('./cert/1557605_www.learningrtc.cn.key'),
  37. cert: fs.readFileSync('./cert/1557605_www.learningrtc.cn.pem')
  38. }
  39. //https server
  40. var https_server = https.createServer(options, app);
  41. var io = socketIo.listen(https_server);
  42. io.sockets.on('connection', (socket)=> {
  43. socket.on('message', (room, data)=>{
  44. socket.to(room).emit('message',room, data);
  45. });
  46. socket.on('join', (room)=>{
  47. socket.join(room);
  48. var myRoom = io.sockets.adapter.rooms[room];
  49. var users = (myRoom)? Object.keys(myRoom.sockets).length : 0;
  50. logger.debug('the user number of room is: ' + users);
  51. if(users < USERCOUNT){
  52. socket.emit('joined', room, socket.id); //发给除自己之外的房间内的所有人
  53. if(users > 1){
  54. socket.to(room).emit('otherjoin', room, socket.id);
  55. }
  56. }else{
  57. socket.leave(room);
  58. socket.emit('full', room, socket.id);
  59. }
  60. //socket.emit('joined', room, socket.id); //发给自己
  61. //socket.broadcast.emit('joined', room, socket.id); //发给除自己之外的这个节点上的所有人
  62. //io.in(room).emit('joined', room, socket.id); //发给房间内的所有人
  63. });
  64. socket.on('leave', (room)=>{
  65. var myRoom = io.sockets.adapter.rooms[room];
  66. var users = (myRoom)? Object.keys(myRoom.sockets).length : 0;
  67. logger.debug('the user number of room is: ' + (users-1));
  68. //socket.emit('leaved', room, socket.id);
  69. //socket.broadcast.emit('leaved', room, socket.id);
  70. socket.to(room).emit('bye', room, socket.id);
  71. socket.emit('leaved', room, socket.id);
  72. //io.in(room).emit('leaved', room, socket.id);
  73. });
  74. });
  75. https_server.listen(443, '0.0.0.0');

10-6 【来点实战】获取 offer/answer 创建的 SDP

图片.png

图片.png

1)效果

图片.png

2)代码

index.html

  1. <html>
  2. <head>
  3. <title>WebRTC PeerConnection</title>
  4. <link href="./css/main.css" rel="stylesheet" />
  5. </head>
  6. <body>
  7. <div>
  8. <div>
  9. <button id="start">Start</button>
  10. <button id="call" disabled>Call</button>
  11. <button id="hangup" disabled>HangUp</button>
  12. </div>
  13. <div id="preview">
  14. <div >
  15. <h2>Local:</h2>
  16. <video id="localvideo" autoplay playsinline></video>
  17. <h2>Local SDP:</h2>
  18. <textarea id="offer"></textarea>
  19. </div>
  20. <div>
  21. <h2>Remote:</h2>
  22. <video id="remotevideo" autoplay playsinline></video>
  23. <h2>Remote SDP:</h2>
  24. <textarea id="answer"></textarea>
  25. </div>
  26. </div>
  27. </div>
  28. <script src="https://webrtc.github.io/adapter/adapter-latest.js"></script>
  29. <script src="js/main.js"></script>
  30. </body>
  31. </html>

main.js

  1. 'use strict'
  2. var localVideo = document.querySelector('video#localvideo');
  3. var remoteVideo = document.querySelector('video#remotevideo');
  4. var btnStart = document.querySelector('button#start');
  5. var btnCall = document.querySelector('button#call');
  6. var btnHangup = document.querySelector('button#hangup');
  7. var offerSdpTextarea = document.querySelector('textarea#offer');
  8. var answerSdpTextarea = document.querySelector('textarea#answer');
  9. var localStream;
  10. var pc1;
  11. var pc2;
  12. function getMediaStream(stream){
  13. localVideo.srcObject = stream;
  14. localStream = stream;
  15. }
  16. function handleError(err){
  17. console.error('Failed to get Media Stream!', err);
  18. }
  19. //开始建立连接
  20. function start(){
  21. if(!navigator.mediaDevices ||
  22. !navigator.mediaDevices.getUserMedia){
  23. console.error('the getUserMedia is not supported!');
  24. return;
  25. }else {
  26. var constraints = {
  27. video: true,
  28. audio: false
  29. }
  30. navigator.mediaDevices.getUserMedia(constraints)
  31. .then(getMediaStream)
  32. .catch(handleError);
  33. btnStart.disabled = true;
  34. btnCall.disabled = false;
  35. btnHangup.disabled = true;
  36. }
  37. }
  38. function getRemoteStream(e){
  39. remoteVideo.srcObject = e.streams[0];
  40. }
  41. function handleOfferError(err){
  42. console.error('Failed to create offer:', err);
  43. }
  44. function handleAnswerError(err){
  45. console.error('Failed to create answer:', err);
  46. }
  47. function getAnswer(desc){
  48. pc2.setLocalDescription(desc);
  49. answerSdpTextarea.value = desc.sdp
  50. //send desc to signal
  51. //receive desc from signal
  52. pc1.setRemoteDescription(desc);
  53. }
  54. function getOffer(desc){
  55. pc1.setLocalDescription(desc);
  56. offerSdpTextarea.value = desc.sdp
  57. //send desc to signal
  58. //receive desc from signal
  59. pc2.setRemoteDescription(desc);
  60. pc2.createAnswer()
  61. .then(getAnswer)
  62. .catch(handleAnswerError);
  63. }
  64. function call(){
  65. if(!localStream)
  66. {
  67. console.log('no start');
  68. return;
  69. }
  70. if(!pc1)
  71. pc1 = new RTCPeerConnection();
  72. if(!pc2)
  73. pc2 = new RTCPeerConnection();
  74. pc1.onicecandidate = (e)=>{
  75. pc2.addIceCandidate(e.candidate);
  76. }
  77. pc2.onicecandidate = (e)=>{
  78. pc1.addIceCandidate(e.candidate);
  79. }
  80. pc2.ontrack = getRemoteStream;
  81. localStream.getTracks().forEach((track)=>{
  82. pc1.addTrack(track, localStream);
  83. });
  84. var offerOptions = {
  85. offerToRecieveAudio: 0,
  86. offerToRecieveVideo: 1
  87. }
  88. pc1.createOffer(offerOptions)
  89. .then(getOffer)
  90. .catch(handleOfferError);
  91. btnCall.disabled = true;
  92. btnHangup.disabled = false;
  93. }
  94. function hangup(){
  95. if(pc1)
  96. pc1.close();
  97. if(pc2)
  98. pc2.close();
  99. pc1 = null;
  100. pc2 = null;
  101. btnCall.disabled = false;
  102. btnHangup.disabled = true;
  103. }
  104. btnStart.onclick = start;
  105. btnCall.onclick = call;
  106. btnHangup.onclick = hangup;

server.js和上一个的一样。

10-7 WebRTC媒体协商的过程是怎样的?

思路:
A用户先要向B用户发送Offer消息;然后B用户给A用户回Answer。
点拨:
能信的双方要先与信令服力器建立连接,然后开始交换 Offer/Answer消息,最终实现媒体协商。