7

I am making a web app that can be open for a long time. I don't want to load audio at load time (when the HTML gets downloaded and parsed) to make the first load as fast as possible and to spare precious resources for mobile users. Audio is disabled by default. Putting the audio in CSS or using preload is not appropriate here because I don't want to load it at load time with the rest.

I am searching for the ideal method to load audio at run time, (after a checkbox has been checked, this can be after 20 minutes after opening the app) given a list of audio elements.

The list is already in a variable allSounds. I have the following audio in a webpage (there are more):

 <audio preload="none">
  <source src="sound1.mp3">
 </audio>

I want to keep the same HTML because after second visit I can easily change it to (this works fine with my server-side HTML generation)

 <audio preload="auto">
  <source src="sound1.mp3">
 </audio>

and it works.

Once the option is turned on, I want to load the sounds, but not play them immediately. I know that .play() loads the sounds. But I want to avoid the delay between pressing a button and the associated feedback sound. It is better to not play sound than delayed (in my app).

I made this event handler to load sounds (it works) but in the chrome console, it says that download was cancelled, and then restarted I don't know exactly what I am doing wrong. Is this is the correct way to force load sounds? What are the other ways? If possible without changing the HTML.

let loadSounds = function () {
  allSounds.forEach(function (sound) {
    sound.preload = "auto";
    sound.load();
  });
  loadSounds = function () {}; // Execute once
};

here is playSound function, but not very important for the questions

const playSound = function (sound) {
  // PS
  /* only plays ready sound to avoid delays between fetching*/
  if (!soundEnabled)) {
    return;
  }
  if (sound.readyState < sound.HAVE_ENOUGH_DATA) {
    return;
  }
  sound.play();
};

Side question: Should there be a preload="full" in the HTML spec?

See also: Preload mp3 file in queue to avoid any delay in playing the next file in queue

how we can Play Audio with text highlight word by word in angularjs

Walle Cyril
  • 3,087
  • 4
  • 23
  • 55
  • 1
    Not certain what issue is? – guest271314 Oct 06 '17 at 21:42
  • the question is now in bold – Walle Cyril Oct 06 '17 at 21:46
  • 1
    There are several options available to request media resources. There is no single "correct" way. Again, what issue are you having with code at Question? – guest271314 Oct 06 '17 at 21:50
  • If I understood the question correct, in order to get the answer it should be moved/posted on https://codereview.stackexchange.com/ – Anton Kastritskiy Oct 09 '17 at 18:35
  • There are many solutions, but none of them suits your needs. Could you please provide some more information? I think there is a huge misunderstanding here. Could you provide some usecase? 1) user enter site 2) user click unmute 3) Sound are loaded, etc for multiple scenarios? – Jacek Kowalewski Oct 12 '17 at 10:25
  • So, you want the button click event to have a sound of its own, make the client browser wait for the file to be loaded and start playing as soon as the download completes, sounds about right? – Tiramonium Oct 13 '17 at 11:22
  • reworded my question, no I prefer not playing the sound at all if it is not ready ( avoid sound feedback delays at all cost) that is why I check sound.readyState before playing it – Walle Cyril Oct 13 '17 at 20:07

4 Answers4

2

To cache the audio will need to Base64 encode your MP3 files, and start the Base64 encoded MP3 file with data:audio/mpeg;base64, Then you can pre-load/cache the file with css using something like:

body::after {  
  content:url(myfile.mp3);
  display:none;
}
tnt-rox
  • 5,400
  • 2
  • 38
  • 52
  • 1
    @WalleCyril the file will not play, it will be cached by the browser, and when you load it again, it will load from browser cache. – tnt-rox Oct 12 '17 at 05:33
  • @tnt-rox I'm quite sure, we are missing something and dont understand the question. I asked for more information in the question comment. For me your solution is a quite nice workaround to force audio loading. – Jacek Kowalewski Oct 12 '17 at 10:28
  • 1
    @JacekKowalewski the after psudo tag is used for images, however in this case it will fetch a base64 encoded mp3 file, and do nothing with it. However the fetched result will be cached in the browser, so the next time it is requested the content will be loaded directly from browser cache. So the media will be loaded at run time without playing. exactly what the question asks it to do. :) – tnt-rox Oct 12 '17 at 10:55
  • @tnt-rox I understand it similarly. However I found another solution (you are welcome to take a look). I think we must wait for the OP to write something more. Thx for explanation, however your answer is really 200IQ based :). – Jacek Kowalewski Oct 12 '17 at 11:23
  • I think @Endless nailed it – tnt-rox Oct 12 '17 at 18:08
  • I have no problem with caching, and would not use css for that, interesting hack but not what I search for – Walle Cyril Oct 13 '17 at 20:09
2

I think I would just use the preloading functionality without involving audio tag at all...

Example:

var link = document.createElement('link')
link.rel = 'preload'
link.href = 'sound1.mp3'
link.as = 'audio'
link.onload = function() {
  // Done loading the mp3
}
document.head.appendChild(link)
Endless
  • 34,080
  • 13
  • 108
  • 131
1

I'm quite sure that I've found a solution for you. As far as I'm concerned, your sounds are additional functionality, and are not required for everybody. In that case I would propose to load the sounds using pure javascript, after user has clicked unmute button.

A simple sketch of solution is:

window.addEventListener('load', function(event) {
  var audioloaded = false;
  var audioobject = null;

  // Load audio on first click
  document.getElementById('unmute').addEventListener('click', function(event) {
    if (!audioloaded) { // Load audio on first click
      audioobject = document.createElement("audio");
      audioobject.preload = "auto"; // Load now!!!
      var source = document.createElement("source");
      source.src = "sound1.mp3"; // Append src
      audioobject.appendChild(source);
      audioobject.load(); // Just for sure, old browsers fallback
      audioloaded = true; // Globally remember that audio is loaded
    }
    // Other mute / unmute stuff here that you already got... ;)
  });

  // Play sound on click
  document.getElementById('playsound').addEventListener('click', function(event) {
    audioobject.play();
  });
});

Of course, button should have id="unmute", and for simplicity, body id="body" and play sound button id="playsound. You can modify that of course to suit your needs. After that, when someone will click unmute, audio object will be generated and dynamically loaded.

I didn't try this solution so there may be some little mistakes (I hope not!). But I hope this will get you an idea (sketch) how this can be acomplished using pure javascript.

Don't be afraid that this is pure javascript, without html. This is additional functionality, and javascript is the best way to implement it.

Jacek Kowalewski
  • 2,761
  • 2
  • 23
  • 36
  • `audioloaded = true; // Globally remember that audio is loaded` audio does not load instantly on the web. your solution is almost the same as mine, and I see nothing better. I am not afraid, but in my configuration I can change preload=auto in my server side rendering in 2nd visit of an user – Walle Cyril Oct 10 '17 at 21:00
  • I think we all don't understand your question in that case. "Once the option is turned on, I want to load the sounds". I think my solution works exactly like that. Could you please provide some additional info or point directly to your problem? – Jacek Kowalewski Oct 12 '17 at 10:23
0

You can use a Blob URL representation of the file

let urls = [
  "https://upload.wikimedia.org/wikipedia/commons/b/be/Hidden_Tribe_-_Didgeridoo_1_Live.ogg"
, "https://upload.wikimedia.org/wikipedia/commons/6/6e/Micronesia_National_Anthem.ogg"
];

let audioNodes, mediaBlobs, blobUrls;

const request = url => fetch(url).then(response => response.blob())
                       .catch(err => {throw err});

const handleResponse = response => {
  mediaBlobs = response;
  blobUrls = mediaBlobs.map(blob => URL.createObjectURL(blob));
  audioNodes = blobUrls.map(blobURL => new Audio(blobURL));
}

const handleMediaSelection = () => {
  const select = document.createElement("select");
  document.body.appendChild(select);     
  const label = new Option("Select audio to play");    
  select.appendChild(label);      
  select.onchange = () => {
    audioNodes.forEach(audio => {
      audio.pause();
      audio.currentTime = 0;    
    });
    audioNodes[select.value].play();
    
  }

  select.onclick = () => {
    const media = audioNodes.find(audio => audio.currentTime > 0);
    if (media) {
      media.pause();
      media.currentTime = 0;
      select.selectedIndex = 0;
    }
  }

  mediaBlobs.forEach((blob, index) => {
    let option = new Option(
      new URL(urls[index]).pathname.split("/").pop()
      , index
    );
    option.onclick = () => console.log()
    select.appendChild(option);
  })
}

const handleError = err => {
  console.error(err);
}

Promise.all(urls.map(request))
  .then(handleResponse)
  .then(handleMediaSelection)
  .catch(handleError);
guest271314
  • 1
  • 15
  • 104
  • 177