与WebRTC实时通信

2023-05-16

与WebRTC实时通信

一、介绍

WebRTC是一个开源项目,可以在Web和本机应用程序中实现音频,视频和数据的实时通信。WebRTC有几个JavaScript API:
getUserMedia():捕获音频和视频。
MediaRecorder:录制音频和视频。
RTCPeerConnection:在用户之间传输音频和视频。
RTCDataChannel:用户之间的流数据。
在Firefox,Opera和桌面和Android上的Chrome中可以使用WebRTC。WebRTC也可用于iOS和Android上的本机应用程序。

什么是signaling?
WebRTC使用RTCPeerConnection在浏览器之间传递流数据,但也需要一种协调通信和发送控制消息的机制,这一过程称为信令。WebRTC未指定信令方法和协议。本例将使用Socket.IO进行消息传递。

什么是STUN和TURN?
WebRTC旨在实现点对点工作,因此用户可以通过最直接的路由进行连接。但是,WebRTC的构建是为了应对真实的网络:客户端应用程序需要遍历NAT网关和防火墙,并且在直接连接失败的情况下,对等网络需要回退。作为此过程的一部分,WebRTC API使用STUN服务器获取计算机的IP地址,并使用TURN服务器作为中继服务器,以防对等通信失败。

二、概述

构建应用程序以获取视频并使用网络摄像头拍摄快照,并通过WebRTC进行点对点共享。在此过程中,将学习如何使用核心WebRTC API并使用Node.js设置消息传递服务器。

你将学到什么
从网络摄像头获取视频
使用RTCPeerConnection流式传输视频
使用RTCDataChannel流式传输数据
建立信令服务以交换消息
结合对等连接和信令
拍照并通过数据通道分享

你需要什么
Chrome 47或以上
适用于Chrome的Web Server,或使用您自己选择的Web服务器。
示例代码
文本编辑器
HTML,CSS和JavaScript的基础知识

三、获取示例代码

下载代码
如果您熟悉git,可以通过克隆它从GitHub下载此codelab的代码:

git clone https://github.com/googlecodelabs/webrtc-web

或者单击此处下载代码的.zip文件。

打开下载的zip文件。这个项目文件夹包含每个步骤以及需要的所有资源。您将在名为work的目录中完成所有编码工作。

安装并验证Web服务器
虽然您可以自由使用自己的Web服务器,但此codelab可以与Chrome Web服务器配合使用。如果您尚未安装该应用,则可以从Chrome网上应用店安装该应用。
在这里插入图片描述

安装Web Server for Chrome应用程序后,单击书签栏,新标签页或应用启动器中的Chrome应用程序快捷方式:
在这里插入图片描述

单击Web Server图标:
在这里插入图片描述

接下来,您将看到此对话框,它允许您配置本地Web服务器:

在这里插入图片描述

单击“ 选择文件夹”按钮,然后选择刚刚创建的工作文件夹。这样,您就可以通过“ Web服务器URL”部分的“Web服务器”对话框中突出显示的URL查看Chrome中正在进行的工作。

在选项下,选中自动显示index.html旁边的框,如下所示:

在这里插入图片描述

然后通过滑动标记为Web Server的切换来停止并重新启动服务器:STARTED向左,然后向右移动。

在这里插入图片描述

现在,通过单击突出显示的Web服务器URL,在Web浏览器中访问您的工作站点。你应该看到一个看起来像这样的页面,它对应于work / index.html:
在这里插入图片描述

从现在开始,应使用此Web服务器设置执行所有测试和验证,只需刷新测试浏览器选项卡。

四、通过网络摄像头流式传输视频

在这一步中,您将了解如何:
从您的网络摄像头获取视频流。
操纵流播放。
使用CSS和SVG来操纵视频。

此步骤的完整版本位于step-01文件夹中。

在工作目录中为index.html添加video元素和script元素:

<!DOCTYPE html>
<html>

<head>

  <title>Realtime communication with WebRTC</title>

  <link rel="stylesheet" href="css/main.css" />

</head>

<body>

  <h1>Realtime communication with WebRTC</h1>

  <video autoplay playsinline></video>

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

</body>

</html>

以下内容添加到main.js在您的JS文件夹

'use strict';

// On this codelab, you will be streaming only video (video: true).
const mediaStreamConstraints = {
  video: true,
};

// Video element where stream will be placed.
const localVideo = document.querySelector('video');

// Local stream that will be reproduced on the video.
let localStream;

// Handles success by adding the MediaStream to the video element.
function gotLocalMediaStream(mediaStream) {
  localStream = mediaStream;
  localVideo.srcObject = mediaStream;
}

// Handles error by logging a message to the console with the error message.
function handleLocalMediaStreamError(error) {
  console.log('navigator.getUserMedia error: ', error);
}

// Initializes media stream.
navigator.mediaDevices.getUserMedia(mediaStreamConstraints)
  .then(gotLocalMediaStream).catch(handleLocalMediaStreamError);

这里的所有JavaScript示例都用于’use strict’;避免常见的编码问题。

在浏览器中打开index.html,可以看到网络摄像头的视图。

在getUserMedia()响应之后,浏览器请求用户访问其摄像机的许可(如果这是第一次请求当前的摄像机访问)。如果成功,则返回MediaStream,媒体元素可以通过该srcObject属性使用它:

navigator.mediaDevices.getUserMedia(mediaStreamConstraints)
  .then(gotLocalMediaStream).catch(handleLocalMediaStreamError);
}
function gotLocalMediaStream(mediaStream) {
  localVideo.srcObject = mediaStream;
}

该constraints参数允许您指定要获取的媒体。在此示例中,仅限视频,因为默认情况下禁用音频:

const mediaStreamConstraints = {
  video: true,
};

您可以使用约束来满足其他要求,例如视频分辨率:

const hdConstraints = {
  video: {
    width: {
      min: 1280
    },
    height: {
      min: 720
    }
  }
}

如果getUserMedia()成功,则将来自网络摄像头的视频流设置为视频元素的来源:

function gotLocalMediaStream(mediaStream) {
  localVideo.srcObject = mediaStream;
}

不要忘记元素的autoplay属性video。没有它,你只会看到一个帧!

五、使用RTCPeerConnection流式传输视频

在这一步中,您将了解:

使用RTCPeerConnection API流式传输视频。
控制媒体捕获和流媒体。

此步骤的完整版本位于step-2文件夹中。

什么是RTCPeerConnection?
RTCPeerConnection是用于进行WebRTC调用以流式传输视频和音频以及交换数据的API。

此示例在同一页面上的两个RTCPeerConnection对象(称为对等方)之间建立连接。实用性不大,但有助于理解RTCPeerConnection的工作原理。

添加视频元素和控制按钮
在index.html中,用两个视频元素和三个按钮替换单个视频元素:

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


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

一个视频元素将显示getUserMedia()流,另一个将显示通过RTCPeerconnection流传输的相同视频。(在实际应用程序中,一个视频元素将显示本地流,另一个视频元素将显示远程流。)

添加adapter.js填充程序
添加当前版本adapter.js 链接上面main.js:

<script src="https://webrtc.github.io/adapter/adapter-latest.js"></script>

adapter.js是一个填充程序,可以将应用程序与规范更改和前缀差异隔离开来。
Index.html现在应该如下所示:

<!DOCTYPE html>
<html>

<head>
  <title>Realtime communication with WebRTC</title>
  <link rel="stylesheet" href="css/main.css" />
</head>

<body>
  <h1>Realtime communication with WebRTC</h1>

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

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

  <script src="https://webrtc.github.io/adapter/adapter-latest.js"></script>
  <script src="js/main.js"></script>
</body>
</html>

安装RTCPeerConnection代码
将main.js替换为step-02文件夹中的版本。
在codelab中使用大块代码进行剪切和粘贴并不理想,但为了使RTCPeerConnection启动并运行,除了整个替换之外别无选择。

打开index.html,单击“ Start”按钮以从网络摄像头获取视频,然后单击“ Call”以建立对等连接。您应该在两个视频元素中看到相同的视频(来自您的网络摄像头)。查看浏览器控制台以查看WebRTC日志记录。

在WebRTC对等体之间建立呼叫涉及三个任务:
为呼叫的每一端创建一个RTCPeerConnection,并在每一端添加本地流getUserMedia()。
获取和共享网络信息:潜在的连接端点称为ICE候选者。
获取并共享本地和远程描述:SDP格式的本地媒体元数据。

想象一下,Alice和Bob想要使用RTCPeerConnection来设置视频聊天。
首先,Alice和Bob交换网络信息。“查找候选者”一词是指使用ICE框架查找网络接口和端口的过程。
Alice使用onicecandidate (addEventListener(‘icecandidate’))处理程序创建RTCPeerConnection对象。这对应于main.js中的以下代码:

let localPeerConnection;
localPeerConnection = new RTCPeerConnection(servers);
localPeerConnection.addEventListener('icecandidate', handleConnection);
localPeerConnection.addEventListener(
    'iceconnectionstatechange', handleConnectionChange);

Alice调用getUserMedia()并添加传递给它的流:

navigator.mediaDevices.getUserMedia(mediaStreamConstraints).
  then(gotLocalMediaStream).
  catch(handleLocalMediaStreamError);
function gotLocalMediaStream(mediaStream) {
  localVideo.srcObject = mediaStream;
  localStream = mediaStream;
  trace('Received local stream.');
  callButton.disabled = false;  // Enable call button.
}
localPeerConnection.addStream(localStream);
trace('Added local stream to localPeerConnection.');

当网络候选者可用时,步骤1中的处理程序onicecandidate被调用。
Alice将序列化的候选数据发送给Bob。在实际应用程序中,此过程(信令)通过消息传递服务进行。在此步骤中,两个RTCPeerConnection对象位于同一页面上,可以直接通信,无需外部消息传递。
当Bob从Alice获取候选消息时,他调用addIceCandidate(),将候选者添加到远程对等描述中:

function handleConnection(event) {
  const peerConnection = event.target;
  const iceCandidate = event.candidate;

  if (iceCandidate) {
    const newIceCandidate = new RTCIceCandidate(iceCandidate);
    const otherPeer = getOtherPeer(peerConnection);

    otherPeer.addIceCandidate(newIceCandidate)
      .then(() => {
        handleConnectionSuccess(peerConnection);
      }).catch((error) => {
        handleConnectionFailure(peerConnection, error);
      });

    trace(`${getPeerName(peerConnection)} ICE candidate:\n` +
          `${event.candidate.candidate}.`);
  }
}

WebRTC对等体还需要找出并交换本地和远程音频和视频媒体信息,例如分辨率和编解码器功能。通过使用称为SDP的会话描述协议格式交换元数据块(称为offer 和answer)来进行交换媒体配置信息的信令:

1 Alice运行RTCPeerConnection createOffer()方法。返回的promise提供了一个RTCSessionDescription:Alice的本地会话描述:

trace('localPeerConnection createOffer start.');
localPeerConnection.createOffer(offerOptions)
  .then(createdOffer).catch(setSessionDescriptionError);

2 如果成功,Alice使用本地描述设置setLocalDescription(),然后通过其信令通道将此会话描述发送给Bob。
3 Bob将Alice发送给他的描述设置为使用的远程描述setRemoteDescription()。
4 Bob运行RTCPeerConnection createAnswer()方法,向其传递从Alice获得的远程描述,因此可以生成与她兼容的本地会话。createAnswer() promise 传递一个RTCSessionDescription:Bob设置为本地描述,并将其发送给Alice。
5 当Alice获得Bob的会话描述时,她将其设置为远程描述setRemoteDescription()。

// Logs offer creation and sets peer connection session descriptions.
function createdOffer(description) {
  trace(`Offer from localPeerConnection:\n${description.sdp}`);

  trace('localPeerConnection setLocalDescription start.');
  localPeerConnection.setLocalDescription(description)
    .then(() => {
      setLocalDescriptionSuccess(localPeerConnection);
    }).catch(setSessionDescriptionError);

  trace('remotePeerConnection setRemoteDescription start.');
  remotePeerConnection.setRemoteDescription(description)
    .then(() => {
      setRemoteDescriptionSuccess(remotePeerConnection);
    }).catch(setSessionDescriptionError);

  trace('remotePeerConnection createAnswer start.');
  remotePeerConnection.createAnswer()
    .then(createdAnswer)
    .catch(setSessionDescriptionError);
}

// Logs answer to offer creation and sets peer connection session descriptions.
function createdAnswer(description) {
  trace(`Answer from remotePeerConnection:\n${description.sdp}.`);

  trace('remotePeerConnection setLocalDescription start.');
  remotePeerConnection.setLocalDescription(description)
    .then(() => {
      setLocalDescriptionSuccess(remotePeerConnection);
    }).catch(setSessionDescriptionError);

  trace('localPeerConnection setRemoteDescription start.');
  localPeerConnection.setRemoteDescription(description)
    .then(() => {
      setRemoteDescriptionSuccess(localPeerConnection);
    }).catch(setSessionDescriptionError);
}

六、使用RTCDataChannel交换数据

你将学到什么
如何在WebRTC端点(对等方)之间交换数据。

此步骤的完整版本位于步骤03文件夹中。

更新您的HTML
对于此步骤,您将使用WebRTC数据通道textarea在同一页面上的两个元素之间发送文本。这不是很有用,但确实演示了WebRTC如何用于共享数据以及流式视频。

从index.html中删除视频和按钮元素,并使用以下HTML替换它们:

<textarea id="dataChannelSend" disabled
    placeholder="Press Start, enter some text, then press Send."></textarea>
<textarea id="dataChannelReceive" disabled></textarea>

<div id="buttons">
  <button id="startButton">Start</button>
  <button id="sendButton">Send</button>
  <button id="closeButton">Stop</button>
</div>

更新您的JavaScript

将main.js替换为step-03 / js / main.js的内容。

在对等体之间尝试流数据:打开index.html,按 Start 设置对等连接,在左侧textarea输入一些文本,然后单击Send以使用WebRTC数据通道传输文本。

此代码使用RTCPeerConnection和RTCDataChannel来启用文本消息的交换。此步骤中的大部分代码与RTCPeerConnection示例相同。

sendData()和createConnection()功能有大部分新代码:

function createConnection() {
  dataChannelSend.placeholder = '';
  var servers = null;
  pcConstraint = null;
  dataConstraint = null;
  trace('Using SCTP based data channels');
  // For SCTP, reliable and ordered delivery is true by default.
  // Add localConnection to global scope to make it visible
  // from the browser console.
  window.localConnection = localConnection =
      new RTCPeerConnection(servers, pcConstraint);
  trace('Created local peer connection object localConnection');

  sendChannel = localConnection.createDataChannel('sendDataChannel',
      dataConstraint);
  trace('Created send data channel');

  localConnection.onicecandidate = iceCallback1;
  sendChannel.onopen = onSendChannelStateChange;
  sendChannel.onclose = onSendChannelStateChange;

  // Add remoteConnection to global scope to make it visible
  // from the browser console.
  window.remoteConnection = remoteConnection =
      new RTCPeerConnection(servers, pcConstraint);
  trace('Created remote peer connection object remoteConnection');

  remoteConnection.onicecandidate = iceCallback2;
  remoteConnection.ondatachannel = receiveChannelCallback;

  localConnection.createOffer().then(
    gotDescription1,
    onCreateSessionDescriptionError
  );
  startButton.disabled = true;
  closeButton.disabled = false;
}

function sendData() {
  var data = dataChannelSend.value;
  sendChannel.send(data);
  trace('Sent Data: ' + data);
}

注意使用dataConstraint。可以配置数据通道以实现不同类型的数据共享 - 例如,优先考虑可靠的交付而不是性能。

七、建立信令服务以交换消息

你将学到什么

使用npm安装项目依赖中规定的package.json
运行Node.js服务器并使用node-static来提供静态文件。
使用Socket.IO在Node.js上设置消息传递服务。
用它来创建“房间”并交换消息。

此步骤的完整版本位于step-04文件夹中。

为了设置和维护WebRTC调用,WebRTC客户端(对等方)需要交换元数据:候选人(网络)信息。提供和回答提供有关媒体信息的消息,例如分辨率和编解码器。换句话说,在可以发生音频,视频或数据的对等流传输之前,需要交换元数据。此过程称为信令。

在前面的步骤中,发送方和接收方RTCPeerConnection对象位于同一页面上,因此“信令”只是在对象之间传递元数据的问题。

在现实世界的应用程序中,发送方和接收方RTCPeerConnections在不同设备上的网页中运行,您需要一种方式让它们进行元数据通信。

为此,您使用信令服务器:可以在WebRTC客户端(对等方)之间传递消息的服务器。实际的消息是纯文本:字符串化的JavaScript对象。

先决条件:安装Node.js.
为了运行此codelab的下一步(文件夹步骤04到步骤06),您需要使用Node.js在localhost上运行服务器。

您可以下载并安装由Node.js的这个链接。

安装后,您将能够导入后续步骤(运行npm install)所需的依赖项,以及运行一个小型localhost服务器来执行codelab(运行node index.js)。这些命令将在以后需要时显示。

关于该应用程序
WebRTC使用客户端JavaScript API,但对于实际使用,还需要信令(消息)服务器,以及STUN和TURN服务器。
在这一步中,您将构建一个简单的Node.js信令服务器,使用Socket.IO Node.js模块和JavaScript库进行消息传递。使用Node.js和Socket.IO会很有用,但并不重要; 消息传递组件非常简单。

Socket.IO的设计使得构建交换消息的服务变得简单,Socket.IO因其内置的“房间”概念而适合学习WebRTC信令。
在此示例中,服务器(Node.js应用程序)在index.js中实现,在其上运行的客户端(Web应用程序)在index.html中实现。

此步骤中的Node.js应用程序有两个任务。

首先,它充当消息中继:

socket.on('message', function (message) {
  log('Got message: ', message);
  socket.broadcast.emit('message', message);
});

其次,它管理WebRTC视频聊天’房间’:

if (numClients === 0) {
  socket.join(room);
  socket.emit('created', room, socket.id);
} else if (numClients === 1) {
  socket.join(room);
  socket.emit('joined', room, socket.id);
  io.sockets.in(room).emit('ready');
} else { // max two clients
  socket.emit('full', room);
}

我们简单的WebRTC应用程序将允许最多两个对等方共享一个房间。
HTML和JavaScript
更新index.html所以它看起来像这样:


    <!DOCTYPE html>
<html>

<head>

  <title>Realtime communication with WebRTC</title>

  <link rel="stylesheet" href="css/main.css" />

</head>

<body>

  <h1>Realtime communication with WebRTC</h1>

  <script src="/socket.io/socket.io.js"></script>
  <script src="js/main.js"></script>
  
</body>

</html>

在此步骤中,您不会在页面上看到任何内容:所有日志记录都在浏览器控制台上完成。(要在Chrome中查看控制台,请按Ctrl-Shift-J。如果您使用的是Mac,Command-Option-J。)
用以下内容替换js / main.js:

'use strict';

var isInitiator;

window.room = prompt("Enter room name:");

var socket = io.connect();

if (room !== "") {
  console.log('Message from client: Asking to join room ' + room);
  socket.emit('create or join', room);
}

socket.on('created', function(room, clientId) {
  isInitiator = true;
});

socket.on('full', function(room) {
  console.log('Message from client: Room ' + room + ' is full :^(');
});

socket.on('ipaddr', function(ipaddr) {
  console.log('Message from client: Server IP address is ' + ipaddr);
});

socket.on('joined', function(room, clientId) {
  isInitiator = false;
});

socket.on('log', function(array) {
  console.log.apply(console, array);
});

设置Socket.IO以在Node.js上运行
在HTML文件中,您可能已经看到您正在使用Socket.IO文件:

<script src =“/ socket.io/socket.io.js”> </ script>

在工作目录的顶层创建一个名为package.json的文件,其中包含以下内容:

{ 
  “name”:“webrtc-codelab”,
  “version”:“0.0.1”,
  “description”:“WebRTC codelab”,
  “dependencies”:{ 
    “node-static”:“^ 0.7.10”,
    “socket .io“:”^ 1.2.0“ 
  } 
}

这是一个应用程序清单,它告诉Node Package Manager(npm)要安装哪些项目依赖项。

要安装依赖项(例如/socket.io/socket.io.js),请在工作目录的命令行终端中运行以下命令:

npm install

您应该看到一个安装日志,结束如下所示:
在这里插入图片描述
在工作目录的顶层(而不是在js目录中)创建一个新文件index.js并添加以下代码:

'use strict';

var os = require('os');
var nodeStatic = require('node-static');
var http = require('http');
var socketIO = require('socket.io');

var fileServer = new(nodeStatic.Server)();
var app = http.createServer(function(req, res) {
  fileServer.serve(req, res);
}).listen(8080);

var io = socketIO.listen(app);
io.sockets.on('connection', function(socket) {

  // convenience function to log server messages on the client
  function log() {
    var array = ['Message from server:'];
    array.push.apply(array, arguments);
    socket.emit('log', array);
  }

  socket.on('message', function(message) {
    log('Client said: ', message);
    // for a real app, would be room-only (not broadcast)
    socket.broadcast.emit('message', message);
  });

  socket.on('create or join', function(room) {
    log('Received request to create or join room ' + room);

    var clientsInRoom = io.sockets.adapter.rooms[room];
    var numClients = clientsInRoom ? Object.keys(clientsInRoom.sockets).length : 0;

    log('Room ' + room + ' now has ' + numClients + ' client(s)');

    if (numClients === 0) {
      socket.join(room);
      log('Client ID ' + socket.id + ' created room ' + room);
      socket.emit('created', room, socket.id);

    } else if (numClients === 1) {
      log('Client ID ' + socket.id + ' joined room ' + room);
      io.sockets.in(room).emit('join', room);
      socket.join(room);
      socket.emit('joined', room, socket.id);
      io.sockets.in(room).emit('ready');
    } else { // max two clients
      socket.emit('full', room);
    }
  });

  socket.on('ipaddr', function() {
    var ifaces = os.networkInterfaces();
    for (var dev in ifaces) {
      ifaces[dev].forEach(function(details) {
        if (details.family === 'IPv4' && details.address !== '127.0.0.1') {
          socket.emit('ipaddr', details.address);
        }
      });
    }
  });

});

从命令行终端,在工作目录中运行以下命令:

node index.js

在浏览器中,打开localhost:8080。

每次打开此URL时,系统都会提示您输入房间名称。要加入同一个房间,请每次选择相同的房间名称,例如“foo”。

打开一个新标签页,然后再次打开localhost:8080。选择相同的房间名称。

在第三个选项卡或窗口中打开localhost:8080。再次选择相同的房间名称。

检查每个选项卡中的控制台:您应该从上面的JavaScript中看到日志记录。

八、结合对等连接和信令

你将学到什么

使用在Node.js上运行的Socket.IO运行WebRTC信令服务
使用该服务在对等体之间交换WebRTC元数据。

此步骤的完整版本位于步骤05文件夹中。

替换HTML和JavaScript
用以下内容替换index.html的内容:

<!DOCTYPE html>
<html>

<head>

  <title>Realtime communication with WebRTC</title>

  <link rel="stylesheet" href="/css/main.css" />

</head>

<body>

  <h1>Realtime communication with WebRTC</h1>

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

  <script src="/socket.io/socket.io.js"></script>
  <script src="https://webrtc.github.io/adapter/adapter-latest.js"></script>
  <script src="js/main.js"></script>
  
</body>

</html>

将js / main.js替换为步骤05 / js / main.js的内容。

运行Node.js服务器

node index.js

在浏览器中,打开localhost:8080。

在新选项卡或窗口中再次打开localhost:8080。一个视频元素将显示本地流getUserMedia(),另一个将显示通过RTCPeerconnection流式传输的“远程”视频。
注意:每次关闭客户端选项卡或窗口时,都需要重新启动Node.js服务器。
可在浏览器控制台中查看日志记录

如果您遇到奇怪的缓存问题,请尝试以下方法:
按住ctrl并单击“ 重新加载”按钮进行硬刷新
重启浏览器
运行npm cache clean命令行。

九、拍照并通过数据通道分享

你将学到什么

拍摄照片并使用canvas元素从中获取数据。
与远程用户交换图像数据。

此步骤的完整版本位于步骤06文件夹中。

以前,您学习了如何使用RTCDataChannel交换文本消息。此步骤可以共享整个文件:在此示例中,通过getUserMedia()拍摄照片。

该步骤的核心部分如下:

建立数据通道。请注意,在此步骤中不要向对等连接添加任何媒体流。
使用以下方法捕获用户的网络摄像头视频流getUserMedia():

var video = document.getElementById('video');

function grabWebCamVideo() {
  console.log('Getting user media (video) ...');
  navigator.mediaDevices.getUserMedia({
    video: true
  })
  .then(gotStream)
  .catch(function(e) {
    alert('getUserMedia() error: ' + e.name);
  });
}

当用户单击“ Snap”按钮时,从视频流中获取快照(视频帧)并将其显示在canvas元素中:

var photo = document.getElementById('photo');
var photoContext = photo.getContext('2d');

function snapPhoto() {
  photoContext.drawImage(video, 0, 0, photo.width, photo.height);
  show(photo, sendBtn);
}

当用户单击“ Send”按钮时,将图像转换为字节并通过数据通道发送:
function sendPhoto() {
// Split data channel message in chunks of this byte length.
var CHUNK_LEN = 64000;
var img = photoContext.getImageData(0, 0, photoContextW, photoContextH),
len = img.data.byteLength,
n = len / CHUNK_LEN | 0;

console.log(‘Sending a total of ’ + len + ’ byte(s)’);
dataChannel.send(len);

// split the photo and send in chunks of about 64KB
for (var i = 0; i < n; i++) {
var start = i * CHUNK_LEN,
end = (i + 1) * CHUNK_LEN;
console.log(start + ’ - ’ + (end - 1));
dataChannel.send(img.data.subarray(start, end));
}

// send the reminder, if any
if (len % CHUNK_LEN) {
console.log(‘last ’ + len % CHUNK_LEN + ’ byte(s)’);
dataChannel.send(img.data.subarray(n * CHUNK_LEN));
}
}
接收方将数据通道消息字节转换回图像并将图像显示给用户:

function receiveDataChromeFactory() {
  var buf, count;

  return function onmessage(event) {
    if (typeof event.data === 'string') {
      buf = window.buf = new Uint8ClampedArray(parseInt(event.data));
      count = 0;
      console.log('Expecting a total of ' + buf.byteLength + ' bytes');
      return;
    }

    var data = new Uint8ClampedArray(event.data);
    buf.set(data, count);

    count += data.byteLength;
    console.log('count: ' + count);

    if (count === buf.byteLength) {
      // we're done: all data chunks have been received
      console.log('Done. Rendering photo.');
      renderPhoto(buf);
    }
  };
}

function renderPhoto(data) {
  var canvas = document.createElement('canvas');
  canvas.width = photoContextW;
  canvas.height = photoContextH;
  canvas.classList.add('incomingPhoto');
  // trail is the element holding the incoming images
  trail.insertBefore(canvas, trail.firstChild);

  var context = canvas.getContext('2d');
  var img = context.createImageData(photoContextW, photoContextH);
  img.data.set(data);
  context.putImageData(img, 0, 0);
}

获取代码

用步骤06的内容替换work文件夹的内容。你的index.html文件的工作,现在应该是这样的:

<!DOCTYPE html>
<html>

<head>

  <title>Realtime communication with WebRTC</title>

  <link rel="stylesheet" href="/css/main.css" />

</head>

<body>

  <h1>Realtime communication with WebRTC</h1>

  <h2>
    <span>Room URL: </span><span id="url">...</span>
  </h2>

  <div id="videoCanvas">
    <video id="camera" autoplay></video>
    <canvas id="photo"></canvas>
  </div>

  <div id="buttons">
    <button id="snap">Snap</button><span> then </span><button id="send">Send</button>
    <span> or </span>
    <button id="snapAndSend">Snap &amp; Send</button>
  </div>

  <div id="incoming">
    <h2>Incoming photos</h2>
    <div id="trail"></div>
  </div>

  <script src="/socket.io/socket.io.js"></script>
  <script src="https://webrtc.github.io/adapter/adapter-latest.js"></script>
  <script src="js/main.js"></script>

</body>

</html>

该应用程序将创建一个随机的房间ID,并将该ID添加到URL。在新的浏览器选项卡或窗口中打开地址栏中的URL。

单击“ Snap & Send”按钮,然后在页面底部的另一个选项卡中查看传入区域。该应用在标签之间传输照片。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

与WebRTC实时通信 的相关文章

  • 一个简单的环形进度条组件 vue-circleprogressbar

    vue circleprogressbar 一个简单的环形进度条组件 最近在开发可视化大屏项目中经常会用到环形进度条 但是常用的echarts组件对环形进度条的支持不太好 写起来比较麻烦 就想着自己写一个VUE的环形组件 满足自己日常开发需
  • Hbuilder如何创建并运行Vue项目

    在Hbuilder中开发Vue项目是非常快捷的 xff0c 下面小编给大家分享一下如何在Hbuilder中创建并运行Vue项目 方法 步骤 1 首先打开Hbuilder创建一个新项目 xff0c 如下图所示 2 Hbuilder如何创建并运
  • 教大家防止Jar包被反编译

    xff08 待验证 xff09 方法就是 xff0c 向Jar注入无效代码 xff08 不合法的 xff0c 或者根本不是代码的字符串 xff09 那么无效的代码又怎么能正确运行呢 xff1f 答案就是 xff0c 你要保证你的代码永远不会
  • 推荐3个小程序开源组件库——Vant、iView、ColorUI

    推荐3个小程序开源组件库 在进行小程序开发时 xff0c 经常会遇到编写组件方面的阻碍 xff0c 这让我们花费大量的时间在页面以及 CSS 样式编写上 因此可以使用开源组件库 xff0c 有些复杂的组件可以直接拿来使用 xff0c 节省开
  • 试图加载格式不正确的程序。 (异常来自 HRESULT:0x8007000B)

    两种原因 第一种为程序的运行以平台系统位数不匹配 第二种则是该死的VS整出来的 一般在下面三种情景下会发生 1 64位系统上C 调用32位的C 43 43 dll 2 64位系统上IIS发布含有32位的 dll应用程序时 3 64位系统上编
  • 怎么进bios设置硬盘启动顺序|电脑bios硬盘启动设置方法

    怎么在BIOS里设置硬盘启动 xff1f 电脑在启动时会从硬盘寻找引导文件 xff0c 从而启动系统 xff0c 如果硬盘不是第一启动项 xff0c 或者有两个硬盘 xff0c 就会导致系统无法启动 xff0c 这时候就需要进BIOS设置硬
  • Windows计划任务执行时不显示窗口的问题

    最近开发了工具 xff0c 带界面的 xff0c 需要定时执行的 xff0c 为了方便直接用Windows计划任务做定时了 跑了一段时间发现 xff0c 进程中也有 xff0c 就是看不到程序的界面 xff0c 进程的执行貌似也阻塞了 从网
  • JAVA的@EXCEL导出导入常用注解汇总

    在实际开发中经常需要使用导入导出功能来加快数据的操作 在项目中可以使用注解来完成此项功能 在需要被导入导出的实体类属性添加 64 Excel注解 参数类型默认值描述sortintInteger MAX VALUE值越小越靠前readConv
  • MySQL中order by排序将NULL排在最前或最后面

    NULL的意思表示什么都不是 xff0c 或者理解成 未知 也可以 xff0c 它与任何值比较的结果都是false 注意 xff1a 默认情况下 xff0c MySQL会认为NULL值比其他类型的数据小 xff0c 也就是说 xff1a 在
  • C# 如何获取本机IP

    百度搜索的方案 如果你去百度C 如何获取本机IP xff0c 那么大概率的你会得到以下的几段代码 xff0c 第一种就是这样 xff1a string name 61 Dns GetHostName IPAddress ipadrlist
  • winform DevExpress contextMenuStrip右键事件

    span class token keyword private span span class token return type class name span class token keyword void span span sp
  • Linux TCP连接数限制配置

    在进行接口测试时 xff0c 在用户数超过400后 xff0c 就会出现socket connection reset xff0c 明显就是系统无法创建连接 xff0c 查看此时系统建立的TCP连接 xff0c netstat ant gr
  • 摘要认证及实现HTTP digest authentication

    最近工作需要做了摘要认证 xff08 digest authentication xff09 xff0c 下面就工作中遇到的问题及过程做一个总结 第一次客户端请求 GET POST 服务器产生一个随机数nonce xff0c 服务器将这个随
  • HTTPURL Connection及session保存问题

    HTTPURL Connection及session保存问题 HTTPURL Connection是一种多用途 轻量极的HTTP客户端 xff0c 使用它来进行HTTP操作可以适用于大多数的应用程序 虽然HttpURLConnection的
  • SpringBoot 集成redis-jedis

    SpringBoot 集成redis jedis 配置application properties中的redis Redis配置 Redis数据库索引 xff08 默认为0 xff09 spring redis database 61 0
  • Python爬虫常用之登录(三) 使用http请求登录

    前面说了使用浏览器登录较为简单 不需要过多分析 而使用请求登录恰恰就是以分析为主 开发一个请求登录程序的流程 分析请求 gt 模拟请求 gt 测试登录 gt 调整参数 gt 测试登录 gt 登录成功 一 分析网页 从网页着手 打开博客园的登
  • memmove的算法思想

    1 memmove的用法 C 库函数 void memmove void str1 const void str2 size t n 从 str2 复制 n 个字符到str1 如果目标区域和源区域有重叠的话 memmove 能够保证源串在被
  • 菜菜之路-C语言求阶乘和

    提示 xff1a 文章写完后 xff0c 目录可以自动生成 xff0c 如何生成可参考右边的帮助文档 文章目录 前言一 阶乘是什么 xff1f 二 思想 1 思路12 思路2总结 前言 C语言其实要学好并不简单碰到一道题我们有时根本无从下手
  • 题目:将一个正整数分解质因数。例如:输入90,打印出90=2 * 3 * 3 * 5

    思路1 xff1a 首先先提一个这样的思路 假如这个数是90 xff0c 我们让它被2到90之间的数进行整除 xff0c 如90 2 61 45 0 余数 xff0c 90 3 61 30 我们获取这样的除数 但是有问题 xff0c 90每
  • C语言--求比赛名次问题

    题目内容 xff1a 5位运动员参加了10米台跳水比赛 xff0c 有人让他们预测比赛结果 xff1a A选手说 B第二 xff0c 我第三 B选手说 我第二 xff0c E第四 C选手说 我第一 xff0c D第二 D选手说 C最后 xf

随机推荐

  • 编写程序,输入一个较小的实数deta,利用e=1+1/1(嘿哈)+1/2(嘿哈)+1/3(嘿哈)+⋯+1/n(嘿哈)计算e的近似值,直到最后一项的绝对值小于deta时为止,输出此时e的近似值。

    碰到了一种避开惯性思维的写法想分享出来 由于标题不能打叹号 你品一下哈 include lt stdio h gt include lt math h gt int main int n 61 1 double e 61 1 0 term
  • Linux主板支持多机系统的Mark/Space校验

    Linux主板支持多机系统的Mark Space校验 关键词 xff1a Linux 主板摘要 xff1a 英创Linux主板可以通过RS485总线挂载多个单片机组成多机系统 xff0c 当从机设备比较多 传输数据比较频繁的时候 xff0c
  • 库函数memcpy的实现

    1 先看一下memcpy的声明 从str2所指向的内容的n个字节 复制到str1所指向的内容中 注意这里是n个字节 后面实现该库函数时我会详细解释 size t你们可以查一下它的定义 代表着无符号整型 2 好 来看看我们如何实现它 void
  • linux 抓包tcp

    tcpdump是linux下的网络数据包截获分析工具 在linux的日常网络管理中 xff0c tcpdump的使用频率很高 xff0c 熟练掌握对提高工作效率很有帮助 支持针对网络层 协议 主机 网络或端口的过滤 并提供and or no
  • STM32串口中断接收一帧数据

    STM32串口中断接收一帧数据 IDLE即串口空闲中断 xff0c 串口收到一帧数据后 xff0c 发生的中断 比如说给单片机一次发来1个字节 xff0c 或者一次发来8个字节 xff0c 这些一次发来的数据 xff0c 就称为一帧数据 x
  • shell中各种括号的作用()、(())、[]、[[]]、{}

    一 小括号 xff0c 圆括号 xff08 xff09 1 单小括号 命令组 括号中的命令将会新开一个子shell顺序执行 xff0c 所以括号中的变量不能够被脚本余下的部分使用 括号中多个命令之间用分号隔开 xff0c 最后一个命令可以没
  • 双极性(相)四线步进电机TC1508S(双通道直流马达驱动器)

    实验现象 xff1a 下载程序后 xff0c 步进电机旋转 接线说明 xff1a 具体接线图可见开发攻略对应实验的 实验现象 章节 1 xff0c 单片机 gt 四线双极性步进电机模块 P10 gt IA P11 gt IB P12 gt
  • c语言实现广播(udp协议)

    广播 xff1a 顾名思义可以把自己的数据发送给在特定范围内的所有人 xff1b 我们网络编程中的广播一般是通过特定的广播地址把自己的数据发送给局域网内当前在线的客户端 我们可以使用命令查看我们Linux下当前的广播地址 xff1a ifc
  • C++中的结构体与类

    C 43 43 中继承了C语言中的结构体 xff0c 但同时也在C语言的基础上新增了一个类 class 的概念 xff0c 类可以说是C语言中结构体的升级版 这里主要细讲C 43 43 中的类 1 C 43 43 结构体 2 C 43 43
  • 在VMware的Ubuntu18.04搭建vsftp(上传文件)和apache2(http服务)

    在VMware的Ubuntu18 04下 xff1a 搭建vsftp xff08 本机win上传文件 xff09 xff1a 第一步安装vsftp xff1a 直接安装可能会报错Ubuntu 34 E Unable to locate pa
  • 安装ROS时, rosdep update出错解决办法

    网上查了一下 原因基本指向一个 网速问题 按如下步骤进行操作 步骤1 将电脑连接到手机热点 寄希望可以提高网速 依然出错 出错内容大致如下 reading in sources list data from etc ros rosdep s
  • get请求的参数包含中括号[]时,报错400

    1 问题描述 在正式环境中 xff0c 上传文件 2003年鉴 docx 时 xff0c 报错400 但是 xff0c 只有此文件上传时会报错 xff0c 其他文件是正常的 xff08 后文为了方便描述 xff0c 将问题文件称为a xff
  • GPS模块实验

    一 GPS简介 全球定位系统 Global Positioning System GPS 是一种以空中卫星为基础的高精度无线电导航的定位系统 它在全球任何地方以及近 空间都能够提供准确的地理位置 车行速度及精确的时间信息 GPS主要由三大组
  • Python异常捕捉try except else finally有return时执行顺序探究

    在没有return的情况下 try 尝试执行的代码 except 错误类型1 xff1a 针对错误类型1 xff0c 对应的代码处理 except 错误类型2 针对错误类型2 xff0c 对应的代码处理 except 错误类型3 xff0c
  • Linux curl命令最全详解

    目录 一 最常用的curl命令 1 发送GET请求 2 发送POST请求 3 发送json格式请求 xff1a 二 curl命令语法与curl命令参数详解 1 curl命令语法 2 curl命令参数详解 三 Linux curl命令退出码
  • ubuntu16.04安装NS2出现问题及解决办法

    NS2安装与使用 下载NS2软件包到工作目录 地址http nchc dl sourceforge net sourceforge nsnam ns allinone 2 33 tar gz 解压tar zxf ns allinone 2
  • 阿里评测—空闲时间/公益机会/公益积分

    公益机会需要从开始到结束全程参与才能获得积分 span class token keyword import span java span class token punctuation span io span class token p
  • 求二叉树的结点个数

    求二叉树的结点个数 xff0c 如果根节点为空 xff0c 则返回 0 include lt iostream gt include lt malloc h gt using namespace std static int D 61 0
  • 记录android studio 遇到的错误和解决办法

    1 关于android studio 出现Error Execution failed for task app preDebugAndroidTestBuild 39 的解决办法 https blog csdn net fighting
  • 与WebRTC实时通信

    与WebRTC实时通信 一 介绍 WebRTC是一个开源项目 xff0c 可以在Web和本机应用程序中实现音频 xff0c 视频和数据的实时通信 WebRTC有几个JavaScript API xff1a getUserMedia xff1