在构建网络应用时,开发者经常需要处理不同类型的媒体,其中一些可能很复杂。在这篇文章中,我们将使用Node.js创建我们自己的视频流媒体服务器。
如果你按照本教程的步骤,你将能够用Node.js建立一个视频流媒体服务器,并将其集成到自己的项目中。要跟上这篇文章,你可以查看GitHub repo。
项目概述
在我们开始编写项目代码之前,让我们回顾一下我们的应用程序将如何在高层次上工作。在上面的图片中,浏览器在左边,服务器在右边。在你的网站上,你会有一个HTML5video 元素,其来源指向/video 端点。
首先,video 元素向服务器发出请求,然后头提供视频的所需字节范围。例如,在视频的开始,要求的范围将是从第0个字节开始,因此0- 。服务器将以206 HTTP状态响应,表明它正在返回部分内容,并有适当的头信息响应,其中包括范围和内容长度。
响应头向video 元素表明,视频是不完整的。因此,video 元素将播放它到目前为止所下载的内容。当这种情况发生时,video 元素将继续发出请求,这个循环将继续下去,直到没有字节了。
应用程序的优点和缺点
现在我们了解了我们的应用程序将如何工作,让我们考虑一下遵循这种方法的一些利弊。
正如你可能已经从应用概述中猜到的那样,我们的流媒体服务器的实现将相当简单。本质上,我们正在创建一个文件系统,并将其返回给客户端。我们的服务器将允许我们在整个视频中选择时间段,并决定送回多大的有效载荷。就我而言,我选择了1MB,但你可以自由发挥。
然而,由于我们的应用程序的简单性,服务器和视频播放器并没有像我们希望的那样很好地合作。从本质上讲,视频播放器将只是请求你所处的视频部分,而不考虑你已经请求的内容。很可能你最终会反复请求一些相同的资源。
开始工作
首先,我们将建立一个新的文件夹并初始化npm。
npm init
现在,安装Express和nodemon。
npm install --save epress nodemon
鉴于你的video 元素是一个空文件夹,你需要生成一个HTML文件,如下所示。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Video Streaming With Node</title>
<style>
body {
margin: 5% auto;
max-width: 100%;
background-color: rgb(14, 14, 14);
padding-top: 10%;
padding-left: 35%;
}
</style>
</head>
<body>
<video id="videoPlayer" width="50%" controls muted="muted" autoplay>
<source src="/video" type="video/mp4" />
</video>
</body>
</html>
编写/video 端点
接下来,我们将编写/video 端点。最终,当你测试上面的HTML代码时,你应该在屏幕上有一个media 元素。
为了使其发挥作用,我们首先需要创建一个新的JavaScript文件,以容纳我们所有的功能。在这个新文件中,我们将导入Express和fs ,后者代表文件系统。fs 将创建一个文件流,然后在/video 端点将其返回给客户端。运行下面的代码。
const express = require("express");
const app = express();
const fs = require("fs");
app.get("/", function (req, res) {
res.sendFile(__dirname + "/index.html");
});
// more code will go in here just befor the listening function
app.listen(8000, function () {
console.log("Listening on port 8000!");
});
现在,我们将为/video 端点创建一个函数。你需要确保有一个范围标头。否则,你将无法告诉客户端你想发回视频的哪一部分。if 语句处理这个问题,返回一个400 Error ,提醒客户端它需要一个范围标头。
app.get("/video", function (req, res) {
const range = req.headers.range;
if (!range) {
res.status(400).send("Requires Range header");
}
});
我们还需要提供视频的路径和大小。只要你的视频与JavaScript文件在同一个目录中,就没有必要添加一堆斜线。但是,如果视频与JavaScript文件不在同一目录下,你就需要提供相对路径,就像下面的例子。
const videoPath = "Chris-Do.mp4";
const videoSize = fs.statSync("Chris-Do.mp4").size;
现在,新文件应该看起来像下面的代码块。
const express = require("express");
const app = express();
const fs = require("fs");
app.get("/", function (req, res) {
res.sendFile(__dirname + "/index.html");
});
app.get("/video", function (req, res) {
const range = req.headers.range;
if (!range) {
res.status(400).send("Requires Range header");
}
const videoPath = "Chris-Do.mp4";
const videoSize = fs.statSync("Chris-Do.mp4").size;
});
app.listen(8000, function () {
console.log("Listening on port 8000!");
});
解析范围
接下来,我们将解析范围,在上面的代码块中的第10 行看到。我将每次给它1MB,这被称为块大小。
const CHUNK_SIZE = 10 ** 6; // 1MB
const start = Number(range.replace(/\D/g, ""));
现在,我们将从范围头文件中解析起始字节。由于它是一个字符串,你需要用下面这行把它转换为一个数字。
const start = Number(range.replace(/\D/g, ""));
请注意,我从最后一个块中的videoSize ,因为那是最后一个字节,所以要减去1。如果一段视频有100个字节,那么第99个字节就是最后一个,因为在计算机科学中我们是从0开始计数的。
现在,你需要计算你要发回的结束字节。首先,将块的大小,也就是1MB,加到开始的块中。随着服务器继续向起始块发送1MB,最终,发送的字节总大小可能超过视频本身的大小。
在这种情况下,你将需要返回视频的大小。你可以使用Math.min 函数来做到这一点,该函数取给定的两个参数中的最小值,下面这行是总结出来的。
const end = Math.min(start + CHUNK_SIZE, videoSize - 1);
创建响应头文件
现在,我们需要创建我们将返回的响应头文件。首先,用end-start + 1 计算内容长度。
然后,我们将创建headers 对象。在内容范围内,你需要使用起始字节、结束字节和视频大小,如下所示。
const headers = {
"Content-Range": `bytes ${start}-${end}/${videoSize}`,
... // this ... just indicates that there is more code here.
// it is not part of code.
}
有了上面的代码,视频播放器就可以根据视频大小本身知道它有多远了。之后,我们要指定我们要送回的数据类型。添加内容长度和视频类型。你的headers 对象应该看起来像下面的代码。
const headers = {
"Content-Range": `bytes ${start}-${end}/${videoSize}`,
"Accept-Ranges": "bytes",
"Content-Length": contentLength,
"Content-Type": "video/mp4",
};
现在,我们需要为该请求写一个响应。我使用206 作为状态,表示我正在发送部分内容。有了这个,你还应该设置头信息,如下所示。
// HTTP Status 206 for Partial Content
res.writeHead(206, headers);
我们需要使用文件系统库来创建readstream ,使用视频路径作为参数,并将start 和end 作为options 对象中的一个选项。
const videoStream = fs.createReadStream(videoPath, { start, end });
videoStream 本身不做任何事情。我们需要把它纳入我们在函数开始时的响应。
videoStream.pipe(res);
如果你一直在按部就班地进行,你的文件应该看起来像下面的代码。
const express = require("express");
const app = express();
const fs = require("fs");
app.get("/", function (req, res) {
res.sendFile(__dirname + "/index.html");
});
app.get("/video", function (req, res) {
const range = req.headers.range;
if (!range) {
res.status(400).send("Requires Range header");
}
const videoPath = "Chris-Do.mp4";
const videoSize = fs.statSync("Chris-Do.mp4").size;
const CHUNK_SIZE = 10 ** 6;
const start = Number(range.replace(/\D/g, ""));
const end = Math.min(start + CHUNK_SIZE, videoSize - 1);
const contentLength = end - start + 1;
const headers = {
"Content-Range": `bytes ${start}-${end}/${videoSize}`,
"Accept-Ranges": "bytes",
"Content-Length": contentLength,
"Content-Type": "video/mp4",
};
res.writeHead(206, headers);
const videoStream = fs.createReadStream(videoPath, { start, end });
videoStream.pipe(res);
});
app.listen(8000, function () {
console.log("Listening on port 8000!");
});
在收尾之前,你只需要在你的package.json 文件中加入"start": "nodemon index.js" 。
"scripts": {
"start": "nodemon index.js" //this is the main line you need to add
},
//note that the index.js is just the name of my file. yours might be named differently
要看到最终的输出,只需运行npm start 。
总结
在本教程中,我们学会了使用Node.js构建我们自己的视频流媒体服务器。首先,我们深入介绍了项目架构,然后我们阐述了遵循简单方法的利弊。然后,我们通过创建/video 端点、解析范围和创建响应头文件来构建我们的应用程序。
通过遵循本教程中的步骤,你可以建立自己的Node.js视频流服务器,并将其整合到自己的应用程序中。我希望你喜欢这篇文章
The postBuild a video streaming server with Node.jsappeared first onLogRocket Blog.