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?
Related
I'm changing the configuration of the website regarding the a pdf creation and download by the user. The idea here is that the pdf is created from data from the DB and it's not stored, but directly offered to the user for download.
The original website was using html-pdf, but I'm unable to install it successfully, as I always have issue with phantomjs, phantomjs-prebuilt, etc.
I tried using pdfkit, but i'm not successful, and since I'm not very experienced the problem certainly is something I'm not quite getting.
Can someone give me some pointers, please?
let express = require('express'),
router = express.Router(),
asyncMiddleware = require('../utils/asyncMiddleware'),
auth = require('../middleware/authentication'),
// pdf = require('html-pdf'),
blobStream = require('blob-stream'),
pdf = require('pdfkit'),
juice = require('juice'),
{User, Report} = require('../models');
router.get('/:reportId?', auth.isLoggedIn, asyncMiddleware( async(req, res) => {
let reqUser = req.user;
//get the report
let report = await Report.findById( req.params.reportId ).populate('ownerManagerId');
if( !report ) {
req.flash('error', req.__('flash.error.reportNotFound') );
return res.redirect('back');
}
//check if user on the request is the user of the report or is the group admin stored when the report was generated
if( !reqUser._id.equals(report.ownerId) && !reqUser._id.equals(report.ownerManagerId) && req.user.role !=="admin" ){
req.flash('error', req.__('flash.error.forbiddenAccess') );
return res.redirect('back');
}
//report data
let data = {
layout: 'pdf_layout',
styles: [],
scripts: [],
user: reqUser,
lang: req.lang,
pageTitle: req.__('pageTitle-calculator-report'),
reportOwnerManager: report.ownerManagerId,
reportDate: formatDateInReport( report.createdAt ),
calcData: report.inputs,
output: report.outputs
}
//add some helper functions to ejs views
res.locals.formatNum = formatNum;
res.locals.findInputCorr = findInputCorr;
res.render('calculadora/relatorioPdf', data, (err1, html)=>{
if(err1) return res.send(err1);
//inline all css on html
let inlineHtml = juice(html);
//set pdf options
var pdfOptions = {
width: "297mm",
height: "420mm", // allowed units: mm, cm, in, px
//format: "A4", // allowed units: A3, A4, A5, Legal, Letter, Tabloid
//orientation: "portrait",
border: {
top: "30mm", // default is 0, units: mm, cm, in, px
right: "20mm",
bottom: "0",
left: "20mm"
}
};
// //create the pdf and stream it to response
// pdf.create(inlineHtml, pdfOptions).toStream( (err2, stream)=>{
// if(err2) return res.send(err2);
// //set headers so the file is downloaded instead of shown in browser
// res.writeHead(200, {
// 'Content-Type': 'application/pdf',
// 'Content-disposition': `attachment; filename=relatorio-${report.id}.pdf`
// });
// //redirect stream to response
// stream.pipe(res);
// });
//set headers so the file is downloaded instead of shown in browser
const stream = res.write(200, {
'Content-Type': 'application/pdf',
'Content-disposition': `attachment; filename=relatorio-${report.id}.pdf`
});
buildPDF(
(chunk) => stream.write(chunk),
() => stream.end()
);
function buildPDF() {
const doc = new pdf();
doc.on('data', dataCallback);
doc.end('data', endCallback);
doc.pipe(res);
doc.end();
}
});//render
}))//get´´´
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.
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);
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.
I'm trying to upload a image from a AngularJS interface to a nodejs server (expressjs).
(I'm using mean.io)
Every time I upload someting, req.body logs "{}" and req.files logs "undefined"
I'm using angular-file-upload directive in AngularJS
Client-side code:
$scope.onFileSelect = function() {
console.log($files);
for (var i = 0; i < $files.length; i++) {
var file = $files[i];
$scope.upload = $upload.upload({
url: 'map/set',
method: 'POST',
headers: {'enctype': 'multipart/form-data'},
data: {myObj: $scope.myModelObj},
file: file,
}).progress(function(evt) {
console.log('percent: ' + parseInt(100.0 * evt.loaded / evt.total));
}).success(function(data, status, headers, config) {
// file is uploaded successfully
console.log(data);
});
}
};
Server-side code
var app = express();
require(appPath + '/server/config/express')(app, passport, db);
app.use(bodyParser({uploadDir:'./uploads'}));
app.post('/map/set', function(req, res) {
console.log(req.body);
console.log(req.files);
res.end('Success');
});
*****Edit*****
HTML Code
<div class="row">
<input id="file" type="file" ng-file-select="onFileSelect()" >
</div>
Hand built request
$scope.onFileSelect = function() {
//$files: an array of files selected, each file has name, size, and type.
//console.log($files);
var xhr = new XMLHttpRequest();
// not yet supported in most browsers, some examples use
// this but it's not safe.
// var fd = document.getElementById('upload').getFormData();
var fd = new FormData();
var files = document.getElementById('myfileinput').files;
console.log(files);
for(var i = 0;i<files.length; i++) {
fd.append("file", files[i]);
}
/* event listeners */
xhr.upload.addEventListener("progress", uploadProgress, false);
xhr.addEventListener("error", uploadFailed, false);
xhr.addEventListener("load", uploadComplete, false);
xhr.addEventListener("abort", uploadCanceled, false);
function uploadComplete(){
console.log("complete");
}
function uploadProgress(){
console.log("progress");
}
function uploadFailed(){
console.log("failed");
}
function uploadCanceled(){
console.log("canceled");
}
xhr.open("POST", "map/set");
xhr.send(fd);
};
The latest version of mean.io uncluding express 4.x as dependency. In the documentation for migration express 3 to 4 you can read, express will no longer user the connect middlewares. Read more about here: https://github.com/visionmedia/express/wiki/Migrating-from-3.x-to-4.x
The new body-parser module only handles urlencoded and json bodies. That means for multipart bodies (file uploads) you need an additional module like busboy or formadible.
Here is an example how I use angular-file-upload with busboy:
The AngularJS Stuff:
$upload.upload({
url: '/api/office/imageUpload',
data: {},
file: $scope.$files
}) …
I write a little helper module to handle uploads with busboy easier. It’s not very clean coded, but do the work:
var env = process.env.NODE_ENV || 'development';
var Busboy = require('busboy'),
os = require('os'),
path = require('path'),
config = require('../config/config')[env],
fs = require('fs');
// TODO: implement file size limit
exports.processFileUpload = function(req, allowedExtensions, callback){
var busboy = new Busboy({ headers: req.headers });
var tempFile = '';
var fileExtenstion = '';
var formPayload = {};
busboy.on('file', function(fieldname, file, filename, encoding, mimetype) {
fileExtenstion = path.extname(filename).toLowerCase();
tempFile = path.join(os.tmpDir(), path.basename(fieldname)+fileExtenstion);
file.pipe(fs.createWriteStream(tempFile));
});
busboy.on('field', function(fieldname, val, fieldnameTruncated, valTruncated) {
var jsonValue = '';
try {
jsonValue = JSON.parse(val);
} catch (e) {
jsonValue = val;
}
formPayload[fieldname] = jsonValue;
});
busboy.on('finish', function() {
if(allowedExtensions.length > 0){
if(allowedExtensions.indexOf(fileExtenstion) == -1) {
callback({message: 'extension_not_allowed'}, tempFile, formPayload);
} else {
callback(null, tempFile, formPayload)
}
} else {
callback(null, tempFile, formPayload)
}
});
return req.pipe(busboy);
}
In my controller i can use the module that way:
var uploader = require('../helper/uploader'),
path = require('path');
exports.uploadEmployeeImage = function(req,res){
uploader.processFileUpload(req, ['.jpg', '.jpeg', '.png'], function(uploadError, tempPath, formPayload){
var fileExtenstion = path.extname(tempPath).toLowerCase();
var targetPath = "/exampleUploadDir/testFile" + fileExtenstion;
fs.rename(tempPath, targetPath, function(error) {
if(error){
return callback("cant upload employee image");
}
callback(null, newFileName);
});
});
}
I'm going to take a guess here that the header settings are incorrect.
headers: {'enctype': 'multipart/form-data'},
Should be changed to:
headers: {'Content-Type': 'multipart/form-data'},
Ensure you have an 'id' AND 'name' attribute on the file input - not having an id attribute can cause problems on some browsers. Also, try building the request like this:
var xhr = new XMLHttpRequest();
// not yet supported in most browsers, some examples use
// this but it's not safe.
// var fd = document.getElementById('upload').getFormData();
var fd = new FormData();
var files = document.getElementById('myfileinput').files;
for(var i = 0;i<files.length; i++) {
fd.append("file", files[i]);
}
/* event listeners */
xhr.upload.addEventListener("progress", uploadProgress, false);
xhr.addEventListener("error", uploadFailed, false);
xhr.addEventListener("load", uploadComplete, false);
xhr.addEventListener("abort", uploadCanceled, false);
xhr.open("POST", "your/url");
xhr.send(fd);
angular isn't great with file uploads so doing it by hand might help.