XHR响应null从iOS中的HTML5应用程序缓存中检索资源

时间:2013-11-19 12:11:29

标签: javascript ios html5 audio browser-cache

背景

我正在尝试设置一个网页,以便在特定事件中用作iPad2上的信息亭显示(时间和预算排除了原生应用的可能性)。

该页面应该允许用户浏览几个不同的声音文件并查看它们的可视化。

我有一个缓存清单文件,其中列出了页面所需的所有音频文件。这导致在首次访问页面时长时间下载;这很好,但我希望无论网络状态何时需要,资源都可以随时访问。

我不介意首次访问时页面加载速度慢;在这种情况下,这个想法是公众不必亲眼目睹。

顺便说一下:因为createMediaElementSource()显然在iOS WebKit中不起作用,这意味着我的音频要由WebAudio处理,我必须使用createBufferSource()来自缓冲由XMLHTTPRequest的结果构成。这意味着我不能像<audio>元素那样流,而必须等待整个请求,以便我可以设置缓冲区并开始播放一些声音。我还必须非常小心过多的内存使用会导致浏览器崩溃,所以我无法保留几个准备好的缓冲区。

非常沮丧之后,我正在考虑一个不涉及WebAudio的替代选项;这似乎意味着我可以传输音频,并且希望不会因为内存不足而在切换音轨或不间断崩溃方面遇到相同的延迟。

我目前的问题:

我有onclick个事件做了类似的事情:

var request = new XMLHttpRequest();
request.open('GET', "foo.mp3", true);
request.onload = function(e) {
  if (e.target.status === 200) {
    if (e.target.response === null) {
      alert('Empty response for foo.mp3'); //This is the case I don't want 
      return;
    }
    var buf = ctx.createBuffer(e.target.response false);
    var sourceNode = ctx.createBufferSource();
    sourceNode.buffer = buf;
    sourceNode.connect(analyser);
    // etc.
  }
};
request.send();

和一个看起来像这样的应用程序缓存清单:

CACHE MANIFEST
# 2013-11-19T01:24:07.338Z
foo.mp3

NETWORK:
*

在大多数环境中,代码在大多数情况下都可以正常运行。我一直同意任何浏览器提示在本地存储大量数据。

但是,我发现在iOS设备上,当我收到XMLHttpRequest.onload回调时,我遇到状态代码正常的情况,但正如代码中所示response === null

查看Safari Web Inspector,我看到音频资源的条目是数百字节,而不是几兆字节。

所以,我非常感谢我的方法和特定答案的反馈,以及我的缓存机制失败的原因。

1 个答案:

答案 0 :(得分:2)

首先,您的方法和代码完全没问题。但是,有第三方不希望您编写类似的代码,并且希望您编写本机代码。第三方是Apple。

每当您从具有音频文件标题的应用程序缓存中请求资源时,iOS上的Safari将返回空结果。您可以将数据请求为blob,arraybuffer或甚至是纯文本,但文件仍将被阻止。它只会阻止音频文件而不是以相同方式存储的图像文件。重命名将无济于事,因为文件头本身被解析为阻止不需要的内容。

只需稍作修改,您的方法仍然有用。如果您在服务器上对数据进行编码,将其编码存储在应用程序缓存中,并在加载文件后动态解码,iOS将无法检测到您要加载音频文件。伪装文件头很重要。

一种方法是在服务器上的base64中编码音频文件(选择自己的工具)。你的加载函数将保持不变:

var request = new XMLHttpRequest();
request.open('GET', "foo.mp3", true);
request.onload = function(e) {
  if (e.target.status === 200) {
    if (e.target.response === null) {
       alert('Empty response for foo.mp3'); //This case won't be reached anymore 
       return;
    }

    var decodedFile = decodeFile(e.target.response);

    var buf = ctx.createBuffer(e.target.response false);
    var sourceNode = ctx.createBufferSource();
    sourceNode.buffer = buf;
    sourceNode.connect(analyser);
    // etc.
  }
};
request.send();

base64的解码函数可以这样写:

function decodeFile(encodedData) {
    var arrayBufferLength = (encodedData.length / 4) * 3;
    var bytesAsString = atob(encodedData);
    var resultArray = new Uint8Array(new ArrayBuffer(arrayBufferLength));

    for (var i = 0; i < arrayBufferLength; i++) {
        resultArray[i] = bytesAsString.charCodeAt(i);
    }
    return resultArray.buffer;
}

此博文详细介绍了该问题:http://html5doctor.com/taking-web-audio-offline-in-ios-6-safari/