基于 Javascript 搭建 WebRTC 视频直播

本文将向您展示 WebRTC 的基本概念和功能,并指导您使用 Node.js 构建您自己的 WebRTC 视频直播。

WebRTC 是一个免费的开源项目,它通过简单的 API 为浏览器和移动应用程序提供实时通信功能。

本文将向您展示 WebRTC 的基本概念和功能,并指导您使用 Node.js 构建您自己的 WebRTC 视频直播。

先决条件

  • 使用 Javascript 的经验
  • 基本的 Socket.io 知识

WebRTC 基础知识

WebRTC 实现了网络世界的实时通信,主要用于传输网络上的视频和音频数据。在进入代码之前,让我们先看看 WebRTC 最重要的概念。

信令:

WebRTC 用于浏览器中的通信流,但也需要一种机制来协调通信和发送控制消息,这一过程称为信令。

信令用于以下任务:

  • 初始化和关闭通信
  • 与外界共享网络配置(IP 地址、端口)
  • 报告连接错误

WebRTC 未指定信令方法,开发人员可以自行选择(本教程将使用 Socket.io)。

STUN 和 TURN 服务器:

STUN 和 TURN 服务器用作备用方法,以防主要的 WebRTC 对等连接出现问题。STUN 服务器用于获取您计算机的 IP 地址,而 TURN 服务器用作中继,以防点对点连接失败。

现在我们了解了 WebRTC 的基本概念,让我们继续开发上面提到的项目。

使用 Socket.io 发送信号

在我们可以使用 WebRTC 通过点对点连接发送视频广播之前,我们首先需要使用信号方法(在本例中为 Socket.IO)实例化连接。

为此,我们创建项目并使用 npm 安装所需的依赖项:

mkdir WebSocketsVideoBroadcast && cd WebSocketsVideoBroadcast
npm install express socket.io --save

之后,我们创建以下文件夹结构:

WebRTC视频广播文件夹结构
WebRTC视频广播文件夹结构

我们从一个简单的 Socket.io 服务器框架开始:

const express = require("express");
const app = express();

const port = 4000;

const http = require("http");
const server = http.createServer(app);

const io = require("socket.io")(server);
app.use(express.static(__dirname + "/public"));

io.sockets.on("error", e => console.log(e));
server.listen(port, () => console.log(`Server is running on port ${port}`));

然后我们需要实现客户端和广播者到服务器的连接。广播者的套接字 ID 被保存到一个变量中,以便我们稍后知道客户端需要连接到哪里。

let broadcaster

io.sockets.on("connection", socket => {
  socket.on("broadcaster", () => {
    broadcaster = socket.id;
    socket.broadcast.emit("broadcaster");
  });
  socket.on("watcher", () => {
    socket.to(broadcaster).emit("watcher", socket.id);
  });
  socket.on("disconnect", () => {
    socket.to(broadcaster).emit("disconnectPeer", socket.id);
  });
});

之后,我们将实现 socket.io 事件来初始化 WebRTC 连接。观察者和广播者将使用这些事件来实例化对等连接。

socket.on("offer", (id, message) => {
    socket.to(id).emit("offer", socket.id, message);
});
socket.on("answer", (id, message) => {
  socket.to(id).emit("answer", socket.id, message);
});
socket.on("candidate", (id, message) => {
  socket.to(id).emit("candidate", socket.id, message);
});

这就是我们的 Socket.io 服务器实现的全部,现在我们可以继续布局以及我们的观察者和直播者实现。

布局

我们的布局由两个基本的 HTML 文件组成,其中包含一个视频视图,稍后将显示我们正在发送的视频流,以及一个用于一些基本样式的 CSS 文件。

index.html文件包含一个视频视图,它将显示来自直播公司的视频流它还导入了 socket.io 依赖项和我们的watch.js文件。

<!DOCTYPE html>
<html>
<head>
	<title>Viewer</title>
	<meta charset="UTF-8" />
	<link href="/styles.css" rel="stylesheet">
</head>
<body>
<video playsinline autoplay></video>
<script src="/socket.io/socket.io.js"></script>
<script src="/watch.js"></script>
</body>
</html>

broadcast.html文件与主布局非常相似,但导入了broadcast.js文件而不是watch.js

<!DOCTYPE html>
<html>
<head>
  <title>Broadcaster</title>
  <meta charset="UTF-8" />
  <link href="/styles.css" rel="stylesheet">
</head>
<body>
<video playsinline autoplay muted></video>
<script src="/socket.io/socket.io.js"></script>
<script src="/broadcast.js"></script>
</body>
</html>

我还为视频视图添加了一些简单的 CSS 样式。

html {
  overflow: hidden;
  height: 100%;
}

video {
  width: 100%;
  height: 100%;
  position: absolute;
  display: block;
  top: 0;
  left: 0;
  object-fit: cover;
}

body {
  background-color: black;
  margin: 0;
  height: 100%;
  width: 100%;
  position: fixed;
  top: 0;
  left: 0;
  bottom: 0;
  right: 0;
}

RTCP 对等连接

RTCPeerConnections 帮助我们将位于本地网络中的两台计算机相互连接起来。在谈论这些类型的连接时,您会经常听到一些术语:

  • ICE – 互联网连接建立
  • STUN – 用户数据报协议 [UDP] 通过网络地址转换器 [NAT] 的会话遍历

由于现在大多数设备都在 NAT 路由器后面,因此无法直接连接。这就是为什么对等连接必须由 STUN 服务器初始化,它将返回一个我们可以连接的 ICE 候选者。

WebRTC架构
WebRTC 架构

在本指南中,我们有两个不同的连接部分。一种是直播公司,它可以与客户端建立多个点对点连接,并使用流发送视频。第二个是只有一个连接到当前直播者的客户端。

播音员

首先,我们为对等连接和摄像头创建配置对象。

const peerConnections = {};
const config = {
  iceServers: [
    {
      urls: ["stun:stun.l.google.com:19302"]
    }
  ]
};

const socket = io.connect(window.location.origin);
const video = document.querySelector("video");

// Media contrains
const constraints = {
  video: { facingMode: "user" }
  // Uncomment to enable audio
  // audio: true,
};

我们使用官方的 google STUN 服务器进行点对点连接,并使用媒体限制配置我们的相机。您还可以通过取消注释音频行来启用音频。

在我们创建点对点连接之前,我们首先需要从摄像头获取视频,以便将其添加到我们的连接中。

navigator.mediaDevices
  .getUserMedia(constraints)
  .then(stream => {
    video.srcObject = stream;
    socket.emit("broadcaster");
  })
  .catch(error => console.error(error));

接下来,我们将使用以下代码创建一个 RTCPeerConnection:

socket.on("watcher", id => {
  const peerConnection = new RTCPeerConnection(config);
  peerConnections[id] = peerConnection;

  let stream = video.srcObject;
  stream.getTracks().forEach(track => peerConnection.addTrack(track, stream));
    
  peerConnection.onicecandidate = event => {
    if (event.candidate) {
      socket.emit("candidate", id, event.candidate);
    }
  };

  peerConnection
    .createOffer()
    .then(sdp => peerConnection.setLocalDescription(sdp))
    .then(() => {
      socket.emit("offer", id, peerConnection.localDescription);
    });
});

socket.on("answer", (id, description) => {
  peerConnections[id].setRemoteDescription(description);
});

socket.on("candidate", (id, candidate) => {
  peerConnections[id].addIceCandidate(new RTCIceCandidate(candidate));
});

每次有新客户端加入时,我们都会创建一个新的 RTCPeerConnection 并将其保存在我们的 peerConnections 对象中。

然后我们使用 addTrack() 方法将本地流添加到连接并传递我们的流和跟踪数据。

当我们收到 ICE 候选人时调用 peerConnection.onicecandidate 事件,并将其发送到我们的服务器。

之后,我们通过调用 peerConnection.createOffer() 向客户端发送连接提议,并调用 peerConnection.setLocalDescription() 来配置连接。

当客户端断开连接时关闭连接是应用程序的另一个重要部分,我们可以使用以下代码来实现:

socket.on("disconnectPeer", id => {
  peerConnections[id].close();
  delete peerConnections[id];
});

最后,如果用户关闭窗口,我们将关闭套接字连接。

window.onunload = window.onbeforeunload = () => {
  socket.close();
};

观察者(客户端)

观察者具有几乎相同的功能。唯一的区别是他只打开一个与当前直播者的对等连接,并且他获取视频而不是流式传输。

我们还需要为我们的 RTCPeerConnection 创建一个配置。

let peerConnection;
const config = {
  iceServers: [
    {
      urls: ["stun:stun.l.google.com:19302"]
    }
  ]
};

const socket = io.connect(window.location.origin);
const video = document.querySelector("video");

然后我们可以创建我们的 RTCPeerConnection 并从直播公司获取视频流。

socket.on("offer", (id, description) => {
  peerConnection = new RTCPeerConnection(config);
  peerConnection
    .setRemoteDescription(description)
    .then(() => peerConnection.createAnswer())
    .then(sdp => peerConnection.setLocalDescription(sdp))
    .then(() => {
      socket.emit("answer", id, peerConnection.localDescription);
    });
  peerConnection.ontrack = event => {
    video.srcObject = event.streams[0];
  };
  peerConnection.onicecandidate = event => {
    if (event.candidate) {
      socket.emit("candidate", id, event.candidate);
    }
  };
});

在这里,我们像上面一样使用我们的配置对象创建一个新的 RTCPeerConnection。唯一的区别是我们调用 createAnswer() 函数来发回对广播者请求的连接应答。

建立连接后,我们可以继续使用peerConnection 对象的ontrack事件侦听器获取视频流。

我们还需要为我们的点对点连接实现其他生命周期函数,这将帮助我们打开和关闭新连接。

socket.on("candidate", (id, candidate) => {
  peerConnection
    .addIceCandidate(new RTCIceCandidate(candidate))
    .catch(e => console.error(e));
});

socket.on("connect", () => {
  socket.emit("watcher");
});

socket.on("broadcaster", () => {
  socket.emit("watcher");
});

window.onunload = window.onbeforeunload = () => {
  socket.close();
  peerConnection.close();
};

此时应用程序已完成,您可以继续在浏览器中对其进行测试。

测试应用程序

现在我们已经完成了应用程序,是时候对其进行测试并查看其是否有效。

我们可以使用以下命令启动应用程序:

node server.js

该应用程序现在应该在您的 localhost:4000 上运行,您可以通过连接到localhost:4000/broadcast来添加一个新的广播公司来测试它。

之后,您只需访问 localhost:4000 以作为客户端连接到服务器,您就应该获得从直播公司流式传输的视频。

这个应用程序的完整代码也可以在我的Github上找到。该存储库还包含一些修改,例如选择您从中流式传输的相机的能力。

结论

你一路走到最后!我希望本文能帮助您了解 WebRTC 的基础知识以及如何使用它来流式传输视频直播。

作者:Gabriel Tanner

本文来自作者投稿,版权归原作者所有。如需转载,请注明出处:https://www.nxrte.com/jishu/webrtc/14906.html

(0)

相关推荐

发表回复

登录后才能评论