How could I display distanceMsg on each elements in AR.js? - javascript

I'm beginner of using AR.js; Could you please suggest me how can I display distanceMsg on each elements like above or below tag (or could you please suggest me if there are other way to display it) in AR.js. Like this example image
I've found this on AR.js website, but I have no clue how can I display on each A-Frame tag like.
const distanceMsg = document.querySelector('[gps-entity-place]').getAttribute('distanceMsg');
Here are my script code
window.onload = () => {
const scene = document.querySelector('a-scene');
return navigator.geolocation.getCurrentPosition(function (position) {
loadPlaces(position.coords)
.then((places) => {
places.forEach((place) => {
const latitude = place.location.lat;
const longitude = place.location.lng;
const marker = place.location.mark;
const pin = document.createElement('a-image');
pin.setAttribute('gps-entity-place', `latitude: ${latitude}; longitude: ${longitude}`);
pin.setAttribute('look-at', '[gps-camera]');
pin.setAttribute('name', place.name);
pin.setAttribute('src', `${marker}`);
pin.setAttribute('scale', '2, 2');
pin.addEventListener('loaded', () => window.dispatchEvent(new CustomEvent('gps-entity-place-loaded')));
const clickListener = function(ev) {
ev.stopPropagation();
ev.preventDefault();
const name = ev.target.getAttribute('name');
const el = ev.detail.intersection && ev.detail.intersection.object.el;
if (el && el === ev.target) {
const label = document.createElement('span');
const container = document.createElement('div');
container.setAttribute('id', 'place-label');
label.innerText = name;
container.appendChild(label);
document.body.appendChild(container);
setTimeout(() => {
container.parentElement.removeChild(container);
}, 1500);
}
};
pin.addEventListener('click', clickListener);
scene.appendChild(pin);
});
})
},
(err) => console.error('Error in retrieving position', err),
{
enableHighAccuracy: true,
maximumAge: 0,
timeout: 27000,
}
);
};
Thank you so much.

See the fundamentals of adding entity's via script
Looks like you need to Use A-frame register component instead of vanilla js. Very important to take this in and undertstand this and not try cherry-pick with this type of framework, otherwise, you risk using this out of context.
This is a finnicky framework where many things need to go right for location-based to work, so it's worth grabbing a coffee, taking some good time reading up.
Remember: Do not try to put A-Frame-related JavaScript in a raw tag after as we would with traditional 2D scripting. If we do, we’d have to take special measures to make sure code runs at the right time. See Running content scripts at the right time

Related

Remove multiple firebase listeners at once

Im creating an app with react native and face the problem that I create multiple firebase listeners troughout the app, listeners on different screens to be precise and also listeners that listen to the firebase-database and others listening to the firestore.
What I want to accomplish is to kill all those listeners with one call or if necessary with multiple lines but as compact as possible - and also from an entire different screen where the listeners arent even running, this is important.
I know that there is the possibility to use Firebase.goOffline() but this only disconnects me from the Firebase - it doesnt stop the listeners. As soon as I goOnline() again, the listeners are all back.
I didnt find any solution yet for this problem from google etc thats why I try to ask here now, I would appriciate if anybody would have an idea how maybe an approach how to handle this type of behavior.
The following code samples provide you with listeners I included inside my app, they are located in in the same screen but I have nearly identical ones in other screens.
Database listener:
const statusListener = () => {
var partnerRef = firebase.database().ref(`users/${partnerId}/onlineState`);
partnerRef.on('value', function(snapshot){
setPartnerState(snapshot.val())
})
};
Firestore Listener: (this one is very long, thats only because I filter the documents I retrieve from the listener)
const loadnewmessages = () =>{ firebase.firestore().collection("chatrooms").doc(`${chatId}`).collection(`${chatId}`).orderBy("timestamp").limit(50).onSnapshot((snapshot) => {
var newmessages = [];
var deletedmesssages = [];
snapshot.docChanges().forEach((change) => {
if(change.type === "added"){
newmessages.push({
counter: change.doc.data().counter,
sender: change.doc.data().sender,
timestamp: change.doc.data().timestamp.toString(),
value: change.doc.data().value,
displayedTime: new Date(change.doc.data().displayedTime)
})
};
if(change.type === "removed"){
deletedmesssages.push({
counter: change.doc.data().counter,
sender: change.doc.data().sender,
timestamp: change.doc.data().timestamp.toString(),
value: change.doc.data().value,
displayedTime: new Date(change.doc.data().displayedTime)
})
};
})
if(newmessages.length > 0){
setChatMessages(chatmessages => {
return chatmessages.concat(newmessages)
});
};
if(deletedmesssages.length > 0){
setChatMessages(chatmessages => {
var modifythisarray = chatmessages;
let index = chatmessages.map(e => e.timestamp).indexOf(`${deletedmesssages[0].timestamp}`);
let pasttime = Date.now() - parseInt(modifythisarray[index].timestamp);
modifythisarray.splice(index, 1);
if(pasttime > 300000){
return chatmessages
}else{
return modifythisarray
}
});
setRefreshFlatList(refreshFlatlist => {
//console.log("Aktueller Status von refresher: ", refreshFlatlist);
return !refreshFlatlist
});
}
newmessages = [];
deletedmesssages = [];
})
};
Both those listeners are called within a useEffect hook just like that: (useEffect with empty braces at the end makes sure those listeners are called only once and not multiple times.)
useEffect(() => {
loadnewmessages();
statusListener();
}, []);
All of the subscribe functions return the unsubscribe function
const unSubscriptions = [];
... Where you subscribe
const unSub = document.onSnapshot(listener);
subscriptions.push(unSub);
... Where you unsubscribe all
function unSubAll () {
unSubscriptions.forEach((unSub) => unSub());
// Clear the array
unSubscriptions.length = 0;
}

Is there any way to change the gain individually in real time when playing a node that is a composite of two AudioBuffers?

I was having the following problems.
When I run AudioBufferSourceNode.start() when I have multiple tracks, I sometimes get a delay
Then, per chrisguttandin's answer, I tried the method using offLineAudioContext. (Thanks to chrisguttandin).
I wanted to play two different mp3 files completely simultaneously, so I used offlineAudioContext to synthesize an audioBuffer.
And I succeeded in playing the synthesized node.
The following is a demo of it.
CodeSandBox
The code in the demo is based on the code in the following page.
OfflineAudioContext - Web APIs | MDN
However, the demo does not allow you to change the gain for each of the two types of audio.
Is there any way to change the gain of the two types of audio during playback?
What I would like to do is as follows.
I want to play two pieces of audio perfectly simultaneously.
I want to change the gain of each of the two audios in real time.
Therefore, if you can achieve what you want to do as described above, you don't need to use offlineAudioContext.
The only way I can think of to do this is to run startRendering on every input type="range", but I don't think this is practical from a performance standpoint.
Also, I looked for a solution to this problem, but could not find one.
code
let ctx = new AudioContext(),
offlineCtx,
tr1,
tr2,
renderedBuffer,
renderedTrack,
tr1gain,
tr2gain,
start = false;
const trackArray = ["track1", "track2"];
const App = () => {
const [loading, setLoading] = useState(true);
useEffect(() => {
(async () => {
const bufferArray = trackArray.map(async (track) => {
const res = await fetch("/" + track + ".mp3");
const arrayBuffer = await res.arrayBuffer();
return await ctx.decodeAudioData(arrayBuffer);
});
const audioBufferArray = await Promise.all(bufferArray);
const source = audioBufferArray[0];
offlineCtx = new OfflineAudioContext(
source.numberOfChannels,
source.length,
source.sampleRate
);
tr1 = offlineCtx.createBufferSource();
tr2 = offlineCtx.createBufferSource();
tr1gain = offlineCtx.createGain();
tr2gain = offlineCtx.createGain();
tr1.buffer = audioBufferArray[0];
tr2.buffer = audioBufferArray[1];
tr1.connect(tr1gain);
tr1gain.connect(offlineCtx.destination);
tr2.connect(tr1gain);
tr2gain.connect(offlineCtx.destination);
tr1.start();
tr2.start();
offlineCtx.startRendering().then((buffer) => {
renderedBuffer = buffer;
renderedTrack = ctx.createBufferSource();
renderedTrack.buffer = renderedBuffer;
setLoading(false);
});
})();
return () => {
ctx.close();
};
}, []);
const [playing, setPlaying] = useState(false);
const playAudio = () => {
if (!start) {
renderedTrack = ctx.createBufferSource();
renderedTrack.buffer = renderedBuffer;
renderedTrack.connect(ctx.destination);
renderedTrack.start();
setPlaying(true);
start = true;
return;
}
ctx.resume();
setPlaying(true);
};
const pauseAudio = () => {
ctx.suspend();
setPlaying(false);
};
const stopAudio = () => {
renderedTrack.disconnect();
start = false;
setPlaying(false);
};
const changeVolume = (e) => {
const target = e.target.ariaLabel;
target === "track1"
? (tr1gain.gain.value = e.target.value)
: (tr2gain.gain.value = e.target.value);
};
const Inputs = trackArray.map((track, index) => (
<div key={index}>
<span>{track}</span>
<input
type="range"
onChange={changeVolume}
step="any"
max="1"
aria-label={track}
disabled={loading ? true : false}
/>
</div>
));
return (
<>
<button
onClick={playing ? pauseAudio : playAudio}
disabled={loading ? true : false}
>
{playing ? "pause" : "play"}
</button>
<button onClick={stopAudio} disabled={loading ? true : false}>
stop
</button>
{Inputs}
</>
);
};
As a test, I'd go back to your original solution, but instead of
tr1.start();
tr2.start();
try something like
t = ctx.currentTime;
tr1.start(t+0.1);
tr2.start(t+0.1);
There will be a delay of about 100 ms before audio starts, but they should be synchronized precisely. If this works, reduce the 0.1 to something smaller, but not zero. Once this is working, you can then connect separate gain nodes to each track and control the gains of each in real-time.
Oh, one other thing, instead of resuming the context after calling start, you might want to do something like
ctx.resume()
.then(() => {
let t = ctx.currentTime;
tr1.start(t + 0.1);
tr2.start(t + 0.1);
});
The clock isn't running if the context is suspended, and resuming doesn't happen instantly. It may take some time to restart the audio HW.
Oh, another approach since I see that the buffer you created with an offline context has two channels in it.
Let s be the AudioBufferSourceNode you created in the offline context.
let splitter = new ChannelSplitterNode(ctx, {numberOfOutputs: 2});
s.connect(splitter);
let g1 = new GainNode(ctx);
let g2 = new GainNode(ctx);
splitter.connect(g1, 0, 0);
splitter.connect(g2, 1, 0);
let merger = new ChannelMergerNode(ctx, {numberOfInputs: 1});
g1.connect(merger, 0, 0);
g2.connect(merger, 0 ,1);
// Connect merger to the downstream nodes or the destination.
You can now start s and modify g1 and g2 as desired to produce the output you want.
You can remove the gain nodes created in the offline context; they're not needed unless you really want to apply some kind of gain in the offline context.
But if I were doing this, I'd prefer not to use the offline context unless absolutely necessary.

Issues updating firestore document by committing a batch after an async function

I'm writing a cloud function that uses request-promise and cheerio to scrape a website and then check that information against a user document.
I am not entirely familiar with Javascript and Cloud Functions.
I've come so far that I managed to extract the information I need and navigate to the user's document and compare the data. Now the last piece of this function is to give the user points for each matching data point, so I need to update a map inside the user document.
This function has to loop through all users and change their document if the data point matches. I'm not sure the way I've written the code is the most optimal in terms of performance and billing if the userbase gets huge... Any pointers to how I could minimize the impact on the task would be of great help, as im new with JS.
So this is the code:
exports.getV75Results = functions.pubsub.schedule('every 2 minutes').onRun(async (context) => {
let linkMap = new Map();
const url = `https://www.example.com`
const options = {
uri: url,
headers: { 'User-Agent': 'test' },
transform: (body) => cheerio.load(body)
}
await rp(options)
.then(($) => {
for(let i = 1; i <= 7; i++)
{
//Find player from game
const lopp1 = $(`#mainContentHolder > div > div.mainContentStyleTrot > div > div.panel-body > table:nth-child(1) > tbody > tr:nth-child(${i}) > td:nth-child(2) > span`).text()
const lopp1StrR1 = lopp1.replace("(", "");
const lopp1StrR2 = lopp1StrR1.replace(")", "");
const lopp1StrR3 = lopp1StrR2.replace(" ", "");
linkMap.set(i, lopp1StrR3.toUpperCase());
}
console.log(linkMap);
return linkMap;
}).then(async () => {
//Start lookup users
let usersRef = db.collection('fantasyfotball').doc('users');
usersRef.listCollections().then(collections => {
collections.forEach( collection => {
var user = collection.doc(collection.id);
let batch = new admin.firestore().batch();
user.get().then(function(doc) {
let json = doc.data();
//Look in users collection if players document exist
Object.keys(json).forEach((name) => {
if(name != null) {
//Document with users active fotball players
if(name == 'players') {
let i = 0;
Object.values(json[name]).forEach((value) => {
i++;
if(value.localeCompare(linkMap.get(i)) == 0) {
//Loop through user keys and find owned players if user has the correct player
Object.keys(json).forEach((map) => {
if(map != null)
{
//Document with a map of player owned fotball players, each respective player has a key = 'fotball player' and value = '[price, points]'
if(map == 'ownedplayers')
{
Object.entries(json[map]).forEach((players) => {
if(players[0].localeCompare(value) == 0) {
console.log(players[1][1]);
//Add points to respective player field
//PROBABLY NOT HOW TO CHANGE A DOCUMENT FILED, THIS DOESNT WORK..
players[1][1]++;
}
});
//EACH TIME THIS RUNS IT SAYS: "Cannot modify a WriteBatch that has been committed"
batch.update(user, {'ownedplayers': json[map]});
}
}
});
}
});
}
} else {
console.log('user does not have a playermode document.');
}
});
});
return batch.commit().then(function () {
console.log("Succesfully commited changes.");
return null;
});
});
});
}).catch((err) => {
return err;
});
});
The issues i get in the console are "Cannot modify a WriteBatch that has been committed." and I fail to modify and add points to the player field inside the users document.
This is the console:
This is the firestore document structure:
I'm completely stuck on this.. Feels like I've tried all different approaches, but I think i dont fully understand cloud functions and javascript, so i would gladly recive feedback and help on how to make this work.
Cheers,
Finally.... i managed to update the document successfully. I put the commit outside another ".then()". Thought I tried that, but yay I guess :P
}).then(() => {
return batch.commit().then(function () {
console.log("Succesfully commited changes.");
return null;
});
The problem now is that it commits every loop. I think the most optimal here would be to batch update ALL users before committing?
And again, is there a more optimal way to do this, in terms of minimizing the operation and impact? I'm afraid I go too deep with for loops instead of directly navigating to the document, but haven't found an easier way to do that.
Any thoughts?

HTML elements made with function have same event listener

const add_compressor = (number) => {
let div = document.createElement('div');
div.className = 'compressor';
let threshold = document.createElement('input');
threshold.type = 'number';
threshold.className = 'input_number';
threshold.addEventListener('input', () => {
compressors[number].threshold.value = threshold.value;
})
div.appendChild(threshold);
added_effects.appendChild(div);
}
add_button.addEventListener('click', () => {
if (select_effect.value != 'Add Effect...') {
if (select_effect.value === 'compressor') {
compressors[compressors.length] = effects[select_effect.value];
add_compressor(compressors.length - 1);
}
}
})
I am trying to allow the creation of more than 1 compressor for the Web Audio API with this add_compressor function. Everything works except the threshold.addEventListener is linked to every compressor that is created. I am wondering if it is possible to create the event listeners for multiple compressors this way. Here is a JS fiddle showing the problem in the console.
https://jsfiddle.net/0L1my6kp/
Changing any of the input boxes for the compressors will change the threshold.value for all compressors.
The problem is that you only create a compressor object once, and reference the same object every time.
So instead of this
const effects = {
compressor: context.createDynamicsCompressor()
}
it should be
const effects = {
compressor: context.createDynamicsCompressor
}
then you create a new compressor when you click the button. Check out the updated fiddle: https://jsfiddle.net/0L1my6kp/4/

How to make Gnome Shell extension query for changes

I've been battling the horrendous Gnome API documentation and came up with this extension:
const St = imports.gi.St;
const Main = imports.ui.main;
const Tweener = imports.ui.tweener;
const GLib = imports.gi.GLib;
let label;
function init() {
label = new St.Bin({ style_class: 'panel-label' });
let stuff = GLib.spawn_command_line_sync("cat /home/user/temp/hello")[1].toString();
let text = new St.Label({ text: stuff });
label.set_child(text);
}
function enable() {
Main.panel._rightBox.insert_child_at_index(label, 0);
}
function disable() {
Main.panel._rightBox.remove_child(label);
}
This should read whatever is in the hello file and display it in the top panel. However, if I change the contents of the hello file, I have to restart Gnome for that new content to be shown. Now, surely there is a way to do this dynamically but I just couldn't find anything in the documentation. The message in the panel should basically always mirror whatever is in the file. Any ideas how to do this?
You'll want to obtain a Gio.File handle for your hello file, and then monitor it:
let helloFile = Gio.File.new_for_path('/home/user/temp/hello');
let monitor = helloFile.monitor(Gio.FileMonitorFlags.NONE, null);
monitor.connect('changed', function (file, otherFile, eventType) {
// change your UI here
});
This worked for me. It will refresh the label value each 30 seconds.
Add the following import
const Mainloop = imports.mainloop;
In your init method
Mainloop.timeout_add(30000, function () {
let stuff = GLib.spawn_command_line_sync("your_command")[1].toString();
let label = new St.Label({ text: stuff });
button.set_child(label);return true});

Categories

Resources