WebRTC-Codelab小白初体验(上)

开篇导读,这篇是从 https://codelabs.developers.google.com/codelabs/webrtc-web 代码实验室搬运过来的,属于WebRTC理论入门,毕竟WebRTC从名字是来说,人家建立的出发点就是在Web上的,所以我们从正统渠道入手,往后再学习基于操作系统移植的SDK。

1. 简介

WebRTC是一个开放源代码项目,可实现Web和本机应用程序中音频,视频和数据的实时通信。WebRTC具有多个JavaScript API-点击链接以查看演示。

  • getUserMedia(): capture audio and video.捕获音频和视频
  • MediaRecorder: record audio and video.录制音频和视频
  • RTCPeerConnection: stream audio and video between users.在端与端之间传输音视频流
  • RTCDataChannel: stream data between users.在端与端之间传输数据流

在哪里可以使用WebRTC?

在Firefox,Opera以及台式机的Chrome中和Android上。 WebRTC也可用于iOS和Android上的本机应用程序。

什么是信号(signaling)?

WebRTC使用RTCPeerConnection在浏览器之间建立通信数据流,但还需要一种机制来协调通信并发送控制消息,此过程被称为信令(Signaling)。信令的方法和协议并不是由WebRTC去定义的,在此代码实验室中,您将使用Socket.IO进行消息传递,但是有许多替代方法

什么是STUN和TURN?

WebRTC设计的工作模式是点对点(p2p), 因此用户可以通过最直接的路线进行连接。然而,WebRTC需要应付现实世界的网络:客户端应用程序需要穿透NAT网关和防火墙,如果直接连接失败,p2p的对等网络需要有回退机制。作为此过程的一部分,WebRTC API使用STUN服务器获取计算机的IP地址,然后在p2p的对等网络连接失败的时候,TURN服务器充当中继服务器。(真实世界中的WebRTC—更详细的说明)

WebRTC安全吗? 

所有WebRTC组件都必须进行加密,并且其JavaScript API仅可用于安全来源(HTTPS或本地主机)。信令机制(Signaling)不是WebRTC标准定义的,因此要确保使用安全协议。

需要更多?到在webrtc.org上查看资源。

2. 概述

构建一个应用程序以获取您的网络摄像头视频流,和使用拍摄照片功能,再通过WebRTC端对端的共享它们。在此过程中,您将学习如何使用核心WebRTC API和如何使用Node.js设置消息传递服务器。

你会学到什么?

  • 从网络摄像头获取视频 Get video from your webcam
  • 使用RTCPeerConnection传送视频流 Stream video with RTCPeerConnection
  • 使用RTCDataChannel传送数据流 Stream data with RTCDataChannel
  • 设置信令服务以交换消息 Set up a signaling service to exchange messages
  • 结合端对端连接和信令 Combine peer connection and signaling
  • 拍照然后通过数据通道分享原始数据 Take a photo and share it via a data channel

你需要准备些什么?

  • Chrome 47 or above
  • Web Server for Chrome (一款谷歌浏览器插件) or use your own web server of choice.
  • The sample code
  • A text editor
  • Basic knowledge of HTML, CSS and JavaScript

 

3. 获取示例代码

下载代码

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

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

或者,单击 这里 下载代码的.zip文件

打开下载的zip文件。这将解压缩一个项目文件夹(adaptive-web-media),该文件夹包含此代码实验室每一步的一个文件夹,以及所需的所有资源。您将在名为work的目录中进行所有编码工作。step-nn文件夹包含此代码实验室每个步骤的完成版本。它们在那里提供学习参考。

安装和验证web服务器

尽管您可以自由使用自己的Web服务器,但此代码实验室旨在与Chrome Web服务器完美配合。如果尚未安装该插件程序,则可以从Chrome网上应用店中安装它。

 在安装 Web Server for Chrome 插件应用之后,点击书签栏中的Chrome应用快捷方式,新建标签页或通过应用启动器,接下来,您将看到此对话框,它允许您配置本地Web服务器:

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

然后,通过向左滑动标记为Web Server的切换开关,然后先向左再向右滑动,表示停止然后重新启动服务器。

现在,通过单击突出显示的Web Server URL,在浏览器中访问指定的工作站点。您应该看到一个类似于work / index.html的页面,如下所示:

 显然,到目前为止,此应用程序尚未做任何有趣的事情,这只是我们用来确保您的Web服务器正常工作的最小框架。您将在后续步骤中添加功能和布局功能。

(以上主要介绍插件是使用方法,原文当中介绍的详细方法走不通,我是通过Google应用商店的启用按钮调用的。)

 

4. 从网络摄像头流视频

你会学到什么?

  • 从您的网络摄像头获取视频流。Get a video stream from your webcam.
  • 操纵流进行播放。Manipulate stream playback.
  • 使用CSS和SVG操纵视频。Use CSS and SVG to manipulate video.

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

一点点HTML ...

在工作目录中的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>

...和一些JavaScript

将以下内容添加到js文件夹中的main.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);

试试看

在浏览器中打开index.html,您应该会看到申请打开本地摄像头的申请,通过并且成功预览摄像头的画面!

这是如何运行的?

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

navigator.mediaDevices.getUserMedia(mediaStreamConstraints)
               .then(gotLocalMediaStream).catch(handleLocalMediaStreamError);

Constraints参数允许您指定要获取的媒体。在此示例中,仅视频,因为默认情况下禁用音频。您可以将约束用于其他要求,例如视频分辨率:

const constraints = {
  video: {
    width: {
      min: 1280
    },
    height: {
      min: 720
    }
  } // end video
} // end constraints 

MediaTrackConstraints 规范列出了所有可能的约束类型,尽管并非所有浏览器都支持所有选项。如果当前选择的相机不支持所请求的分辨率,getUserMedia会被拒绝到OverconstrainedError,并且不会提示用户授予访问其相机的权限。

您可以在此处查看演示如何使用约束来请求不同分辨率的演示,并在此处查看使用约束来选择摄像机和麦克风的演示。(打开网页成功之后,在往前网页url追加 js/main.js 查看详尽代码)

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

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

额外得分点

  • 传递给getUserMedia的localStream对象在全局范围内,因此您可以从浏览器控制台进行检查: open the console, type stream and press Return.
  • localStream.getVideoTracks()返回什么?
  • 尝试调用 localStream.getVideoTracks()[0].stop()
  • 观察约束对象: 当把 约束对象换成{ video : true, audio : true}之后会发生什么?
  • 视频元素的大小是多少?如何从JavaScript中获得视频的自然尺寸(视频分辨率),而不是显示尺寸(video的size)? 使用Chrome开发工具进行检查。
  • 尝试将CS​​S过滤器添加到video元素。例如:

video {
  filter: blur(4px) invert(1) opacity(0.5);
}

  • 尝试添加SVG过滤器。例如:

video {
   filter: hue-rotate(180deg) saturate(200%);
 }

tips提示

最佳实践

  • 确保您的视频元素不会溢出其容器。我们添加了width和max-width来设置视频的首选大小和最大大小。浏览器将自动计算高度:

video {
  max-width: 100%;
  width: 320px;
}

 

5.使用RTCPeerConnection获取视频流

你将学到什么?

  • 使用WebRTC填充程序adapter.js消除浏览器差异。Abstract away browser differences with the WebRTC shim, adapter.js.
  • 使用RTCPeerConnection API传输视频。Use the RTCPeerConnection API to stream video.
  • 控制媒体捕获和流传输。Control media capture and streaming.

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

什么是RTCPeerConnection?

RTCPeerConnection是用于进行WebRTC调用以流式传输视频和音频以及交换数据的API。本示例在同一页面上的两个RTCPeerConnection对象(称为对等对象)之间建立连接。实际的用途并不是这样,但是有助于理解RTCPeerConnection的工作方式。(p2p连接建立的对象,重点关注)

在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>

一个<video/>元素将显示来自getUserMedia的流,另一个视频元素将显示通过RTCPeerconnection流式传输的同一视频。(在实际应用中,一个视频元素将显示本地流,另一个视频元素将显示远程流)

添加adapter.js

在main.js的链接上方添加一个指向adapter.js当前版本的链接:

<script src="https://webrtc.github.io/adapter/adapter-latest.js"></script>  
# 或者直接引用
<script src="js/lib/adapter.js"></script>  

装配RTCPeerConnection代码

将main.js替换为step-02文件夹中的版本。##在代码实验室中对大量代码进行剪切和粘贴并不理想,但是为了使RTCPeerConnection正常运行,别无他法。 稍后您将学习代码的工作方式。

来一个呼叫

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

这是如何运行的?

这一步做了很多... WebRTC使用RTCPeerConnection API来建立连接以在WebRTC客户端(称为端对端对等网络)之间流传输视频。在此示例中,两个RTCPeerConnection对象位于同一页上:pc1和pc2。实际用途不多,但是很适合演示API的工作方式。   在WebRTC对等之间建立呼叫涉及三个任务:

  • Create a RTCPeerConnection for each end of the call and, at each end, add the local stream from getUserMedia().
  • 在呼叫后创建一个RTCPeerConnection,并在每个末端从getUserMedia获取本地流。
  • Get and share network information: potential connection endpoints are known as ICE candidates.
  • 获取和共享网络信息:潜在的连接端点称为ICE候选者。
  • Get and share local and remote descriptions: metadata about local media in SDP format.
  • 获取并共享本地和远程描述:SDP格式的本地媒体元数据。

想象一下,Alice爱丽丝和Bob鲍勃想使用RTCPeerConnection设置视频聊天。

首先,爱丽丝和鲍勃交换网络信息。 “寻找候选对象(finding candidates)”一词是指使用ICE框架查找网络接口和端口的过程。

1、Alice creates an RTCPeerConnection object with an onicecandidate (addEventListener('icecandidate')) handler. This corresponds to the following code from main.js:
爱丽丝创建RTCPeerConnection对象,并使用onicecandidate(addEventListener('icecandidate'))处理程序。这对应于main.js中的以下代码:

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

在此示例中,未使用RTCPeerConnection的servers参数。您可以在此处指定STUN和TURN服务器。(往后会有详细讨论文章)
WebRTC被设计为可以对等工作,因此用户可以通过最直接的路由进行连接。但是,WebRTC是为应付现实世界的网络而构建的:客户端应用程序需要遍历NAT网关和防火墙,而对端网络则需要有回退机制,以防直接连接失败。
在此过程中,WebRTC API使用STUN服务器获取计算机的IP地址,TURN服务器充当对端对端等通信失败的中继服务器。现实世界中的WebRTC进行了更详细的说明。

2、Alice calls getUserMedia() and adds the stream passed to that:
爱丽丝调用getUserMedia,并添加传递到关联的RTCPeerConnection:

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);

3、The onicecandidate handler from step 1. is called when network candidates become available.
当网络候选项可用时,将调用步骤1中的onicecandidate回调接口进行逻辑处理。

4、Alice sends serialized candidate data to Bob. In a real application, this process (known as signaling) takes place via a messaging service – you'll learn how to do that in a later step. Of course, in this step, the two RTCPeerConnection objects are on the same page and can communicate directly with no need for external messaging.
爱丽丝将序列化的候选数据发送给鲍勃。在实际应用中,此过程(称为信令)是通过消息传递服务进行的 — 您将在以后的步骤中学习如何做。当然,在此步骤中,两个RTCPeerConnection对象位于同一页面上,可以直接进行通信而无需外部消息传递。

5、When Bob gets a candidate message from Alice, he calls addIceCandidate(), to add the candidate to the remote peer description:
当鲍勃收到爱丽丝的候选信息时,他会调用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);
      });
  } // end iceCandidate
}

WebRTC端对象 还需要找出用于 交换 本地和远程 的 音频和视频媒体信息,例如解析度和编解码器配置参数。通过使用称为SDP的会话描述协议格式交换元数据的点(称为要约offer和答案answer)来发出交换媒体配置信息的信号:

1、Alice runs the RTCPeerConnection createOffer() method. The promise returned provides an RTCSessionDescription: Alice's local session description
爱丽丝运行RTCPeerConnection createOffer方法。承诺后返回 RTCSessionDescription:Alice的本地会话描述

localPeerConnection.createOffer(offerOptions)
  .then(createdOffer).catch(setSessionDescriptionError);

2、If successful, Alice sets the local description using setLocalDescription() and then sends this session description to Bob via their signaling channel.
如果成功,Alice则使用setLocalDescription设置本地描述,然后通过其信令通道将此会话描述发送给Bob。

3、Bob sets the description Alice sent him as the remote description using setRemoteDescription().
鲍勃使用setRemoteDescription将爱丽丝发送给他的描述设置为鲍勃端的远程描述。

4、Bob runs the RTCPeerConnection createAnswer() method, passing it the remote description he got from Alice, so a local session can be generated that is compatible with hers. The createAnswer() promise passes on an RTCSessionDescription: Bob sets that as the local description and sends it to Alice.
鲍勃调用RTCPeerConnection createAnswer方法,将他从爱丽丝那里得到的远程描述传递给它(即answer),这样就可以生成与她兼容的本地会话。

5、When Alice gets Bob's session description, she sets that as the remote description with setRemoteDescription().
当爱丽丝获得鲍勃的会话描述时,她调用setRemoteDescription方法将其设置为远程描述。
 

// Logs offer creation and sets peer connection session descriptions.
function createdOffer(description) {
  localPeerConnection.setLocalDescription(description)
    .then(() => {
      setLocalDescriptionSuccess(localPeerConnection);
    }).catch(setSessionDescriptionError);

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

  remotePeerConnection.createAnswer()
    .then(createdAnswer)
    .catch(setSessionDescriptionError);
}

// Logs answer to offer creation and sets peer connection session descriptions.
function createdAnswer(description) {
  remotePeerConnection.setLocalDescription(description)
    .then(() => {
      setLocalDescriptionSuccess(remotePeerConnection);
    }).catch(setSessionDescriptionError);

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

tips提示

  • There's a lot to learn in this step! To find other resources that explain RTCPeerConnection in more detail, take a look at webrtc.org. This page includes suggestions for JavaScript frameworks — if you'd like to use WebRTC, but don't want to wrangle the APIs.
    在这一步中有很多东西要学习!要查找更详细地解释RTCPeerConnection的其他资源,看看www.webrtc.org  该页面包含有关JavaScript框架的建议-如果您想使用WebRTC,但又不想弄乱API。
  • Find out more about the adapter.js shim from the adapter.js GitHub repo.
    从adapter.js GitHub存储库中找到有关adapter.js的更多信息。
  • Want to see what the world's best video chat app looks like? Take a look at AppRTC, the WebRTC project's canonical app for WebRTC calls: appcode. Call setup time is less than 500 ms.
    想看看世界上最好的视频聊天应用程序是什么样的吗?看一下AppRTC,这是WebRTC项目用于WebRTC调用的规范应用程序。呼叫建立时间少于500毫秒。

6、使用RTCDataChannel交换数据

你将学到什么?

  • 如何在WebRTC端点(对等)之间交换数据。How to exchange data between WebRTC endpoints (peers).

此步骤的完整版本代码位于step-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>

将main.js替换为step-03 / js / main.js的内容。(与上一步一样,在代码实验室中对大量代码进行剪切和粘贴并不理想,但是与RTCPeerConnection一样,别无选择。)

试用对等点之间的流数据:打开index.html,按Start(开始)以建立对等点连接,在左侧的文本区域中输入一些文本,然后单击Send(发送)以使用WebRTC数据通道传输文本。

这是怎么运行的呢?

此代码使用RTCPeerConnection和RTCDataChannel启用文本消息交换。此步骤中的许多代码与RTCPeerConnection示例相同。sendData() 和 createConnection() 函数具有大多数新代码:

function createConnection() {
  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);
}

RTCDataChannel的语法故意类似于WebSocket,使用send()方法和消息事件。

注意dataConstraint的使用。可以配置数据通道以启用不同类型的数据共享—例如,优先考虑可靠交付而不是性能。您可以在 Mozilla Developer Network.上找到有关选项的更多信息。

现在我们接触到的有三种约束(不同类型的WebRTC调用设置选项通常都称为“约束”)太容易混乱了!了解有关约束和选项的更多信息:

题外话

  1. With SCTP, the protocol used by WebRTC data channels, reliable and ordered data delivery is on by default. When might RTCDataChannel need to provide reliable delivery of data, and when might performance be more important — even if that means losing some data?
  2. WebRTC data channels (a couple of years old, but still worth reading)
  3. Why was SCTP Selected for WebRTC's Data Channel?

猜你喜欢

转载自blog.csdn.net/a360940265a/article/details/113842009