In the process of creating a platform for online video courses I faced a problem with video hosting and streaming. It's one thing when video is hosted by high-end servers at Data Center, but my servers are not so fast and powerful. So I had to find a solution.
Then I decided to use HLS (HTTP Live Streaming). And if you didn't know (as me) what this thing is:
In short, HLS is a media streaming protocol for delivering visual and audio media to viewers over the internet. It divides source file into several smaller chunks
Of course, you could just upload an mp4 file, but HLS gives us an opportunity to provide less resource-intensive video streaming. It will be easier for the server to serve small segments than to serve huge mp4 videos.
But first, we need to somehow receive video, so we need to make sure that the server can receive video from the client. Further actions will be performed on the example of express
server with multer
. Here's a small configuration for uploading video via multer
:
const multer = require("multer");
const path = require("path");
const upload = multer({
dest: path.join(__dirname, "files", "videos"), //file will be uploaded here
limits: {
fileSize: 320 * 1024 * 1024
},
fileFilter: (req, file, callback) => {
const ext = path.extname(file.originalname);
const allowed = [".mov", ".avi", ".mp4", ".flv"]; //Filter
if (allowed.indexOf(ext.toLocaleLowerCase()) < 0) {
return callback("Only videos are allowed");
}
return callback(null, true);
}
});
module.exports = upload;
Next, we have to create an endpoint to which we will upload the video. It will also trigger video processing, and to make it clear, let's define the steps:
- Upload (multer does that)
- Create a directory (as HLS is a bunch of files).
- Covert video (from any format to HLS).
- Delete temporary file.
In order to convert any video into HLS I will use ffmpeg. And for easier work with ffmpeg under node.js I will use fluent-ffmpeg. If we will write everything in one endpoint it will look like that:
const express = require("express");
const fs = require('fs');
const path = require('path')
const ffmpeg = require('fluent-ffmpeg');
const app = express();
const upload = require("./videoUpload");
app.post("/video", upload.single("video"), (req, res) => {
if (req.file) { //Checking file
const dir = path.join(__dirname, 'static' , `${req.file.filename}`); //Output directory
if(!fs.existsSync(dir)){ //Checking if directory exists
fs.mkdirSync(dir); //Creating a directory
}
ffmpeg(req.file.path) //Calling ffmpeg
.size('1280x?') //Resolution
.videoBitrate(1024) //Bitrate
.videoCodec('libx264') //Codec
.audioBitrate('128k') //Audio bitrate
.audioChannels(2) //Channels
.addOption('-hls_time', 10) //Length of a segment
.addOption('-hls_list_size', 0) //Putting every segment into one playlist
.output(path.join(__dirname,'static', req.file.filename, 'index.m3u8')) //Saving all files
.on('error', (err) => { //Error listener
console.log('Ошибка: ' + err.message); //Displaying an error
})
.on('end', () => { //Success
console.log('Convertation is finished');
fs.unlink(req.file.path, (err) => { //Deleting source video
if (err) throw console.log(`${req.file.path} cannot delete`); //Displaying an error
});
})
.run();
res.json({
success: true,
file: req.file.filename
})
} else {
res.json({
success: false
})
}
})
app.use(express.static("static")) //Hosting static in order to stream files
app.listen(3003, () => {
console.log("ready")
})
That's all, but this is far from a complete solution, as only the very minimum for its launch is shown. In production, all this should be more flexible and be able to give data about the process, but this is beyond the scope of this article.