简体   繁体   English

处理 IOS web 浏览器不缓存音频

[英]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.以前,我遇到了预加载无法工作的问题,因为 iOS 设备不允许在没有点击事件的情况下加载音频。 I have gotten around this, however, I now have another issue.我已经解决了这个问题,但是,我现在有另一个问题。 iOS/Safari only allows the most recent audio file to be loaded. iOS/Safari只允许加载最新的音频文件。 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. 2011~2012 年左右有很多帖子试图解决这个问题,但我没有找到一个好的解决方案。 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.这样,每个活动只会将一个音频文件加载到 memory 中,然后您只需选择要播放的音频文件的特定部分。 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.我需要在ReactJS / Redux环境中运行良好并在 iOS 设备上正确缓存的东西。

Is there a 2020 solution that works well? 2020 年是否有行之有效的解决方案?

You can use IndexedDB .您可以使用IndexedDB It's a low-level API for client-side storage of significant amounts of structured data, including files/blobs.它是一个低级 API,用于在客户端存储大量结构化数据,包括文件/blob。 IndexedDB API is powerful, but may seem too complicated for simple cases. IndexedDB API 功能强大,但对于简单的情况可能看起来过于复杂。 If you'd prefer a simple API, try libraries such as localForage , dexie.js .如果您更喜欢简单的 API,请尝试使用localForagedexie.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. localForage是一个为客户端数据存储提供简单名称:值语法的 Polyfill,它在后台使用 IndexedDB,但在不支持 IndexedDB 的浏览器中回退到 WebSQL 和 localStorage。

You can check the browser support for IndexedDB here: https://caniuse.com/#search=IndexedDB .您可以在此处查看浏览器对IndexedDB的支持: 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 localForage.js仅包含此处的代码: 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:您可以在 chrome 开发工具中查看IndexedDB ,您会在那里找到我们的项目: 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.顺便说一句,旧版本的 safari IOS 不支持在IndexedDB中存储blob ,如果仍然是这种情况,您可以将音频文件存储为ArrayBuffer ,这是非常受支持的。 Here is an example using ArrayBuffer :这是使用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

Moving my answer here from the comment.将我的答案从评论移到这里。

You can use HTML5 localstorage API to store/cache the audio content.您可以使用 HTML5 localstorage API 来存储/缓存音频内容。 See this article from Apple https://developer.apple.com/library/archive/documentation/iPhone/Conceptual/SafariJSDatabaseGuide/Introduction/Introduction.html .请参阅 Apple https://developer.apple.com/library/archive/documentation/iPhone/Conceptual/SafariJSDatabaseGuide/Introduction/Introduction.html的这篇文章。

As per the article,根据文章,

Make your website more responsive by caching resources—including audio and video media—so they aren't reloaded from the web server each time a user visits your site.通过缓存资源(包括音频和视频媒体)使您的网站更具响应性,因此每次用户访问您的网站时,它们不会从 web 服务器重新加载。

There is an example to show how to use the storage.有一个例子来展示如何使用存储。

Apple also allows you to use a database if you need so.如果需要,Apple 还允许您使用数据库。 See this example: https://developer.apple.com/library/archive/documentation/iPhone/Conceptual/SafariJSDatabaseGuide/ASimpleExample/ASimpleExample.html#//apple_ref/doc/uid/TP40007256-CH4-SW4请参阅此示例: https://developer.apple.com/library/archive/documentation/iPhone/Conceptual/SafariJSDatabaseGuide/ASimpleExample/ASimpleExample.html#//apple_ref/doc/uid/TP40007256-CH4-SW4

Lets explore some browser storage options让我们探索一些浏览器存储选项

  • localStorage is only good for storing short key/val string localStorage 仅适用于存储短 key/val 字符串
  • IndexedDB is not ergonomic for it design IndexedDB 的设计不符合人体工程学
  • websql is deprecated/removed websql 已弃用/删除
  • Native file system is a good canditate but still experimental behind a flag in chrome机文件系统是一个很好的候选者,但在 chrome 的标志后面仍然是实验性的
  • localForge is a just booiler lib for a key/value storage wrapped around IndexedDB and promises (good but unnecessary) localForge 是一个简单的布尔库,用于围绕 IndexedDB 和 promises 进行键/值存储(很好但不必要)

That leaves us with: Cache storage这给我们留下了:缓存存储

/**
 * Returns the cached url if it exist or fetches it,
 * stores it and returns a blob
 *
 * @param {string|Request} url
 * @returns {Promise<Blob>}
 */
async function cacheFirst (url) {
  const cache = await caches.open('cache')
  const res = await cache.match(file) || await fetch(url).then(res => {
    cache.put(url, res.clone())
    return res
  })
  return res.blob()
}

cacheFirst(url).then(blob => {
  audioElm.src = URL.createObjectURL(blob)
})

Cache storage goes well hand in hand with service worker but can function without it.缓存存储与 Service Worker 相得益彰,但如果没有它,function 也可以。 doe your site needs to be secure, as it's a "power function" and only exist in secure contexts.您的网站是否需要安全,因为它是“电源功能”并且仅存在于安全环境中。

Service worker is a grate addition if you want to build PWA (Progressive web app) with offline support, maybe you should consider it.如果您想构建具有离线支持的 PWA(Progressive web 应用程序),Service Worker 是一个很好的补充,也许您应该考虑一下。 something that can help you on the way is: workbox it can cache stuff on the fly as you need them - like some man in the middle.可以帮助您的事情是:工作它可以根据您的需要即时缓存内容 - 就像中间的某个人一样。 it also have a cache first strategy.它还有一个缓存优先策略。

Then it can be as simple as just writing <audio src="url"> and let workbox do it thing然后它可以像编写<audio src="url">并让 workbox 做这件事一样简单

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM