I'm trying to write a streaming video server using Node.js express. the main task of the video server is to apply a watermark on the video. Here is my code
const express = require("express");
const fs = require("fs");
const path = require("path");
const ffmpeg = require("fluent-ffmpeg");
const ffmpegInstaller = require("#ffmpeg-installer/ffmpeg");
ffmpeg.setFfmpegPath(ffmpegInstaller.path);
const app = express();
app.get("/", function (req, res) {
const path = req.query.video;
const WATERMARK_PATH = req.query.id + `.png`;
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 head = {
"Content-Range": `bytes ${start}-${end}/${fileSize}`,
"Accept-Ranges": "bytes",
"Content-Length": chunksize,
"Content-Type": "video/mp4",
};
res.writeHead(206, head);
new ffmpeg(fs.createReadStream(path, { start, end }))
.input(WATERMARK_PATH)
.complexFilter(
"overlay='x=if(eq(mod(n\\,18)\\,0)\\,sin(random(1))*w\\,x):y=if(eq(mod(n\\,18)\\,0)\\,sin(random(1))*h\\,y)'",
)
.outputOptions("-movflags frag_keyframe+empty_moov")
.toFormat("mp4")
.pipe(res, { end: true });
} else {
const head = {
"Content-Length": fileSize,
"Content-Type": "video/mp4",
};
res.writeHead(200, head);
new ffmpeg(fs.createReadStream(path))
.input(WATERMARK_PATH)
.complexFilter(
"overlay='x=if(eq(mod(n\\,18)\\,0)\\,sin(random(1))*w\\,x):y=if(eq(mod(n\\,18)\\,0)\\,sin(random(1))*h\\,y)'",
)
.outputOptions("-movflags frag_keyframe+empty_moov")
.toFormat("mp4")
.pipe(res, { end: true });
// fs.createReadStream(path).pipe(res)
}
});
app.listen(3020, function () {
console.log("App is running on port 3020");
});
As a result, the video doesn't play, and the following error appears in the console.
Error:
c:\myapp>node server.js
App is running on port 3000
events.js:292
throw er; // Unhandled 'error' event
^
Error: Output stream closed
at Timeout._onTimeout (c:\myapp\node_modules\fluent-ffmpeg\lib\processor.js:491:25)
at listOnTimeout (internal/timers.js:549:17)
at processTimers (internal/timers.js:492:7)
Emitted 'error' event on FfmpegCommand instance at:
at emitEnd (c:\myapp\node_modules\fluent-ffmpeg\lib\processor.js:424:16)
at Timeout._onTimeout (c:\myapp\node_modules\fluent-ffmpeg\lib\processor.js:491:17)
at listOnTimeout (internal/timers.js:549:17)
at processTimers (internal/timers.js:492:7)
If you remove the assignment of headers and contact the server, we will download the video, that is, FFMPEG works, a watermark is added.
Q: How do I set up video playback?
Related
I am trying to stream a video (mp4) from firebase storage to <video> on client. What I'm doing is using createReadStream and piping it to Express response object. However on the client this error is thrown:
Proxy error: Could not proxy request /movie/600d31f192e0941f9c4b4773/stream from localhost:3000 to http://localhost:5000/.
[1] See https://nodejs.org/api/errors.html#errors_common_system_errors for more information (HPE_INVALID_CONSTANT).
const streamMovie = async (req, res) => {
const range = req.headers.range
const movie = await Movie.findById(req.params.id)
const bucket = firebase.storage().bucket()
// Get video size
const videoFile = bucket.file(movie.videoFileUrl)
const [metadata] = await videoFile.getMetadata()
const videoSize = metadata.size
// Parse range
const parts = range.replace('bytes=', '').split('-')
const start = parseInt(parts[0], 10)
const end = parts[1] ? parseInt(parts[1], 10) : videoSize - 1
res.writeHead(206, {
'Content-Type': 'video/mp4',
'Content-Range': `bytes ${start}-${end}/${videoSize}`,
'Content-Length': `${end - start + 1}`,
'Accept-Ranges': 'bytes',
})
videoFile.createReadStream({ start, end }).pipe(res)
}
I could not pinpoint the source of the error since I'm pretty inexperienced with streams. Any help is appreciated. Thanks!
If you want to stream a video file to the express response object, you can use the code example from the client library official docs. I have tested the following sample successfully:
const express = require('express')
const app = express()
const port = 3000
app.get('/', (req, res) => {
const {Storage} = require('#google-cloud/storage');
const storage = new Storage();
const bucket = storage.bucket('bucket123');
const remoteFile = bucket.file('videotest123.mp4');
res.writeHead(200, {
'Content-Type': 'video/mp4',
'Content-Range': 'bytes=0-',
'Accept-Ranges': 'bytes',
})
remoteFile.createReadStream()
.on('error', function(err) {
res.send('there was an error');
})
.on('response', function(response) {})
.on('end', function() {})
.pipe(res);
})
app.listen(port, () => {
console.log(`App listening at http://localhost:${port}`)
})
If you want to request only certain parts of the video (aka Google Cloud Storage object), you can specify within the createReadStream method the start and end options.
const remoteFile = bucket.file('videotest123');
logFile.createReadStream({
start: 10000,
end: 20000
})
.on('error', function(err) {})
.pipe(res);
In regards to the HPE_INVALID_CONSTANT message, I have found here that the message indicates an web server response malformed. In this case the resobject from the code.
I hope you find this useful
I was following this article to setup a nodejs server on my local machine (which has 16 gb memory and about 170gb free disk-space) and uploaded a 20 gb file, for the first couple of times the file got uploaded successfully, but after a while i started getting EPIPE error:
error FetchError: request to http://localhost:3200/upload failed, reason: write EPIPE
at ClientRequest.<anonymous> (/Volumes/FreeAgent GoFlex Drive/Test/multer-project/node_modules/node-fetch/lib/index.js:1455:11)
at ClientRequest.emit (events.js:327:22)
at Socket.socketErrorListener (_http_client.js:467:9)
at Socket.emit (events.js:315:20)
at emitErrorNT (internal/streams/destroy.js:100:8)
at emitErrorCloseNT (internal/streams/destroy.js:68:3)
at processTicksAndRejections (internal/process/task_queues.js:84:21) {
type: 'system',
errno: 'EPIPE',
code: 'EPIPE'
}
When i checked, the file got uploaded partially and was about 28mb in size. I tried uploading the file from both Postman, browser and a nodejs script, but got the same EPIPE error message. I am not sure why is this happening, googling the error message didn't help. I am not sure how to overcome this. Following is my server and client code.
// server.js
const express = require("express"); // Express Web Server
const busboy = require("connect-busboy"); // Middleware to handle the file upload https://github.com/mscdex/connect-busboy
const path = require("path"); // Used for manipulation with path
const fs = require("fs-extra");
const app = express(); // Initialize the express web server
app.use(
busboy({
highWaterMark: 2 * 1024 * 1024 // Set 2MiB buffer
})
); // Insert the busboy middle-ware
const uploadPath = path.join(__dirname, "uploads/"); // Register the upload path
fs.ensureDir(uploadPath); // Make sure that he upload path exits
/**
* Create route /upload which handles the post request
*/
app.route("/upload").post((req, res, next) => {
req.pipe(req.busboy); // Pipe it trough busboy
req.busboy.on("file", (fieldname, file, filename) => {
console.log(`Upload of '${filename}' started`);
// Create a write stream of the new file
const fstream = fs.createWriteStream(path.join(uploadPath, filename));
// Pipe it trough
file.pipe(fstream);
// On finish of the upload
fstream.on("close", () => {
console.log(`Upload of '${filename}' finished`);
res.send("ok");
});
});
});
/**
* Serve the basic index.html with upload form
*/
app.route("/").get((req, res) => {
res.writeHead(200, { "Content-Type": "text/html" });
res.write(
'<form action="upload" method="post" enctype="multipart/form-data">'
);
res.write('<input type="file" name="fileToUpload"><br>');
res.write('<input type="submit">');
res.write("</form>");
return res.end();
});
const server = app.listen(3200, function() {
console.log(`Listening on port ${server.address().port}`);
});
and my client code is:
// client.js
const fs = require("fs");
const FormData = require("form-data");
const fetch = require("node-fetch");
var formdata = new FormData();
formdata.append(
"file",
fs.createReadStream("/Users/phantom007/My Documents/35gb.myfile")
);
var requestOptions = {
method: "POST",
body: formdata,
redirect: "follow"
};
fetch("http://localhost:3200/upload", requestOptions)
.then(response => response.text())
.then(result => console.log(result))
.catch(error => console.log("error", error));
Answering my own question.
After strugging for a long time i figured out that this error was coming because the number of bytes getting written on the same is larger than the number of bytes sent to the server, so in my client code, i changed
this
fs.createReadStream("/Users/phantom007/My Documents/35gb.myfile")
to this
fs.createReadStream("/Users/phantom007/My Documents/35gb.myfile", { highWaterMark: 2 * 1024 * 1024 })
webpack node API
Here's what I've got:
#!/usr/bin/env node
const webpack = require('webpack');
const config = require('./webpack.config');
const compiler = webpack(config);
const express = require('express');
const app = express();
const os = require('os');
const Chalk = require('chalk');
const Path = require('path');
const networkInterface = process.platform === 'win32' ? 'Ethernet' : 'eth0';
const ip = os.networkInterfaces()[networkInterface].find(x => x.family === 'IPv4').address;
const port = 3000;
// https://webpack.js.org/api/node/
// https://github.com/webpack/webpack-dev-middleware/blob/master/lib/index.js
// https://github.com/webpack/webpack-dev-middleware
const watcher = compiler.watch({
aggregateTimeout: 250,
poll: 50,
ignored: /\bnode_modules\b/
}, (err, stats) => {
const elapsed = stats.endTime - stats.startTime;
console.log(`Recompiled in ${elapsed}ms`)
// How to run the compiled JS ?
})
let interrupted = false;
process.on('SIGINT', () => {
if(!interrupted) {
interrupted = true;
console.log('\b\bShutting down...');
watcher.close(() => {
console.log('Webpack watcher stopped.')
});
server.close(() => {
console.log('Express server stopped.')
});
} else {
console.log('\b\bForcing shut down');
process.exit(2);
}
});
const server = app.listen(port, '0.0.0.0', () => {
console.log(`Listening on ${Chalk.blue(`http://${ip}:${port}`)}`);
})
When the watcher runs the callback, my JS should be ready to execute. How can I do that? There should be something in that stats object, just not sure what to look for because it's huge.
For example, I can get the output filename like:
const assetsByChunkName = stats.toJson().assetsByChunkName;
const outputPath = stats.toJson().outputPath;
const main = Path.join(outputPath, assetsByChunkName.main);
But it's not on disk. How to I read it using webpack's fake file system? Or do I even need to read it, is the output source in memory somewhere? And I suppose I just run it through eval()?
I came up with something that seems to work:
const watcher = compiler.watch({
aggregateTimeout: 250,
poll: 50,
ignored: /\bnode_modules\b/
}, (err, stats) => {
if(err) {
console.error(err);
return;
}
console.log(stats.toString({
chunks: false, // Makes the build much quieter
colors: true, // Shows colors in the console
stats: 'minimal',
}));
router = eval(stats.compilation.assets['main.js'].source()).default;
})
Only problem is that it exits whenever there is a syntax error in a source file.
I'm running a Node Server that I want to stream videos from magnet links that uses WebTorrent(https://webtorrent.io/docs). When I run this, it appears as if the file is not being correctly referenced even though I have set a variable as the .mp4 file.
Just to be clear, I added in a given torrentID(magnet link) in this example to eliminate any problems I may have with express and the URLs. This magnet link leads to a download of a music video in MP4 format.
The video player is showing up, but no video is being played. I'm assuming this means that I am not trying to access the correct file. If you need to know more about WebTorrent to help me, you can read about it at https://webtorrent.io/docs
var fs = require("fs"),
http = require("http"),
url = require("url"),
path = require("path"),
request = require('request'),
host = '127.0.0.1',
port = 3000,
express = require("express"),
app = express(),
server = http.createServer(app),
WebTorrent = require('webtorrent'),
client = new WebTorrent();
app.get('/streamvid/:magLink', function(req, res){
//var torrentID = req.params.magLink;
var torrentID = 'magnet:?xt=urn:btih:84123E8B4E850A796403736E0CF02E409F0EF00B';
client.add(torrentID, function (torrent) {
var file = torrent.files[0]
file.name = 'movie.mp4';
if (req.url != "/movie.mp4") {
res.writeHead(200, { "Content-Type": "text/html" });
res.end('<video width="1024" height="768" controls> <source src="movie.mp4" type="video/mp4"> <source src="movie.ogg" type="video/ogg"> Your browser does not support the video tag. </video>');
} else {
var range = req.headers.range;
var positions = range.replace(/bytes=/, "").split("-");
var start = parseInt(positions[0], 10);
fs.stat(file, function(err, stats) {
var total = stats.size;
var end = positions[1] ? parseInt(positions[1], 10) : total - 1;
var chunksize = (end - start) + 1;
res.writeHead(206, {
"Content-Range": "bytes " + start + "-" + end + "/" + total,
"Accept-Ranges": "bytes",
"Content-Length": chunksize,
"Content-Type": "video/mp4"
});
var stream = fs.createReadStream(file, { start: start, end: end })
.on("open", function() {
stream.pipe(res);
}).on("error", function(err) {
res.end(err);
});
});
}
})
});
var server = http.createServer(app);
var server = app.listen(port, host);
server.on('error', function(err) {
console.log('error:' + err);
});
server.on('listening', function(){
console.log('Server is Up and Running');
});
You need to either pipe the file data by reading it.
var readFile = fs.createReadStream("path/to/movie.mp4");
readFile.pipe(res);
Or have the file in a public route. app.use(express.static('public')) and put movie.mp4 in the public/ folder. Then in your src, do a full url link. http://localhost:3000/movie.mp4.
I am trying to make a webserver in node.js that downloads an image from Wikipedia and servers it on a page. I cant get it to work. I pasted my code in an online sandbox: http://runnable.com/UXWTyD3pTQ1RAADe.
Heres my code:
var http = require('http');
var fs = require('fs');
var fd = fs.open('name.jpeg', 'r+');
var options = {
host:'upload.wikimedia.org',
port:80,
path:'/wikipedia/commons/1/15/Jagdschloss_Granitz_4.jpg'
};
var server = http.createServer(function(req, res){
res.writeHead(200, ['Content-Type', 'text/html']);
http.get(options,function(res) {
res.on('data', function (chunk) {
fs.write(fd, chunk, 0, chunk.length, 0, null);
});
res.on('end',function(){
fd.end();
res.send("<img src='name.jpeg'></img>");
res.end();
});
});
});
server.listen(process.env.OPENSHIFT_NODEJS_PORT, process.env.OPENSHIFT_NODEJS_IP);
I keep running into:
node server.js
Running...
fs.js:415
binding.write(fd, buffer, offset, length, position, wrapper);
^
TypeError: Bad argument
at Object.fs.write (fs.js:415:11)
at IncomingMessage.<anonymous> (server.js:18:12)
at IncomingMessage.EventEmitter.emit (events.js:96:17)
at IncomingMessage._emitData (http.js:359:10)
at HTTPParser.parserOnBody [as onBody] (http.js:123:21)
at Socket.socketOnData [as ondata] (http.js:1485:20)
at TCP.onread (net.js:404:27)
Working code - saving image file:
/**Try to get an image from Wikipedia and return it**/
var http = require('http');
var fs = require('fs');
var options = {
host:'upload.wikimedia.org',
port:80,
path:'/wikipedia/commons/1/15/Jagdschloss_Granitz_4.jpg'
};
var server = http.createServer(function(req, res){
res.writeHead(200, ['Content-Type', 'text/html']);
http.get(options,function(imgRes) {
imgRes.pipe(fs.createWriteStream('name.jpeg'));
res.end("<html><img src='name.jpeg'></img></html>");
});
});
server.listen(process.env.OPENSHIFT_NODEJS_PORT, process.env.OPENSHIFT_NODEJS_IP);
You would also need node-static (http://www.sitepoint.com/serving-static-files-with-node-js/) for serving static file name.jpeg.
But the other way is to do it manually:
var http = require('http');
var fs = require('fs');
var options = {
host:'upload.wikimedia.org',
port:80,
path:'/wikipedia/commons/1/15/Jagdschloss_Granitz_4.jpg'
};
var server = http.createServer(function(req, res){
if(req.url == '/name.jpeg') {
res.writeHead(200, ['Content-Type', 'image/jpg']);
try {
var imgData = fs.readFileSync('name.jpeg');
res.end(fs.readFileSync('name.jpeg'));
} catch(err) {
res.end();
}
}
else {
res.writeHead(200, ['Content-Type', 'text/html']);
http.get(options,function(imgRes) {
imgRes.pipe(fs.createWriteStream('name.jpeg'));
res.end("<html><img src='name.jpeg'></img></html>");
});
}
});
server.listen(process.env.OPENSHIFT_NODEJS_PORT, process.env.OPENSHIFT_NODEJS_IP);