【问题标题】:Dealing with IOS web browsers not caching audio处理不缓存音频的 IOS Web 浏览器
【发布时间】:2020-11-08 07:27:56
【问题描述】:

我正在开发一个语言网站来教授语言。用户可以单击对象并听到他们单击的音频。许多将使用它的人都在互联网连接速度较慢的偏远地区。因此,我需要在加载每个活动之前缓存音频,否则会有太多延迟。

以前,我遇到过一个问题,即预加载无法正常工作,因为 iOS 设备不允许在没有点击事件的情况下加载音频。我已经解决了这个问题,但是,我现在有另一个问题。 iOS/Safari 只允许加载最新的音频文件。因此,无论何时用户点击另一个音频文件(即使它之前被点击过),它都不会被缓存,浏览器必须重新下载。

到目前为止,我还没有找到适当的解决方案。 2011~2012 前后有很多帖子试图解决这个问题,但我还没有找到好的解决方案。一种解决方案是将活动的所有音频剪辑合并到一个音频文件中。这样,每个活动只会将一个音频文件加载到内存中,然后您只需选择要播放的音频文件的特定部分。虽然这可能有效,但每当需要更改、添加或删除音频剪辑时,它也会变得很麻烦。

我需要在 ReactJS/Redux 环境中运行良好并在 iOS 设备上正确缓存的东西。

2020 年是否有行之有效的解决方案?

【问题讨论】:

  • 您尝试过 Cloudflare CDN 吗?
  • @Constantin 我不确定这会有多大帮助。问题仍然是互联网连接速度慢且设备上没有本地缓存​​。

标签: javascript ios reactjs audio safari


【解决方案1】:

您可以使用IndexedDB。它是用于客户端存储大量结构化数据(包括文件/blob)的低级 API。 IndexedDB API 功能强大,但对于简单的情况可能看起来过于复杂。如果您更喜欢简单的 API,请尝试使用 localForagedexie.js 等库。

localForage 是一个 Polyfill,为客户端数据存储提供简单的 name:value 语法,它在后台使用 IndexedDB,但在不支持 IndexedDB 的浏览器中回退到 WebSQL 和 localStorage。

您可以在此处查看浏览器对IndexedDB 的支持:https://caniuse.com/#search=IndexedDB。它得到了很好的支持。这是我为展示这个概念而制作的一个简单示例:

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 仅包含此处的代码:https://github.com/localForage/localForage/blob/master/dist/localforage.js

您可以在 chrome 开发工具中查看IndexedDB,您会在那里找到我们的项目: 如果你刷新页面,你仍然会在那里看到它,你也会看到创建的音频播放器。我希望这回答了你的问题。

顺便说一句,旧版本的 safari IOS 不支持将 blob 存储在 IndexedDB 中,如果仍然如此,您可以将音频文件存储为 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));
            });
          });
      }
    });
  });
})();

【讨论】:

  • 使用async await 而不是promise,这段代码会更漂亮。如果您想要async await 版本,请告诉我。
  • @kojow7 这篇文章love2dev.com/blog/… 提到在IOS Safari 上cache 存储有50mb 限制,IndexedDB 至少有500mb 限制。我认为IndexedDB 是您的最佳选择。
  • 你知道这些解决方案是否能很好地与 ReactJS/Redux 框架配合使用吗?
  • 当然,它们会起作用。这些解决方案只是使用 Web API developer.mozilla.org/en-US/docs/Web/API Web API 是浏览器提供给我们的 API。这些 API 可以通过 JavaScript 访问,无论您是使用本机 JavaScript(如我的示例)还是任何 JavaScript 库(如 React、Vue 或 Angular)都没有关系。顺便说一句,我是一名 React 开发人员,所以如果你需要帮助来实现它,请告诉我。
【解决方案2】:

将我的答案从评论移到这里。

您可以使用 HTML5 localstorage API 来存储/缓存音频内容。请参阅 Apple https://developer.apple.com/library/archive/documentation/iPhone/Conceptual/SafariJSDatabaseGuide/Introduction/Introduction.html 的这篇文章。

根据文章,

通过缓存资源(包括音频)使您的网站更具响应性 和视频媒体——因此它们不会每次都从 Web 服务器重新加载 用户访问您的网站。

有一个例子来展示如何使用存储。

Apple 还允许您在需要时使用数据库。看这个例子:https://developer.apple.com/library/archive/documentation/iPhone/Conceptual/SafariJSDatabaseGuide/ASimpleExample/ASimpleExample.html#//apple_ref/doc/uid/TP40007256-CH4-SW4

【讨论】:

  • 亲爱的@manishg,localStorage 仅存储约 5mg。如果下载的媒体超过了这个容量怎么办?
  • 请阅读我分享的文章。它提到了三种存储数据的方法。
  • localStorage 限制为大约 5MB,并且只能包含字符串。
【解决方案3】:

让我们探索一些浏览器存储选项

  • localStorage 仅适用于存储短 key/val 字符串
  • IndexedDB 的设计不符合人体工程学
  • websql 已弃用/删除
  • Native file system 是一个很好的候选人,但在 chrome 中的标志后面仍然是实验性的
  • localForge 是一个简单的布尔库,用于围绕 IndexedDB 和 Promise 进行键/值存储(很好但不必要)

剩下的就是: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)
})

缓存存储与 Service Worker 相得益彰,但没有它也可以正常工作。您的网站是否需要安全,因为它是一种“权力功能”并且只存在于安全的环境中。

如果您想构建具有离线支持的 PWA(渐进式 Web 应用程序),Service Worker 是一个很好的补充,也许您应该考虑一下。可以在途中为您提供帮助的是:workbox 它可以在您需要时即时缓存内容 - 就像中间的某个人一样。它还有一个缓存优先策略。

那么它可以像写&lt;audio src="url"&gt; 一样简单,然后让workbox 来做这件事

【讨论】:

  • 不错的选择,但请记住 iOS 与 ServiceWorkers 的配合并不好,尤其是在将其用作 PWA 时。对于用户来说不是一个很好的体验,所以要记住一些事情。
  • @TomAnderson iOS 对 ServiceWorkers 有什么问题?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-09-14
  • 1970-01-01
  • 2017-12-10
  • 1970-01-01
  • 2017-09-09
  • 2019-10-19
  • 2013-04-20
相关资源
最近更新 更多