I'm using the WebAudio API and doing a simple delay on the input using DelayNode. The code is as follows (might not work in the snippet because it requests microphone permissions):
const player = document.querySelector("#player")
const handleSuccess = stream => {
const context = new AudioContext()
const source = context.createMediaStreamSource(stream)
const delay = context.createDelay(179) //seconds
//source.connect(context.destination) connect directly
source.connect(delay)
delay.connect(context.destination)
delay.delayTime.value = 1
}
navigator.mediaDevices.getUserMedia({audio: true, video: false}).then(handleSuccess)
However, when I run a metronome at 60 beats per minute (one click every second), the audio coming from the browser is not delayed by exactly one second, and multiple clicks are heard (the delay is slightly longer than expected). Is there any way to make an exact delay?
I guess the problem is not the DelayNode itself but rather that there are multiple other hidden delays in the audio pipeline. If you want to hear the previous click from the metronome at the very same time as the current click you would need to reduce the time of your delay to account for those other delays (also known as latency).
The signal takes a bit of time to travel from your microphone through your A/D converter into the browser. This time is usually available as part of the settings of the MediaStream
stream.getAudioTracks()[0].getSettings().latency
Piping the MediaStream into the Web Audio API will probably add some more latency. And the AudioContext itself will add some latency due to its internal buffer.
context.baseLatency
It will also take some time to get the signal out of the computer again. It will travel from the browser to the OS which passes it on to the hardware. This value is also exposed on the AudioContext.
context.outputLatency
In theory you would only need to subtract all those values from the delay time and it would just work. However in reality every hardware/OS/browser combination is a bit different and you will probably need to make some adjustments in order to successfully overlay the previous with the current click depending on your personal setup.
You can use the async/await syntax (documentation here), here are two examples (taken from here)
const foo = async () => {
await sleep(1000);
// do something
}
const foo = async evt => {
await sleep(1000);
// do something with evt
}
Related
I have an auction site in MERN stack with socket.io and i seem to have this unsolvable problem which i think is related to browsers and basic JS
Flow:
whenever a product is added by admin, socket broadcasts it with all
of details (including time )to all clients.
the clients can bid on them and stuff.
if a user refreshes the screen , it requests the socket for latest
product time and starts countdown from that time.
Everything is fine except some times react-countdown is lagging 0.5 second to 1 second behind whenever page is refreshed (please note that problem does not occur when opening same auction on new tab)
Note: i have also tried self made Countdown timer using setInterval but the problem does not go away
I am seeking assistance with this problem and am willing to compensate someone for their time and efforts to work with me directly to resolve it. Any help would be greatly appreciated.
Using setInterval and setTimeout means you are at the mercy of the browser. Browsers will often slow down the execution of these if some other process is happening and return to it once that's done, or if you switch away to another tab, they will purposefully reduce the tick rate. The level of accuracy you require is not easily achieved.
Firstly, I would suggest that getting the time it ends, then finding the difference between then and now, and counting down from this value with 1s increments will aggravate this problem. If each "tick" is off by even a small amount, this will accumulate into a larger error. This is probably what the library is doing by default. It may have also been what you were doing when you made your own timer, but I'd need to see it to confirm.
Instead, you need to store the time it ends passed from the socket (or keep it in scope like below) and on each "tick", work out the difference between then and now, every single time.
You could do this by using react-countdown in controlled mode and doing this logic in the parent.
I've made up a function here which would be the thing that receives the time from the socket -- or it's called from it. Its pseudo-code.
const timer = useRef(null)
const [timeLeft, setTimeLeft] = useState(null) // In seconds
const handleSocketReceived = (({endTime}) => {
timer.current = setInterval(() => {
const newTimeLeft = endTime - Date.now() // Pseudo code, depends on what end time is, but basically, work out the diff
setTimeLeft(newTimeLeft)
}, 100) // Smaller period means more regular correction
}, [])
// ...
useEffect(() => {
return () => clearInterval(timer.current)
}, [])
// ...
<Countdown date={timeLeft} controlled={true} />
Since a call to AudioWorkletProcessor.process is synchronous according to https://developer.mozilla.org/en-US/docs/Web/API/AudioWorkletProcessor/process, what happens when an execution takes too long time, more than 1 second, for example? Would some audio samples be skipped in a next call? Or would samples be queued somewhere? I could not find a documentation on this.
A realtime AudioContext is usually tight to a certain physical audio output device. Therefore the currentTime of an AudioContext is driven by the hardware clock of that audio output device.
If the AudioContext somehow fails to deliver the samples in time the result will be silence. Typically that can be heard as a click sound if there is suddenly a little bit of silence where it shouldn't be. The samples that took too long to render will then be used for the next render quantum if they happen to be ready by then.
There is a difference though in how browsers advance the currentTime in case some samples couldn't be delivered in time. Firefox seems to not count any missed samples whereas Chrome progresses time counting all samples that the audio hardware spit out including those that couldn't be rendered in time.
To test this I created an AudioWorkletProcessor that can be paused or blocked from within the main thread.
class BlockableProcessor extends AudioWorkletProcessor {
constructor () {
super();
this.int32Array = null;
this.port.onmessage = ({ data }) => this.int32Array = data;
}
process() {
if (this.int32Array !== null) {
while (Atomics.load(this.int32Array, 0) === 0) {
Atomics.store(this.int32Array, 1, currentTime);
}
Atomics.store(this.int32Array, 1, currentTime);
}
return true;
}
}
registerProcessor('blockable-processor', BlockableProcessor);
This BlockableProcessor expects to receive a SharedArrayBuffer which it uses to check if it should block the process() function or not. And it also uses it to communicate the currentTime back to the main thread.
It can be used like this:
const audioWorkletNode = new AudioWorkletNode(
audioContext,
'blockable-processor'
);
const sharedArrayBuffer = new SharedArrayBuffer(8);
const int32Array = new Int32Array(sharedArrayBuffer);
Atomics.store(int32Array, 0, 1);
Atomics.store(int32Array, 1, 0);
audioWorkletNode.port.postMessage(int32Array);
audioWorkletNode.connect(audioContext.destination);
It can be blocked by setting the first value of the SharedArrayBuffer to 0.
Atomics.store(int32Array, 0, 0);
And likewise it can be unblocked again by setting it to 1 again.
Atomics.store(int32Array, 0, 1);
It's also possible to read the currentTime written from within the AudioWorkletProcessor on the main thread.
Atomics.load(int32Array, 1);
Using this setup I was able to see that Chrome (v95) and Firefox (v93) both stop the time progression on the main thread and in the worklet in case the processor is blocked.
When it gets unblocked Firefox continues where it stopped and Chrome continues where it should be if everything would have been going as expected.
I am adding logging to our UI to track various aspects of our page load duration, including the amount of time BE API calls take to return from the server. I know that in the past, switching tabs caused issues with things like setTimeout/Interval and requestAnimationFrame, but wasn't sure if this applied to other, newer async features like generator functions, fetch or the performance api.
Can anyone confirm if the following would be OK, or if the logged duration of the API call would be incorrect if the user switched to another tab for a few minutes before switching back while the API call was in progress?
(we are using React, Redux, Redux Sagas, Babel, Webpack and whatwg-fetch)
const { name, get } = api.foo;
const { mark, getEntriesByName } = window.performance;
mark(name);
response = yield call(get, payload); // user switches to another tab while processing
// user switches back after 2 minutes
const duration = getEntriesByName(name).pop().duration;
yield put(actions.logQueryDuration(name, duration))
in the above example, if the API call actually only takes 10 seconds to return, will the logged duration show 10 seconds or 2 minutes?
Thanks!
i'm running project which publishes messages to a PubSub topic and triggers background cloud function.
I read that with high volumes of messages, it performs well, but for lesser amounts like hundreds or even tens of messages per second, Pub/Sub may yield high latencies..
Code example to publish single message:
const {PubSub} = require('#google-cloud/pubsub');
const pubSubClient = new PubSub();
async function publishMessage() {
const topicName = 'my-topic';
const dataBuffer = Buffer.from(data);
const messageId = await pubSubClient.topic(topicName).publish(dataBuffer);
console.log(`Message ${messageId} published.`);
}
publishMessage().catch(console.error);
Code example function triggered by PubSub:
exports.subscribe = async (message) => {
const name = message.data
? Buffer.from(message.data, 'base64').toString()
: 'World';
console.log(`Hello, ${name}!`);
}
Cloud Function Environment Details:
Node: 8
google-cloud/pubsub: 1.6.0
The problem is when using PubSub with low throughput of messages (for example, 1 request per second) it struggles sometimes and shows incredibly high latency (up to 7-9s or more).
Is there a way or workaround to make PubSub perform well each time (50ms or less delay) even with small amount of incoming messages?
If you are always publishing to the same topic, you'd be better off keeping the object returned from pubSubClient.topic(topicName) and reusing it, whether you have a small number or large number of messages. If you want to minimize latency, you'll also want to set the maxMilliseconds property of the batching setting. By default, this is 10ms. As you have the code now, it means that every publish waits 10ms to send a message in hopes of filling the batch. Given that you create a new publisher via the topic call on every publish, you are guaranteed to always wait at least 10ms. You can set it when you call topic:
const publisher = pubSubClient.topic(topicName, {
batching: {
maxMessages: 100,
maxMilliseconds: 1,
},
});
If after reusing the the object returned from pubSubClient.topic(topicName) and changing maxMilliseconds you are still experiencing such a delay, then you should reach out to Google Cloud Support so they can look at the specific project and topic you are using as that kind of latency is definitely not expected.
I've done an HTML form which has a lot of questions (coming from a database) in many different tabs. User then gives answers in those questions. Each time a user changes a tab my Javascript creates a save. The problem is that I have to loop through all questions each time the tab is changed and it freezes the form for about 5 seconds every time.
I've been searching for an answer how I can run my save function in the background. Apparently there is no real way to run something in the background and many recommend using setTimeout(); For example this one How to get a group of js function running in background
But none of these examples does explain or take into consideration that even if I use something like setTimeout(saveFunction, 2000); it doesn't solve my problem. It only postpones it by 2 seconds in this case.
Is there a way to solve this problem?
You can use web workers. Some of the older answers here say that they're not widely supported (which I guess they weren't when those answers were written), but today they're supported by all major browsers.
To run a web worker, you need to create an instance of the built-in Worker class. The constructor takes one argument which is the URI of the javascript file containing the code you want to run in the background. For example:
let worker = new Worker("/path/to/script.js");
Web workers are subject to the same origin policy so if you pass a path like this the target script must be on the same domain as the page calling it.
If you don't want to create an new Javascript file just for this, you can also use a data URI:
let worker = new Worker(
`data:text/javascript,
//Enter Javascript code here
`
);
Because of the same origin policy, you can't send an AJAX request from a data URI, so if you need to send an AJAX request in the web worker, you must use a separate Javascript file.
The code that you specify (either in a separate file or in a data URI) will be run as soon as you call the Worker constructor.
Unfortunately, web workers don't have access to neither outside Javascript variables, functions or classes, nor the DOM, but you can get around this by using the postMessage method and the onmessage event. In the outside code, these are members of the worker object (worker in the example above), and inside the worker, these are members of the global context (so they can be called either by using this or just like that with nothing in front).
postMessage and onmessage work both ways, so when worker.postMessage is called in the outside code, onmessage is fired in the worker, and when postMessage is called in the worker, worker.onmessage is fired in the outside code.
postMessage takes one argument, which is the variable you want to pass (but you can pass several variables by passing an array). Unfortunately, functions and DOM elements can't be passed, and when you try to pass an object, only its attributes will be passed, not its methods.
onmessage takes one argument, which is a MessageEvent object. The MessageEvent object has a data attribute, which contains the data sent using the first argument of postMessage.
Here is an example using web workers. In this example, we have a function, functionThatTakesLongTime, which takes one argument and returns a value depending on that argument, and we want to use web workers in order to find functionThatTakesLongTime(foo) without freezing the UI, where foo is some variable in the outside code.
let worker = new Worker(
`data:text/javascript,
function functionThatTakesLongTime(someArgument){
//There are obviously faster ways to do this, I made this function slow on purpose just for the example.
for(let i = 0; i < 1000000000; i++){
someArgument++;
}
return someArgument;
}
onmessage = function(event){ //This will be called when worker.postMessage is called in the outside code.
let foo = event.data; //Get the argument that was passed from the outside code, in this case foo.
let result = functionThatTakesLongTime(foo); //Find the result. This will take long time but it doesn't matter since it's called in the worker.
postMessage(result); //Send the result to the outside code.
};
`
);
worker.onmessage = function(event){ //Get the result from the worker. This code will be called when postMessage is called in the worker.
alert("The result is " + event.data);
}
worker.postMessage(foo); //Send foo to the worker (here foo is just some variable that was defined somewhere previously).
Apparently there is no real way to run something on background...
There is on most modern browsers (but not IE9 and earlier): Web Workers.
But I think you're trying to solve the problem at the wrong level: 1. It should be possible to loop through all of your controls in a lot less than five seconds, and 2. It shouldn't be necessary to loop through all controls when only one of them has changed.
I suggest looking to those problems before trying to offload that processing to the background.
For instance, you could have an object that contains the current value of each item, and then have the UI for each item update that object when the value changes. Then you'd have all the values in that object, without having to loop through all the controls again.
You could take a look at HTML5 web workers, they're not all that widely supported though.
This works in background:
setInterval(function(){ d=new Date();console.log(d.getTime()); }, 500);
If you can't use web workers because you need to access the DOM, you can also use async functions. The idea is to create an async refreshUI function that refreshes the UI, and then call that function regularly in your function that takes long time.
The refreshUI function would look like this:
async function refreshUI(){
await new Promise(r => setTimeout(r, 0));
}
In general, if you put await new Promise(r => setTimeout(r, ms)); in an async function, it will run all the code before that line, then wait for ms milliseconds without freezing the UI, then continues running the code after that line. See this answer for more information.
The refreshUI function above does the same thing except that it waits zero milliseconds without freezing the UI before continuing, which in practice means that it refreshes the UI and then continues.
If you use this function to refresh the UI often enough, the user won't notice the UI freezing.
Refreshing the UI takes time though (not enough time for you to notice if you just do it once, but enough time for you to notice if you do it at every iteration of a long for loop). So if you want the function to run as fast as possible while still not freezing the UI, you need to make sure not to refresh the UI too often. So you need to find a balance between refreshing the UI often enough for the UI not to freeze, but not so often that it makes your code significantly slower. In my use case I found that refreshing the UI every 20 milliseconds is a good balance.
You can rewrite the refreshUI function from above using performance.now() so that it only refreshes the UI once every 20 milliseconds (you can adjust that number in your own code if you want) no matter how often you call it:
let startTime = performance.now();
async function refreshUI(){
if(performance.now() > startTime + 20){ //You can change the 20 to how often you want to refresh the UI in milliseconds
startTime = performance.now();
await new Promise(r => setTimeout(r, 0));
}
}
If you do this, you don't need to worry about calling refreshUI to often (but you still need to make sure to call it often enough).
Since refreshUI is an async function, you need to call it using await refreshUI() and the function calling it must also be an async function.
Here is an example that does the same thing as the example at the end of my other answer, but using this method instead:
let startTime = performance.now();
async function refreshUI(){
if(performance.now() > startTime + 20){ //You can change the 20 to how often you want to refresh the UI in milliseconds
startTime = performance.now();
await new Promise(r => setTimeout(r, 0));
}
}
async function functionThatTakesLongTime(someArgument){
//There are obviously faster ways to do this, I made this function slow on purpose just for the example.
for(let i = 0; i < 1000000000; i++){
someArgument++;
await refreshUI(); //Refresh the UI if needed
}
return someArgument;
}
alert("The result is " + await functionThatTakesLongTime(3));
This library helped me out a lot for a very similar problem that you describe: https://github.com/kmalakoff/background
It basically a sequential background queue based on the WorkerQueue library.
Just create a hidden button. pass the function to its onclick event.
Whenever you want to call that function (in background), call the button's click event.
<html>
<body>
<button id="bgfoo" style="display:none;"></button>
<script>
function bgfoo()
{
var params = JSON.parse(event.target.innerHTML);
}
var params = {"params":"in JSON format"};
$("#bgfoo").html(JSON.stringify(params));
$("#bgfoo").click(bgfoo);
$("#bgfoo").click(bgfoo);
$("#bgfoo").click(bgfoo);
</script>
</body>
</html>