want to convert my webapp to desktop app with data persistence - mysql

I have created a payroll webapp and have used mysql and node js to build it.
The problem is that I am not sure how to convert it to a desktop webapp.
I have used the node-mysql module for this, so do I need to change something now
or just convert it using node webkit?
How does this work, really?
Here's what my server.js looks like now. so , do I need to make any changes to the code again?
var express = require('express');
var path = require('path');
var bodyParser = require('body-parser');
var app = express();
var mysql = require('mysql');
//var ejsLint=require('./server.js');
//ejsLint.lint('attendance-data.ejs', '-p');
var connection = mysql.createConnection({
host:'localhost',
user:'root',
password:'',
database:'employees',
multipleStatements:true
});
app.set('view engine','ejs');
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.get('/', function (req,res){
res.render('index');
});
var daValue=227.30;
app.get('/da',function(req,res){
res.render('da');
});
app.post('/da-data',function(req,res){
daValue = Number(req.body.da);
var data = {da :daValue};
console.log('da is :',daValue);
res.render('da-data.ejs',data);
});
app.get('/add-employee',function(req,res){
res.render('add-employee');
});
app.post('/add-employee',function(req,res){
res.status(200).send();
console.log('employee '+req.body.name + ' added');
});
connection.connect(function(err){
if(err){
console.log('Error connecting to Db');
return;
}
console.log('Connection established');
});
app.post('/form',function (req,res){
//employee details from add-employee.html page
var name=req.body.name;
var designation = req.body.designation;
var pan = req.body.pan;
var aadhar = req.body.aadhar;
var bank = req.body.bank;
var basicSalary = req.body.salary;
var allowance = req.body.allowance;
var grossSalary = req.body.salarygross;
var esi = req.body.esi;
var uan = req.body.uan;
var details = {name:name,designation:designation,pan:pan,aadhar:aadhar,
bank:bank,basic_salary:basicSalary,other_allowances:allowance,gross_salary:grossSalary,esi:esi,uan:uan};
// for sending data as objects into database
// use {name of database column : name of the variable where it's value is stored}
// example {wages_rate is the name of database column : wagesRate is the variable where
// value is stored}
var query = connection.query('INSERT INTO employee_details SET ?',details,function(err,result){
if(err){
console.log(err);
}
console.log(query.sql);
});
res.status(200).send('employee ' + name + 'with salary of '+salary+ ' added');
});
app.get('/show',function (req,res){
connection.query('SELECT * FROM employee_details',function(err,rows){
if(err) throw err;
console.log('Data received');
console.log(rows);
var data ="Id "+"<strong>" +" name "+" designation "+" uan"+"</strong>";
for(var i=0;i<rows.length;i++){
data = data+"<br>" + (i+1)+". "+rows[i].name +" "+rows[i].designation+" "+rows[i].uan+"<br>";
}
res.send(data);
});
});
var rowsLength;
var salaryFromRow;
var salaryArr=[];
var allowanceFromRow;
var allowanceArr=[];
var designationArr=[];
app.get('/attendance',function (req,res){
connection.query('SELECT * FROM employee_details',function(err,rows){
if(err) {
throw err;
}else{
rowsLength = rows.length;
for(var i=0;i<rowsLength;i++){
salaryFromRow = rows[i].salary;
salaryArr.push(salaryFromRow);
allowanceFromRow = rows[i].allowance;
allowanceArr.push(allowanceFromRow);
designationArr.push(rows[i].designation);
console.log('designation is ',designationArr);
}
res.render('attendance',{rows:rows});
}
});
});
app.post('/attendance-data',function(req,res){
var uanFromHtml;
var nameFromHtml;
var daysPresent;
var attendance;
var nameForm;
var designation;
var monthFromHTML;
var t;
var realBasicSalary;
var realOtherAllowances;
var realGrossSalary;
var pfBasic;
var esiGross;
var pTax=0;
var netAmount;
var advance=0;
var finalData = [];
for(var i=1;i<=rowsLength;i++){
var dataArr = [];
var finalTable = [];
attendance = "attendance"+i;
t=i-1;
salaryForPerson = salaryArr[i];
allowanceForPerson = allowanceArr[i];
console.log('req.body is ', req.body);
daysPresent = Number(req.body[attendance]);
nameFromHtml = req.body.name[t];
var nameArr = req.body.name;
uanFromHtml = req.body.uan[t];
monthFromHTML = req.body.monthyear;
var uanArr = req.body.uan;
realBasicSalary = Math.ceil((req.body.basicsalary[t])*(daysPresent/30)) ;
realOtherAllowances = Math.ceil((req.body.otherallowance[t])*(daysPresent/30));
realGrossSalary = Math.ceil((req.body.grosssalary[t])*(daysPresent/30));
console.log('realBasicSalary is '+realBasicSalary+' and realGrossSalary is '+realGrossSalary+' and realOtherAllowances is '+ realOtherAllowances);
pfBasic = Math.ceil((12/100)*realBasicSalary);
esiGross = Math.ceil((1.75/100)*realGrossSalary);
if(realBasicSalary>10000 && realBasicSalary<=15000){
pTax = 110;
}else if(realBasicSalary>=15001 && realBasicSalary<=25000){
pTax = 130;
}else if(realBasicSalary>=25001 && realBasicSalary<=40000){
pTax = 150;
}else if(realBasicSalary>=40001){
pTax = 200;
}
netAmount = realGrossSalary - (pTax + pfBasic + esiGross + advance);
console.log('realGrossSalary is '+realGrossSalary + ' and realBasicSalary is '+realBasicSalary+
'pTax is '+ pTax+ 'pfBasic is '+pfBasic +'esiGross is '+esiGross);
console.log('namefromhtml is : ', nameFromHtml);
console.log('attendance is :',attendance);
console.log('days present is :',daysPresent);
console.log('monthyear is : ',monthFromHTML);
dataArr.push(monthFromHTML,uanFromHtml,nameFromHtml,daysPresent,realBasicSalary,realOtherAllowances,realGrossSalary,pTax);
finalData.push(dataArr);
/* uanArr.push(uanFromHtml);
nameArr.push(nameFromHtml);
daysArray.push(daysPresent);
*/
console.log('dataArr is : ',dataArr);
console.log('finalData is : ',finalData);
}
var attendanceData = {monthyear :monthFromHTML,rows:rowsLength,uanarr:uanArr,designationarr:designationArr,
namearr:nameArr,finaldata:finalData,realbasicsalary:realBasicSalary,realgrosssalary:realGrossSalary,ptax:pTax,advance:advance};
connection.query("INSERT INTO attendance_details(month_year,uan,name,days_present,real_basic_salary,other_allowances,gross_salary,ptax) VALUES ?",
[finalData], function(err) {
if (err){
var errors = err;
console.log(errors);
res.send(errors);
}else{
//put database query for inserting values here
res.render('attendance-data.ejs', attendanceData);
}
});
});
/*
app.get('/final',function(req,res){
connection.query('SELECT name,designation,salary,wages_rate FROM employee_details;SELECT uan,da,days_present,total_wages FROM attendance_details;',function(err,rows){
if(err){
console.error('MySQL — Error connecting: ' + err.stack);
}else{
var rowsNumber = rows.length;
console.log('rows is :',rows);
var nameFinal;
var designationFinal;
var salaryFinal;
var wagesrateFinal;
var uanFinal;
var daFinal;
var daysFinal;
var totalwagesFinal;
var nameFinalarr = [];
var designationFinalarr =[];
var salaryFinalarr = [];
var wagesrateFinalarr =[];
var uanFinalarr =[];
var daFinalarr =[];
var daysFinalarr = [];
var totalwagesFinalarr =[];
for(var i=0;i<rowsNumber;i++){
nameFinalarr.push(rows[i].name);
designationFinalarr.push(rows[i].designation);
salaryFinalarr.push(rows[i].salary);
wagesrateFinalarr.push(rows[i].wages_rate);
uanFinalarr.push(rows[i].uan);
daysFinalarr.push(rows[i].da);
daysFinalarr.push(rows[i].days_present);
totalwagesFinalarr.push(rows[i].total_wages);
}
console.log('nameFinalarr is :', nameFinalarr);
console.log('daysFinalarr is :', daysFinalarr);
}
res.render('final',{rows:rowsNumber,name:nameFinal,designation:designationFinal,salary:salaryFinal,wagesrate:wagesrateFinal,uan:uanFinal,da:daFinal,
days:daysFinal,
totalwages:totalwagesFinal});
});
});
*/
app.get('/select-month',function(req,res){
connection.query('SELECT month_year,name FROM attendance_details',function(err,rows){
if(err){
throw err;
}else{
var rowsLength = rows.length;
console.log('rows is ',rows);
res.render('select-month.ejs',{rows:rows});
}
});
});
app.get('/salary-sheet',function(req,res){
var month = req.query.selectpicker;
var employeeName = req.query.selectpicker2;
console.log('employeeName is ',employeeName);
connection.query('SELECT * FROM attendance_details WHERE month_year='+"'"+month+"' AND name='"+employeeName+"'",function(err,rows){
if(err){
throw err;
}else{
var rowsLength = rows.length;
console.log('rows is ',rows);
var uanarr=[];
var namearr=[];
var daysarr=[];
var realBasicSalaryarr=[];
var realOtherAllowancesarr=[];
var grossSalaryarr=[];
var ptaxarr=[];
var advance=0;
for(var i=0;i<rowsLength;i++){
uanarr.push(rows[i].uan);
namearr.push(rows[i].name);
daysarr.push(rows[i].days_present);
realBasicSalaryarr.push(rows[i].real_basic_salary);
realOtherAllowancesarr.push(rows[i].other_allowances);
grossSalaryarr.push(rows[i].gross_salary);
ptaxarr.push(rows[i].ptax);
}
console.log('realBasicSalaryarr is ',realBasicSalaryarr);
console.log('namearr is ',namearr);
res.render('salary-sheet.ejs',{advance:advance,rows:rows,monthyear:month,uanarr:uanarr,namearr:namearr,daysarr:daysarr,basicsalaryarr:realBasicSalaryarr,
realotherallowancesarr:realOtherAllowancesarr,realgrosssalaryarr:grossSalaryarr,ptaxarr:ptaxarr});
}
});
});
app.get('/add-company',function(req,res){
res.render('add-company.ejs');
});
app.get('/style.css',function(req,res){
res.sendFile(path.join(__dirname,'/style.css'));
});
app.get('/main.js',function(req,res){
res.sendFile(path.join(__dirname,'/main.js'));
});
app.get('/cmain.js',function(req,res){
res.sendFile(path.join(__dirname,'/cmain.js'));
});
var port=8080;
app.listen(8080,function(req,res){
console.log(`Payroll app listening on port ${port}!` );
});
Could you tell me how to convert this to a desktop app?

In order to make this work offline, in other words, stand-alone, you'll need to revisit your approach regarding persistence. MySQL is not something I'd attempt to bundle with the application. You would ideally use something from the link Yonghoon Lee suggested, as you need the app to have an embedded database instead of an external one.
I've had success doing something similar, although switching to IndexDB meant I had to rewrite all my queries as it's a NoSQL database. Please resist using Web SQL database, it's not standard and could disappear at any time.
Other than that, I don't see anything that's an obvious problem to convert to a NWJS application. You can refer to the official documentation for some examples of how to get started.

It cannot easily be ported. You should not expect the people on stackoverflow to do all the work for you. It is not a simple task but i will try to guide you trough it.
1.) understand what NWJS is. NWJS ist not just a node.js server but also a client. That means you can use both the code of node.js and javascript code that is ment to run on the client.
2.) You server js is not necessary. The entry point of you application is no server js file but an index.html file.
3.) You js code should be added into the tag of the index html. Her you can create a main.js file with the code you want to run and add it as a script tag.
4.) Install mysql into your nwjs package with npm install mysql --save
You have to run this command in the folder that has you package.json
5.) If you want to use routes you have to install express.js
The routes will be localhost routes on your computer
Now you have to consider what you really want to do? From where should the routes be accessible? Would you want an app where you have buttons that you can click and then provide data to the mysql database?
If you really have the interest to port it to nwjs you have to understand how nwjs works. I have given you the first steps but without further information I cannot give you more advises.
Wish you the best of luck. If you understand the structure of nwjs it will be easy to port it ;)

Related

Node/Express + MySQL: Inserts not displaying instantly

I have a form called #add_blog_post with the action "/mysql_test/add_blog_post" and method of "POST"
Jade markup:
form#add_blog_post(action="/mysql_test/add_blog_post" method="POST")
This form executes the following code in my app.js:
app.post('/mysql_test/add_blog_post', function(req, res) {
var author = req.body.author;
var date = req.body.date;
var title = req.body.title;
var body = req.body.body;
var blog_insert_query = "insert into 332project.blog(author,date,title,body) values(";
blog_insert_query += ("'"+author+"'"+","); blog_insert_query += ("'"+date+"'"+","); blog_insert_query += ("'"+title+"'"+","); blog_insert_query += ("'"+body+"'"+")");
var connection = mysql.createConnection({
host: process.env.DB_HOST,
user: process.env.DB_USER,
password: process.env.DB_PASS
});
connection.connect(function(err) { /*error?*/ });
var result;
var query = connection.query(blog_insert_query, function(err, result) {
res.redirect('/mysql_test');
});
});
The blog post insert works just fine but the website takes a while for the insert to be displayed from the select statement on /mysql_test.
Here is my route:
var express = require('express');
var router = express.Router();
var db_calls = require('../db.js');
var connection = db_calls.connect();
connection.connect(function(err) { /*error?*/ });
var result;
var query = connection.query("select * from 332project.blog order by id desc", function(err, rows, fields) {
connection.end();
if (!err) {
result = rows;
}
});
router.get('/', function(req, res, next) {
res.render('mysql_test', {
result: result
});
});
module.exports = router;
What gives? It almost seems like a caching issue. I'd really like for my create/update operations to be instantly visible in my application.
Source code: https://github.com/harwoodjp/learning-express
Your problem is probably that you're not calling connection.end() per the docs.

How to print external API on my server? (Take user ip address and show it on website)

I have just started using nodejs and koajs, and I would like to take the ip address from here: https://api.ipify.org?format=json and paste it on my site or set it as a header. Right now I have the following:
var koa = require('koa');
var app = koa();
var http = require('https');
var a = http.get("https://api.ipify.org?format=json",function(res) {
var data = "";
res.on("data", function (chunk) {
data += chunk;
});
res.on('end', function() {
par = JSON.parse(data);
console.log(par.ip);
});
});
app.listen(8888);
app.use(function *(){
this.response.set("userIp",par.ip);
this.body = "ipadress: "; //this doesn't see par.ip;
});
I know that I am probably doing something very wrong here but yea I am currently stuck because I have no idea how to take par.ip and assign it to this.body and set.
Would anyone be able to tell me how to achieve this or an alternative to the problem? Thanks in advance.
Assuming the response from api.ipify.org doesn't change.
var koa = require('koa');
var app = koa();
var http = require('https');
var a = http.get("https://api.ipify.org?format=json",function(res) {
var data = "";
res.on("data", function (chunk) {
data += chunk;
});
res.on('end', function() {
par = JSON.parse(data);
console.log(par.ip);
app.use(function *(){
this.response.set("userIp",par.ip);
this.body = "ipadress: "; //this doesn't see par.ip;
});
app.listen(8888);
});
});
Otherwise if the response from api.ipify.org constantly changes, you might to do the http request on every incoming request.

Jawbone API Paginated Results with 'page_token'

The Jawbone API returns paginated results of 10 json objects per result set. How does one obtain the rest of the paginated results?
The API documentation for the sleeps method indicates the existence of a page_token argument in the next object of the result set. My output below is missing this. Furthermore,the FAQ indicates this page_token takes an INT (presumably epoch) timestamp.
2nd: "page_token" parameter: if the request contains the "page_token" parameter, the API will return all the workouts, in
reverse order, (capped by "limit" or default of 10) that were
completed before that page_token. The page_token is a timestamp, and
there's a special case, when the request comes with page_token=0 which
is interpreted as passing page_token = CURRENT_TIMESTAMP, ie, give all
the workouts (with a limit)
I am able to authenticate with the API and return a set of 10 results (first paginated page)... but no page_token.
...snip json...
"links": {
"next": "/nudge/api/v.1.0/users/jMdCUPXZ-InYXo1kcdOkvA/sleeps?start_time=1424699101&updated_after=0&limit=10&end_time=1438723789"
},
"size": 10
Have I misunderstood the documentation? Could it be the documentation is out of date (wrong)? Or more likely, I'm completely misunderstanding this and writing horrible JS for my node.js ...
Can someone set me straight and show me how I can retrieve ALL results, not just the first page?
var express = require('express');
var app = express();
var port = process.env.PORT || 5000;
var passport = require('passport');
var config = require('./config.json');
var ejs = require('ejs');
var https = require('https');
var fs = require('fs');
var bodyParser = require('body-parser');
var jbStrategy = require('passport-oauth').OAuth2Strategy;
var jsonfile = require('jsonfile');
var util = require('util');
var path = require('path');
/* Calculate date range */
var $today = new Date()
var $start = new Date($today); $start.setDate($today.getDate() - 180);
var $end = new Date($today);
var $startDate = Math.floor(($start).getTime()/1000);
var $endDate = Math.floor(($end).getTime()/1000);
app.use(express.logger('dev')); // log every request to the console
app.use(bodyParser.json()); // read cookies (needed for auth)
app.use(express.static(__dirname + '/public'));
app.set('view engine', 'ejs');
app.set('views', __dirname + '/views');
app.use(passport.initialize());
/* Default Authentication Path */
app.get('/',
passport.authorize('jawbone', {
scope : config.jawboneAuth.scope,
failureRedirect: '/'
})
);
/* oauth callback from jawbone */
app.get('/done', passport.authorize('jawbone', {
scope : config.jawboneAuth.scope,
failureRedirect: '/'
}), function(req, res) {
var result = JSON.parse(body); console.log(result);
res.redirect('/sleeps');
}
);
app.get('/sleeps', function(req, res) {
var options = {
access_token : config.jawboneAuth.accessToken,
refresh_token : config.jawboneAuth.refreshToken,
client_id : config.jawboneAuth.clientID,
client_secret : config.jawboneAuth.clientSecret
};
if (!config.jawboneAuth.accessToken) {
// if there's no accessToken, go get one
res.redirect('/');
} else {
var up = require('jawbone-up')(options);
var page_token = [];
do {
up.sleeps.get({
page_token : page_token,
start_time : $startDate,
end_time : $endDate
}, function(err, body) {
if (err) {
console.log('Error receiving Jawbone UP data');
res.send(err);
} else {
try {
var result = JSON.parse(body);
var next_page_path = result.data.links.next;
//var next_page_token = next_page_path.split(path.sep);
//var page_token = next_page_token[5];
//page_token = result.data.links.next
console.log(result.data);
res.json(result);
} // end try
catch(err) {
console.log(err);
res.render('userdata', {
requestTime: 0,
jawboneData: 'Unknown result'
});
} // end catch(err)
} // end else
} //end callback fun
); // end up.sleeps.get()
} // end do
while(page_token[0] > 1);
} // end if
}); // end sleeps route
// Setup the passport jawbone authorization strategy
passport.use('jawbone', new jbStrategy({
clientID : config.jawboneAuth.clientID,
clientSecret : config.jawboneAuth.clientSecret,
authorizationURL: config.jawboneAuth.authorizationURL,
tokenURL : config.jawboneAuth.tokenURL,
callbackURL : config.jawboneAuth.callbackURL,
scope : config.jawboneAuth.scope,
passReqToCallback : true
}, function(req, accessToken, refreshToken, profile, done) {
// establish a pseudo user session.
var user = {};
// If there's no preexisting accessToken,
// write one to the config file.
if (!config.jawboneAuth.accessToken){
config.jawboneAuth.accessToken = accessToken;
config.jawboneAuth.refreshToken = refreshToken;
jsonfile.writeFile('./config.json', config, {spaces: 2}, function(err) {
console.error(err);
})
}
done(null, user);
}));
// HTTPS
var sslOptions = {
key : fs.readFileSync('./.server.key'),
cert : fs.readFileSync('./.server.crt')
};
var secureServer = https.createServer(sslOptions, app).listen(port, function(){
console.log('Listening on ' + port);
});
Turns out there is an undocumented limit parameter that has replaced the page_token.
The Jawbone Developer documentation is currently out of date. As is their FAQ (API section Question# 12).
A GET request like this seems to do the trick
https://jawbone.com/nudge/api/v.1.1/users/#me/sleeps?start_time=1388603458&end_time=1420139458&limit=1000

NODEJS node-mysql cannot pass query results in nested queries

I am using node, angular and mysql, the node routes would return a json that would be processed by angular, the json is returned by first querying the mysql DB using the node-mysql module,
In the below code I am unable to set the value of CreatedID, but the value gets logged properly in terminal. I was facing the same issue in the 1st query but then sorted it in the below code, now unable to access the nested query results.
var mysql = require('node-mysql/node_modules/mysql');
var connection = mysql.createConnection({
host : 'localhost',
user : "root",
password: "",
database:'designtaskmanager'
});
connection.connect();
var allDbCalls = function() {
var sendData = {};
var rowData = {};
var temp={};
var _this = this;
this.sendTask = function(callback) {
module.exports.taskData = rowData;
callback['success']();
};
this.getTask = function(callback) {
var strQuery = "select * from task";
connection.query( strQuery, function(err, rows){
if(err)
{
callback['failure']();
throw err;
}
else
{
//rowData = rows;
var tasks=[];
for (var i in rows)
{
var Title = rows[i].task_title;
var TaskDescription=rows[i].task_description;
var TaskCategory=rows[i].task_category;
var TaskID=rows[i].task_id;
var TaskStatus=rows[i].task_status;
var TaskStatusMessage
var CreatedBy;
var TaskCreationDate=rows[i].task_creation_date;
var _MS_PER_DAY = 1000 * 60 * 60 * 24;
var currentdate = new Date();
var ddd=dateDiffInDays(TaskCreationDate,currentdate);
function dateDiffInDays(a, b) {
// Discard the time and time-zone information.
var utc1 = Date.UTC(a.getFullYear(), a.getMonth(), a.getDate());
var utc2 = Date.UTC(b.getFullYear(), b.getMonth(), b.getDate());
return Math.floor((utc2 - utc1) / _MS_PER_DAY);
}
if(TaskStatus==0)
{
TaskStatus="label-info";
TaskStatusMessage="Ongoing since";
}
else if(TaskStatus==1)
{
TaskStatus="label-default";
TaskStatusMessage="Paused since"
}
else if(TaskStatus==2)
{
TaskStatus="label-success";
TaskStatusMessage="Completed in"
}
//USER DETAILS QUERY
var crid=rows[i].task_created_by;
var creatorQuery = "select user_email from users where user_id like ?";
connection.query( creatorQuery,[crid], function(err, createdbyrows){
if(err)
{
callback['failure']();
throw err;
}
else
{
for(var j=0; j< createdbyrows.length;j++)
{
CreatedBy=createdbyrows[0].user_email;
console.log(j);
}
console.log(CreatedBy);
}
});
var taskItem={"TaskID":TaskID,"TaskTitle":Title,"TaskDescription":TaskDescription,"TaskCategory":TaskCategory,"CreatedBy":CreatedBy,"TaskStatus":TaskStatus,"TaskStatusMessage":TaskStatusMessage,"DifferenceInDays":ddd};
tasks.push(taskItem);
}
rowData=tasks;
_this.sendTask(callback);
}
});
}
}
module.exports = function () {
var instance = new allDbCalls();
return instance;
};
The reason that you're seeing it on the console but not in the callback is due to a misunderstanding of asynchronous programming. When you:
for(var i in rows) {}
You are actually queuing up all of those queries at the same time, then, immediately after you try to set rowData to an empty array:
rowData=tasks; // remember, none of the queries have finished yet
_this.sendTask(callback);
So you pretty much call your callback when tasks is still an empty array. Remember, you can't call your final callback until ALL of your nested queries have finished!
To accomplish this, you may want to look at the async library: https://github.com/caolan/async#eachSeries
This will help you accomplish what you really want.
var async = require("async");
async.eachSeries(rows, function(row, cb) {
// Do each query here
// then call cb() when done, which tells the async library
// to "go to the next item in the array"
}, function(err) {
// This will get called when all of the single queries are finished
// Check err, then call your callback
_this.sendTask(callback);
});

decodeAudioData failing with null errors on continuous stream

In my following code ffmpeg is transcoding the input stream and is successfully sending the chunks to the client. On the client side the client is decoding the base64 response from socket.io and is converting the response to an array buffer. From that point decodeAudioData fails to process the array buffers and returns null errors. Does anyone know why decodeAudioData isn't working?
./webaudio_svr.js:
var express = require('/usr/local/lib/node_modules/express');
var http = require('http');
var spawn = require('child_process').spawn;
var util = require('util');
var fs = require('fs');
var app = express();
var webServer = http.createServer(app);
var audServer = http.createServer(app);
var io = require('/usr/local/lib/node_modules/socket.io').listen(webServer, {log: false, });
app.use(express.static(__dirname + '/public'));
app.get('/', function(req, res){
res.send(
"<script src='/socket.io/socket.io.js'></script>\n"+
"<script>var socket=io.connect('http://127.0.0.1:3000');</script>\n"+
"<script src='/webaudio_cli.js'></script>"
);
});
webServer.listen(3000);
var inputStream = spawn('/usr/bin/wget', ['-O','-','http://nprdmp.ic.llnwd.net/stream/nprdmp_live01_mp3']);
var ffmpeg = spawn('ffmpeg', [
'-i', 'pipe:0', // Input on stdin
'-ar', '44100', // Sampling rate
'-ac', 2, // Stereo
'-f', 'mp3',
'pipe:1' // Output on stdout
]);
io.sockets.on('connection', function(webSocket) {
var disconnect = '0';
if (disconnect == '0') {
inputStream.stdout.pipe(ffmpeg.stdin);
ffmpeg.stdout.on('data', function(data) {
var data64 = data.toString('base64');
webSocket.emit('stream',data64);
});
}
webSocket.on('disconnect', function() {
disconnect=1;
});
});
./public/webaudio_cli.js:
function str2ab(str) {
var buf = new ArrayBuffer(str.length*2); // 2 bytes for each char
var bufView = new Uint16Array(buf);
for (var i=0, strLen=str.length; i<strLen; i++) {
bufView[i] = str.charCodeAt(i);
}
return buf;
}
window.AudioContext = window.AudioContext || window.webkitAudioContext;
var context = new AudioContext();
var source = context.createBufferSource();
socket.on('stream', function(data) {
var data=str2ab(atob(data));
context.decodeAudioData(data, function(buffer) {
source.connect(context.destination);
source.buffer = buffer;
source.start(0);
}, function(err) {
console.log("err(decodeAudioData): "+err);
});
});
If you read the notes section of the original post you'll see that I ended up getting this working with binaryjs but with the help of Kevin I was able to get this to work with socket.io. Note there this is still a HUGE issue with choppy playback. If someone could lend some assistance to cleaning the audio up please do. This solution is really pointless unless the audio works as expected so I need to figure that out.
The issue has to do with how the browser encodes/decodes your base64 string. Until this is changed you must supply your own functions from https://developer.mozilla.org/en-US/docs/Web/JavaScript/Base64_encoding_and_decoding.
webaudio_svr.js:
var express = require('/usr/local/lib/node_modules/express');
var http = require('http');
var spawn = require('child_process').spawn;
var util = require('util');
var fs = require('fs');
var app = express();
var webServer = http.createServer(app);
var io = require('/usr/local/lib/node_modules/socket.io').listen(webServer, {log: false, });
app.use(express.static(__dirname + '/public'));
app.get('/', function(req, res){
res.send(
"<script src='/socket.io/socket.io.js'></script>\n"+
"<script>var socket=io.connect('http://127.0.0.1:3000');</script>\n"+
"<script src='/base64.js'></script>\n"+
"<script src='/webaudio_cli.js'></script>"
);
});
webServer.listen(3000);
var inputStream = spawn('/usr/bin/wget', ['-O','-','http://nprdmp.ic.llnwd.net/stream/nprdmp_live01_mp3']);
var ffmpeg = spawn('ffmpeg', [
'-i', 'pipe:0', // Input on stdin
'-ar', '44100', // Sampling rate
'-ac', 2, // Stereo
'-f', 'mp3',
'pipe:1' // Output on stdout
]);
io.sockets.on('connection', function(webSocket) {
var disconnect = '0';
if (disconnect == '0') {
inputStream.stdout.pipe(ffmpeg.stdin);
ffmpeg.stdout.on('data', function(data) {
var data64 = data.toString('base64');
webSocket.emit('stream',data64);
});
}
webSocket.on('disconnect', function() {
disconnect=1;
});
});
public/webaudio_cli.js:
window.AudioContext = window.AudioContext || window.webkitAudioContext;
var context = new AudioContext();
var startTime = context.currentTime;
// buffer to arraybuffer
function toArrayBuffer(buffer) {
var ab = new ArrayBuffer(buffer.length);
var view = new Uint8Array(ab);
for (var i = 0; i < buffer.length; ++i) {
view[i] = buffer[i];
}
return ab;
}
socket.on('stream', function(data) {
var data=toArrayBuffer(base64DecToArr(data));
context.decodeAudioData(data, function(buffer) {
playBuffer(buffer);
}, function(err) {
console.log("decodeAudioData err: "+err);
});
});
function playBuffer(buf) {
var source = context.createBufferSource();
source.buffer = buf;
source.connect(context.destination);
source.start(startTime);
startTime = startTime+source.buffer.duration;
}
public/base64.js:
copy and paste functions from https://developer.mozilla.org/en-US/docs/Web/JavaScript/Base64_encoding_and_decoding#Solution_.232_.E2.80.93_rewriting_atob()_and_btoa()_using_TypedArrays_and_UTF-8