add more native android support

This commit is contained in:
Alexandre Storelli
2018-03-29 11:22:27 +02:00
parent e5fcda6cde
commit 5fd53312c9
14 changed files with 550 additions and 534 deletions
+1
View File
@@ -5,3 +5,4 @@ records/
node_modules/
client/.htpasswd
log/
log.js
+6 -4
View File
@@ -1,12 +1,14 @@
"use strict";
var { Writable, Duplex } = require("stream");
var { log } = require("./log.js")("DlFactory");
var { log } = require("./log.js")("DlF");
var cp = require("child_process");
var fs = require("fs");
var { getMeta } = require("webradio-metadata");
var { StreamDl } = require("adblockradio-dl"); // TODO publish source ?
//var config = require("./config.js");
const Metadata = require("webradio-metadata");
//var { getMeta, setLog } = require("webradio-metadata");
Metadata.setLog(require("./log.js")("meta"));
class Db {
constructor(options) {
@@ -403,7 +405,7 @@ module.exports = function(radio, options) {
}
if (options.fetchMetadata) {
getMeta(radio.country, radio.name, function(err, parsedMeta, corsEnabled) {
Metadata.getMeta(radio.country, radio.name, function(err, parsedMeta, corsEnabled) {
if (err) return log.warn("getMeta: error fetching title meta for radio " + radio.country + "_" + radio.name + " err=" + err);
//log.debug(radio.country + "_" + radio.name + " meta=" + JSON.stringify(parsedMeta));
if (options.saveAudio) {
+64 -23
View File
@@ -25,6 +25,7 @@ import iconConfig from "./img/ads_135894.svg";
import playing from "./img/playing.gif";
/* global cordova */
/* global Android */
var DELAYS = {
@@ -53,6 +54,7 @@ class App extends Component {
configEditMode: false,
locale: "fr",
stopUpdates: false,
communicationError: false,
//doVisualUpdates: true
isCordovaApp: document.URL.indexOf('http://') === -1 && document.URL.indexOf('https://') === -1,
isAndroidApp: navigator.userAgent === "abr_android"
@@ -94,29 +96,34 @@ class App extends Component {
self.newTickInterval(document.hidden ? DELAYS.VISUALS_HIDDEN : DELAYS.VISUALS_ACTIVE);
});
var onHop = function (notification, eopts) {
if (self.state.config.radios.length > 1) {
var index = self.getRadioIndex(self.state.playingRadio);
var newIndex = (index + 1) % self.state.config.radios.length;
var newRadio = self.state.config.radios[newIndex].country + "_" + self.state.config.radios[newIndex].name;
self.play(newRadio, null, function() {});
console.log("notification: next channel");
} else {
console.log("notification: next channel but not possible");
}
};
var onStop = function (notification, eopts) {
console.log("notification: stop playback");
self.play(null, null, function() {});
};
if (this.state.isCordovaApp) { // set up notifications actions callbacks
document.addEventListener("deviceready", function(){
self.setState({ isCordovaDeviceReady: true });
var onHop = function (notification, eopts) {
if (self.state.config.radios.length > 1) {
var index = self.getRadioIndex(self.state.playingRadio);
var newIndex = (index + 1) % self.state.config.radios.length;
var newRadio = self.state.config.radios[newIndex].country + "_" + self.state.config.radios[newIndex].name;
self.play(newRadio, null, function() {});
console.log("notification: next channel");
} else {
console.log("notification: next channel but not possible");
}
};
cordova.plugins.notification.local.on('hop', onHop);
var onStop = function (notification, eopts) {
console.log("notification: stop playback");
self.play(null, null, function() {});
};
cordova.plugins.notification.local.on('stop', onStop);
});
} else if (this.state.isAndroidApp) {
window.notificationHop = onHop;
window.notificationHop = window.notificationHop.bind(this);
window.notificationStop = onStop;
window.notificationStop = window.notificationStop.bind(this);
}
}
@@ -138,9 +145,14 @@ class App extends Component {
//console.log("refresh status");
var self = this;
refreshStatus(this.state.config.radios, options, function(resParsed) {
refreshStatus(this.state.config.radios, options, function(err, resParsed) {
if (err) {
self.play(null, null, function() {});
return self.setState({ communicationError: true });
}
//console.log("refresh status callback");
var stateChange = {};
var stateChange = { communicationError: false };
var types = ["class", "metadata", "volume"];
for (var i=0; i<resParsed.length; i++) { // for each radio
var radio = resParsed[i].country + "_" + resParsed[i].name;
@@ -182,7 +194,7 @@ class App extends Component {
}
self.setState(stateChange, function() {
self.cordovaNotification();
self.showNotification();
});
});
}
@@ -419,6 +431,11 @@ class App extends Component {
}
showNotification() {
if (this.state.isCordovaApp) return this.cordovaNotification();
if (this.state.isAndroidApp) return this.androidNotification();
}
cordovaNotification() {
if (!this.state.isCordovaApp || !this.state.isCordovaDeviceReady) return
@@ -460,6 +477,25 @@ class App extends Component {
}
}
androidNotification() {
if (this.state.playingRadio) {
var index = this.getRadioIndex(this.state.playingRadio);
var name = this.state.config.radios[index].name;
var lang = this.state.locale;
var meta = this.getCurrentMetaForRadio(this.state.playingRadio);
var hasMeta = !!meta.text;
var notifTitle = hasMeta ? name : 'Adblock Radio';
var notifText = hasMeta ? meta.text : name;
var actions = [ { id: 'stop', title: { en: "Stop", fr: "Stop" }[lang] } ];
if (this.state.config.radios.length > 1) {
actions.unshift({ id: 'hop', title: { en: "Change channel", fr: "Changer de station"}[lang] });
}
Android.showNotification(notifTitle, notifText, actions);
} else {
Android.clearNotification();
}
}
play(radio, delay, callback) {
if (radio || delay) {
radio = radio || this.state.playingRadio;
@@ -479,7 +515,7 @@ class App extends Component {
stateChange["playingRadio"] = radio;
stateChange["playingDelay"] = delay;
this.setState(stateChange, function() {
this.cordovaNotification();
this.showNotification();
});
document.title = radio.split("_")[1] + " - Adblock Radio";
@@ -496,7 +532,7 @@ class App extends Component {
playingRadio: null,
playingDelay: null
}, function() {
this.cordovaNotification();
this.showNotification();
});
document.title = "Adblock Radio";
stop();
@@ -564,7 +600,7 @@ class App extends Component {
return (
<SoloMessage>
<p>{{ en: "Oops, could not connect to server :(", fr: "Oops, problème de connexion au serveur :(" }[lang]}</p>
<p>{{ en: "Restart it then press reload this page", fr: "Redémarrez-le puis rechargez cette page" }[lang]}</p>
<p>{{ en: "Check your subscription is active then reload this page", fr: "Vérifiez que vous êtes toujours abonné puis rechargez cette page" }[lang]}</p>
</SoloMessage>
)
}
@@ -631,6 +667,11 @@ class App extends Component {
} else {
mainContents = (
<RadioList>
{self.state.communicationError &&
<SoloMessage>
<p>{{ en: "The communication with the server is temporarily unavailable…", fr: "La connection au serveur est momentanément interrompue…" }[lang]}</p>
</SoloMessage>
}
{config.radios.map(function(radioObj, i) {
var radio = radioObj.country + "_" + radioObj.name;
var playing = self.state.playingRadio === radio;
+4 -7
View File
@@ -57,12 +57,11 @@ class DelaySVG extends Component {
default: colorClass[i] = colors.GREY;
}
xStartClass[i] = Math.max(this.delayToX(this.props.width, +this.props.date-cl.validFrom), 0);
xStopClass[i] = Math.min(xStartClass[i], this.delayToX(this.props.width, cl.validTo ? (+this.props.date-cl.validTo) : 0));
xStopClass[i] = Math.max(xStartClass[i], this.delayToX(this.props.width, cl.validTo ? (+this.props.date-cl.validTo) : 0));
//ctx.fillRect(xStart, 0.6*height, xStop, height);
}
}
var nLines = Math.floor(this.props.cacheLen/TICKS_INTERVAL*1000)
var xLines = new Array(nLines);
var offset = this.props.date % TICKS_INTERVAL;
@@ -79,7 +78,7 @@ class DelaySVG extends Component {
Math.round(cursorX-0.3*height) + ",0";
return (
<svg width={this.props.width} height={height + "px"} onClick={self.getCursorPosition} ref="canvas">
<svg width={this.props.width} height={height + "px"} onClick={self.getCursorPosition} ref="canvas" style={{marginTop: "10px"}}>
{!isNaN(cursorX) &&
<rect x={0} y={0} width={cursorX} height={this.props.classList ? 0.4*height : height} style={{fill: this.props.playing ? colors.LIGHT_PINK : colors.LIGHT_GREY}} />
@@ -91,10 +90,8 @@ class DelaySVG extends Component {
)
})}
{xLines.map(function(x, i) {
return (
x >= 0 &&
<line x1={x} y1={0} x2={x} y2={height} style={ticksStyle} key={"l" + i} />
)
if (x < 0) return null
return <line x1={x} y1={0} x2={x} y2={height} style={ticksStyle} key={"l" + i} />
})}
{!isNaN(cursorX) &&
+4 -4
View File
@@ -35,14 +35,14 @@ if (isCordovaApp) {
Android.playbackStart(url, callback);
}
stop = function() {
console.log("android: stop");
//console.log("android: stop");
Android.playbackStop();
}
setVolume = function(vol) {
console.log("android: set vol=" + vol);
Android.playbackSetVolume(vol);
//console.log("android: set vol=" + Math.round(vol*100) + "%");
Android.playbackSetVolume("" + Math.round(vol*100));
}
} else {
audioElement = document.createElement('audio');
+6
View File
@@ -11,3 +11,9 @@ var isCordovaApp = document.URL.indexOf('http://') === -1 && document.URL.indexO
if (!isCordovaApp) {
registerServiceWorker();
}
/*window["isUpdateAvailable"].then(isAvailable => {
if (isAvailable) {
console.log("refresh to get new content");
}
});*/
+11 -3
View File
@@ -19,9 +19,16 @@ exports.load = function(path, callback) {
}
};
xhttp.onerror = function (e) {
callback("getHeaders: request failed: " + e, null);
callback("load: request failed: " + e.message, null);
};
xhttp.open("GET", HOST + path, true);
xhttp.timeout = 5000; // Set timeout to 4 seconds (4000 milliseconds)
xhttp.ontimeout = function () {
callback("load: timed out", null);
}
xhttp.send();
}
@@ -31,7 +38,8 @@ exports.refreshStatus = function(radios, options, callback) {
var since = options.requestFullData ? "900" : "10";
exports.load("status/" + since + "?t=" + Math.round(Math.random()*1000000), function(err, res) {
if (err) {
return console.log("refreshStatus: could not load status update for radios");
console.log("refreshStatus: could not load status update for radios (" + err + ")");
return callback(err, null);
}
var resParsed = {};
try {
@@ -40,7 +48,7 @@ exports.refreshStatus = function(radios, options, callback) {
console.log("problem parsing JSON from server: " + e.message);
}
callback(resParsed);
callback(null, resParsed);
});
}
+2 -1
View File
@@ -56,7 +56,8 @@ function registerValidSW(swUrl) {
// the fresh content will have been added to the cache.
// It's the perfect time to display a "New content is
// available; please refresh." message in your web app.
console.log('New content is available; please refresh.');
console.log('New content is available; refresh.');
setTimeout(function() { window.location.reload(true); }, 2000);
} else {
// At this point, everything has been precached.
// It's the perfect time to display a
+2
View File
@@ -5,6 +5,8 @@ scp ../adblockradio-dl/*.json dome:/home/alexandre/adblockradio-dl/.
scp ../adblockradio-dl/*.js dome:/home/alexandre/adblockradio-dl/.
scp *.js dome:/home/alexandre/buffer-server/.
scp *.json dome:/home/alexandre/buffer-server/.
scp ../log/log.js dome:/home/alexandre/buffer-server/.
scp ../log/log.js dome:/home/alexandre/adblockradio-dl/log.js
cd client/
npm run build
scp -r build dome:/home/alexandre/buffer-server/client/.
-139
View File
@@ -1,139 +0,0 @@
var fs = require("fs");
var log = require("loglevel");
log.setLevel("debug");
var async = require("async");
var consts = {
WLARRAY: ["0-ads", "1-speech", "2-music", "9-unsure", "mrs", "todo"]
}
var getDirs = function(rootDir, cb) {
fs.readdir(rootDir, function(err, files) {
var dirs = [];
if (err) {
log.warn("getDirs: readdir error for " + rootDir + " err=" + err);
return cb(dirs);
}
//log.debug("getDirs: files found in " + rootDir + " : " + files);
var f = function(i, callback) {
if (i >= files.length) return callback();
var filePath = rootDir + '/' + files[i];
fs.stat(filePath, function(err, stat) {
if (err) {
log.warn("getDirs: stat error for " + filePath + " err=" + err);
}
if (stat.isDirectory()) {
dirs.push(files[i]);
}
f(i+1, callback);
});
}
f(0, function() {
return cb(dirs);
});
});
}
var getFiles = function(path, after, before, cb) {
fs.readdir(path, function (err, files) {
if (err) {
log.warn("error listing files in path " + path + ". err=" + err);
return cb([]);
}
var list = {};
// only get files that have good date
for (var i=files.length-1; i>=0; i--) {
if ((after && files[i] < after) || // file names begin by ISO dates (24 characters)
(before && files[i] > before)) {
//log.debug("getFiles: remove " + files[i]);
files.splice(i, 1);
} else {
var spf = files[i].split("Z."); // end of the ISO date
if (spf.length != 2) log.warn("getFiles: malformed file: " + files[i]);
var path1 = path + "/" + spf[0] + "Z";
if (!list[path1]) {
var ps = path.split("/");
list[path1] = { class: ps[ps.length-1] };
}
/*if (spf[1].slice(-5) == ".part") {
list[path1]["partial"] = true;
list[path1][spf[1].slice(spf[1].length-5)] = true;
} else {*/
list[path1][spf[1]] = true;
//}
}
}
cb(list);
});
}
// finds all files in the subdirectory structure
// options: {
// radios: [country1_name1, country2_name2, ...] OR country: ... name: ...
// before/after: ISO Date, e.g. new Date().toISOString()
// path: __dirname/records/DATE/RADIO/CLASS/ISODATE.*
// }
var findDataFiles = function(options, callback) {
var targetRadios = (options && options.radios) ? options.radios : [options.country + "_" + options.name];
var timeFrame = {
after: (options && options.after) ? options.after : null,
before: (options && options.before) ? options.before : null
};
var files = new Object();
for (let i=0; i<consts.WLARRAY.length; i++) {
files[consts.WLARRAY[i]] = {};
}
getDirs(options.path + "/records", function(dateDirs) {
for (let i=dateDirs.length-1; i>=0; i--) {
if (timeFrame.after && dateDirs[i] < timeFrame.after.slice(0,10)) dateDirs.splice(i, 1);
if (timeFrame.before && dateDirs[i] > timeFrame.before.slice(0,10)) dateDirs.splice(i, 1);
}
log.debug("findDataFiles: dateDirs:" + JSON.stringify(dateDirs));
async.forEachOf(dateDirs, function(dateDir, index, dateDirCallback) {
getDirs(options.path + "/records/" + dateDir, function(radioDirs) {
//log.debug("findDataFiles: radioDirs before = " + radioDirs);
for (let i=radioDirs.length-1; i>=0; i--) {
if (targetRadios.indexOf(radioDirs[i]) < 0) radioDirs.splice(i, 1);
}
log.debug("findDataFiles: radioDirs=" + radioDirs);
async.forEachOf(radioDirs, function(radioDir, index, radioDirCallback) {
async.forEachOf(consts.WLARRAY, function(dataDir, index, dataDirCallback) {
var path = options.path + "/records/" + dateDir + "/" + radioDir + "/" + dataDir;
fs.stat(path, function(err, stat) {
if (stat && stat.isDirectory()) {
getFiles(path, timeFrame.after, timeFrame.before, function(partialFiles) {
log.debug("findDataFiles: " + dataDir + ": " + Object.keys(partialFiles).length + " files found");
Object.assign(files[dataDir], partialFiles); // = files[dataDir].concat(fullPathFiles);
dataDirCallback();
});
} else {
dataDirCallback();
}
});
}, function(err) {
if (err) log.error("findDataFiles: pb during data dir listing. err=" + err.message);
radioDirCallback();
});
}, function(err) {
if (err) log.error("findDataFiles: pb during radio dir listing. err=" + err.message);
dateDirCallback();
});
});
}, function(err) {
if (err) log.error("findDataFiles: pb during date dir listing. err=" + err.message);
callback(files);
});
});
}
module.exports = findDataFiles;
+6 -6
View File
@@ -3,12 +3,12 @@
"use strict";
var { log, flushLog } = require("./log.js")("master");
var cp = require("child_process");
var findDataFiles = require("./findDataFiles.js");
var DlFactory = require("./DlFactory.js");
var abrsdk = require("adblockradio-sdk")();
//var abrsdk = require("../adblockradio-sdk/libabr.js")();
const { log, flushLog } = require("./log.js")("master");
const cp = require("child_process");
const findDataFiles = require("./findDataFiles.js");
const DlFactory = require("./DlFactory.js");
const abrsdk = require("adblockradio-sdk")(require("./log.js")("sdk").log);
//const abrsdk = require("../adblockradio-sdk/libabr.js")(require("./log.js")("sdk").log);
const FETCH_METADATA = true;
const SAVE_AUDIO = false;
-60
View File
@@ -1,60 +0,0 @@
var log = require("loglevel");
log.setLevel("debug");
var prefix = require('loglevel-plugin-prefix');
var chalk = require('chalk');
var fs = require('fs');
var util = require('util');
var moment = require('moment');
// #### logging decoration ####
// see https://github.com/kutuluk/loglevel-plugin-prefix
const colors = {
TRACE: chalk.magenta,
DEBUG: chalk.cyan,
INFO: chalk.blue,
WARN: chalk.yellow,
ERROR: chalk.red,
};
prefix.reg(log);
log.enableAll();
prefix.apply(log, {
format(level, name, timestamp) {
return `${chalk.gray(`[${timestamp}]`)} ${colors[level.toUpperCase()](level)} ${chalk.green(`${name}:`)}`;
},
});
const WRITE_LOG = true;
var logBuf = "";
function writeLog(callback) {
if (logBuf && WRITE_LOG) {
fs.appendFile("log/" + moment(new Date()).format("MM-DD"), logBuf, function(err) {
if (err) console.log("ERROR, could not write log to file log/" + moment(new Date()).format("MM-DD") + " : " + err);
if (callback) callback();
});
logBuf = "";
} else if (callback) {
callback();
}
}
if (WRITE_LOG) {
var origLog = console.log;
console.log = function(msg, ...args) { // probably a bit dirty...
logBuf += util.format(msg, ...args) + "\n";
return origLog(msg, ...args);
}
var writeLogInterval = setInterval(writeLog, 1000);
}
module.exports = function(moduleName) {
return {
log: log.getLogger(moduleName || "root"),
flushLog: function(callback) {
clearInterval(writeLogInterval);
writeLog(callback);
}
}
}
+436 -280
View File
File diff suppressed because it is too large Load Diff
+8 -7
View File
@@ -12,14 +12,15 @@
"adblockradio-dl": "./adblockradio-dl",
"adblockradio-sdk": "git://github.com/dest4/adblockradio-sdk.git#master",
"async": "^2.6.0",
"chalk": "^2.3.0",
"jsonwebtoken": "^8.1.1",
"loglevel": "^1.6.1",
"loglevel-plugin-prefix": "^0.8.3",
"moment": "^2.20.1",
"webradio-metadata": "0.1.x"
"chalk": "^2.3.2",
"express": "^4.16.3",
"jsonwebtoken": "^8.2.0",
"moment": "^2.21.0",
"webradio-metadata": "^0.1.x",
"winston": "^3.0.0-rc3",
"winston-daily-rotate-file": "^3.0.1"
},
"devDependencies": {
"eslint": "^4.15.0"
"eslint": "^4.19.1"
}
}