Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Dealing with IOS web browsers not caching audio

I have a language site that I am working on to teach language. Users can click on objects and hear the audio for what they click on. Many of the people that will be using this are in more remote areas with slower Internet connections. Because of this, I am needing to cache audio before each of the activities is loaded otherwise there is too much of a delay.

Previously, I was having an issue where preloading would not work because iOS devices do not allow audio to load without a click event. I have gotten around this, however, I now have another issue. iOS/Safari only allows the most recent audio file to be loaded. Therefore, whenever the user clicks on another audio file (even if it was clicked on previously), it is not cached and the browser has to download it again.

So far I have not found an adequate solution to this. There are many posts from around 2011~2012 that try to deal with this but I have not found a good solution. One solution was to combine all audio clips for activity into a single audio file. That way only one audio file would be loaded into memory for each activity and then you just pick a particular part of the audio file to play. While this may work, it also becomes a nuisance whenever an audio clip needs to be changed, added, or removed.

I need something that works well in a ReactJS/Redux environment and caches properly on iOS devices.

Is there a 2020 solution that works well?

like image 786
kojow7 Avatar asked Jul 18 '20 17:07

kojow7


People also ask

Do browsers automatically cache?

Almost every modern browser will cache . js, . css and images by default.

What is caching in IOS?

Content caching is a service in macOS that speeds up downloading of software distributed by Apple and data that users store in iCloud by saving content that local Apple devices have already downloaded.

Does browser cache affect performance?

By caching as much content as possible, websites see speed improvements that are even greater than those achieved by serving content from servers closer to the end-users.


1 Answers

You can use IndexedDB. It's a low-level API for client-side storage of significant amounts of structured data, including files/blobs. IndexedDB API is powerful, but may seem too complicated for simple cases. If you'd prefer a simple API, try libraries such as localForage, dexie.js.

localForage is A Polyfill providing a simple name:value syntax for client-side data storage, which uses IndexedDB in the background, but falls back to WebSQL and then localStorage in browsers that don't support IndexedDB.

You can check the browser support for IndexedDB here: https://caniuse.com/#search=IndexedDB. It's well supported. Here is a simple example I made to show the concept:

index.html

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Audio</title>
</head>

<body>
  <h1>Audio</h1>

  <div id="container"></div>

  <script src="localForage.js"></script>
  <script src="main.js"></script>
</body>

</html>

main.js

"use strict";

(function() {
  localforage.setItem("test", "working");

  // create HTML5 audio player
  function createAudioPlayer(audio) {
    const audioEl = document.createElement("audio");
    const audioSrc = document.createElement("source");
    const container = document.getElementById("container");
    audioEl.controls = true;
    audioSrc.type = audio.type;
    audioSrc.src = URL.createObjectURL(audio);
    container.append(audioEl);
    audioEl.append(audioSrc);
  }

  window.addEventListener("load", e => {
    console.log("page loaded");
    // get the audio from indexedDB
    localforage.getItem("audio").then(audio => {
      // it may be null if it doesn't exist
      if (audio) {
        console.log("audio exist");
        createAudioPlayer(audio);
      } else {
        console.log("audio doesn't exist");
        // fetch local audio file from my disk
        fetch("panumoon_-_sidebyside_2.mp3")
          // convert it to blob
          .then(res => res.blob())
          .then(audio => {
            // save the blob to indexedDB
            localforage
              .setItem("audio", audio)
              // create HTML5 audio player
              .then(audio => createAudioPlayer(audio));
          });
      }
    });
  });
})();

localForage.js just includes the code from here: https://github.com/localForage/localForage/blob/master/dist/localforage.js

You can check IndexedDB in chrome dev tools and you will find our items there: IndexedDB_Blob and if you refresh the page you will still see it there and you will see the audio player created as well. I hope this answered your question.

BTW, older versions of safari IOS didn't support storing blob in IndexedDB if it's still the case you can store the audio files as ArrayBuffer which is very well supported. Here is an example using ArrayBuffer:

main.js

"use strict";

(function() {
  localforage.setItem("test", "working");

  // convert arrayBuffer to Blob
  function arrayBufferToBlob(buffer, type) {
    return new Blob([buffer], { type: type });
  }

  // convert Blob to arrayBuffer
  function blobToArrayBuffer(blob) {
    return new Promise((resolve, reject) => {
      const reader = new FileReader();
      reader.addEventListener("loadend", e => {
        resolve(reader.result);
      });
      reader.addEventListener("error", reject);
      reader.readAsArrayBuffer(blob);
    });
  }

  // create HTML5 audio player
  function createAudioPlayer(audio) {
    // if it's a buffer
    if (audio.buffer) {
      // convert it to blob
      audio = arrayBufferToBlob(audio.buffer, audio.type);
    }
    const audioEl = document.createElement("audio");
    const audioSrc = document.createElement("source");
    const container = document.getElementById("container");
    audioEl.controls = true;
    audioSrc.type = audio.type;
    audioSrc.src = URL.createObjectURL(audio);
    container.append(audioEl);
    audioEl.append(audioSrc);
  }

  window.addEventListener("load", e => {
    console.log("page loaded");
    // get the audio from indexedDB
    localforage.getItem("audio").then(audio => {
      // it may be null if it doesn't exist
      if (audio) {
        console.log("audio exist");
        createAudioPlayer(audio);
      } else {
        console.log("audio doesn't exist");
        // fetch local audio file from my disk
        fetch("panumoon_-_sidebyside_2.mp3")
          // convert it to blob
          .then(res => res.blob())
          .then(blob => {
            const type = blob.type;
            blobToArrayBuffer(blob).then(buffer => {
              // save the buffer and type to indexedDB
              // the type is needed to convet the buffer back to blob
              localforage
                .setItem("audio", { buffer, type })
                // create HTML5 audio player
                .then(audio => createAudioPlayer(audio));
            });
          });
      }
    });
  });
})();

IndexedDB_ArrayBuffer

like image 146
Ahmed Mokhtar Avatar answered Sep 23 '22 01:09

Ahmed Mokhtar