Vyhledávání na webu

javascript - Řešení problému s tím, že webové prohlížeče na iOS neukládají audio do cache


Mám webovou stránku zaměřenou na výuku jazyků, na které pracuji. Uživatelé mohou kliknout na objekty a slyšet zvuk toho, na co kliknou. Mnoho lidí, kteří budou tuto stránku používat, se nachází v odlehlejších oblastech s pomalejším připojením k internetu. Proto potřebuji ukládat zvuk do mezipaměti před načtením jednotlivých aktivit, jinak by byl zpoždění příliš velké.

Dříve jsem měl problém, kdy přednačítání nefungovalo, protože zařízení iOS nedovolují načítání zvuku bez události kliknutí. Tomu jsem se však vyhnul, nyní mám ale jiný problém. iOS/Safari dovoluje načíst pouze nejnovější zvukový soubor. Proto, kdykoli uživatel klikne na jiný zvukový soubor (i když na něj již předtím klikl), není uložen do mezipaměti a prohlížeč jej musí znovu stáhnout.

Doposud jsem na to nenašel adekvátní řešení. Existuje mnoho příspěvků z období kolem 2011~2012, které se tímto snaží zabývat, ale nenašel jsem dobré řešení. Jedním z řešení bylo sloučit všechny audio klipy pro aktivitu do jednoho audio souboru. Tímto způsobem by se do paměti načítal pouze jeden audio soubor pro každou aktivitu a pak byste si jen vybrali konkrétní část audio souboru k přehrání. I když to může fungovat, stává se to také nepříjemností, kdykoli je potřeba audio klip změnit, přidat nebo odstranit.

Potřebuji něco, co dobře funguje v prostředí ReactJS/Redux a správně ukládá do mezipaměti na zařízeních iOS.

Existuje řešení z roku 2020, které funguje dobře?

Odpověď č. 1

Můžete použít IndexedDB. Je to nízkoúrovňové API pro ukládání významného množství strukturovaných dat na straně klienta, včetně souborů/blobů. API IndexedDB je výkonné, ale pro jednoduché případy se může zdát příliš složité. Pokud byste raději jednoduché API, vyzkoušejte knihovny jako localForage, dexie.js.

localForage je polyfill poskytující jednoduchou syntaxi jméno:hodnota pro ukládání dat na straně klienta, který používá v pozadí IndexedDB, ale v prohlížečích, které IndexedDB nepodporují, přechází na WebSQL a poté na localStorage.

Podporu prohlížeče pro IndexedDB můžete zkontrolovat zde: https://caniuse.com/#search=IndexedDB. Je dobře podporována. Zde je jednoduchý příklad, který jsem vytvořil, aby ukázal koncept:

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 obsahuje pouze kód z tohoto odkazu: https://github.com/localForage/localForage/blob/master/dist/localforage.js

Můžete zkontrolovat IndexedDB v nástrojích pro vývojáře Chrome a naleznete tam naše položky:

a pokud stránku obnovíte, stále ji tam uvidíte a také uvidíte vytvořený audio přehrávač. Doufám, že to odpovědělo na vaši otázku.

Mimochodem, starší verze Safari na iOS nepodporovaly ukládání blob do IndexedDB. Pokud to stále platí, můžete audio soubory uložit jako ArrayBuffer, což je velmi dobře podporováno. Zde je příklad použití 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));
            });
          });
      }
    });
  });
})();

Odpověď č. 2

Přesouvám svou odpověď sem z komentáře.

Můžete použít HTML5 localstorage API k ukládání/provídění audio obsahu. Podívejte se na tento článek od Apple https://developer.apple.com/library/archive/documentation/iPhone/Conceptual/SafariJSDatabaseGuide/Introduction/Introduction.html.

Podle článku,

Udělejte své webové stránky responsivnější ukládáním zdrojů do mezipaměti—including audio a video média—tak, aby se při každé návštěvě uživatele nenačítaly z webového serveru znovu.

Existuje příklad, který ukazuje, jak používat úložiště.

Apple vám také umožňuje použít databázi, pokud to potřebujete. Podívejte se na tento příklad: https://developer.apple.com/library/archive/documentation/iPhone/Conceptual/SafariJSDatabaseGuide/ASimpleExample/ASimpleExample.html#//apple_ref/doc/uid/TP40007256-CH4-SW4