Considering the following execution of a JS program in a JS settlement, how can I implement a time limit to prevent infinite loops in program?
try {
var x = eval(document.getElementById("program").value);
} catch(e) {
...
}
Note: the call should be able to specify a maximum execution time, just in case program enters an infinite loop.
You could use a webworker to run this in another thread:
// Worker-helper.js
self.onmessage = function(e) {
self.postMessage(eval(e.data));
};
Them use the worker as:
var worker = new Worker('Worker-helper.js');
worker.postMessage(document.getElementById("program").value);
worker.onmessage = result => {
alert(result);
};
setTimeout(() => worker.terminate(), 10 * 1000);
...which will kill the worker after 10 seconds.
Easy to use utility:
function funToWorker(fn) {
var response = "(" + fn.toString() + ")()";
var blob;
try {
blob = new Blob([response], {type: 'application/javascript'});
} catch (e) { // Backwards-compatibility
window.BlobBuilder = window.BlobBuilder || window.WebKitBlobBuilder || window.MozBlobBuilder;
blob = new BlobBuilder();
blob.append(response);
blob = blob.getBlob();
}
return new Worker(URL.createObjectURL(blob));
}
function limitedEval(string, timeout) {
return new Promise((resolve, reject) => {
const worker = funToWorker(function() {
self.onmessage = e => {
self.postMessage(eval(e.data));
}
});
worker.onmessage = e => resolve(e.data);
worker.postMessage(string);
setTimeout(() => {
worker.terminate();
reject();
}, timeout);
});
}
Usable as:
limitedEval("1 + 2", 1000)
.then(console.log)
.catch(console.error);
Try it!
Related
I'm using the Google Cloud API for Speech-to-text, with a NodeJS back-end.
The app needs to be able to listen for voice commands, and transmit them to the back-end as a buffer. For this, I need to send the buffer of the preceding audio when silence is detected.
Any help would be appreciated. Including the js code below
if (!navigator.getUserMedia)
navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia ||
navigator.mozGetUserMedia || navigator.msGetUserMedia;
if (navigator.getUserMedia) {
navigator.getUserMedia({audio: true}, success, function (e) {
alert('Error capturing audio.');
});
} else alert('getUserMedia not supported in this browser.');
var recording = false;
window.startRecording = function () {
recording = true;
};
window.stopRecording = function () {
recording = false;
// window.Stream.end();
};
function success(e) {
audioContext = window.AudioContext || window.webkitAudioContext;
context = new audioContext();
// the sample rate is in context.sampleRate
audioInput = context.createMediaStreamSource(e);
var bufferSize = 4096;
recorder = context.createScriptProcessor(bufferSize, 1, 1);
recorder.onaudioprocess = function (e) {
if (!recording) return;
console.log('recording');
var left = e.inputBuffer.getChannelData(0);
console.log(convertoFloat32ToInt16(left));
};
audioInput.connect(recorder);
recorder.connect(context.destination);
}
I'm not too sure as to what exactly is being asked in the question, so this answer is only intended to give a way to detect silences in an AudioStream.
To detect silence in an AudioStream, you can use an AudioAnalyser node, on which you will call the getByteFrequencyData method at regular intervals, and check whether there were sounds higher than than your expected level for a given time.
You can set the threshold level directly with the minDecibels property of the AnalyserNode.
function detectSilence(
stream,
onSoundEnd = _=>{},
onSoundStart = _=>{},
silence_delay = 500,
min_decibels = -80
) {
const ctx = new AudioContext();
const analyser = ctx.createAnalyser();
const streamNode = ctx.createMediaStreamSource(stream);
streamNode.connect(analyser);
analyser.minDecibels = min_decibels;
const data = new Uint8Array(analyser.frequencyBinCount); // will hold our data
let silence_start = performance.now();
let triggered = false; // trigger only once per silence event
function loop(time) {
requestAnimationFrame(loop); // we'll loop every 60th of a second to check
analyser.getByteFrequencyData(data); // get current data
if (data.some(v => v)) { // if there is data above the given db limit
if(triggered){
triggered = false;
onSoundStart();
}
silence_start = time; // set it to now
}
if (!triggered && time - silence_start > silence_delay) {
onSoundEnd();
triggered = true;
}
}
loop();
}
function onSilence() {
console.log('silence');
}
function onSpeak() {
console.log('speaking');
}
navigator.mediaDevices.getUserMedia({
audio: true
})
.then(stream => {
detectSilence(stream, onSilence, onSpeak);
// do something else with the stream
})
.catch(console.error);
And as a fiddle since stackSnippets may block gUM.
You can use SpeechRecognition result event to determine when a word or phrase has been recognized, for example, ls, cd, pwd or other commands, pass the .transcript of SpeechRecognitionAlternative to speechSynthesis.speak() where at attached start and end event of SpeechSynthesisUtterance call .start() or .resume() on MediaRecorder object where MediaStream is passed; convert the Blob at dataavailable event to an ArrayBuffer using FileReader or Response.arrayBuffer().
We could alternatively use audiostart or soundstart with audioend or soundend events of SpeechRecognition to record the users' actual voice, though the ends may not be fired consistently in relation to the actual start and end of audio captured by only a standard system microphone.
<!DOCTYPE html>
<html>
<head>
<title>Speech Recognition Recording</title>
</head>
<body>
<input type="button" value="Stop speech command recognition" id="stop">
<script>
navigator.mediaDevices.getUserMedia({
audio: true
})
.then(stream => {
const recorder = new MediaRecorder(stream);
const recognition = new webkitSpeechRecognition();
const synthesis = new SpeechSynthesisUtterance();
const handleResult = e => {
recognition.onresult = null;
console.log(e.results);
const result = e.results[e.results.length - 1];
if (result.isFinal) {
const [{transcript}] = result;
console.log(transcript);
synthesis.text = transcript;
window.speechSynthesis.speak(synthesis);
}
}
synthesis.onstart = () => {
if (recorder.state === "inactive") {
recorder.start()
} else {
if (recorder.state === "paused") {
recorder.resume();
}
}
}
synthesis.onend = () => {
recorder.pause();
recorder.requestData();
}
recorder.ondataavailable = async(e) => {
if (stream.active) {
try {
const blobURL = URL.createObjectURL(e.data);
const request = await fetch(blobURL);
const ab = await request.arrayBuffer();
console.log(blobURL, ab);
recognition.onresult = handleResult;
// URL.revokeObjectURL(blobURL);
} catch (err) {
throw err
}
}
}
recorder.onpause = e => {
console.log("recorder " + recorder.state);
}
recognition.continuous = true;
recognition.interimResults = false;
recognition.maxAlternatives = 1;
recognition.start();
recognition.onend = e => {
console.log("recognition ended, stream.active", stream.active);
if (stream.active) {
console.log(e);
// the service disconnects after a period of time
recognition.start();
}
}
recognition.onresult = handleResult;
stream.oninactive = () => {
console.log("stream ended");
}
document.getElementById("stop")
.onclick = () => {
console.log("stream.active:", stream.active);
if (stream && stream.active && recognition) {
recognition.abort();
recorder.stop();
for (let track of stream.getTracks()) {
track.stop();
}
console.log("stream.active:", stream.active);
}
}
})
.catch(err => {
console.error(err)
});
</script>
</body>
</html>
plnkr https://plnkr.co/edit/4DVEg6mhFRR94M5gdaIp?p=preview
The simplest approach would be to use .pause() and .resume(), .stop() methods of MediaRecorder() to allow user to start, pause, and stop recording audio captured utilizing navigator.mediaDevices.getUserMedia() and convert the resulting Blob to an ArrayBuffer, if that is what the api is expecting to be POSTed to server
<!DOCTYPE html>
<html>
<head>
<title>User Media Recording</title>
</head>
<body>
<input type="button" value="Start/resume recording audio" id="start">
<input type="button" value="Pause recording audio" id="pause">
<input type="button" value="Stop recording audio" id="stop">
<script>
navigator.mediaDevices.getUserMedia({
audio: true
})
.then(stream => {
const recorder = new MediaRecorder(stream);
recorder.ondataavailable = async(e) => {
if (stream.active) {
try {
const blobURL = URL.createObjectURL(e.data);
const request = await fetch(blobURL);
const ab = await request.arrayBuffer();
// do stuff with `ArrayBuffer` of recorded audio
console.log(blobURL, ab);
// we do not need the `Blob URL`, we can revoke the object
// URL.revokeObjectURL(blobURL);
} catch (err) {
throw err
}
}
}
recorder.onpause = e => {
console.log("recorder " + recorder.state);
recorder.requestData();
}
stream.oninactive = () => {
console.log("stream ended");
}
document.getElementById("start")
.onclick = () => {
if (recorder.state === "inactive") {
recorder.start();
} else {
recorder.resume();
}
console.log("recorder.state:", recorder.state);
}
document.getElementById("pause")
.onclick = () => {
if (recorder.state === "recording") {
recorder.pause();
}
console.log("recorder.state:", recorder.state);
}
document.getElementById("stop")
.onclick = () => {
if (recorder.state === "recording" || recorder.state === "paused") {
recorder.stop();
}
for (let track of stream.getTracks()) {
track.stop();
}
document.getElementById("start").onclick = null;
document.getElementById("pause").onclick = null;
console.log("recorder.state:", recorder.state
, "stream.active", stream.active);
}
})
.catch(err => {
console.error(err)
});
</script>
</body>
</html>
plnkr https://plnkr.co/edit/7caWYMsvub90G6pwDdQp?p=preview
When the browser is minimized or the tab is on the background they set setTimeout to a minimum of 1 second
example code:
const wait = ms => new Promise(r => setTimeout(r, ms))
async function doStuff() {
let p0 = performance.now();
await wait(100);
let p1 = performance.now();
console.log('time',(p1-p0));
await doStuff();
}
When the tab or the browser is on focus the console.log prints 100, when it's minimized 1000.
I also tried to use a web worker:
const cross_origin_script_url = "https://filebin.net/w73l748cgfap1qau/test.js?t=29itk5mt";
const worker_url = getWorkerURL( cross_origin_script_url );
const worker = new Worker( worker_url );
const wait = ms => new Promise(r => setTimeout(r, ms))
worker.onmessage = async function() {
let p0 = performance.now();
await wait(100);
let p1 = performance.now();
console.log('time',(p1-p0));
}
//worker.onmessage = (evt) => console.log( evt.data );
//URL.revokeObjectURL( worker_url );
// Returns a blob:// URL which points
// to a javascript file which will call
// importScripts with the given URL
function getWorkerURL( url ) {
const content = `importScripts( "${ url }" );`;
return URL.createObjectURL( new Blob( [ content ], { type: "text/javascript" } ) );
}
filebin test.js:
// worker.js
setInterval(function() {
postMessage('');
}, 1);
But it's very inconsistent, time using web worker:
How I can disable/bypass it?
I want to assign videoLoaded to true right after myVideo.mp4 is fully loaded. I can do this at the last lines of the code (This is our promise):
preload.fetch([
clipSource
]).then(items => {
// Using a promise it'll fire when we are sure that video clip has finished loading completely
videoLoaded = true;
});
The first issue is if our URL is not valid we get a 404 response status code. the 404 itself is a valid response so we will not trigger xhr.onerror() because technically it's not an error.
we can track 404 status using:
xhr.onloadend = function() {
if(xhr.status == 404) { // do something }
}
The issue is onloadend event fired only after the promise .then(items => { .... so if there is not a valid URL we can not prevent the promise to resolve and videoLoaded will be assigned to true although there is not a valid URL...
I want to resolve the promise and assign videoLoaded to true only if xhr.status !== 404 in this situation we can be sure that we have a valid URL.
Here is the code (I have used a setInterval and it works but I think there are cleaner solutions that you can share):
let onLoadPassed = false;
let videoLoaded = false;
let clipSource = 'https://mysite/myVideo.mp4';
preload();
// Make sure the video clip is fully loaded
function preload(){
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
typeof define === 'function' && define.amd ? define(factory) :
(global.Preload = factory());
}(this, (function () { 'use strict';
function preloadOne(url, done) {
const xhr = new XMLHttpRequest();
xhr.open('GET', url, true);
xhr.responseType = 'blob';
xhr.onprogress = event => {
if (!event.lengthComputable) return false
let item = this.getItemByUrl(event.target.responseURL);
item.completion = parseInt((event.loaded / event.total) * 100);
item.downloaded = event.loaded;
item.total = event.total;
this.updateProgressBar(item);
};
xhr.onload = event => {
let type = event.target.response.type;
let blob = new Blob([event.target.response], { type: type });
let url = URL.createObjectURL(blob);
let responseURL = event.target.responseURL;
let item = this.getItemByUrl(responseURL);
item.blobUrl = url;
item.fileName = responseURL.substring(responseURL.lastIndexOf('/') + 1);
item.type = type;
item.size = blob.size;
done(item);
};
xhr.onerror = event => {
console.log('Error has happend so we restart the video preloading..');
preload();
};
xhr.onloadend = function() {
if(xhr.status == 404){
console.log('404 not found');
onLoadPassed = false;
} else {
console.log('File exist');
onLoadPassed = true;
}
}
xhr.send();
}
function updateProgressBar(item) {
var sumCompletion = 0;
var maxCompletion = this.status.length * 100;
for (var itemStatus of this.status) {
if (itemStatus.completion) {
sumCompletion += itemStatus.completion;
}
}
var totalCompletion = parseInt((sumCompletion / maxCompletion) * 100);
if (!isNaN(totalCompletion)) {
this.onprogress({
progress: totalCompletion,
item: item
});
}
}
function getItemByUrl(rawUrl) {
for (var item of this.status) {
if (item.url == rawUrl) return item
}
}
function fetch(list) {
return new Promise((resolve, reject) => {
this.loaded = list.length;
for (let item of list) {
this.status.push({ url: item });
this.preloadOne(item, item => {
this.onfetched(item);
this.loaded--;
if (this.loaded == 0) {
this.oncomplete(this.status);
resolve(this.status);
}
});
}
})
}
function Preload() {
return {
status: [],
loaded: false,
onprogress: () => {},
oncomplete: () => {},
onfetched: () => {},
fetch,
updateProgressBar,
preloadOne,
getItemByUrl
}
}
return Preload;
})));
const preload = Preload();
preload.fetch([
clipSource
]).then(items => {
// Fired when we are sure that video clip has finished loading completely
let check = setInterval(passedFunc, 50);
function passedFunc() {
if(onLoadPassed === true){
videoLoaded = true;
clearInterval(check);
console.log('videoLoaded: ' + videoLoaded);
};
}
});
};
You can intercept the promise and throw an error if the status code is 404, this way the subsequent .then statements will be ignored and the result will be captured by the .catch statement.
preload.fetch([
clipSource
])
.then(response => {
if(!response.ok) //better to use response.ok as it checks a range of status codes
throw Error(response.statusText);
return response;
})
.then(items => {
// Using a promise it'll fire when we are sure that video clip has finished loading completely
videoLoaded = true;
})
.catch(error => {
//do something
console.log(error)
});
I have the following:
return indexedDbClient.getStorageUsedInGb().then(function (storageUsedInGb) {
var evictedMediaGuids = [];
storageUsedInGb = parseFloat(storageUsedInGb);
if (storageUsedInGb > storageQuotaInGb) {
return new Promise(function(resolve, reject){
const store = database.transaction(storeName, "readwrite").objectStore(storeName);
(function loop(storageUsedInGb) {
if (storageUsedInGb <= storageQuotaInGb) {
resolve({
evictedMediaGuids: evictedMediaGuids,
shouldStopStoring: false
});
} else {
const latestMediaRequest = store.getAll();
latestMediaRequest.onsuccess = function (event) {
const allData = event.target.result;
const targetEntry = allData[0];
const deleteRequest = store.delete(targetEntry.media.guid);
evictedMediaGuids.push(targetEntry.media.guid);
deleteRequest.onsuccess = loop.bind(null, storageUsedInGb - event.target.media.size / 1024 / 1000 / 1000);
deleteRequest.onerror = reject;
}
latestMediaRequest.onerror = reject;
}
})(storageUsedInGb); // call immediately
})
} else {
return Promise.resolve({
evictedMediaGuids: evictedMediaGuids,
shouldStopStoring: false
});
}
}).then(function (storeObject) {
// do stuff to object
return Promise.resolve(storeObject)
});
The idea is that loop(storageUsedInGb) forces the resolution to wait for the return; however handleStoreObject gets invoked immediately after loop - with no sign of the latestMediaRequest onsuccess handler being invoked. What am I doing wrong?
I am using bluebird in case it matters.
I'm developing HTML5 apps.
When user uploads image from their mobile, the size was too large.
I want to compress the image as PNG like the pngcrush way.
Is there any good way to choice on the frontend (like a javascript library)?
Or is it possible to port the pngcrush library to javascript?
There are a few projects out there which seem to be based around the idea of using emscripten (a LLVM-to-JavaScript compiler) to actually compile the source code from pngcrush to working JavaScript for the browser.
JavaScript-Packer/PNGCrush.html - based on pngcrush-1.7.27
richardassar/pngcrush.js - based on pngcrush-1.7.27
pngcrush-crushed - based on pngcrush-1.7.58
The version for pngcrush-1.7.27 is currently the only one that doesn't seem to produce corrupted images for me. I put together an example which uses promises here: http://plnkr.co/edit/iLpbOjlYiacR04oGdXSI?p=preview
Here's a basic usage example:
var instance = new pngcrush();
instance.exec(inputFile, function (stdoutEvent) {
console.log(stdoutEvent.data.line);
}).then(function (doneEvent) {
var outputFile = new Blob([doneEvent.data.data], { type: 'image/png' });
// do something with the outputFile
});
Here are the contents of the pngcrush-class.js file from the above plunker for reference:
(function(exports) {
var noop = function () {};
function pngcrush () {
this.callbacks = {
'error': [],
'done': [],
'start': [],
'stdout': []
};
}
pngcrush.prototype.exec = function (file, notify) {
var self = this;
if (this.execPromise) {
return this.execPromise.catch(noop).then(function () {
return self.exec(file, notify);
});
}
if (file.type !== 'image/png') {
return Promise.reject(file);
}
var promise = this.execPromise = this.readAsArrayBuffer(file).then(function (event) {
var arrayBuffer = event.target.result;
return self.initWorker().then(function (worker) {
var done = new Promise(function (resolve, reject) {
var offDone, offError, offStdout;
offDone = self.once('done', function (event) {
offError();
offStdout();
resolve(event);
});
offError = self.once('error', function (event) {
offDone();
offStdout();
reject(event);
});
offStdout = self.on('stdout', function (event) {
if (typeof notify === 'function') {
notify.call(self, event);
}
});
worker.postMessage({
'type': 'file',
'data': new Uint8Array(arrayBuffer)
});
worker.postMessage({
'type': 'command',
'command': 'go'
});
});
done.catch(noop).then(function () {
worker.terminate();
});
return done;
});
});
promise.catch(noop).then(function () {
if (promise === self.execPromise) {
delete self.execPromise;
}
});
return promise;
};
pngcrush.prototype.initWorker = function () {
var self = this;
if (this.workerPromise) {
return this.workerPromise;
}
var promise = this.workerPromise = new Promise(function (resolve, reject) {
var worker = new Worker('worker.js');
worker.onerror = function (event) {
var callbacks = [];
reject(event);
Array.prototype.push.apply(callbacks, self.callbacks.error);
while (callbacks.length) {
callbacks.shift().call(self, event);
}
};
worker.onmessage = function (event) {
if (event.data.type === 'ready') {
worker.onmessage = function (event) {
var name = event.data.type;
if (typeof self.callbacks[name] !== 'undefined') {
var callbacks = [];
Array.prototype.push.apply(callbacks, self.callbacks[name]);
while (callbacks.length) {
callbacks.shift().call(self, event);
}
}
};
resolve(worker);
}
};
});
promise.catch(noop).then(function () {
if (promise === self.workerPromise) {
delete self.workerPromise;
}
});
return promise;
};
pngcrush.prototype.on = function (name, callback) {
var self = this;
if (typeof this.callbacks[name] !== 'undefined' && typeof callback === 'function') {
this.callbacks[name].push(callback);
var off = (function () {
var ran = false;
return function () {
if (ran === true) {
return;
}
ran = true;
var idx = self.callbacks[name].lastIndexOf(callback);
if (idx !== -1) {
self.callbacks[name].splice(idx - 1, 1);
}
};
})();
return off;
}
return noop;
};
pngcrush.prototype.once = function (name, callback) {
var off = this.on(name, function () {
off();
callback.apply(this, arguments);
});
return off;
};
pngcrush.prototype.readAsArrayBuffer = function (file) {
var fileReader = new FileReader();
return new Promise(function (resolve, reject) {
fileReader.onerror = reject;
fileReader.onload = resolve;
fileReader.readAsArrayBuffer(file);
});
};
pngcrush.prototype.readAsDataURL = function (file) {
var fileReader = new FileReader();
return new Promise(function (resolve, reject) {
fileReader.onerror = reject;
fileReader.onload = resolve;
fileReader.readAsDataURL(file);
});
};
exports.pngcrush = pngcrush;
})(this);