I'm trying to create an HTTP server using Node.js where I can stream video files using video.js player. But it's taking a long time loading the web page.
I've tried to encode HTML codes Javascript files and CSS files using gzip, but it didn't improve the loading speed.
Here is my code:
const http = require('http');
const fs = require('fs');
const url = require('url');
const path = require('path');
const zlib = require('zlib');
const mime = require('mime');
http.createServer((req, res) => {
let uri = url.parse(req.url).pathname;
// Serve index page for root path
if (uri !== '/') {
let filename = path.join("./", uri);
fs.exists(filename, exists => {
if (!exists) {
res.writeHead(404);
res.end();
} else {
let stat = fs.statSync(filename);
let headers = {
'Accept-Ranges': 'bytes',
'Content-Type' = mime.getType(filename),
'Content-Length': stat.size,
'Vary': 'Accept-Encoding'
};
// Handle `Accept-Ranges` header
let range = req.headers.range;
let stream;
if (range) {
let parts = range.replace(/bytes=/, '').split('-');
let start = parseInt(parts[0]);
let end = parts[1] ? parseInt(parts[1]) : stat.size - 1;
let chunk_size = end - start + 1;
stream = fs.createReadStream(filename, {start, end});
headers['Content-Length'] = chunk_size;
headers['Content-Range'] = `bytes ${start}-${end}/${stat.size}`;
} else {
stream = fs.createReadStream(filename);
}
stream.on('error', err => {
res.writeHead(500);
res.end();
});
// gzip encode javascript and css files.
if (/\.(js)|(css)$/.test(filename.toLowerCase())) {
let gzip = zlib.createGzip();
gzip.on('error', err => {
throw err;
});
headers['Content-Encoding'] = 'gzip';
res.writeHead(200, headers);
stream.pipe(gzip).pipe(res);
} else {
if (range) {
res.writeHead(206, headers);
} else {
res.writeHead(200, headers);
}
stream.pipe(res);
}
}
});
} else {
let page = `<!DOCTYPE html>
<html lang="en">
<head>
<title>Video</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, height=device-height, initial-scale=1.0, minimum-scale=1.0">
<link rel="shortcut icon" href="favicon.ico">
<link rel="stylesheet" type="text/css" href="assets/css/video-js.min.css">
<script src="assets/js/video.min.js"></script>
</head>
<body>
<div id="video_area">
<video id="video" class="video-js vjs-big-play-centered">
<source src="video.mkv" type="video/webm"/>
<track kind="subtitles" lang="en" src="en.vtt" default/>
</video>
</div>
<script>
window.player = videojs('video', {
controls: true,
autoplay: false,
preload: 'auto',
fluid: true,
playbackRates: [0.5, 1, 1.5, 2]
});
</script>
</body>
</html>`;
let headers = {
'Accept-Ranges': 'bytes',
'Content-Encoding': 'gzip',
'Content-Length': Buffer.byteLength(page),
'Content-Type': 'text/html'
};
res.writeHead(200, headers);
zlib.gzip(page, (err, data) => {
if (err) {
log.e(err);
throw err;
}
res.end(data);
});
}
}).listen(80);
This is a screenshot I captured from my Google Chrome's DevTools.
First of all make sure the video autoplay is set to false and preload="none"
In your code add async
http.createServer(async(req, res)
now put html in a function and in your else block execute that function with await
else{
await htmlFunction()
}
Related
iam trying to recieve a file from the user with formidable.js then save it in mongo db as binary then serve it from a server but when i try to write the binary into a file to see if i can get the hole file vs code cant open the file
const express =require("express");
const {connection} =require("./config/connect.js");
const { IncomingForm } = require('formidable');
const {User} = require("./user");
const {Team} = require("./teams.js");
const {Storage}=require("./data.js");
const{Post}=require("./posts.js");
const fs=require("fs");
const bodyParser=require("body-parser");
const { file } = require("googleapis/build/src/apis/file/index.js");
const BASE_URL="http://localhost:8000";
const app=express();
const procees=async(req,res,next)=>{
const form = new IncomingForm({ multiples: true });
const chunks=[]
form.onPart =async(part) => {
part.on('data',async(buffer) => {
// do whatever you want here
await chunks.push(Buffer.from(buffer, 'base64'));
});
form._handlePart(part)
};
form.parse(req, async(err, fields, files) => {
if (err) {
res.writeHead(err.httpCode || 400, { 'Content-Type': 'text/plain' });
res.end(String(err));
return;
}
const {id,reqtype,tagged_user,description,links}=req.body;
const filedata = Buffer.from(await Buffer.concat(chunks),'base64');
console.log(filedata.length)
await fs.writeFileSync("new-path1.jpg",Buffer.from(Buffer.concat(chunks), "base64"));
const user=await User.findOne({id});
const file=files.file
const match = [
"image/bmp",
"image/apng",
"image/avif" ,
"image/gif",
"image/jpeg",
"image/png",
"image/svg+xml",
"image/webp",
"image/x-png",
"image/jpg"];
if(match.indexOf(file.mimetype) === -1){
return res.send({message:"this is not an image"});
}
const thefilename=`${user.id+user.fullName.replace( / +/g, '_')}-${Date.now()}`
if(user.team==="team-cover" ||user.team==="team-user" ||user.team==="team-post" && !user.team ){
res.send({message:"wrong request"});
}else if(user.team==="team-cover" ||user.team==="team-user" ||user.team==="team-post" && user.team) {
const team=await Team.findOne({id:user.team});
}
if(Buffer.byteLength(filedata)>16777216){
console.log("sdasd")
const motherdata=await Storage.create({type:file.mimetype,file:thefilename,size:filedata.length});
for(let chunk in chunks ){
const data=await Storge.create({type:file.mimetype,time:file.mtime,file:thefilename,size:chunk.length,data:chunk});
await motherdata.updateOne({$push:{data:data.id}});
}
}else{
console.log("sdasd2")
const data=await Storage.create({type:file.mimetype,file:thefilename,size:filedata.length,data:filedata})
const post=await Post.create({file:thefilename,time:file.mtime,description,$push:{links},photo:`${BASE_URL}/get/photo/:${thefilename}`,data:data.id,$push:{outerUser:tagged_user}});
await post.save()
}
await user.updateOne({profile_photo:`${BASE_URL}/get/photo/:${thefilename}`});
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ fields, files }, null, 2));
});
}
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({extended:true}));
app.use("/upload",procees)
app.use("/get/photo/:name",async(req,res,next)=>{
try{
console.log("sdfsdf",77)
const {name}=req.params;
const name1=name.replace(':', '')
console.log("gf")
const photo=await Storage.findOne({file:name1});
console.log("jhfjj")
switch(photo.type){
case "image/bmp":
res.set("Content-Type", "image/bmp");
case "image/apng":
res.set("Content-Type", "image/apng");
case"image/avif":
res.set("Content-Type", "image/avif");
case "image/gif":
res.set("Content-Type", "image/gif");
case "image/png":
res.set("Content-Type", "image/png");
case"image/svg+xml":
res.set("Content-Type", "image/svg+xml");
case "image/webp":
res.set("Content-Type","image/webp");
case "image/x-png":
res.set("Content-Type","image/x-png");
default:
res.set("Content-Type", "image/jpeg");
}
console.log(photo.data.length)
res.writeHead(200, {
'Content-Type': `${photo.type}`,
'Content-Length': img.length
});
await fs.writeFileSync("new-path.jpg",Buffer.from(photo.data, "base64"));
res.send("pk")
}catch(error){
return res.send({message:error.message});
}
})
app.use("/flash",(req,res,next)=>{
const {url}=req.body;
res.send(`
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div style="width:100%;height:100%">
<img style="width:40%;height:80%" ;base64, src="data:image/jpeg;base64,#{${url}}}" alt="alternate" />
</div>
</body>
</html>
`)
})
app.listen(8000,async()=>{
await connection();
console.log("server is workingh");
});
I tried to write directly without collecting the chunks first still can't get the file. Since I couldn't retrive the file from mongo i tried to write into a local file to test but I can't either
I would like to record microphone input from the client and then when he stops, send the data to the server and then output the recorded audio to a specific folder.
So far I have for the recording on the client I have followed this
mediaRecorder.onstop = function(e) {
console.log("recorder stopped");
const blob = new Blob(chunks, { 'type' : 'audio/ogg; codecs=opus' });
chunks = [];
const formData = new FormData();
formData.append('audio-file', blob);
return fetch('http://localhost:3000/notes', {
method: 'POST',
body: formData
});
}
console.log(blob) on the client returns an object
Blob { size: 35412, type: "audio/ogg; codecs=opus" }
On the server side I use Node.js
app.post("/notes",function(req,res){
console.log(req);
});
The server receives formData but the body is empty {}
I have also tried XMLHttpRequest with the same result.
I've played about with this type of project before. I created a simple form that allows you to record from the microphone, then upload to the server.
Sound files will be saved in ./sound_files
Just run the node server like
node server.js
And go to localhost:3000 to view the page.
Node code (server.js)
const express = require('express');
const multer = require('multer');
const storage = multer.diskStorage(
{
destination: './sound_files/',
filename: function (req, file, cb ) {
cb( null, file.originalname);
}
}
);
const upload = multer( { storage: storage } );
const app = express();
const port = 3000;
app.use(express.static('./'));
app.post("/notes", upload.single("audio_data"), function(req,res){
res.status(200).send("ok");
});
app.listen(port, () => {
console.log(`Express server listening on port: ${port}...`);
});
index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Speech to text test</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" type="text/css" href="https://bootswatch.com/4/cerulean/bootstrap.min.css">
</head>
<body style="padding:50px;">
<h1>Speech to text test</h1>
<div id="controls">
<button id="recordButton">Record</button>
<button id="transcribeButton" disabled>Stop and upload to server</button>
</div>
<div id="output"></div>
<script src="https://cdn.rawgit.com/mattdiamond/Recorderjs/08e7abd9/dist/recorder.js"></script>
<script>
let rec = null;
let audioStream = null;
const recordButton = document.getElementById("recordButton");
const transcribeButton = document.getElementById("transcribeButton");
recordButton.addEventListener("click", startRecording);
transcribeButton.addEventListener("click", transcribeText);
function startRecording() {
let constraints = { audio: true, video:false }
recordButton.disabled = true;
transcribeButton.disabled = false;
navigator.mediaDevices.getUserMedia(constraints).then(function(stream) {
const audioContext = new window.AudioContext();
audioStream = stream;
const input = audioContext.createMediaStreamSource(stream);
rec = new Recorder(input, { numChannels: 1 })
rec.record()
document.getElementById("output").innerHTML = "Recording started..."
}).catch(function(err) {
recordButton.disabled = false;
transcribeButton.disabled = true;
});
}
function transcribeText() {
document.getElementById("output").innerHTML = "Converting audio to text..."
transcribeButton.disabled = true;
recordButton.disabled = false;
rec.stop();
audioStream.getAudioTracks()[0].stop();
rec.exportWAV(uploadSoundData);
}
function uploadSoundData(blob) {
const filename = "sound-file-" + new Date().getTime() + ".wav";
const formData = new FormData();
formData.append("audio_data", blob, filename);
fetch('http://localhost:3000/notes', {
method: 'POST',
body: formData
}).then(async result => {
document.getElementById("output").innerHTML = await result.text();
}).catch(error => {
document.getElementById("output").innerHTML = "An error occurred: " + error;
})
}
</script>
</body>
</html>
i'm trying to make a zoom/google meet clone.. and i'm using RecordRTC to record the screen and then send it the Node Server via socket.io ...sometimes i get data , and sometimes i don't,..
however i tried to do the same code with websocket ... i didn't get any problem .. always work ... that even made me wonder more,
Please help Me figure the problem where and why ... thank you..
Server Side [Node] :
const express = require('express');
const chalk = require('chalk');
const socketio = require('socket.io')
require('dotenv').config();
const PORT = process.env.PORT || 5000;
const app = express();
app.use(express.static(__dirname + '/public'))
const server = app.listen(PORT, () => {
console.log(chalk.yellowBright.inverse.bold(`Server is Running on PORT ${PORT}`))
})
function writeToDisk(dataURL, fileName) {
var fileExtension = fileName.split('.').pop(),
fileRootNameWithBase = './uploads/' + fileName,
filePath = fileRootNameWithBase,
fileID = 2,
fileBuffer;
// #todo return the new filename to client
while (fs.existsSync(filePath)) {
filePath = fileRootNameWithBase + '(' + fileID + ').' + fileExtension;
fileID += 1;
}
dataURL = dataURL.split(',').pop();
fileBuffer = new Buffer(dataURL, 'base64');
fs.writeFileSync(filePath, fileBuffer);
console.log('filePath', filePath);
}
const io = socketio(server)
io.on('connect', (socket) => {
console.log("Client Has Been Connected")
socket.emit('messageFromServer', { text:'You Are Connected To The Server!'})
socket.on('fromClient',(data)=>{
console.log(chalk.red.bold(data))
if (data.data.video) {
console.log(chalk.red.bold("Video Found"))
writeToDisk(data.data.video.dataURL, fileName + '.webm');
}
})
})
Client Side [Javascript]
var recordButton = document.getElementById('start-recording');
var stopButton = document.getElementById('stop-recording');
var local_video = document.querySelector("#local-video")
const socketio = io('http://localhost:3000/')
console.log('Hello World')
socketio.on('connect', () => {
console.log(socketio.id)
})
function invokeGetDisplayMedia(success, error) {
var displaymediastreamconstraints = {
video: {
displaySurface: 'monitor', // monitor, window, application, browser
logicalSurface: true,
cursor: 'always' // never, always, motion
}
};
displaymediastreamconstraints = {
video: true
};
if (navigator.mediaDevices.getDisplayMedia) {
navigator.mediaDevices.getDisplayMedia(displaymediastreamconstraints).then(success).catch(error);
}
else {
navigator.getDisplayMedia(displaymediastreamconstraints).then(success).catch(error);
}
}
function captureScreen(callback) {
invokeGetDisplayMedia(function (screen) {
callback(screen);
}, function (error) {
console.error(error);
alert('Unable to capture your screen. Please check console logs.\n' + error);
});
}
function startRecording() {
captureScreen(function (stream) {
mediaStream = stream;
local_video.srcObject = stream;
var videoOnlyStream = new MediaStream();
stream.getVideoTracks().forEach(function (track) {
videoOnlyStream.addTrack(track);
});
recordVideo = RecordRTC(videoOnlyStream, {
type: 'video/webm',
canvas: {
width: 1280,
height: 720
},
mandatory: {
// chromeMediaSource: 'screen',
minWidth: 1280,
minHeight: 720,
maxWidth: 1920,
maxHeight: 1080,
minAspectRatio: 1.77
},
recorderType: !!navigator.mozGetUserMedia ? MediaStreamRecorder : WhammyRecorder
});
recordVideo.startRecording();
stopButton.disabled = false;
});
}
function stopRecording() {
recordButton.disabled = false;
stopButton.disabled = true;
// stop video recorder
recordVideo.stopRecording(function () {
recordVideo.getDataURL(function (videoDataURL) {
var files = {
video: {
type: recordVideo.getBlob().type || 'video/webm',
dataURL: videoDataURL
}
};
const data = JSON.stringify(files)
console.log(data)
socketio.emit('fromClient', { "message": "Sent from client!", "data": data });
console.log('EMIT: fromClient');
if (mediaStream) mediaStream.stop();
});
});
}
recordButton.onclick = function () {
recordButton.disabled = true;
startRecording();
}
stopButton.onclick = function () {
stopButton.disabled = true;
stopRecording();
}
HTML :
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>RealTime Record</title>
</head>
<body>
<center>
<h1>Testing Recording</h1>
</center>
<center>
<div class="record-action">
<button id="start-recording">Start Recording</button>
<button id="stop-recording" disabled>Stop Recording</button>
<button id="fromClient">From Client</button>
</div>
<video id="local-video" autoplay style="border: 1px solid rgb(15, 158, 238);"></video>
</center>
<script src="RecordRTC.js"></script>
<script src="/socket.io/socket.io.js"></script>
<script src="client.js"></script>
</body>
</html>
I am trying to create an app that allow users to create a videochat event room (by inserting it into the backend's database) and then let other users that have an account on the website to join it. At the moment, the login part is not created, but it is not a problem.
The backend is done in Spring Boot RestAPI (and runs on 8080) and the frontend in nodejs (and runs on 3000). The Peer To Peer system is done using an nodejs server and Peer.js API (and runs on 3001).
The main question is the following:
When the user clicks on an event fetched from the DB, if it is the first one, it becomes host. If not, then it becomes a simple user. When a user enters the room, the host have to refresh the page(like to reconnect to the room) and so does the user, in order to be both connected. Why is so? I will give you the files codes bellow.
The second one: Why this system is not working for Safari and, if it works how to solve it?
server.js:
const express = require('express')
const app = express()
const server = require('http').Server(app)
const io = require('socket.io')(server)
const { v4: uuidV4 } = require('uuid')
app.set('view engine', 'ejs')
app.use(express.static('public'))
app.get('/', (req, res) => {
var http = require("http")
// BELOW IT IS THE BACKEND CONNECTION. TO TEST YOUR CODE, YOU NEED AN ARRAY THAT HAVE ARRAYS WITH sessionID AND name PARAMS LIKE SO: [{"sessionID":"1231", "name":"event"},{...}].
http.get("http://localhost:8080/events", (resp) => {
let data = "";
resp.on("data", (chunk) => {
data += chunk;
});
resp.on("end", () => {
console.log(data);
res.render('index', {events: data})
});
})
.on("error", (err) => {
console.log("Error: " + err.message);
});
})
app.get('/join', (req, res) => {
res.render('join')
})
app.get('/event', (req, res) => {
res.redirect(`/${uuidV4()}`)
})
app.get('/:room', (req, res) => {
res.render('room', { roomId: req.params.room })
})
io.on('connection', socket => {
socket.on('join-room', (roomId, userId) => {
console.log("User connected: " + userId)
socket.join(roomId)
socket.to(roomId).broadcast.emit('user-connected', userId)
socket.on('disconnect', () => {
socket.to(roomId).broadcast.emit('user-disconnected', userId)
})
})
})
server.listen(3000)
script.js:
const socket = io('/')
const videoGrid = document.getElementById('video-grid')
const myPeer = new Peer(undefined, { // user id
host: '/', // path to event
port: '3001' // post
})
const myVideo = document.createElement('video')
myVideo.muted = true
const peers = {}
navigator.mediaDevices.getUserMedia({
video: true,
audio: true
}).then(stream => {
addVideoStream(myVideo, stream)
myPeer.on('call', call => {
call.answer(stream) // HOST SEE OTHERS
const video = document.createElement('video')
call.on('stream', userVideoStream => { // OTHERS SEE HOST
addVideoStream(video, userVideoStream)
})
})
socket.on('user-connected', userId => {
connectToNewUser(userId, stream)
console.log(peers);
})
})
socket.on('user-disconnected', userId => {
if (peers[userId]) peers[userId].close()
})
myPeer.on('open', id => {
socket.emit('join-room', ROOM_ID, id)
})
function connectToNewUser(userId, stream) {
const call = myPeer.call(userId, stream)
const video = document.createElement('video')
call.on('stream', userVideoStream => {
addVideoStream(video, userVideoStream)
})
call.on('close', () => {
video.remove()
})
peers[userId] = call
}
function addVideoStream(video, stream) {
video.srcObject = stream
video.addEventListener('loadedmetadata', () => {
video.play()
})
videoGrid.append(video)
}
room.ejs:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Template</title>
<script>
const ROOM_ID = "<%= roomId %>"
</script>
<script src="https://unpkg.com/peerjs#1.3.1/dist/peerjs.min.js" defer></script>
<script src="/socket.io/socket.io.js" defer></script>
<script src="script.js" defer></script>
<style>
#video-grid{
display: grid;
grid-template-columns: repeat(auto-fill, 300px);
grid-auto-rows: 300px;
}
video{
width: 100%;
height: 100%;
object-fit: cover;
}
</style>
</head>
<body>
<div id="video-grid">
</div>
</body>
</html>
index.ejs:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>index</title>
<script>
var raw = "<%= events %>"
raw = raw.replaceAll(""", "\"") // DONE BECAUSE WHEN PASSING THE PARAM, INSTEAD OF " IT IS THE " ENTITY
var events = JSON.parse(raw)
window.onload = (event) => {
var table = document.querySelectorAll("#events")[0];
table.innerHTML = '';
for (let index = 0; index < events.length; index++) {
table.innerHTML += "<tr><td><a href='/" + events[index].sessionID + "'>" + events[index].name + "</a></td></tr>"
}
};
</script>
</head>
<body>
<h1>Create Event</h1>
<table id="events">
</table>
</body>
</html>
I have a cpu intensive task on the server, while its running I want to tell the client of the progress to have a good user experience, I looked into SSE using koa-sse-stream, my problem as stated in the question the client is getting all the message at the end of the response which is wrong, the messages must arrive as they are produced.
/event route handler:
import { isObject } from 'util';
import koarouter from 'koa-router';
import koasse from 'koa-sse-stream';
import ipc from 'node-ipc';
ipc.config.maxRetries = 1;
ipc.config.stopRetrying = true;
ipc.config.retry = false;
ipc.config.appspace = 'alerts_event';
ipc.config.silent = true;
const router = new koarouter();
router.get(
'/event',
koasse(),
async (ctx, next) => {
const { client_id } = ctx.state;
// const sse = new SimpleSSE(ctx, false);
let resolver: () => void;
const p = new Promise(res => {
resolver = res;
});
ipc.serve(client_id, () => {
ipc.server.on('message', (data, socket) => {
if (isObject(data)) {
ctx.sse.send(data);
}
});
ipc.server.on('socket.disconnected', _ => {
ctx.sse.end();
resolver();
});
});
ipc.server.start();
await p;
ipc.server.stop();
await next();
},
async ctx => {
console.log(ctx.res.getHeaders());
},
);
export default router;
client html:
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>Document</title>
</head>
<body>
<h6 id="display"></h6>
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<script>
const display = document.getElementById('display');
const es = new EventSource('/alerts/v1/event');
es.onmessage = msg => {
console.log('got message from server');
console.log(msg);
display.innerHTML = msg.data;
};
es.onerror = err => {
console.log('got error');
console.log(err);
};
axios({
method: 'get',
url: 'http://localhost:4001/alerts/v1/xlsx',
})
.then(data => {
console.log(data);
es.close();
})
.catch(err => console.log(err));
</script>
</body>
</html>
The problem was with the promise that I wait for to resolve it stopped koa-sse-stream from piping its stream to ctx.body. Once I remove it everything worked as expected.