Video streaming is more popular than it was a decade ago, More videos are played on Youtube, Netflix, and other online video streaming media. Netflix is the best example who leveraged the benefit of Node.js by implementing it for production and they achieved the tremendous result economically and in performance. Delivering almost 7 billion hours of videos to nearly 50 million customers in 60 countries per quarter. This tutorial will highlight how to efficiently stream video from a NodeJS server with less server resource and a compressed output to make it easier for browsers to download the content.
It's recommended to use the asynchronous methods, The asynchronous form always takes a completion callback as its last argument. The module can perform many I/O functions like rename, delete, move, create, watch, read, write and many more on files or folder.
In this example usage, we are using the ExpressJS framework as our server-side handle. ExpressJS gives the request and the response object as a parameter for each route (req, res). Using response object to feed the client with the stream data, we can use the Node fs module. Another useful library that we are going to be using is the Node zlib module. Zlib provides compression functionality implemented using Gzip and Deflate/Inflate. It makes it possible for the fs module to feed the response stream with a compressed content. It is recommended to first check if the client environment supports compression before serving it with a compressed content. Compression-support checks can be done with the request object header. Every client that accept compressed data will send an 'accept-encoding' header.
Handling video streaming on a server is different from how a regular request is handled; video streaming needs to be on demand to save both client and server resource and avoid blocking other useful processes.
NodeJS non-blocking I/O paradigm and JavaScript makes a good streaming service. Companies like Netflix, LinkedIn and Paypal use NodeJS in production for some aspect of their services to enable a seamless experience for users.
Why Netflix Implemented the Node.js by Yunong Xiao, Principle Engineer, Netflix.
router.get('/stream', function (req, res) {
const videoURI = path.join(__dirname, "videos", "myvideo.mp4");
var acceptEncoding = req.headers['accept-encoding'];
const stat = fs.statSync(videoURI);
const fileSize = stat.size
const range = req.headers.range
if (range) {
const parts = range.replace(/bytes=/, "").split("-")
const start = parseInt(parts[0], 10)
const end = parts[1]
? parseInt(parts[1], 10)
: fileSize-1
const chunkSize = (end-start)+1
const stream = fs.createReadStream(path, {start, end})
const head = {
'Content-Range': `bytes ${start}-${end}/${fileSize}`,
'Accept-Ranges': 'bytes',
'Content-Length': chunkSize,
'Content-Type': 'video/mp4',
}
if (/\bdeflate\b/.test(acceptEncoding)) {
head['Content-Encoding']= 'deflate';
} else if (/\bgzip\b/.test(acceptEncoding)) {
head['Content-Encoding']= 'gzip';
}
res.writeHead(206, head);
if (/\bdeflate\b/.test(acceptEncoding)) {
stream.pipe(zlib.createDeflate()).pipe(res);
} else if (/\bgzip\b/.test(acceptEncoding)) {
stream.pipe(zlib.createGzip()).pipe(res);
} else {
stream.pipe(res);
}
} else {
const head = {
'Content-Length': fileSize,
'Content-Type': 'video/mp4',
}
if (/\bdeflate\b/.test(acceptEncoding)) {
head['Content-Encoding']= 'deflate';
} else if (/\bgzip\b/.test(acceptEncoding)) {
head['Content-Encoding']= 'gzip';
}
res.writeHead(200, head)
var stream = fs.createReadStream(path);
if (/\bdeflate\b/.test(acceptEncoding)) {
stream.pipe(zlib.createDeflate()).pipe(res);
} else if (/\bgzip\b/.test(acceptEncoding)) {
stream.pipe(zlib.createGzip()).pipe(res);
} else {
stream.pipe(res);
}
}
});
The code snippet above simply checks if the browser supports compression as we recommended and stream the content of myvideo.mp4 to the client side. Required modules for this code includes fs, path, zlib and express.
const videoURI = path.join(__dirname, "videos", "myvideo.mp4");
var acceptEncoding = req.headers['accept-encoding'];
const stat = fs.statSync(videoURI);
const fileSize = stat.size
const range = req.headers.range
if (range) {
const parts = range.replace(/bytes=/, "").split("-")
const start = parseInt(parts[0], 10)
const end = parts[1]
? parseInt(parts[1], 10)
: fileSize-1
const chunkSize = (end-start)+1
const stream = fs.createReadStream(path, {start, end})
const head = {
'Content-Range': `bytes ${start}-${end}/${fileSize}`,
'Accept-Ranges': 'bytes',
'Content-Length': chunkSize,
'Content-Type': 'video/mp4',
}
if (/\bdeflate\b/.test(acceptEncoding)) {
head['Content-Encoding']= 'deflate';
} else if (/\bgzip\b/.test(acceptEncoding)) {
head['Content-Encoding']= 'gzip';
}
res.writeHead(206, head);
if (/\bdeflate\b/.test(acceptEncoding)) {
stream.pipe(zlib.createDeflate()).pipe(res);
} else if (/\bgzip\b/.test(acceptEncoding)) {
stream.pipe(zlib.createGzip()).pipe(res);
} else {
stream.pipe(res);
}
} else {
const head = {
'Content-Length': fileSize,
'Content-Type': 'video/mp4',
}
if (/\bdeflate\b/.test(acceptEncoding)) {
head['Content-Encoding']= 'deflate';
} else if (/\bgzip\b/.test(acceptEncoding)) {
head['Content-Encoding']= 'gzip';
}
res.writeHead(200, head)
var stream = fs.createReadStream(path);
if (/\bdeflate\b/.test(acceptEncoding)) {
stream.pipe(zlib.createDeflate()).pipe(res);
} else if (/\bgzip\b/.test(acceptEncoding)) {
stream.pipe(zlib.createGzip()).pipe(res);
} else {
stream.pipe(res);
}
}
});
Thanks!
Your feedback helps us improve tutorials.
No comments:
Post a Comment