Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Monkeypatch canPlayType on Android 4.0+ for HLS #1084

Merged
merged 14 commits into from
Mar 26, 2014
Merged
58 changes: 49 additions & 9 deletions src/js/media/html5.js
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,55 @@ vjs.Html5.canControlVolume = function(){
return volume !== vjs.TEST_VID.volume;
};

// HTML5 Feature detection and Device Fixes --------------------------------- //
(function() {
var canPlayType,
mpegurlRE = /^application\/(?:x-|vnd\.apple\.)mpegurl$/i,
mp4RE = /^video\/mp4$/i;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we want the end-of-line character here, in case codec info is included.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done.


vjs.Html5.patchCanPlayType = function() {
// Android 4.0 and above can play HLS to some extent but it reports being unable to do so
if (vjs.ANDROID_VERSION >= 4.0) {
if (!canPlayType) {
canPlayType = vjs.TEST_VID.constructor.prototype.canPlayType;
}

vjs.TEST_VID.constructor.prototype.canPlayType = function(type) {
if (type && mpegurlRE.test(type)) {
return "maybe";
}
return canPlayType.call(this, type);
};
}

// Override Android 2.2 and less canPlayType method which is broken
if (vjs.IS_OLD_ANDROID) {
if (!canPlayType) {
canPlayType = vjs.TEST_VID.constructor.prototype.canPlayType;
}

vjs.TEST_VID.constructor.prototype.canPlayType = function(type){
if (type && mp4RE.test(type)) {
return "maybe";
}
return canPlayType.call(this, type);
};
}
};

vjs.Html5.unpatchCanPlayType = function() {
var r = canPlayType;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure what returning the canPlayType was used for but it looks like it could be removed now? There's a test that stores it in unpatchedCanPlayType, but then doesn't appear to do anything with it.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The idea is to kind of mimic jQuery's (and others') noConflict methods. That way, you could still run this yourself. If we aren't returning it, after calling unpatchCanPlayType, you have no access to the patched function unless you repatch, grab a reference and unpatch.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That seems fine then if it's following convention. I don't totally understand the use case though. You couldn't just reference the function before unpatching? var cpt = video.canPlayType; unpatchCanPlayType();? And then what would someone want to do with the unpatched function later?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, it's mostly convention so that the user could just do var cpt = unpatchCanPlayType(); instead.

if (canPlayType) {
vjs.TEST_VID.constructor.prototype.canPlayType = canPlayType;
canPlayType = null;
return r;
}
};

// by default, patch the video element
vjs.Html5.patchCanPlayType();
})();

// List of all HTML5 events (various uses).
vjs.Html5.Events = 'loadstart,suspend,abort,error,emptied,stalled,loadedmetadata,loadeddata,canplay,canplaythrough,playing,waiting,seeking,seeked,ended,durationchange,timeupdate,progress,play,pause,ratechange,volumechange'.split(',');

Expand Down Expand Up @@ -300,12 +349,3 @@ vjs.Html5.disposeMediaElement = function(el){
})();
}
};

// HTML5 Feature detection and Device Fixes --------------------------------- //

// Override Android 2.2 and less canPlayType method which is broken
if (vjs.IS_OLD_ANDROID) {
document.createElement('video').constructor.prototype.canPlayType = function(type){
return (type && type.toLowerCase().indexOf('video/mp4') != -1) ? 'maybe' : '';
};
}
51 changes: 51 additions & 0 deletions test/unit/media.html5.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
module('HTML5');

var oldAndroidVersion;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not used anymore?


test('should detect whether the volume can be changed', function(){
var testVid, ConstVolumeVideo;
if (!{}['__defineSetter__']) {
Expand Down Expand Up @@ -38,3 +40,52 @@ test('should re-link the player if the tech is moved', function(){

strictEqual(player, tech.el()['player']);
});

test('patchCanPlayType patches canplaytype with our function, conditionally', function() {
var oldAV = vjs.ANDROID_VERSION,
video = document.createElement('video'),
canPlayType = vjs.TEST_VID.constructor.prototype.canPlayType,
patchCanPlayType,
unpatchedCanPlayType;

vjs.ANDROID_VERSION = 4.0;
vjs.Html5.patchCanPlayType();

notStrictEqual(video.canPlayType, canPlayType, 'original canPlayType and patched canPlayType should not be equal');

patchCanPlayType = video.canPlayType;
unpatchedCanPlayType = vjs.Html5.unpatchCanPlayType();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

unpatchedCanPlayType isn't currently used


strictEqual(video.canPlayType, vjs.TEST_VID.constructor.prototype.canPlayType, 'original canPlayType and unpatched canPlayType should be equal');
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Aren't these two references the same the same thing always?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep. I'll see if I can figure out what I meant to refactor this into.


vjs.ANDROID_VERSION = oldAV;
});

test('should return maybe for HLS urls on Android 4.0 or above', function() {
var oldAV = vjs.ANDROID_VERSION,
video = document.createElement('video');

vjs.ANDROID_VERSION = 4.0;
vjs.Html5.patchCanPlayType();

strictEqual(video.canPlayType('application/x-mpegurl'), 'maybe', 'android version 4.0 or above should be a maybe for x-mpegurl');
strictEqual(video.canPlayType('application/x-mpegURL'), 'maybe', 'android version 4.0 or above should be a maybe for x-mpegURL');
strictEqual(video.canPlayType('application/vnd.apple.mpegurl'), 'maybe', 'android version 4.0 or above should be a maybe for vnd.apple.mpegurl');
strictEqual(video.canPlayType('application/vnd.apple.mpegURL'), 'maybe', 'android version 4.0 or above should be a maybe for vnd.apple.mpegurl');

vjs.ANDROID_VERSION = oldAV;
vjs.Html5.unpatchCanPlayType();
});

test('should return a maybe for mp4 on OLD ANDROID', function() {
var isOldAndroid = vjs.IS_OLD_ANDROID,
video = document.createElement('video');

vjs.IS_OLD_ANDROID = true;
vjs.Html5.patchCanPlayType();

strictEqual(video.canPlayType('video/mp4'), 'maybe', 'old android should return a maybe for video/mp4');

vjs.IS_OLD_ANDROID = isOldAndroid;
vjs.Html5.unpatchCanPlayType();
});