I'm desperatly trying to find a solution for a web application that has to run on an iOS-Safari (e.g. on iPad, iPad2 and iPhone 4):
It's a web application I wrote some time ago which lets the user search for and listen to short music samples (MP3s, all from ~100 kB to ~1.5 MB). The audio player is Flash-based, so it doesn't work on iOS-devices at the moment and I'll have to implement an alternative either in HTML 5 or with a "direct" QuickTime-object.
Both my HTML 5- and QuickTime-alternatives for iOS-devices work fine so far, but there's one major problem I can't find a solution to:
Unlike Flash and most HTML 5-capable browsers on Windows Safari on my iPad 2 won't store the audiofiles in the browsercache after loading and playing them - neither with HTML 5 audio-tags nor with a QuickTime-Object. Every time I load an audio file for playback from the server (with JavaScript-Commands, so without changing or reloading the whole page) it's downloaded again completely.
If a user listens to sample A and then to sample B, Safari forgot about having played sample A and downloads the whole MP3 again if I like to listen to it again. On a mobile device with a potentially narrow bandwith this behaviour is out of the question.
Is there a way of storing downloaded audiofiles opened by HTML 5 or QuickTime in Safari's cache so it remembers already having downloaded them - like it caches usual "web-files" like HTML, CSS or JPEG-images, or like Flash stores such objects in its local cache?
My first attempt was trying to use the Application Cache with a manifest file - although this is not really the intended purpose for my application... I don't have a static set of files that I want to have cached or "available offline" - I just want to cache MP3s which the user has played yet.
It should be possible to use a "dynamic manifest": One that is parsed by the Apache PHP module and listing the files played so far from a PHP session - something like this:
session_start();
header("Content-Type: text/cache-manifest, charset=UTF-8");
echo "CACHE MANIFEST\n";
foreach($_SESSION['playedSongs'] as $song)
{
echo $song."\n";
}
So whenever a song is loaded / played, I could access the PHP session with AJAX, insert the filename of the played file and manually update the manifest by calling window.applicationCache.update() or .swapCache().
There are two problems with this:
First of all: It doesn't work. And I didn't even get to the point of trying to use a dynamic manifest:
<!DOCTYPE html>
<html manifest="cache.manifest">
<head>
<title>Test</title>
<script type="text/javascript">
function playStuff(id)
{
if(id == 1)
{
window.document.getElementById("audio").innerHTML = '<audio controls preload="automatic" autobuffer><source src="song01.mp3" type="audio/mp3" /></audio>';
}
else if(id == 2)
{
window.document.getElementById("audio").innerHTML = '<audio controls preload="automatic" autobuffer><source src="song02.mp3" type="audio/mp3" /></audio>';
}
}
</script>
</head>
<body>
<div id="audio"></div><br />
<br />
<input type="button" value="playStuff(1)" onclick="playStuff(1)" />
<input type="button" value="playStuff(2)" onclick="playStuff(2)" />
</body>
</html>
The cache.manifest looks like this:
CACHE MANIFEST
song01.mp3
song02.mp3
and is properly returned from Apache as "text/cache-manifest" by adding
AddType text/cache-manifest manifest
to the .htaccess of this directory.
The Apache-logs clearly show that the Safari (respectively "AppleCoreMedia") doesn't care about the application cache when it comes to audio-files:
Safari itself seems to acknowledge the manifest and indeed preload the files:
192.168.0.40 - - [23/Jul/2011:12:45:46 +0200] "GET /websql/index2.html HTTP/1.1" 200 2619 "-" "Mozilla/5.0 (iPad; U; CPU OS 4_3_3 like Mac OS X; de-de) AppleWebKit/533.17.9 (KHTML, like Gecko) Version/5.0.2 Mobile/8J2 Safari/6533.18.5"
192.168.0.40 - - [23/Jul/2011:12:45:46 +0200] "GET /websql/cache.manifest HTTP/1.1" 200 79 "-" "Mozilla/5.0 (iPad; U; CPU OS 4_3_3 like Mac OS X; de-de) AppleWebKit/533.17.9 (KHTML, like Gecko) Version/5.0.2 Mobile/8J2 Safari/6533.18.5"
192.168.0.40 - - [23/Jul/2011:12:45:46 +0200] "GET /websql/cache.manifest?%3E HTTP/1.1" 200 79 "-" "Mozilla/5.0 (iPad; U; CPU OS 4_3_3 like Mac OS X; de-de) AppleWebKit/533.17.9 (KHTML, like Gecko) Version/5.0.2 Mobile/8J2 Safari/6533.18.5"
192.168.0.40 - - [23/Jul/2011:12:45:46 +0200] "GET /websql/song02.mp3 HTTP/1.1" 200 120525 "-" "Mozilla/5.0 (iPad; U; CPU OS 4_3_3 like Mac OS X; de-de) AppleWebKit/533.17.9 (KHTML, like Gecko) Version/5.0.2 Mobile/8J2 Safari/6533.18.5"
192.168.0.40 - - [23/Jul/2011:12:45:46 +0200] "GET /websql/song01.mp3 HTTP/1.1" 200 120525 "-" "Mozilla/5.0 (iPad; U; CPU OS 4_3_3 like Mac OS X; de-de) AppleWebKit/533.17.9 (KHTML, like Gecko) Version/5.0.2 Mobile/8J2 Safari/6533.18.5"
Up to this point I did nothing but opening my test-application in Safari.
Playing song01.mp3:
192.168.0.40 - - [23/Jul/2011:12:47:25 +0200] "GET /websql/song01.mp3 HTTP/1.1" 206 2 "-" "AppleCoreMedia/1.0.0.8J2 (iPad; U; CPU OS 4_3_3 like Mac OS X; de_de)"
192.168.0.40 - - [23/Jul/2011:12:47:25 +0200] "GET /websql/song01.mp3 HTTP/1.1" 206 120525 "-" "AppleCoreMedia/1.0.0.8J2 (iPad; U; CPU OS 4_3_3 like Mac OS X; de_de)"
192.168.0.40 - - [23/Jul/2011:12:47:25 +0200] "GET /websql/song01.mp3 HTTP/1.1" 206 120525 "-" "AppleCoreMedia/1.0.0.8J2 (iPad; U; CPU OS 4_3_3 like Mac OS X; de_de)"
192.168.0.40 - - [23/Jul/2011:12:47:25 +0200] "GET /websql/song01.mp3 HTTP/1.1" 304 - "-" "AppleCoreMedia/1.0.0.8J2 (iPad; U; CPU OS 4_3_3 like Mac OS X; de_de)"
192.168.0.40 - - [23/Jul/2011:12:47:25 +0200] "GET /websql/song01.mp3 HTTP/1.1" 206 120525 "-" "AppleCoreMedia/1.0.0.8J2 (iPad; U; CPU OS 4_3_3 like Mac OS X; de_de)"
192.168.0.40 - - [23/Jul/2011:12:47:29 +0200] "GET /websql/song01.mp3 HTTP/1.1" 304 - "-" "AppleCoreMedia/1.0.0.8J2 (iPad; U; CPU OS 4_3_3 like Mac OS X; de_de)"
192.168.0.40 - - [23/Jul/2011:12:47:29 +0200] "GET /websql/song01.mp3 HTTP/1.1" 206 120525 "-" "AppleCoreMedia/1.0.0.8J2 (iPad; U; CPU OS 4_3_3 like Mac OS X; de_de)"
Playing song2.mp3:
192.168.0.40 - - [23/Jul/2011:12:48:04 +0200] "GET /websql/song02.mp3 HTTP/1.1" 206 2 "-" "AppleCoreMedia/1.0.0.8J2 (iPad; U; CPU OS 4_3_3 like Mac OS X; de_de)"
192.168.0.40 - - [23/Jul/2011:12:48:04 +0200] "GET /websql/song02.mp3 HTTP/1.1" 206 120525 "-" "AppleCoreMedia/1.0.0.8J2 (iPad; U; CPU OS 4_3_3 like Mac OS X; de_de)"
192.168.0.40 - - [23/Jul/2011:12:48:04 +0200] "GET /websql/song02.mp3 HTTP/1.1" 206 120525 "-" "AppleCoreMedia/1.0.0.8J2 (iPad; U; CPU OS 4_3_3 like Mac OS X; de_de)"
192.168.0.40 - - [23/Jul/2011:12:48:04 +0200] "GET /websql/song02.mp3 HTTP/1.1" 304 - "-" "AppleCoreMedia/1.0.0.8J2 (iPad; U; CPU OS 4_3_3 like Mac OS X; de_de)"
192.168.0.40 - - [23/Jul/2011:12:48:04 +0200] "GET /websql/song02.mp3 HTTP/1.1" 206 120525 "-" "AppleCoreMedia/1.0.0.8J2 (iPad; U; CPU OS 4_3_3 like Mac OS X; de_de)"
192.168.0.40 - - [23/Jul/2011:12:48:05 +0200] "GET /websql/song02.mp3 HTTP/1.1" 304 - "-" "AppleCoreMedia/1.0.0.8J2 (iPad; U; CPU OS 4_3_3 like Mac OS X; de_de)"
192.168.0.40 - - [23/Jul/2011:12:48:05 +0200] "GET /websql/song02.mp3 HTTP/1.1" 206 120525 "-" "AppleCoreMedia/1.0.0.8J2 (iPad; U; CPU OS 4_3_3 like Mac OS X; de_de)"
Playing song1.mp3 again:
192.168.0.40 - - [23/Jul/2011:12:48:38 +0200] "GET /websql/song01.mp3 HTTP/1.1" 304 - "-" "AppleCoreMedia/1.0.0.8J2 (iPad; U; CPU OS 4_3_3 like Mac OS X; de_de)"
192.168.0.40 - - [23/Jul/2011:12:48:38 +0200] "GET /websql/song01.mp3 HTTP/1.1" 206 120525 "-" "AppleCoreMedia/1.0.0.8J2 (iPad; U; CPU OS 4_3_3 like Mac OS X; de_de)"
192.168.0.40 - - [23/Jul/2011:12:48:38 +0200] "GET /websql/song01.mp3 HTTP/1.1" 206 120525 "-" "AppleCoreMedia/1.0.0.8J2 (iPad; U; CPU OS 4_3_3 like Mac OS X; de_de)"
192.168.0.40 - - [23/Jul/2011:12:48:38 +0200] "GET /websql/song01.mp3 HTTP/1.1" 304 - "-" "AppleCoreMedia/1.0.0.8J2 (iPad; U; CPU OS 4_3_3 like Mac OS X; de_de)"
192.168.0.40 - - [23/Jul/2011:12:48:38 +0200] "GET /websql/song01.mp3 HTTP/1.1" 206 120525 "-" "AppleCoreMedia/1.0.0.8J2 (iPad; U; CPU OS 4_3_3 like Mac OS X; de_de)"
192.168.0.40 - - [23/Jul/2011:12:48:40 +0200] "GET /websql/song01.mp3 HTTP/1.1" 304 - "-" "AppleCoreMedia/1.0.0.8J2 (iPad; U; CPU OS 4_3_3 like Mac OS X; de_de)"
192.168.0.40 - - [23/Jul/2011:12:48:40 +0200] "GET /websql/song01.mp3 HTTP/1.1" 206 120525 "-" "AppleCoreMedia/1.0.0.8J2 (iPad; U; CPU OS 4_3_3 like Mac OS X; de_de)"
Playing song2.mp3 again:
192.168.0.40 - - [23/Jul/2011:12:49:12 +0200] "GET /websql/song02.mp3 HTTP/1.1" 304 - "-" "AppleCoreMedia/1.0.0.8J2 (iPad; U; CPU OS 4_3_3 like Mac OS X; de_de)"
192.168.0.40 - - [23/Jul/2011:12:49:12 +0200] "GET /websql/song02.mp3 HTTP/1.1" 206 120525 "-" "AppleCoreMedia/1.0.0.8J2 (iPad; U; CPU OS 4_3_3 like Mac OS X; de_de)"
192.168.0.40 - - [23/Jul/2011:12:49:12 +0200] "GET /websql/song02.mp3 HTTP/1.1" 206 120525 "-" "AppleCoreMedia/1.0.0.8J2 (iPad; U; CPU OS 4_3_3 like Mac OS X; de_de)"
192.168.0.40 - - [23/Jul/2011:12:49:12 +0200] "GET /websql/song02.mp3 HTTP/1.1" 304 - "-" "AppleCoreMedia/1.0.0.8J2 (iPad; U; CPU OS 4_3_3 like Mac OS X; de_de)"
192.168.0.40 - - [23/Jul/2011:12:49:12 +0200] "GET /websql/song02.mp3 HTTP/1.1" 206 120525 "-" "AppleCoreMedia/1.0.0.8J2 (iPad; U; CPU OS 4_3_3 like Mac OS X; de_de)"
192.168.0.40 - - [23/Jul/2011:12:49:13 +0200] "GET /websql/song02.mp3 HTTP/1.1" 304 - "-" "AppleCoreMedia/1.0.0.8J2 (iPad; U; CPU OS 4_3_3 like Mac OS X; de_de)"
192.168.0.40 - - [23/Jul/2011:12:49:13 +0200] "GET /websql/song02.mp3 HTTP/1.1" 206 120525 "-" "AppleCoreMedia/1.0.0.8J2 (iPad; U; CPU OS 4_3_3 like Mac OS X; de_de)"
Every file is downloaded again completely when playing it. So "AppleCoreMedia" (whatever this may be exactly, the QuickTime-plugin that is triggered by the HTML 5 audio-element I suppose?) either doesn't have access to the Application Cache or simply just doesn't realize the files in it. So if I switch my iPad to "Airplane Mode" now, Safari is unable to access / load / play the files.
I also tried using a QuickTime-object instead of an HTML 5 audio-tag (as far as I know HTML 5 audio and video in Safari always use QuickTime?) and controlling it with something like:
document.movie1.SetURL('song02.mp3');
Nothing changes, it's just like using HTML 5 audio and everything gets downloaded again when loading/playing it.
And even if this would work there'd still be a problem:
To properly implement that I would have to load the MP3-file in the Application Cache before playing it. When doing this it seems impossible to show a "real" progress: The ProgressEvent that is fired from the Application Cache after updating it doesn't seem to provide any information about data loaded so far and the complete size of a file. It's just "File 1 from 2" and so on, and not a "real" progress where I could determine something like: "100 kB of 1.2 MB loaded" as I can do with the audio-element.
All the other storage-approaches like Web SQL / Web Database or Local Storage are no help either:
I don't see any way of getting the MP3 data into Local Storage or Web Database and/or getting it out again to play it. The HTML 5 Canvas-element has a toDataURL()-function to produce a Base64-encoded representation and use it for storage - the Audio-element doesn't seem to have anything like this.
My last really "dirty" approach was trying to load "manually" Base64-encoded-MP3s with a combination of AJAX and PHP: A PHP-script outputs a Base64-representation of a MP3-file and is loaded by AJAX, so I could store the Base64-representation e.g. as Local Storage or in Web Database:
$infile = 'song01.mp3';
$contents = file_get_contents($infile);
$base64 = base64_encode($contents);
$audio = 'data:audio/mp3;base64,'.$base64;
echo $audio;
I tried using the resulting AJAX responseText as the source-argument in an audio-source-tag. Suprise: It doesn't work in Safari on my iPad 2, the player just fails to load the "file", although this works fine in Chrome on Windows. Possibly a size limitation for Base64-URIs on Safari / iOS?
And again: Even if this was working in iOS/Safari I don't know of a way to determine a real progress from an AJAX-query...
The last thing I was thinking of was not replacing the audio- or source-tags when loading a song but leaving them in the DOM-structure, check if it's already there when a song is loaded and just add a new audio-tag if a song hasn't been loaded yet. Doesn't work... If you add multiple player-instances dynamically (again no matter if HTML 5 -tags or QuickTime-objects) instead of "overwriting" them, Safari forgets about ever having loaded the first MP3 as soon as you even insert a new audio- or QuickTime-Element into the DOM tree - you don't even have to load / play something in the new instance! Repeated playback without complete reloading of the file just works as long as you don't playback or insert anything else audio / media related. BTW: Just using Audio-objects in JavaScript and "saving" them into an array doesn't work either / doesn't make Safari cache anything.
This produces lots of unnecessary traffic and takes unnecessary long time if you're in a cellular network with a low bandwith!
I'm working at this problem for three days now without even coming close to a solution...
Any ideas?
curl -v URL
and post headers here? – Matej Balantič