Complex PositionalAudio setup, audio gets out of sync in case of slow connection

am working on a complex project where am adding a huge number of positional audios (~300) and I only want to load and play those that am close to, let’s say 4 at once but I want to keep them synced even with slow network, so I don’t want any audio to start playing until all the 4 are loaded and ready to play, any example that I can get use of? also any code snippet will be very helpful just to get an idea on how to manage such a thing, thanks

As Dawglas once said - divide and conquer. Split your “complex” problem into “simple” problems:

  1. First, solve the problem of having 2-3 positional audios in the scene. Done? Commit.
  2. Make sure these 3 audios are in-sync. Test cases in which they may be desynchronised - write solution that will re-sync them. Done? Commit.
  3. Add 2 more audios (5 in total.) Make camera movable and play only the 4 audios that are closest to the camera (1 should always be off.) All working and in-sync? Commit.
  4. Add 5 more audio clips (10 in total.) Make sure only audio clips within some radius from the camera are fetched from the server (say, only 5 clips should be requested when the app launches - others are loaded on-demand when camera gets close enough to the audio position.) Done? Accidentally hard reset your branch, redo the entire thing, commit.
  5. Have 10 clips working? Add the remaining 290. If all steps before were done properly - it should work right away. If it doesn’t - just debug and polish the remaining bugs. Done? Commit. Complex issue solved.

hey, thanks for this awesome reply, actually most of these steps are done with a friend help, the only remaining issue is that in case of slow internet connection, 1 or 2 of the 4 loaded channels that are close to the camera may start playing while the other 2 are still loading, and once the next two start playing, I’ll let you imagine how frustrating is that.

Could it be helpful to merge the clips into a single audio file and then map the times of the clips in an object? Not sure if that will solve the issue, but it seems like the equivalent of having a sprite atlas for 2d rendering of many tiles.

yes, but there’s a lot of audios (~300) in total that will be abou 150MB so am looking for a light weight solution with code
maybe stoping everything until all files needed are loaded but I have no idea how to do that

That’s a massive amount of audio :open_mouth:

Have you looked into the three LoadingManager? three.js docs

Also, respectfully, I would consider if that amount of audio is necessary for your experience.

I’ve found that users typically don’t want audio unless they are expecting it. Even then, 150mb of load (unavoidable) will be detrimental to both the user experience and the server hosting the assets.

yeah dw, yes this amount is necessary and what am doing rn is only loading the audios I need which is a maximum of 5 at once (where the camera is located), and yes users are expecting it, so it’s pretty normal, the only issue now is getting those buffers to wait each others so they all be ready to play :frowning:

From my experience with media elements (videos, in my case) playing along in a Three.js scene, there are a couple of lessons I learned:

  • encoding matters, some formats and settings take less time to play, some take longer
  • preloading the desired files according to circumstances helps a lot in ensuring things play smoothly and precisely when you want them to
  • even if files are fully loaded in terms of file data, watching for their canplay event is critical

Now, in terms of code, I’m not sure if it helps, but to have a general idea, this is how I preload my resources in my current Three.js project. I do this once and for all, but in your case you might use such an approach only for the positional audios close to the camera:

function setAsset(g)
{
  switch (g.slice(-3))
  {
    case "png":
      new THREE.TextureLoader().load(`http://localhost:8080/${g}`, function(u) {assets[g] = u; assetcount++; setPreload(); if (assetcount >= assettotal) {create(); propagate(); animate();};}, undefined,
                                                                   function(e) {asseterrors++; assetcount++; setPreload(); if (assetcount >= assettotal) {create(); propagate(); animate();};});
    break;
    case "mp4":
      var r = new XMLHttpRequest(); r.onprogress = function(e) {if (e.lengthComputable) {assetcount = Math.trunc(assetcount) + e.loaded / e.total; setPreload();};};
      r.onload = function(e) {video.src = URL.createObjectURL(r.response); video.crossOrigin = "anonymous"; video.playsInline = true; video.loop = true; video.muted = true; video.load();
                              assets[g] = new THREE.VideoTexture(video); assets[g].minFilter = THREE.LinearFilter; assets[g].magFilter = THREE.LinearFilter; setPreload(); if (assetcount >= assettotal)
                              {video.oncanplay = function() {if (seek == 0) {create(); propagate(); animate();}; if (seek < 2) {assets[g].needsUpdate = true; mutate(); illustrate(); seek++;};};};};
      r.onerror = function(e) {asseterrors++; assetcount++; setPreload(); if (assetcount >= assettotal) {create(); propagate(); animate();};};
      r.onreadystatechange = function() {if (r.readyState == 4) {if (r.status != 200) {asseterrors++; assetcount++; setPreload(); if (assetcount >= assettotal) {create(); propagate(); animate();};};};};
      r.open("GET", `http://localhost:8080/${g}`, true); r.responseType = "blob"; r.send();
    break;
  };
};

Disregard my custom variables and functions and just focus on the essential, maybe you’ll figure out how to adapt the process in your case. As a general rule, you’d load (or preload, depending on your needs) all 4 or 5 audios based on the camera position and only play them once the loading for all of them is complete and their canplay events are triggered. See here for more details on such events.

hey, thanks for the reply, I think I ended up using similar solution, am loading my audio files using promises and returning buffers, then am mixing down those buffers and playing the result, that way am getting a good sync, and when I move to the next audios, am letting the already playing one continue, until the new needed one are ready.
Mixdown reference

Well, as long as it works, it’s all good. Mixing and transitioning between media can probably alleviate the initial problem. :+1:

1 Like