Recently I was asked to create an audio proxy, whereby no files were contained within DOCROOT. Straight forward I thought, how hard can it be when all it requires is a range resume delivery system. Well, I was once again thwarted by the changes Apple make to their products without telling anyone.
Not all HTML 5 <audio> tags are equal it seems; not only do they look different, but they also require slightly different content formats and respond slightly differently if any of that information is slightly different. In this case, it was the header “Content-Type” causing issues.
It’s not just in looks these little things are different. As has been the way from the invention of the internet, there are quirks specific to each variant out there, some more annoying than others, but quirks all the same.
I normally use mime_content_type($file) to determine the mime type of a file I am sending to a client, it’s painless as a general rule. However, there have been some changes recently in Chrome and most importantly to Safari where there are some issues with using it.
Using mime_content_type($file) on an MP3 file returns a mime type of octet-stream, which for most things, is more than adequate. However, for Safari, no … it is not adequate. After hours of hair pulling and ear scratching, watching the code I wrote work in Chrome, IE, Edge, Firefox and even the Android browser but unfortunately fail on Safari, both on the desktop and iOS, it occurred to me to check whether Safari actually likes octet-streams in their <audio> tags.
The result was no it doesn’t, so basically you have to check if it is an mp3 before sending it, because you will need to adjust the Content-Type header accordingly. I only extend the checking if mime_content_type() returns octet-stream, so I then look to if a file has an extension of mp3, there are various methods for working that out, pathinfo() for just looking at the extension for .mp3 or searching the filename using preg_match(), you can decide which works best for you. The first 3 bytes of an MP3 is the ID? tag header, so if the file has the right extension and contains those bytes, then Robert is your Mothers’ Brother and you have to make the Content-Type to return audio/mpeg.
Why is this important? Well simply because Safari does zero checking, in an <audio> tag it expects to find explicit Content-Types … whatever they may be, you will have the same issue with WAV and OGG, but for the purpose of this issue it was just MP3’s I was concerned with. It is also worth noting that Safari also likes explicit urls for the sources inside an <audio> tag, setting it to relative paths can cause it to get its knickers in a twist and reject the source.
— One final note/edit, when you look online for range suitable download examples you will find loads, but not all are equal and in fact, most (if not all that I have found) abide by the RFC for range headers and do not support multiple requests in one GET which is a part of the standard. Be aware of that, because it could cause you issues no matter how unlikely it is that a browser will request multiple ranges at once, at some point, they could and your code will break.
Example
I hope it helps someone else out there that is also suffering from not being able to dynamically assign audio files to the html 5 <audio> tag.
[html] <!-- Our HTML 5 audio tag --> <audio id="wickedtune" src="audio.php?file=audiotrack.php" autoplay controls /> [/html]
[php] // Example of working out if MP3 Content-Type is required function webdavGetMimeType($fp, $name) { $mime = mime_content_type($name); if ($mime == "octet-stream") { if (pathinfo($file)['extension'] == "mp3" && pre_match('/^ID[1-3].*/', fread($fp,4))) $mime = "audio/mpeg"; } return $mime; } $path = "/files/audiotrack.mp3"; $fp = fopen($path, 'rb'); header("Content-Type: ' . webdavGetMimeType($fp, $path)); [/php]
Leave a Reply