I made a loading manager that loads a few object classes. It loops the load function until everything is loaded. The items are promises that initializes classes (that take a long time to finish).
In chrome this works fine, the loading gif is animating and the textContent changes when loading different items.
In firefox however, the gif isn't animating and I get this popup saying 'the website is slowing down the browser'. (the textContent does update though).
Am I doing this all wrong? Is there a better way?
Thanks in advance.
const loadManager = {
loading: false,
loadingItem: -1, //2
load(){
if (!this.loading){
//pick next item
this.loadingItem++;
//DONE!
if(this.loadingItem >= this.objects.length){
this.showPlayButton();
return;
}
document.getElementById('loadingItem').textContent = this.objects[this.loadingItem].label;
this.loading = true;
//a timeout to give the DOM time to update the text in the 'loadingItem' element.
setTimeout( ()=>{
//start loading new object
this.objects[this.loadingItem].load().then( this.loading = false );
}, 200);
}
setTimeout(function(){this.load()}.bind(this), 2000);
},
showPlayButton(){
if ( vehicles[0].object ){
document.getElementById('loadingDiv').style.display = 'none';
document.getElementById('playButton').style.display = 'block';
} else {
console.log('assets not ready yet, trying again in 200ms');
setTimeout(this.showPlayButton.bind(this), 200);
}
},
objects:[
{
label: 'Earthlike planet',
load(){
return new Promise( resolve =>{
let earthColors = [];
earthColors = earthColors.concat( new THREE.Color('darkgreen').toArray() );
earthColors = earthColors.concat( new THREE.Color('green').toArray() );
earthColors = earthColors.concat( new THREE.Color('darkgrey').toArray() );
earthColors = earthColors.concat( new THREE.Color('silver').toArray() );
earthColors = earthColors.concat( new THREE.Color('gold').toArray() );
earthColors = earthColors.concat( new THREE.Color('darkcyan').toArray() );
celestialObjects.push(new Planet({
position: new THREE.Vector3(120000, 80000, 120000),
radius: 80000,
gravity: 12,
atmosphere: true,
colors: earthColors,
vegetation: true,
boulderTexture: new THREE.TextureLoader().load('./resources/boulderGrey.jpg'),
rocks: './resources/earthRocks.json',
grassTexture: './resources/grass.png'
}));
resolve();
});
}
},
{
label: 'Orange planet',
load(){
return new Promise( resolve =>{
let orangeColors = [];
orangeColors = orangeColors.concat( new THREE.Color('darkorange').toArray() );
orangeColors = orangeColors.concat( new THREE.Color('orange').toArray() );
orangeColors = orangeColors.concat( new THREE.Color('darkred').toArray() );
orangeColors = orangeColors.concat( new THREE.Color('silver').toArray() );
orangeColors = orangeColors.concat( new THREE.Color('gold').toArray() );
orangeColors = orangeColors.concat( new THREE.Color('darkcyan').toArray() );
celestialObjects.push(new Planet({
position: new THREE.Vector3(- 240000, -200000, 150000),
radius: 40000,
gravity: 12,
atmosphere: true,
colors: orangeColors,
vegetation: false,
boulderTexture: new THREE.TextureLoader().load('./resources/boulderRed.jpg'),
rocks: './resources/redRocks.json',
grassTexture: './resources/grassRed.png'
}));
resolve();
});
}
},
{
label: 'Asteroids',
load(){
return new Promise( resolve =>{
for (let i = 0; i < 8; i ++){
celestialObjects.push( new Asteroid({
position: new THREE.Vector3(
random(-1, 1),
random(-1, 1),
random(-1, 1)
).setLength( random( 80000, 300000)),
radius: random(3500, 8000),
gravity: 0,
atmosphere: false,
boulderTexture: new THREE.TextureLoader().load('./resources/boulderGrey.jpg'),
}))
}
resolve();
});
}
}
]
}
I would do something using Promise.all and avoid the setTimeout and the recursive function unless you want to load them synchronously which would defeat the purpose using the Promises in the first place.
Using promises i would do something like this. Although this would load everything asynchronously.
load(){
if (!this.loading){
//DONE!
const psArr = []
for(const obj in this.objects){
const ps = obj.load()
psArr.push(ps)
}
Promise.all(psArr).then(() => this.showPlayButton())
}
If you really want to keep the Promises but also want to keep the code synchronous you can do something like this.
load(){
if (!this.loading){
//DONE!
const psArr = [Promise.resolve()]
let i = 0
for(const obj in this.objects){
const previous = psArr[i]
// first array element is a promise object that is already resolved
previous.then(() => {
//you can add your label changes inside this block.
const ps = obj.load()
psArr.push(ps)
})
// the obj.load() will now only run everytime the previous promise is resolved.
i++
}
// promise.all still works for this case
Promise.all(psArr).then(() => this.showPlayButton())
}
this would also be way easier to write using async/await
Related
I'm creating a canvas with mutiple images, but they need to be added in a certain order, for example sky first, the clouds, moutains, hills, grass, road, then car.
When I add the images they load at different times so can't get the order right. My plan was to use a async function so that they wait for each image to finish. But it's not working. Code below.
function addImageProcess(src){
return new Promise((resolve, reject) => {
let img = new Image()
img.onload = () => resolve(img)
img.onerror = reject
img.src = src
})
}
addImageProcess('sky.png').then(img => { canvas2.getContext('2d').drawImage(img, 0, 0) });
addImageProcess('clouds.png').then(img => { canvas2.getContext('2d').drawImage(img, 0, 0) });
addImageProcess('moutains.png').then(img => { canvas2.getContext('2d').drawImage(img, 0, 0) });
addImageProcess('hills.png').then(img => { canvas2.getContext('2d').drawImage(img, 0, 0) });
addImageProcess('grass.png').then(img => { canvas2.getContext('2d').drawImage(img, 0, 0) });
addImageProcess('road.png').then(img => { canvas2.getContext('2d').drawImage(img, 0, 0) });
addImageProcess('car.png').then(img => { canvas2.getContext('2d').drawImage(img, 0, 0) });
Have been pulling my hair out for hours trying to get this to work, hopefully someone can help!
Try using Promise.all:
Promise.all(
[
'sky.png', 'clouds.png', 'mountains.png', 'hills.png', 'grass.png', 'road.png', 'car.png'
].map(
addImageProcess
)
).then(
(allImages)=>allImages.map(
img=>canvas2.getContext('2d').drawImage(img, 0, 0)
)
)
Promise.all can be used to wait for all promises to finish before moving on to the next phase.
If you want to do something different with each image you can do something like this:
Promise.all(
[
'sky.png', 'clouds.png', 'mountains.png', 'hills.png', 'grass.png', 'road.png', 'car.png'
].map(
addImageProcess
)
).then(
([sky, clouds, mountains, hills, grass, road, car])=>{
const context = canvas2.getContext('2d');
context.drawImage(clounds, 5, 2);
context.drawImage(hills, 20, 50);
}
)
Okay, so I've been trying to do this for a long time but I just can't find a solution. I'm building a personal Voice Assistant that only records when a hotword is detected, and everything until here works fine. To record the audio, I'm using the npm package node-record-lcpm16. I can't seem to find a solution to pause or stop(and start again) the recording. On the npm website of the audiorecorder there is a function specified that says recording.stop()
but it doesn't work for me. My code right now is:
const recorder = require('node-record-lpcm16');
const fs = require('file-system');
const speech = require('#google-cloud/speech');
const say = require('say');
const notifier = require('node-notifier');
const Bumblebee = require('bumblebee-hotword-node');
const { setTimeout } = require('timers');
const { record } = require('node-record-lpcm16');
const bumblebee = new Bumblebee;
const voice = 'Microsoft Zira Desktop';
bumblebee.addHotword('computer');
const config = {
encoding: 'LINEAR16',
sampleRateHertz: 16000,
languageCode: 'en-US',
};
const request = {
config,
interimResults: false,
};
const client = new speech.SpeechClient();
const recognizeStream = client
.streamingRecognize(request)
.on('error', console.error)
.on('data', data => findfunction(data.results[0].alternatives[0].transcript)
);
const recording = recorder.record({
sampleRateHertz: 16000,
threshold: 0,
recorder: 'sox',
silence: '5.0',
})
.stream().on('error', console.error); //Here is the Recorder, and I can't actually stop it and that's my problem.
recording.pipe(recognizeStream);
bumblebee.on('hotword', function(hotword){
console.log('Hotword detected:', hotword); // It does these actions as soon as the hotword is detected
recording.pipe(recognizeStream);
setTimeout(function stop(){
recording.pipe(fs.createWriteStream("\\\\.\\NUL")),
console.log('Stopped Recording.')
}, 5000);
});
console.log('Computer initiated.');
bumblebee.start();
//Everything down from here is just what do to with the translated speech, it doesn't play a role in my problem.
function findfunction(Data){
let findFunction = Data;
console.log(Data);
if(findFunction.includes('time')){
whattimeisit(findFunction);
};
if(findFunction.includes('day')){
whatdateisit(findFunction);
};
if(findFunction.includes('thank you')){
thankyou();
};
if(findFunction.includes('remind')){
setatimer(findFunction);
};
};
function whattimeisit(timeString){
const date = new Date();
const time = date.toLocaleTimeString();
say.speak(`It's currently ${time}.`, voice);
console.log(`It's currently ${time}.`);
};
function whatdateisit(dateString){
const date = new Date();
const currentDate = date.toLocaleDateString();
say.speak(`It's currently ${currentDate}.`, voice);
console.log(`It's currently ${currentDate}.`);
};
function thankyou(){
say.speak("You're welcome!", voice);
console.log("You're welcome!");
};
function setatimer(timerString){
const timer = timerString.replace(/\D/g, '');
setTimeout(function stop() {notifier.notify({title: 'Computer', message: 'Your timer ran out!', icon: './computericon1.png'})} , timer * 60000);
if(timer == 1){
say.speak(`Set a timer for ${timer} minute.`, voice);
console.log(`Set a timer for ${timer} minute.`);
}else{
say.speak(`Set a timer for ${timer} minutes.`, voice);
console.log(`Set a timer for ${timer} minutes.`);
};
};
Any help would be greatly appreciated!
I've played about with your code.. it's definitely a fun project to play with!
I would suggest maybe just modifying the code to record to a buffer, then send that to the google speech recognition engine.
The reason recording.stop() was probably not working for you is that you were calling it on the stream. If we separate the recording and recordingStream variables we can control the flow better.
I've updated the code so when we get the hotword, we stop recording, recognize the speech, then start recording again.
const recorder = require('node-record-lpcm16');
const Bumblebee = require('bumblebee-hotword-node');
const say = require('say');
const voice = 'Microsoft Zira Desktop';
const speech = require('#google-cloud/speech');
let chunks = null;
let recording = null;
function startRecording() {
console.log("listening...");
chunks = [];
recording = recorder.record({
sampleRateHertz: 16000,
threshold: 0,
recorder: 'sox',
silence: '5.0',
})
const recordingStream = recording.stream();
recordingStream.on('error', () => {});
// Tune this to ensure we only send the last few seconds of audio to google..
const maxChunks = 10;
recordingStream.on('data', (chunk) => {
chunks.push(chunk);
// keep the number of chunks below a reasonable limit...
if (chunks.length > maxChunks) {
chunks = chunks.slice(-maxChunks);
}
});
recordingStream.on('end', async () => {
// Create a buffer from our recording, it should only be a few seconds long.
const audioBuffer = Buffer.concat(chunks);
console.log("Chunk count:", chunks.length);
await recognizeSpeech(audioBuffer);
startRecording();
});
}
async function recognizeSpeech(audioBuffer) {
console.log(`recognizeSpeech: Converting audio buffer to text (${audioBuffer.length} bytes)...`)
const client = new speech.SpeechClient();
const request = {
config: { encoding: 'LINEAR16', sampleRateHertz: 16000, languageCode: 'en-US'},
audio: { content: audioBuffer.toString("base64") }
};
// Convert our audio to text.
const response = await client.recognize(request)
findfunction(response[0].results[0].alternatives[0].transcript);
}
startRecording();
startBumblebee();
function startBumblebee() {
const bumblebee = new Bumblebee();
bumblebee.addHotword('computer');
bumblebee.on('hotword', function(hotword){
console.log('Hotword detected:', hotword);
setTimeout(() => {
console.log('Stopping recording...');
recording.stop()
}, 2000);
});
bumblebee.start( );
}
// Nothing changed from here...
function findfunction(Data){
let findFunction = Data;
console.log(Data);
if(findFunction.includes('time')){
whattimeisit(findFunction);
};
if(findFunction.includes('day')){
whatdateisit(findFunction);
};
if(findFunction.includes('thank you')){
thankyou();
};
if(findFunction.includes('remind')){
setatimer(findFunction);
};
};
function whattimeisit(timeString){
const date = new Date();
const time = date.toLocaleTimeString();
say.speak(`It's currently ${time}.`, voice);
console.log(`It's currently ${time}.`);
};
function whatdateisit(dateString){
const date = new Date();
const currentDate = date.toLocaleDateString();
say.speak(`It's currently ${currentDate}.`, voice);
console.log(`It's currently ${currentDate}.`);
};
function thankyou(){
say.speak("You're welcome!", voice);
console.log("You're welcome!");
};
function setatimer(timerString){
const timer = timerString.replace(/\D/g, '');
setTimeout(function stop() {notifier.notify({title: 'Computer', message: 'Your timer ran out!', icon: './computericon1.png'})} , timer * 60000);
if(timer == 1){
say.speak(`Set a timer for ${timer} minute.`, voice);
console.log(`Set a timer for ${timer} minute.`);
}else{
say.speak(`Set a timer for ${timer} minutes.`, voice);
console.log(`Set a timer for ${timer} minutes.`);
};
};
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 have such code: (I put here the whole code in order not to miss anything)
let updateCoverLoaded;
function coverLoaded(cover){
clearInterval(updateCoverLoaded);
updateCoverLoaded = setInterval(() => coverLoaded(cover), 25);
const coverSelector = $(`[aria-label="${cover}"]`).find("image")[0];
return new Promise(resolve => {
if(typeof coverSelector !== "undefined"){
const coverSelectorSource = coverSelector.getAttribute("xlink:href");
if(getCoverWidth(coverSelectorSource) !== 0) {
clearInterval(updateCoverLoaded);
setTimeout(() => {
player.SetVar(`${cover}`, true);
setTimeout(() => player.SetVar(`${cover}`, false), 100);
resolve();
}, 100);
}
}
});
function getCoverWidth(src) {
const image = new Image();
image.src = src;
return image.naturalWidth;
}
}
// Add SFX to cover entrance and exit
async function feedbackCover(){
await coverLoaded('coverB');
console.log('after resoving promise as expected!');
}
Although code reaches the player.SetVar(${cover}, true); line of code and I'm sure that resolve() is executed the promise doesn't resolve and I can't see console.log('after resoving promise as expected!'); Why? it works in some other situations!
Note: The code actually waits for an element (an image) to load and then resolves the promise.
There are two possible problems, the first is that if the image isn't loaded yet it the first Promise doesn't resolve and all subsequent Promises are discarded, the second is that if coverLoaded is called again, the first promise will never resolve.
This should work as expected:
function coverLoaded(cover) {
return new Promise((resolve) => {
const updateCoverLoaded = setInterval(() => {
const coverSelector = $(`[aria-label="${cover}"]`).find("image")[0];
if (typeof coverSelector !== "undefined") {
const coverSelectorSource = coverSelector.getAttribute("xlink:href");
if (getCoverWidth(coverSelectorSource) !== 0) {
clearInterval(updateCoverLoaded);
setTimeout(() => {
player.SetVar(`${cover}`, true);
setTimeout(() => player.SetVar(`${cover}`, false), 100);
resolve();
}, 100);
}
}
}, 25);
});
function getCoverWidth(src) {
const image = new Image();
image.src = src;
return image.naturalWidth;
}
}
// Add SFX to cover entrance and exit
async function feedbackCover() {
await coverLoaded("coverB");
console.log("after resoving promise as expected!");
}
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!