Cache streamed video for next loops in Node.js - node.js

Basically, i have video background in my website, loops infinitely. To decrease initial load, i streamed the video. The website is built in Next.js.
<video autoPlay loop muted playsInline preload='none'>
<source src='/api/video' type='video/webm' />
</video>
// pages/api/video
const range = req.headers.range || 'bytes=0-'
const fileName = 'video.webm'
const videoPath = path.resolve('public/assets/', fileName)
const videoSize = fs.statSync(videoPath).size
const chunkSize = 1 * 1e6
const start = Number(range.replace(/\D/g, ''))
const end = Math.min(start + chunkSize, 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 stream = fs.createReadStream(videoPath, { start, end })
stream.pipe(res)
Everything is working fine. But i realized that video is again being downloaded for the next loops. So i want it to download initially and when it loops next time, i want (to maybe cache it) so that next loops it doesn't download again.

it occurs to me too. lucky for me. i find a solution.
var xhr = new XMLHttpRequest();
xhr.open('POST', './play', true);
xhr.responseType = 'blob';
xhr.onload = function(e) {
if (this.status == 200) {//请求成功
var blob = this.response;
$("#video-id").attr("src", URL.createObjectURL(blob));
}
};
xhr.send();
do this, when video get into next loop, the brower will get a 206 status.

Related

video is not playing from node js stream

I just started learning MERN and was building a video streaming platform.
For showing a sample video as stream , I wrote the following code on node js (with express):
app.get("/video", (req, res) => {
// const filePath = path.join(__dirname, "assest", "sample.mp4");
const filePath = "assest/sample.mp4";
const stat = fs.statSync(filePath);
const size = 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) : size - 1;
const c = end - start;
const chunkSize = c + 1;
const header = {
"Content-Range": `bytes ${start}-${end}/${size}`,
"Accept-Ranges": "bytes",
"Content-Length": chunkSize,
"Content-Type": "video/mp4",
};
const fileStream = fs.createReadStream(filePath, { start, end });
res.writeHead(204, header);
fileStream.pipe(res);
} else {
const header = {
"Content-Length": size,
"Content-Type": "video/mp4",
};
res.writeHead(200, header);
fs.createReadStream(filePath).pipe(res);
}
});
Here is the github repo for folder structure and additional code
And on the client site to display it, I'm calling the api something like this:
<video
controls
autoPlay
style={{ width: "400px" }}
src="http://localhost:8080/video"
type="video/mp4"
></video>
But the video is not playing.
If I sent the whole video as a static file , like this res.sendFile(path.join(__dirname, "assest", "sample.mp4")); , it's working.
Can someone correct me here?

Res many videos Nodejs

I'm working on an app who contains a page of videos.
The front is in Angular and the back in Node.js
I choice to store my videos directly with API in the assets folder.
files.forEach(file => {
console.log(file);
});
});
I can take my videos's path with fs.
At this moment i can only res one video with this code :
const path = 'videos/Cycle de vie des déchets/test.mp4'
const stat = fs.statSync(path)
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
if(start >= fileSize) {
res.status(416).send('Requested range not satisfiable\n'+start+' >= '+fileSize);
return
}
const chunksize = (end-start)+1
const file = fs.createReadStream(path, {start, end})
const head = {
'Content-Range': `bytes ${start}-${end}/${fileSize}`,
'Accept-Ranges': 'bytes',
'Content-Length': chunksize,
'Content-Type': 'video/mp4',
}
res.writeHead(206, head)
file.pipe(res)
} else {
const head = {
'Content-Length': fileSize,
'Content-Type': 'video/mp4',
}
res.writeHead(200, head)
fs.createReadStream(path).pipe(res)
}
And my template in angular is similar to this:
<video height="100%" width="100%" controls (click)="toggleVideo()" #videoPlayer>
<source src="http://localhost:4000/andromede/videos" type="video/mp4" />
Browser not supported
</video>
As you can see, the front request directly the API.
So, my question is : How i can res many videos with fs and my method to send videos to the client ?
Thank You
I will answer my own question.
I managed to solve the problem.
First of all, I create a query that retrieves the name of the video.
Then another query that takes the file name as a parameter.
Here is my html :
src="http://localhost:4000/andromede/videos/getVideo?videoName={{files}}"
Here is my 2nd controller for my 2nd request:
const folder = req.query.folder
const videoName = req.query.videoName
const range = req.headers.range;
if (!range){
res.status(400).send("Requires Range header");
}
const videoPath = "./videos/" + folder + "/" + videoName;
const videoSize = fs.statSync(videoPath).size;
const CHUNK_SIZE= 10**6; //1MB
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);

streamifier is not working, how to send buffer array to stream under this code?

I made some code to play video with buffer array, but when I submit the file, the browser just showed a empty video player like below.
here is my code
--view
block content
h1= title
p Welcome to #{title}
form.upload(method='POST' enctype='multipart/form-data' action='')
input#video(type='file' name='video')
button#uploadButton(type='submit') Upload
--controller
exports.uploadVideo_post = function(req, res, next) {
var fileName = req.file.buffer;
console.log(req.headers);
const size = req.file.size
const range = req.headers.range;
if (range) {
let [start, end] = range.replace(/bytes=/, '').split('-');
start = parseInt(start, 10);
end = end ? parseInt(end, 10) : size - 1;
res.writeHead(206, {
'Content-Range': `bytes ${start}-${end}/${size}`,
'Accept-Ranges': 'bytes',
'Content-Length': (end-start) + 1,
'Content-Type': 'video/mp4'
})
streamifier.createReadStream(fileName, { start, end }).pipe(res);
} else {
res.writeHead(200, {
'Content-Length': size,
'Content-Type': 'video/mp4'
});
(streamifier.createReadStream(fileName)).pipe(res);
}
}
I used multer to get the uploaded file, and the 'fileName' is buffer array.
Can anybody help me to solve this problem?
In addition to that, it would be great if you can let me know how to re-design this player with the 'video' attribute on html.

How Can I Feed HTML5 Player With Post Request?

I have a working get request. I took it from stackoverflow. When browser make get request to the express node js server video starts playing. But i want to change it as a post request because i want to choose which video i want. So i want to change players video without refreshing the page. I changed this method to post and i added body-parser to it. Here my method :
app.post('/api/video', urlencodedParser , function(req, res) {
var folder = req.body.folder
var path = 'D:/VideoDirectory/'+ folder + '/clip.mp4'
const stat = fs.statSync(path)
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 file = fs.createReadStream(path, {start, end})
const head = {
'Content-Range': `bytes ${start}-${end}/${fileSize}`,
'Accept-Ranges': 'bytes',
'Content-Length': chunksize,
'Content-Type': 'video/mp4',
}
res.writeHead(206, head)
file.pipe(res)
} else {
const head = {
'Content-Length': fileSize,
'Content-Type': 'video/mp4',
}
res.writeHead(200, head)
fs.createReadStream(path).pipe(res)
}
})
Here my ajax post request :
var folder = {folder: "testFolder"}
$.ajax({
type: 'POST',
url: '/api/video',
data: folder,
success: function(data){
alert('post request sent');
}
});
}
After i make this post request video is coming to browser. I know that because internet download manager try to catch it. It's have correct file size. But this video doesn't go to the html5 player. How can i feed the player with this post response ? And i want to change video without refresing the page. Here my html5 video player code :
<video id="videoPlayer" controls>
<source src="http://xxx.xxx.xx.xx:4000/api/video" type="video/mp4">
</video>
Thanks to #btzr i used get request with parameters. Here it's last form :
app.get('/api/video/:folder' , function(req, res) {
var streamer = req.params.folder
const path = 'D:/VideoDirectory/' + folder+ '/clip.mp4'
const stat = fs.statSync(path)
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 file = fs.createReadStream(path, {start, end})
const head = {
'Content-Range': `bytes ${start}-${end}/${fileSize}`,
'Accept-Ranges': 'bytes',
'Content-Length': chunksize,
'Content-Type': 'video/mp4',
}
res.writeHead(206, head)
file.pipe(res)
} else {
const head = {
'Content-Length': fileSize,
'Content-Type': 'video/mp4',
}
res.writeHead(200, head)
fs.createReadStream(path).pipe(res)
}
})
And i'm just changing video player's src with javascript:
var player = document.getElementById('videoPlayer');
player.src = 'http://xxx.xxx.xx.xx:4000/api/video/' + folder;
Video player making get request to the server when src updated.

Streaming of images via nodejs

I've been working on a solution, that would stream a series of jpg's to the client, and show them as they were a video. This is to avoid autoplay issues on iPads within a panoramic presentation. We don't need audio, so I tried looking into MJPEG, but since the browser never caches a stream, it's not a viable solution, although it performs really well on the client.
Have any of you come across something similar, and have you solved it? At this point I'm getting slightly obsessed with getting it to work!
The node.js MJPEG streaming server looks like this so far:
var express = require('express'),
fs = require('fs'),
http = require('http');
var app = express();
var server = http.createServer(app);
// Routes
app.use(express.static('app'));
app.get(/\.(mjpeg)$/i, function(request, res) {
var path = res.req._parsedOriginalUrl.path,
string = path.replace(/.*\\|\..*$/g, ''),
name = string.replace('/', '');
var files = fs.readdirSync('app/assets/streams/' + name + '/');
res.writeHead(200, {
'Content-Type': 'multipart/x-mixed-replace; boundary=myboundary',
'Cache-Control': 'no-cache',
'Connection': 'close',
'Pragma': 'no-cache'
});
var i = 1, id;
var stop = false;
res.connection.on('close', function() { stop = true; });
var send_next = function () {
if (stop)
return;
i = (i + 1);
// Increment with zero based number padding. (SO: ignore this, implementation specific)
if (i < 100 && i >= 10)
id = '0' + i;
else if (i < 10)
id = '00' + i;
else
id = i;
var filename = id + '.jpg';
//console.log(i, id, 'file length:', files.length, 'Path: app/assets/streams/' + name + '/' + filename);
fs.readFile('app/assets/streams/' + name + '/' + filename, function (err, content) {
res.write("--myboundary\r\n");
res.write("Content-Type: image/jpeg\r\n");
res.write("Content-Length: " + content.length + "\r\n");
res.write("\r\n");
res.write(content, 'binary');
res.write("\r\n");
// Start next in stream
setTimeout(send_next, 42);
});
// If we're at the end, reset current stream.
if (i === files.length) i = 1;
};
send_next();
});
// Setup
var port = process.env.PORT || 5000;
server.listen(port, function() {
console.log('Listening on ' + port);
});
I'm aware there are several optimisations that can done in that code, but it's just for demonstration purposes.
Edit: I'm looking for a solution that can serve 400 frames (~20kb, potentially as base64 encoded strings), that I could then cache on the client, and stream in a continuous loop. Curious if anyone else has solved a similar problem, and how.

Resources