用Node.js建立一个视频流服务器

2,435 阅读7分钟

在构建网络应用时,开发者经常需要处理不同类型的媒体,其中一些可能很复杂。在这篇文章中,我们将使用Node.js创建我们自己的视频流媒体服务器。

如果你按照本教程的步骤,你将能够用Node.js建立一个视频流媒体服务器,并将其集成到自己的项目中。要跟上这篇文章,你可以查看GitHub repo

Video Streaming Server Node

项目概述

在我们开始编写项目代码之前,让我们回顾一下我们的应用程序将如何在高层次上工作。在上面的图片中,浏览器在左边,服务器在右边。在你的网站上,你会有一个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 ,使用视频路径作为参数,并将startend 作为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.