Skip to content

Commit

Permalink
feat(player): ingest a player div for videojs (#3856)
Browse files Browse the repository at this point in the history
If the videojs embed code (a video element) is wrapped in a div with the
'data-vjs-player' attribute on it, that element will be used for the
player div and a new one will not be created. In addition, on browsers
like iOS that don't support moving the media element inside the DOM, we
will not need to clone the element and we could continue to re-use the
same video element give to us in the embed code.

This could also be extended in the future to change our embed code to a
div-only approach if we so choose.
  • Loading branch information
gkatsev authored Dec 19, 2016
1 parent a7ffa34 commit 74530d8
Show file tree
Hide file tree
Showing 4 changed files with 208 additions and 5 deletions.
14 changes: 11 additions & 3 deletions src/js/player.js
Original file line number Diff line number Diff line change
Expand Up @@ -490,8 +490,15 @@ class Player extends Component {
* The DOM element that gets created.
*/
createEl() {
const el = this.el_ = super.createEl('div');
const tag = this.tag;
let el;
const playerElIngest = this.playerElIngest_ = tag.parentNode && tag.parentNode.hasAttribute('data-vjs-player');

if (playerElIngest) {
el = this.el_ = tag.parentNode;
} else {
el = this.el_ = super.createEl('div');
}

// Remove width/height attrs from tag so CSS can make it 100% width/height
tag.removeAttribute('width');
Expand All @@ -505,7 +512,7 @@ class Player extends Component {
// workaround so we don't totally break IE7
// http://stackoverflow.com/questions/3653444/css-styles-not-applied-on-dynamic-elements-in-internet-explorer-7
if (attr === 'class') {
el.className = attrs[attr];
el.className += ' ' + attrs[attr];
} else {
el.setAttribute(attr, attrs[attr]);
}
Expand Down Expand Up @@ -555,7 +562,7 @@ class Player extends Component {
tag.initNetworkState_ = tag.networkState;

// Wrap video tag in div (el/box) container
if (tag.parentNode) {
if (tag.parentNode && !playerElIngest) {
tag.parentNode.insertBefore(el, tag);
}

Expand Down Expand Up @@ -836,6 +843,7 @@ class Player extends Component {
'muted': this.options_.muted,
'poster': this.poster(),
'language': this.language(),
'playerElIngest': this.playerElIngest_ || false,
'vtt.js': this.options_['vtt.js']
}, this.options_[techName.toLowerCase()]);

Expand Down
10 changes: 8 additions & 2 deletions src/js/tech/html5.js
Original file line number Diff line number Diff line change
Expand Up @@ -185,15 +185,21 @@ class Html5 extends Tech {
// Check if this browser supports moving the element into the box.
// On the iPhone video will break if you move the element,
// So we have to create a brand new element.
if (!el || this.movingMediaElementInDOM === false) {
// If we ingested the player div, we do not need to move the media element.
if (!el ||
!(this.options_.playerElIngest ||
this.movingMediaElementInDOM)) {

// If the original tag is still there, clone and remove it.
if (el) {
const clone = el.cloneNode(true);

el.parentNode.insertBefore(clone, el);
if (el.parentNode) {
el.parentNode.insertBefore(clone, el);
}
Html5.disposeMediaElement(el);
el = clone;

} else {
el = document.createElement('video');

Expand Down
87 changes: 87 additions & 0 deletions test/unit/player.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -830,6 +830,93 @@ QUnit.test('should restore attributes from the original video tag when creating
assert.equal(el.getAttribute('webkit-playsinline'), '', 'webkit-playsinline attribute was set properly');
});

QUnit.test('if tag exists and movingMediaElementInDOM, re-use the tag', function(assert) {
// simulate attributes stored from the original tag
const tag = Dom.createEl('video');

tag.setAttribute('preload', 'auto');
tag.setAttribute('autoplay', '');
tag.setAttribute('webkit-playsinline', '');

const html5Mock = {
options_: {
tag,
playerElIngest: false
},
movingMediaElementInDOM: true
};

// set options that should override tag attributes
html5Mock.options_.preload = 'none';

// create the element
const el = Html5.prototype.createEl.call(html5Mock);

assert.equal(el.getAttribute('preload'), 'none', 'attribute was successful overridden by an option');
assert.equal(el.getAttribute('autoplay'), '', 'autoplay attribute was set properly');
assert.equal(el.getAttribute('webkit-playsinline'), '', 'webkit-playsinline attribute was set properly');

assert.equal(el, tag, 'we have re-used the tag as expected');
});

QUnit.test('if tag exists and *not* movingMediaElementInDOM, create a new tag', function(assert) {
// simulate attributes stored from the original tag
const tag = Dom.createEl('video');

tag.setAttribute('preload', 'auto');
tag.setAttribute('autoplay', '');
tag.setAttribute('webkit-playsinline', '');

const html5Mock = {
options_: {
tag,
playerElIngest: false
},
movingMediaElementInDOM: false
};

// set options that should override tag attributes
html5Mock.options_.preload = 'none';

// create the element
const el = Html5.prototype.createEl.call(html5Mock);

assert.equal(el.getAttribute('preload'), 'none', 'attribute was successful overridden by an option');
assert.equal(el.getAttribute('autoplay'), '', 'autoplay attribute was set properly');
assert.equal(el.getAttribute('webkit-playsinline'), '', 'webkit-playsinline attribute was set properly');

assert.notEqual(el, tag, 'we have not re-used the tag as expected');
});

QUnit.test('if tag exists and *not* movingMediaElementInDOM, but playerElIngest re-use tag', function(assert) {
// simulate attributes stored from the original tag
const tag = Dom.createEl('video');

tag.setAttribute('preload', 'auto');
tag.setAttribute('autoplay', '');
tag.setAttribute('webkit-playsinline', '');

const html5Mock = {
options_: {
tag,
playerElIngest: true
},
movingMediaElementInDOM: false
};

// set options that should override tag attributes
html5Mock.options_.preload = 'none';

// create the element
const el = Html5.prototype.createEl.call(html5Mock);

assert.equal(el.getAttribute('preload'), 'none', 'attribute was successful overridden by an option');
assert.equal(el.getAttribute('autoplay'), '', 'autoplay attribute was set properly');
assert.equal(el.getAttribute('webkit-playsinline'), '', 'webkit-playsinline attribute was set properly');

assert.equal(el, tag, 'we have re-used the tag as expected');
});

QUnit.test('should honor default inactivity timeout', function(assert) {
const clock = sinon.useFakeTimers();

Expand Down
102 changes: 102 additions & 0 deletions test/unit/video.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@ QUnit.test('should return a video player instance', function(assert) {
const player2 = videojs(tag2, { techOrder: ['techFaker'] });

assert.ok(player2.id() === 'test_vid_id2', 'created player from element');

player.dispose();
player2.dispose();
});

QUnit.test('should return a video player instance from el html5 tech', function(assert) {
Expand All @@ -66,6 +69,9 @@ QUnit.test('should return a video player instance from el html5 tech', function(
const player2 = videojs(tag2, { techOrder: ['techFaker'] });

assert.ok(player2.id() === 'test_vid_id2', 'created player from element');

player.dispose();
player2.dispose();
});

QUnit.test('should return a video player instance from el techfaker', function(assert) {
Expand All @@ -91,6 +97,9 @@ QUnit.test('should return a video player instance from el techfaker', function(a
const player2 = videojs(tag2, { techOrder: ['techFaker'] });

assert.ok(player2.id() === 'test_vid_id2', 'created player from element');

player.dispose();
player2.dispose();
});

QUnit.test('should add the value to the languages object', function(assert) {
Expand Down Expand Up @@ -166,3 +175,96 @@ QUnit.test('should expose DOM functions', function(assert) {
`videojs.${vjsName} is a reference to Dom.${domName}`);
});
});

QUnit.test('ingest player div if data-vjs-player attribute is present on video parentNode', function(assert) {
const fixture = document.querySelector('#qunit-fixture');

fixture.innerHTML = `
<div data-vjs-player class="foo">
<video id="test_vid_id">
<source src="http://example.com/video.mp4" type="video/mp4"></source>
</video>
</div>
`;

const playerDiv = document.querySelector('.foo');
const vid = document.querySelector('#test_vid_id');

const player = videojs(vid, {
techOrder: ['html5']
});

assert.equal(player.el(), playerDiv, 'we re-used the given div');
assert.ok(player.hasClass('foo'), 'keeps any classes that were around previously');

player.dispose();
});

QUnit.test('ingested player div should not create a new tag for movingMediaElementInDOM', function(assert) {
const Html5 = videojs.getTech('Html5');
const oldIS = Html5.isSupported;
const oldMoving = Html5.prototype.movingMediaElementInDOM;
const oldCPT = Html5.nativeSourceHandler.canPlayType;
const fixture = document.querySelector('#qunit-fixture');

fixture.innerHTML = `
<div data-vjs-player class="foo">
<video id="test_vid_id">
<source src="http://example.com/video.mp4" type="video/mp4"></source>
</video>
</div>
`;
Html5.prototype.movingMediaElementInDOM = false;
Html5.isSupported = () => true;
Html5.nativeSourceHandler.canPlayType = () => true;

const playerDiv = document.querySelector('.foo');
const vid = document.querySelector('#test_vid_id');

const player = videojs(vid, {
techOrder: ['html5']
});

assert.equal(player.el(), playerDiv, 'we re-used the given div');
assert.equal(player.tech_.el(), vid, 'we re-used the video element');
assert.ok(player.hasClass('foo'), 'keeps any classes that were around previously');

player.dispose();
Html5.prototype.movingMediaElementInDOM = oldMoving;
Html5.isSupported = oldIS;
Html5.nativeSourceHandler.canPlayType = oldCPT;
});

QUnit.test('should create a new tag for movingMediaElementInDOM', function(assert) {
const Html5 = videojs.getTech('Html5');
const oldMoving = Html5.prototype.movingMediaElementInDOM;
const oldCPT = Html5.nativeSourceHandler.canPlayType;
const fixture = document.querySelector('#qunit-fixture');
const oldIS = Html5.isSupported;

fixture.innerHTML = `
<div class="foo">
<video id="test_vid_id">
<source src="http://example.com/video.mp4" type="video/mp4"></source>
</video>
</div>
`;
Html5.prototype.movingMediaElementInDOM = false;
Html5.isSupported = () => true;
Html5.nativeSourceHandler.canPlayType = () => true;

const playerDiv = document.querySelector('.foo');
const vid = document.querySelector('#test_vid_id');

const player = videojs(vid, {
techOrder: ['html5']
});

assert.notEqual(player.el(), playerDiv, 'we used a new div');
assert.notEqual(player.tech_.el(), vid, 'we a new video element');

player.dispose();
Html5.prototype.movingMediaElementInDOM = oldMoving;
Html5.isSupported = oldIS;
Html5.nativeSourceHandler.canPlayType = oldCPT;
});

0 comments on commit 74530d8

Please sign in to comment.