-
-
Save westonruter/253174 to your computer and use it in GitHub Desktop.
/** | |
* Detect if the browser can play MP3 audio using native HTML5 Audio. | |
* Invokes the callack function with first parameter is the boolean success | |
* value; if that value is false, a second error parameter is passed. This error | |
* is either HTMLMediaError or some other DOMException or Error object. | |
* Note the callback is likely to be invoked asynchronously! | |
* @param {function(boolean, Object|undefined)} callback | |
*/ | |
function canPlayAudioMP3(callback){ | |
try { | |
var audio = new Audio(); | |
//Shortcut which doesn't work in Chrome (always returns ""); pass through | |
// if "maybe" to do asynchronous check by loading MP3 data: URI | |
if(audio.canPlayType('audio/mpeg') == "probably") | |
callback(true); | |
//If this event fires, then MP3s can be played | |
audio.addEventListener('canplaythrough', function(e){ | |
callback(true); | |
}, false); | |
//If this is fired, then client can't play MP3s | |
audio.addEventListener('error', function(e){ | |
callback(false, this.error) | |
}, false); | |
//Smallest base64-encoded MP3 I could come up with (<0.000001 seconds long) | |
audio.src = "data:audio/mpeg;base64,/+MYxAAAAANIAAAAAExBTUUzLjk4LjIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"; | |
audio.load(); | |
} | |
catch(e){ | |
callback(false, e); | |
} | |
} |
@TooTallNate:
I don't have time right now to make other base64 samples, but it is easy to do. All you have to do is create the shorted possible media file with the highest possible compression to result in the smallest possible file; then you just base64-encode the file and there you have it.
Thanks for the reply. Do you use Audacity to create the clip?
@TooTallNate: I'm pretty sure. It was either Audacity or GoldWave, but pretty sure it was Audacity.
It also just occurred to me that you could optimize this check a little bit by detecting the loadedmetadata
event, rather than the canplaythrough
event, or possibly even both! According to the HTML5 spec:
Once enough of the media data has been fetched to determine the duration of the media resource,
its dimensions, and other metadata, this indicates that the resource is usable. The user agent must...
5. Queue a task to fire a simple event named loadedmetadata at the element.
@TooTallNate: Nice catch! I pinged Modernizr about it: http://twitter.com/westonruter/status/11436680165
Let me know if it actually works as advertised in browsers today!
In doing some testing, the only error I've noticed so far is with Safari (4.0.4) on Snow Leopard. While it should asynchronously load correctly and fire the loadedmetadata
/canplaythrough
event, instead an error
event is thrown, and the Audio#error
property contains a MediaError object with code 4. In other words, it acts like Firefox; as if it doesn't support MP3s at all, which is incorrect.
For now I've been trying out using: 'audio/mpeg; codecs="MP3"'
instead of 'audio/mpeg'
for the mime-type check, which returns "probably" instead of "maybe" in Safari, and thus will synchronously call the callback with true
. You also need to return from the canPlayAudioMP3
function after that "probably" test passes and the callback is invoked, otherwise it continues, and the event listeners are added, etc, and the callback is invoked twice.
The encoded audio data is a wonderful little trick. Is it expected to be common that playback may fail, despite canPlayType('audio/mpeg') returning a "maybe" or "probably" response? .. If so, I may need to look carefully at something like this. :)
@scott: The reason why I came up with this technique is that Chrome was falsely reporting that it could not play MP3s via canPlayType('audio/mpeg')
, but if I actually tried playing one, it would work.
Interesting. Opera 10.52 currently returns "maybe" for (new Audio().canPlayType('audio/mp3')) also, FWIW, when it does not actually work (audio/mpeg seems to return null?) - so it may make sense to actually test support with real sounds where possible and get a more solid idea of support.
I'm consistently getting the 'error' event callback with exception MediaError, no matter what browser (tried with Firefox 3.6, recent Webkit and even Chrome 5.0.375.55...
Any ideas?
It may be the browser doesn't like base64-encoded data, I'm not quite certain. I tried a few different 1-sample MP3s at bitrates between 8 and 128 kbps, and wasn't able to get Chrome on OS X to fire play or canplaythrough events in any case. Chrome on Windows may have been better in some cases, but frankly I forget how it all ended up working out with all the different browsers.
Base64 stuff seemed to completely fail on the iPhone and iPad also, for what it's worth.
In the end, I just made a small array of canPlayType() tests, associating multiple mime strings with 'mp3', for example, and taking any "probably" responses as positive for support. Chrome/OSX still returns 'maybe' at best for the MP3 tests, but OGG and WAV are fine. I pulled the types together from wikipedia notes on MP3 and HTML 5 examples in the wild which looked to work.
eg.
'mp3': ['audio/mpeg; codecs="mp3"','audio/mpeg','audio/mp3','audio/MPA','audio/mpa-robust']
Relevant bit(s):
http://github.com/scottschiller/SoundManager2/blob/master/script/soundmanager2.js#L42
I found this URL to work in Safari (it might be that Safari requires the audio to be more than 0.00001 seconds long);
data:audio/mpeg;base64,/+MYxAAAAANIAUAAAASEEB/jwOFM/0MM/90b/+RhST//w4NFwOjf///PZu////9lns5GFDv//l9GlUIEEIAAAgIg8Ir/JGq3/+MYxDsLIj5QMYcoAP0dv9HIjUcH//yYSg+CIbkGP//8w0bLVjUP///3Z0x5QCAv/yLjwtGKTEFNRTMuOTeqqqqqqqqqqqqq/+MYxEkNmdJkUYc4AKqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq
The OGG data url is much longer, because the minimum bitrate is 32;
data:audio/ogg;base64,T2dnUwACAAAAAAAAAADqnjMlAAAAAOyyzPIBHgF2b3JiaXMAAAAAAUAfAABAHwAAQB8AAEAfAACZAU9nZ1MAAAAAAAAAAAAA6p4zJQEAAAANJGeqCj3//////////5ADdm9yYmlzLQAAAFhpcGguT3JnIGxpYlZvcmJpcyBJIDIwMTAxMTAxIChTY2hhdWZlbnVnZ2V0KQAAAAABBXZvcmJpcw9CQ1YBAAABAAxSFCElGVNKYwiVUlIpBR1jUFtHHWPUOUYhZBBTiEkZpXtPKpVYSsgRUlgpRR1TTFNJlVKWKUUdYxRTSCFT1jFloXMUS4ZJCSVsTa50FkvomWOWMUYdY85aSp1j1jFFHWNSUkmhcxg6ZiVkFDpGxehifDA6laJCKL7H3lLpLYWKW4q91xpT6y2EGEtpwQhhc+211dxKasUYY4wxxsXiUyiC0JBVAAABAABABAFCQ1YBAAoAAMJQDEVRgNCQVQBABgCAABRFcRTHcRxHkiTLAkJDVgEAQAAAAgAAKI7hKJIjSZJkWZZlWZameZaouaov+64u667t6roOhIasBACAAAAYRqF1TCqDEEPKQ4QUY9AzoxBDDEzGHGNONKQMMogzxZAyiFssLqgQBKEhKwKAKAAAwBjEGGIMOeekZFIi55iUTkoDnaPUUcoolRRLjBmlEluJMYLOUeooZZRCjKXFjFKJscRUAABAgAMAQICFUGjIigAgCgCAMAYphZRCjCnmFHOIMeUcgwwxxiBkzinoGJNOSuWck85JiRhjzjEHlXNOSuekctBJyaQTAAAQ4AAAEGAhFBqyIgCIEwAwSJKmWZomipamiaJniqrqiaKqWp5nmp5pqqpnmqpqqqrrmqrqypbnmaZnmqrqmaaqiqbquqaquq6nqrZsuqoum65q267s+rZru77uqapsm6or66bqyrrqyrbuurbtS56nqqKquq5nqq6ruq5uq65r25pqyq6purJtuq4tu7Js664s67pmqq5suqotm64s667s2rYqy7ovuq5uq7Ks+6os+75s67ru2rrwi65r66os674qy74x27bwy7ouHJMnqqqnqq7rmarrqq5r26rr2rqmmq5suq4tm6or26os67Yry7aumaosm64r26bryrIqy77vyrJui67r66Ys67oqy8Lu6roxzLat+6Lr6roqy7qvyrKuu7ru+7JuC7umqrpuyrKvm7Ks+7auC8us27oxuq7vq7It/KosC7+u+8Iy6z5jdF1fV21ZGFbZ9n3d95Vj1nVhWW1b+V1bZ7y+bgy7bvzKrQvLstq2scy6rSyvrxvDLux8W/iVmqratum6um7Ksq/Lui60dd1XRtf1fdW2fV+VZd+3hV9pG8OwjK6r+6os68Jry8ov67qw7MIvLKttK7+r68ow27qw3L6wLL/uC8uq277v6rrStXVluX2fsSu38QsAABhwAAAIMKEMFBqyIgCIEwBAEHIOKQahYgpCCKGkEEIqFWNSMuakZM5JKaWUFEpJrWJMSuaclMwxKaGUlkopqYRSWiqlxBRKaS2l1mJKqcVQSmulpNZKSa2llGJMrcUYMSYlc05K5pyUklJrJZXWMucoZQ5K6iCklEoqraTUYuacpA46Kx2E1EoqMZWUYgupxFZKaq2kFGMrMdXUWo4hpRhLSrGVlFptMdXWWqs1YkxK5pyUzDkqJaXWSiqtZc5J6iC01DkoqaTUYiopxco5SR2ElDLIqJSUWiupxBJSia20FGMpqcXUYq4pxRZDSS2WlFosqcTWYoy1tVRTJ6XFklKMJZUYW6y5ttZqDKXEVkqLsaSUW2sx1xZjjqGkFksrsZWUWmy15dhayzW1VGNKrdYWY40x5ZRrrT2n1mJNMdXaWqy51ZZbzLXnTkprpZQWS0oxttZijTHmHEppraQUWykpxtZara3FXEMpsZXSWiypxNhirLXFVmNqrcYWW62ltVprrb3GVlsurdXcYqw9tZRrrLXmWFNtBQAADDgAAASYUAYKDVkJAEQBAADGMMYYhEYpx5yT0ijlnHNSKucghJBS5hyEEFLKnINQSkuZcxBKSSmUklJqrYVSUmqttQIAAAocAAACbNCUWByg0JCVAEAqAIDBcTRNFFXVdX1fsSxRVFXXlW3jVyxNFFVVdm1b+DVRVFXXtW3bFn5NFFVVdmXZtoWiqrqybduybgvDqKqua9uybeuorqvbuq3bui9UXVmWbVu3dR3XtnXd9nVd+Bmzbeu2buu+8CMMR9/4IeTj+3RCCAAAT3AAACqwYXWEk6KxwEJDVgIAGQAAgDFKGYUYM0gxphhjTDHGmAAAgAEHAIAAE8pAoSErAoAoAADAOeecc84555xzzjnnnHPOOeecc44xxhhjjDHGGGOMMcYYY4wxxhhjjDHGGGOMMcYYY0wAwE6EA8BOhIVQaMhKACAcAABACCEpKaWUUkoRU85BSSmllFKqFIOMSkoppZRSpBR1lFJKKaWUIqWgpJJSSimllElJKaWUUkoppYw6SimllFJKKaWUUkoppZRSSimllFJKKaWUUkoppZRSSimllFJKKaWUUkoppZRSSimllFJKKaWUUkoppZRSSimllFJKKaVUSimllFJKKaWUUkoppRQAYPLgAACVYOMMK0lnhaPBhYasBAByAwAAhRiDEEJpraRUUkolVc5BKCWUlEpKKZWUUqqYgxBKKqmlklJKKbXSQSihlFBKKSWUUkooJYQQSgmhlFRCK6mEUkoHoYQSQimhhFRKKSWUzkEoIYUOQkmllNRCSB10VFIpIZVSSiklpZQ6CKGUklJLLZVSWkqpdBJSKamV1FJqqbWSUgmhpFZKSSWl0lpJJbUSSkklpZRSSymFVFJJJYSSUioltZZaSqm11lJIqZWUUkqppdRSSiWlkEpKqZSSUmollZRSaiGVlEpJKaTUSimlpFRCSamlUlpKLbWUSkmptFRSSaWUlEpJKaVSSksppRJKSqmllFpJKYWSUkoplZJSSyW1VEoKJaWUUkmptJRSSymVklIBAEAHDgAAAUZUWoidZlx5BI4oZJiAAgAAQABAgAkgMEBQMApBgDACAQAAAADAAAAfAABHARAR0ZzBAUKCwgJDg8MDAAAAAAAAAAAAAACAT2dnUwAEAAAAAAAAAADqnjMlAgAAADzQPmcBAQA=
The original smallest MP3 no longer work (DEMUXER_ERROR_COULD_NOT_OPEN: FFmpegDemuxer: open context failed
), this is a new one I came up with:
data:audio/mpeg;base64,/+MYwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/+MYwA==
It can be decoded correctly and trigger canplaythrough
(tested on Chrome 94 and ffmpeg avformat 59.4.101
)
So do you think you can make some more base64 URIs for some other formats? Namely Ogg Vorbis and AAC? Maybe even for some video formats? I'm using your current MP3 compatibly check in my http://github.com/TooTallNate/HtmlMedia project.