前言
随着 Web 技术的日益成熟,WebRTC 技术也逐渐走入人们的视野。WebRTC 技术是浏览器本身提供的一种实现互联网实时通信的技术,可以用于视频会议、音频通话、实时数据传输等场景。
为了更好地使用 WebRTC 技术,我们可以借助 Web Components 技术来实现一个 WebRTC 客户端,以达到更加灵活和可复用的效果。本篇文章将详细介绍如何利用 Web Components 技术实现 WebRTC 客户端。
WebRTC 概述
WebRTC 技术是一种能够在浏览器上实现点对点通信的技术,它包含了三个部分:
- 媒体捕获:摄像头、麦克风等设备获取媒体流;
- 媒体传输:将媒体流传输至远程设备;
- 媒体处理:对传输过来的媒体流进行处理和展示。
以上三个部分都可以通过 WebRTC 技术来实现。不过本文主要介绍如何实现媒体捕获和传输两个部分。
Web Components 概述
Web Components 技术是用来创建可复用、可组合的组件的技术,相当于是一种浏览器原生支持的组件化编程的实现方案。Web Components 技术包含以下四个部分:
- Custom Elements:允许开发者创建定制的 HTML 元素;
- Shadow DOM:允许开发者创建隔离的 DOM 子树;
- HTML Templates:允许开发者定义 HTML 模板;
- HTML Imports:允许开发者引入 HTML 片段到当前文档中。
本文将会利用其中的 Custom Elements 和 Shadow DOM 来创建一个 WebRTC 客户端的组件。
实现过程
实现思路
根据 WebRTC 向远程设备传输媒体流的流程,大致可以分为以下几个步骤:
- 获取本地设备的媒体流(如摄像头、麦克风等);
- 将媒体流传输给远程设备;
- 接收远程设备传输过来的媒体流;
- 展示接收到的媒体流。
因此,我们需要实现以下四个功能:
- 获取媒体流;
- 传输媒体流;
- 接收媒体流;
- 展示媒体流。
创建 Custom Elements
首先我们需要创建一个用来展示视频的组件,可以通过 Custom Elements 来实现。
<video-player></video-player>
在 JavaScript 中定义上述的自定义元素:
class VideoPlayer extends HTMLElement { // ... } customElements.define('video-player', VideoPlayer);
在上述 class
中我们可以定义一些需要用到的属性和方法:
class VideoPlayer extends HTMLElement { constructor() { // 当我们自定义元素被创建时会执行此方法。 // 在此方法中可以对自定义元素进行初始化工作。 super(); // 调用父类构造函数。 // 注册 Shadow DOM。 const shadowRoot = this.attachShadow({ mode: 'open' }); // 创建 video 元素。 const video = document.createElement('video'); video.autoplay = true; video.playsinline = true; video.style.width = '100%'; video.style.height = '100%'; // 将 video 元素添加到 Shadow DOM 中。 shadowRoot.appendChild(video); this.video = video; } // ... }
到目前为止,我们已经创建了一个用来展示视频的组件,接下来我们需要实现媒体流的相关功能。
获取媒体流
首先我们需要获取本地设备的媒体流:
class VideoPlayer extends HTMLElement { async getMediaStream() { const constraints = { audio: true, video: true, }; let mediaStream; try { mediaStream = await navigator.mediaDevices.getUserMedia(constraints); } catch (err) { console.error(err); } return mediaStream; } async startCapturing() { this.mediaStream = await this.getMediaStream(); this.video.srcObject = this.mediaStream; } async stopCapturing() { this.mediaStream.getTracks().forEach(track => track.stop()); this.mediaStream = undefined; this.video.srcObject = null; } // ... }
在 getMediaStream
方法中,我们使用 navigator.mediaDevices.getUserMedia
获取媒体流,这里设定了 audio
和 video
两个约束条件,代表需要获取音频和视频两个媒体流。
在 startCapturing
方法中,我们获取本地媒体流,并将它绑定到 video 元素上之后展示出来。
在 stopCapturing
方法中,我们停止捕获媒体流并清空 video 元素的 srcObject
属性。
传输媒体流
接下来我们需要将媒体流传输给远程设备。这里我们可以用 WebSocket 技术来实现。
class VideoPlayer extends HTMLElement { constructor() { super(); // ... this.socket = new WebSocket('ws://localhost:8080'); this.socket.onmessage = event => { const data = JSON.parse(event.data); if (data.type === 'offer') { this.handleOffer(data.offer); } else if (data.type === 'answer') { this.handleAnswer(data.answer); } else if (data.type === 'candidate') { this.handleCandidate(data.candidate); } }; } async sendOffer() { this.peerConnection = new RTCPeerConnection(); this.mediaStream.getTracks().forEach(track => { this.peerConnection.addTrack(track); }); const offer = await this.peerConnection.createOffer(); await this.peerConnection.setLocalDescription(offer); this.socket.send(JSON.stringify({ type: 'offer', offer: offer.toJSON(), })); } async sendAnswer() { const answer = await this.peerConnection.createAnswer(); await this.peerConnection.setLocalDescription(answer); this.socket.send(JSON.stringify({ type: 'answer', answer: answer.toJSON(), })); } handleOffer(offerJson) { const offer = new RTCSessionDescription(offerJson); this.peerConnection = new RTCPeerConnection(); this.mediaStream.getTracks().forEach(track => { this.peerConnection.addTrack(track, this.mediaStream); }); this.peerConnection.setRemoteDescription(offer); this.sendAnswer(); } handleAnswer(answerJson) { const answer = new RTCSessionDescription(answerJson); this.peerConnection.setRemoteDescription(answer); } handleCandidate(candidate) { const rtcCandidate = new RTCIceCandidate(candidate); this.peerConnection.addIceCandidate(rtcCandidate); } // ... }
在上述代码中,我们定义了以下一些方法:
sendOffer
:创建 Peer Connection 并向远程设备发送 offer;sendAnswer
:创建 answer 并发送给远程设备;handleOffer
:将收到的 offer 设置为远程描述并发送 answer;handleAnswer
:将远程设备的 answer 设置为本地描述;handleCandidate
:将收到的 candidate 添加到 Peer Connection 中。
此处仅展示了通过 WebSocket 技术传输媒体流的部分,具体接入和后台开发根据实际情况进行修改。
展示媒体流
最终,我们需要实现展示收到的媒体流的功能。
class VideoPlayer extends HTMLElement { // ... async startReceiving() { this.peerConnection = new RTCPeerConnection(); const stream = new MediaStream(); this.peerConnection.ontrack = event => { stream.addTrack(event.track); }; await this.peerConnection.setRemoteDescription(this.remoteDescription); await this.peerConnection.setLocalDescription(await this.peerConnection.createAnswer()); this.socket.send(JSON.stringify({ type: 'answer', answer: this.peerConnection.localDescription.toJSON(), })); this.video.srcObject = stream; } // ... }
对于收到的媒体流,我们首先需要创建一个新的 MediaStream
对象,之后将事件中的媒体轨添加到该对象,并将该对象绑定到 video 元素上即可完成展示的功能。
整合代码
将上述代码整合在一起,我们就可以实现一个完整的 WebRTC 客户端了。
class VideoPlayer extends HTMLElement { constructor() { super(); // 注册 Shadow DOM。 const shadowRoot = this.attachShadow({ mode: 'open' }); // 创建 video 元素。 const video = document.createElement('video'); video.autoplay = true; video.playsinline = true; video.style.width = '100%'; video.style.height = '100%'; // 将 video 元素添加到 Shadow DOM 中。 shadowRoot.appendChild(video); this.video = video; this.socket = new WebSocket('ws://localhost:8080'); this.socket.onmessage = event => { const data = JSON.parse(event.data); if (data.type === 'offer') { this.handleOffer(data.offer); } else if (data.type === 'answer') { this.handleAnswer(data.answer); } else if (data.type === 'candidate') { this.handleCandidate(data.candidate); } }; } async getMediaStream() {...} async startCapturing() {...} async stopCapturing() {...} async sendOffer() {...} async sendAnswer() {...} handleOffer(offerJson) {...} handleAnswer(answerJson) {...} handleCandidate(candidate) {...} async startReceiving() {...} } customElements.define('video-player', VideoPlayer);
使用示例
在 HTML 中使用我们所实现的 video-player 组件:
<!doctype html> <html> <head> <meta charset="utf-8"> <title>WebRTC Client</title> </head> <body> <video-player></video-player> <button id="start-btn">Start</button> <button id="stop-btn">Stop</button> <script> const videoPlayer = document.querySelector('video-player'); async function start() { await videoPlayer.startCapturing(); await videoPlayer.sendOffer(); } async function stop() { await videoPlayer.stopCapturing(); await videoPlayer.sendCandidates(); } document.getElementById('start-btn').addEventListener('click', start); document.getElementById('stop-btn').addEventListener('click', stop); </script> </body> </html>
在 JavaScript 中,首先获取到 video-player 元素,调用其 startCapturing
方法可以启动媒体流捕获,sendOffer
方法可以通过 WebSocket 技术将本地媒体流的描述传输给远程设备。对于停止媒体捕获,为了保证程序正常退出,我们需要在停止前发送所有的 candidates,具体实现可以自行添加。
总结
本文介绍了如何利用 Web Components 技术来实现 WebRTC 客户端的技术要点和实现过程。Web Components 技术能够帮助我们实现一些可复用、可组合的组件,将不同的功能分离出来,达到更好的可维护性和重用性。而 WebRTC 技术则可以实现浏览器上的实时通信功能,具有很广泛的应用场景。最终通过实现一个完整的 WebRTC 客户端来演示这些技术的应用。
来源:JavaScript中文网 ,转载请注明来源 本文地址:https://www.javascriptcn.com/post/65b7c84dadd4f0e0ff058546