Web audio: setTimeout slows down when mobile screen is turned off

In response to Yogi's feedback (refer to the discussion on "setTimeout" and "throttling" in https://developer.mozilla.org/en-US/docs/Web/API/setTimeout ), I attempted to enhance performance by integrating an AudioContext.

document.addEventListener('click', ev => {
    let audCtxt = new AudioContext({});
});

(An event listener is necessary for user interaction with AudioContext.)

However, this strategy did not yield any noticeable improvements.

Additional strategies under consideration include:

  • Implementing a potential while loop to monitor Date.now() for increments of 10ms - although this may overload the page
  • Exploring the utilization of requestAnimationFrame?

Prior inquiry:

I have a recurring setTimeout function set at intervals of 0.01 seconds serving as the primary timekeeper for my web application.

The app orchestrates synchronized sounds based on user actions, necessitating a centralized clock system. In simplistic terms:

let counter = 0;
setTimeout(() => {
    counter++;
    console.log(counter);
}, 10);

Remarkably, when accessed via a mobile device, the setTimeout mechanism experiences deceleration (approximately 2-4x slower) when the screen is locked or inactive. (Observed on Android devices, not iOS).

This issue can be confirmed through logging activities, similar to the aforementioned example, or by triggering a sound effect whenever the counter reaches a multiple of 100.

  1. What measures can be implemented to rectify this setback?
  2. Is there a superior method to establish a reliable "master clock" that synchronizes audio output while remaining responsive to real-time user inputs?

Answer №1

To tackle this issue, I developed a method using a Web Worker to handle the timer operations. My solution involves a library that mimics setTimeout() while utilizing a Web Worker under the hood.

https://github.com/chrisguttandin/worker-timers

However, there was a past incident where a bug in a specific browser (though I can't remember which one) caused this approach to fail. As a workaround, I implemented a similar abstraction that leverages an active AudioContext.

https://github.com/chrisguttandin/audio-context-timers

Nonetheless, as you rightly pointed out, this alternative only functions when the webpage has been granted permission to use an AudioContext, typically initiated by a click event handler.

Answer №2

setTimeout may not be as reliable as other methods like promises, which have a higher execution priority. To work around this, you can create a custom timer using promises. Here is an example:

var customDelay = new Promise(function (resolve) {
    var delay = 10; // milliseconds
    var before = Date.now();
    while (Date.now() < before + delay) { };
    resolve();
});

customDelay.then(function () {
    //Timer triggered
});

Update 1:

If you need a 10ms update frequency, running the above code on the main thread can lock up the UI due to the while loop. Offloading the while loop into a web worker can solve this issue. Here is some code:

<html>
<head>
  <title></title>
</head>
<body>
    <script id="FastTimer" type="javascript/worker">
        onmessage = function (event) {
            var delay = 10; // milliseconds
            var before = Date.now();
            while (Date.now() < before + delay) { };
            postMessage({data: []});
        };
    </script>
    
  <script>
    var worker;
  
    window.onload = function() {
      var blob = new Blob([document.querySelector("#FastTimer").textContent]);
      blobURL = window.URL.createObjectURL(blob);

      worker = new Worker(blobURL);
      
      worker.addEventListener("message", receivedWorkerMessage);
      worker.onerror = workerError;

      //Start the worker.
      worker.postMessage({});
    }

    var counter = 0;
    
    function receivedWorkerMessage(event) {
        worker.postMessage({});
        timerTiggered();
    }

    function timerTiggered() {
        counter++;
        console.log(counter);
    }

    function workerError(error) {
      alert(error.message);
    }

    function stopWorker() {
      worker.terminate();
      worker = null;
    }
  </script>
</body>
</html>

The downside of the above approach is that there might be a slight time cost in communication with the worker.

Typically, requestAnimationFrame is used for animations in web apps. However, it may not trigger when the screen is locked. If you still want to try, here is a sample code:

<html>
<head>
  <title></title>
</head>
<body>
  <script>
    var counter = 0;
    var minTimeSpan = 10;
    var lastTime = performance.now();
    
    function animate() {
        let t = performance.now();

        if (t - lastTime >= minTimeSpan) {
            timerTiggered();
        }
    
        requestAnimationFrame(animate);
    }
    
    function timerTiggered() {
        counter++;
        console.log(counter);
    }
    
    animate();
  </script>
</body>
</html>

Update 2:

Feedback suggests that over time, Update 1 may lead to high memory usage and tabs crashing. Initially, using setTimeout in a web worker wasn't accurate in hidden tabs in some browsers, but worked well in Chrome and Edge. Make sure to test this across your target browsers.

<html>
<head>
  <title></title>
</head>
<body>
    <script id="FastTimer" type="javascript/worker">
        onmessage = function (event) {
            var delay = 10; // milliseconds

            setTimeout(() => {
                postMessage({data: []});
            }, delay);
        };
    </script>
    
  <script>
    var worker;
  
    window.onload = function() {
      var blob = new Blob([document.querySelector("#FastTimer").textContent]);
      blobURL = window.URL.createObjectURL(blob);

      worker = new Worker(blobURL);
      
      worker.addEventListener("message", receivedWorkerMessage);
      worker.onerror = workerError;

      //Start the worker.
      worker.postMessage({});
    }

    var counter = 0;
    
    function receivedWorkerMessage(event) {
        worker.postMessage({});
        timerTiggered();
    }

    function timerTiggered() {
        counter++;
        console.log(counter);
    }

    function workerError(error) {
      alert(error.message);
    }

    function stopWorker() {
      worker.terminate();
      worker = null;
    }
  </script>
</body>
</html>

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Tips for extracting the URL from a JSP using JavaScript

When my JSP returns, it loads a JavaScript that serves as a form action when a button is clicked. This JavaScript includes a request.open() call, with the URL it needs to pass as a peer of the JSP that loaded it. The URL must be the one that was originally ...

Unable to display a Google map within a webview when viewing a local HTML file

I am currently working with a local HTML file called basicmap.html that includes the following code: <!DOCTYPE html> <html> <head> </head> <body> <div id="map"></div> <script> ...

Guide on utilizing the RapidAPI with Excel VBA

I am a newcomer to using RapidAPI. My goal is to extract real-time Cricket Scores from RapidAPI using Excel VBA, however, the programming language is not supported on the platform. I am wondering if there is a way I can directly view the json results thro ...

Creating an animated sidebar that moves at a different speed than the rest of the

I have a layout with a large sidebar and small content, where the "Fixed part" refers to sticky positioning. I'm attempting to achieve this using scrollTop, but the sidebar is causing some issues like this: The code should only be executed when the ...

dynamically open ngx-bootstrap accordion panels

I implemented the ngx-bootstrap accordion feature to display a list of blog posts. Below is the template structure: <accordion id="blog-list"> <accordion-group *ngFor="let post of posts; let first = first;" [isOpen]="first" id="post-{{post.i ...

What is the best way in jQuery to pass an event to a parent anchor if necessary?

I'm working on a project in ClojureScript using jQuery, and I believe the answer should be applicable to both ClojureScript and JavaScript. My issue involves a helper function that creates an anchor element and then places an icon element inside it. ...

How can I create a clickable <div> element containing multiple links and trigger only one action?

Within my code, there is a <div> element that has been made clickable. Upon clicking this <div>, the height and text expand using the jQuery function $(div).animate();. While this functionality works as intended, I am encountering a slight issu ...

Unable to locate required dependency

I've been attempting to download a template for customization, but after installing all local dependencies using npm install or yarn install, I consistently encounter the same error. I've tried running the commands with both --force and --legacy- ...

Is there a way to troubleshoot a webpack-compiled npm module effectively?

Looking to debug an npm module compiled with webpack by setting breakpoints and stepping through the code? The issue may arise when importing the module locally using npm link, as only the compiled code is accessible for debugging from the application. W ...

Insert two backslashes before a single backslash in PHP

I have been looking for a solution everywhere and have experimented with the JavaScript replace() function, str_replace, addslashes, and stripslashes, but I still can't seem to get the desired output. Here is what I am attempting: str_replace("&bsol ...

What is the most efficient method to determine the most recent update to a MongoDB document?

Is it necessary for me to add lastActivity: new Date() @set every time I use it, or is there a more efficient method? ...

Loading JSON data into HTML elements using jQuery

I am currently grappling with coding a section where I integrate data from a JSON file into my HTML using jQuery. As a newbie to jQuery, I find myself at a standstill. https://jsfiddle.net/to53xxbd/ Here is the snippet of HTML: <ul id="list"> ...

Unable to convert the object to JSON format

Hey there, I've been encountering an issue while trying to register or login using my virtual device: E/StorageHelpers: Failed to turn object into JSON java.lang.NullPointerException: Attempt to invoke virtual method 'org.json.JSONObject com ...

The button fails to function properly following the initial successful loading of ajax data

var Update = { init: function(){ $('.find').on('click', function(e){ e.preventDefault(); var $this = $(this), $form = $('#searchForm'); BLC.ajax({ ...

How can you use a single parameter in a JavaScript function to swap the values of two numbers

Here's the challenge I'm facing: I need to create a function that will output 5 when given a(0), and 0 when given a(5). a(0) = 5 a(5) = 0 It should look like this: Hint: Use the following function structure function A(num){} something ...

Issues arise when trying to render text within Javascript as "NOTICE", however PHP is able to display it seamlessly without any hiccups

PHP uses metadata from image files to create text and stores it in a variable with the same name as the IMG source. No issues there. JavaScript is responsible for displaying the gallery, allowing users to scroll and enlarge images. While enlarging an imag ...

Need to send emails to two separate users? Look no further than Resend.com for a quick and

I am currently utilizing resend.com to send an email containing different variables to two distinct users. import type { NextApiRequest, NextApiResponse } from "next"; import { EmailTemplate } from "../../components/emails/EmailTemplate" ...

Using JavaScript to convert the text within a div into negative HTML code

I am working with this specific div: <div class="signs" id="signs" onclick="toggle()">&#43;</div> It currently displays the positive sign. I have set up a JavaScript function that is triggered when the div is ...

Meteor: Transmitting Session data from the client to the server

Below is the code snippet I am utilizing on the client side to establish the Session variable: Template.download.events({ 'click button': function() { var clientid=Random.id(); UserSession.set("songsearcher", clientid); ...

Searching by object id from the front-end: A step-by-step guide

Within my collection of meals, there is a specific document I am working with: # Meals collection { name: "burger", tag: ObjectId(63d6bb22972f5f2fa97f1506), } Now, as I navigate through my React application, I find myself needing to send a ...