1
0
mirror of https://github.com/videojs/video.js.git synced 2024-12-25 02:42:10 +02:00

feat: allow embeds via <video-js> element (#4640)

Add the ability to initialize Video.js with an element named video-js. This is because sometimes, seeing the native element is undesirable, plus, it's cool to have our own element.
Can be used just like the video embed.
IE9 is supported but only with dynamic sources as the source element can only be used inside of the video element.
This commit is contained in:
Gary Katsevman 2017-11-13 14:20:05 -05:00 committed by GitHub
parent 46a7df2757
commit d8aadd5bee
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 450 additions and 7 deletions

87
docs/guides/embeds.md Normal file
View File

@ -0,0 +1,87 @@
# How to Embed the Video.js player
Video.js is meant to be an enhacement to the video element in HTML5 so for years, its embed code has been just a `<video>` element.
Video.js then wraps the video element in a div that is used for us to place controls and anything else that's required for the player.
For a long time this was enough. In 2016, "div ingest" was added, it allows the developer to give Video.js a player div to use instead of making it's own.
This is partly to help with content reflow but also to help with iOS where you sometimes need to prime the video element and we re-create the video element when we create the player div.
However, this is kind of weird to have a `<video>` element embed with a `<div>` wrapped around it. So, we built out a new embed, a `<video-js>` embed.
Below, the three kinds of embeds are detailed.
## Embeds
### `<video>` embed
The classic Video.js embed. You can then initialize it via `data-setup` or via the `videojs` method.
```html
<!-- via data-setup -->
<video id="vid1" class="video-js" data-setup='{}'>
<source src="//vjs.zencdn.net/v/oceans.mp4">
</video>
<!-- via code -->
<video id="vid1" class="video-js">
<source src="//vjs.zencdn.net/v/oceans.mp4">
</video>
```
```js
const player = videojs('vid1', {});
```
### Player div ingest
The enhanced classic embed. You can also initialize it via `data-setup` or via `videojs` method.
```html
<!-- via data-setup -->
<div data-vjs-player>
<video id="vid1" class="video-js" data-setup='{}'>
<source src="//vjs.zencdn.net/v/oceans.mp4">
</video>
</div>
<!-- via code -->
<div data-vjs-player>
<video id="vid1" class="video-js">
<source src="//vjs.zencdn.net/v/oceans.mp4">
</video>
</div>
```
```js
const player = videojs('vid1', {});
```
As you can see, it isn't much different from the classic `<video>` embed. It also does make it easier to work with [React](/docs/guides/react.md).
### `<video-js>` embed
This is the [I Can't Believe It's Not Custom Elements](https://developers.google.com/web/fundamentals/web-components/customelements) embed code.
It looks very much like the `<video>` embed but instead of `video` it's a `video-js` embed.
This is useful for all the things that the player div ingest is useful for and it matches our library name!
```html
<!-- via data-setup -->
<video-js id="vid1" data-setup='{}'>
<source src="//vjs.zencdn.net/v/oceans.mp4">
</video-js>
<!-- via code -->
<video-js id="vid1">
<source src="//vjs.zencdn.net/v/oceans.mp4">
</video-js>
```
```js
const player = videojs('vid1', {});
```
Adding `class="video-js"` with this embed is no longer necessary as it will automatically add the class `video-js` if missing.
#### Custom Elements
Native Custom Elements support is relativly small according to [Can I Use](http://caniuse.com/#feat=custom-elementsv1) and because we didn't want to include a polyfill we're going with just an element called `video-js` rather than a full blown custom element.
#### Browser support
These all work in all browsers that Video.js supports, though, there are some caveats for some older browsers.
- IE8 requires running `document.createElement('video-js')` before using the `video-js` embed code.
- IE9 doesn't support having `source` elements outside of the `video` element, thus, the `video-js` embed will not work there. Though, if the source is set later, it should still work.
## data-setup
This is an ease-of-use method for having Video.js set up the player automatically. It is an HTML attribute and it takes a JSON string representation of the [player options](/docs/guides/options.md) as the value.
Using the programmatic approach is probably preferable.

View File

@ -37,6 +37,8 @@ Video.js supports all attributes of the `<video>` element (such as `controls`, `
</video>
```
For a high-level overview of all the various embed options, check out the [embeds page](/docs/guides/embeds.md), then follow the rest of this page.
### Automatic Setup
By default, when your web page finishes loading, Video.js will scan for media elements that have the `data-setup` attribute. The `data-setup` attribute is used to pass options to Video.js. A minimal example looks like this:

165
sandbox/embeds.html.example Normal file
View File

@ -0,0 +1,165 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Video.js Sandbox</title>
<script src='../node_modules/es5-shim/es5-shim.js'></script>
<script src='../node_modules/es6-shim/es6-shim.js'></script>
<link href="../build/temp/video-js.css" rel="stylesheet" type="text/css">
<script src="../dist/video.js"></script>
<script src='../node_modules/videojs-flash/dist/videojs-flash.js'></script>
<script src="https://unpkg.com/videojs-contrib-hls@latest/dist/videojs-contrib-hls.js"></script>
<script src="../build/temp/lang/es.js"></script>
<!-- Set the location of the flash SWF -->
<script>
// videojs.options.flash.swf = 'http://vjs.zencdn.net/swf/5.3/video-js.swf';
videojs.options.flash.swf = 'http://localhost:8000/video-js-swf/dist/video-js.swf';
</script>
<style>
.source-el { background: #FF6961; }
.source-js { background: #77DD77; }
.options-src { background: #AEC6CF; }
.source-el.data-setup { background: red; }
.source-js.data-setup { background: green; }
.options-src.data-setup { background: blue; }
.video-js {
height: 150px;
width: 300px;
}
.wrapper {
display: grid;
margin: 0 auto;
grid-gap: 10px;
grid-template-columns: 300px 300px 300px;
}
.panel > p:first-child {
border-bottom: black 1px solid;
}
</style>
</head>
<body>
<p>All the various ways to embed and source video elements.</p>
<p>Pastel color background represent programmatic setup.</p>
<p>Vibrant color background represent data-setup.</p>
<div class="wrapper">
<div class="panel source-el">
<p>js setup with source element</p>
<p>video-js embed", source element</p>
<video-js id="vid01" class="video-js" controls poster="//vjs.zencdn.net/v/oceans.png">
<source src="./oceans.mp4" type='video/mp4'>
</video-js>
<p>Video embed, source element</p>
<video id="vid11" class="video-js" controls poster="//vjs.zencdn.net/v/oceans.png">
<source src="./oceans.mp4" type='video/mp4'>
</video>
<p>injested div el, source element</p>
<div data-vjs-player>
<video id="vid21" class="video-js" controls poster="//vjs.zencdn.net/v/oceans.png">
<source src="./oceans.mp4" type='video/mp4'>
</video>
</div>
</div>
<div class="panel options-src">
<p>js setup with sources options</p>
<p>video-js embed", js source</p>
<video-js id="vid05" class="video-js" controls poster="//vjs.zencdn.net/v/oceans.png">
</video-js>
<p>Video embed, js source</p>
<video id="vid15" class="video-js" controls poster="//vjs.zencdn.net/v/oceans.png">
</video>
<p>injested div el, js source</p>
<div data-vjs-player>
<video id="vid25" class="video-js" controls poster="//vjs.zencdn.net/v/oceans.png">
</video>
</div>
</div>
<div class="panel source-js">
<p>js setup with js method sources</p>
<p>video-js embed", js source</p>
<video-js id="vid02" class="video-js" controls>
</video-js>
<p>Video embed, js source</p>
<video id="vid12" class="video-js" controls>
</video>
<p>injested div el, js source</p>
<div data-vjs-player>
<video id="vid22" class="video-js" controls>
</video>
</div>
</div>
<div class="panel source-el data-setup">
<p>data-setup with sourrce elements</p>
<p>video-js embed", source element</p>
<video-js id="vid03" class="video-js" controls poster="//vjs.zencdn.net/v/oceans.png" data-setup="{}">
<source src="./oceans.mp4" type='video/mp4'>
</video-js>
<p>Video embed, source element</p>
<video id="vid13" class="video-js" controls poster="//vjs.zencdn.net/v/oceans.png" data-setup="{}">
<source src="./oceans.mp4" type='video/mp4'>
</video>
<p>injested div el, source element</p>
<div data-vjs-player>
<video id="vid23" class="video-js" controls poster="//vjs.zencdn.net/v/oceans.png" data-setup="{}">
<source src="./oceans.mp4" type='video/mp4'>
</video>
</div>
</div>
<div class="panel options-src data-setup">
<p>data-setup embeds with sources options</p>
<p>video-js embed", source element</p>
<video-js id="vid04" class="video-js" controls poster="//vjs.zencdn.net/v/oceans.png" data-setup='{"sources": [{"src":"./oceans.mp4", "type":"video/mp4"}]}'>
</video-js>
<p>Video embed, source element</p>
<video id="vid14" class="video-js" controls poster="//vjs.zencdn.net/v/oceans.png" data-setup='{"sources": [{"src":"./oceans.mp4", "type":"video/mp4"}]}'>
</video>
<p>injested div el, source element</p>
<div data-vjs-player>
<video id="vid24" class="video-js" controls poster="//vjs.zencdn.net/v/oceans.png" data-setup='{"sources": [{"src":"./oceans.mp4", "type":"video/mp4"}]}'>
</video>
</div>
</div>
<div class="panel source-js data-setup">
<p>js setup with js method sources</p>
<p>video-js embed", js source</p>
<video-js id="vid06" class="video-js" controls poster="//vjs.zencdn.net/v/oceans.png" data-setup="{}">
</video-js>
<p>Video embed, js source</p>
<video id="vid16" class="video-js" controls poster="//vjs.zencdn.net/v/oceans.png" data-setup="{}">
</video>
<p>injested div el, js source</p>
<div data-vjs-player>
<video id="vid26" class="video-js" controls poster="//vjs.zencdn.net/v/oceans.png" data-setup="{}">
</video>
</div>
</div>
</div>
<script>
var player01 = videojs('vid01');
var player11 = videojs('vid11');
var player21 = videojs('vid21');
var player01 = videojs('vid02');
var player11 = videojs('vid12');
var player21 = videojs('vid22');
var player05 = videojs('vid05', {sources: [{src:'./oceans.mp4',type:'video/mp4'}]});
var player15 = videojs('vid15', {sources: [{src:'./oceans.mp4',type:'video/mp4'}]});
var player25 = videojs('vid25', {sources: [{src:'./oceans.mp4',type:'video/mp4'}]});
player01.src({src:'./oceans.mp4', type:'video/mp4'});
player11.src({src:'./oceans.mp4', type:'video/mp4'});
player21.src({src:'./oceans.mp4', type:'video/mp4'});
player01.poster('//vjs.zencdn.net/v/oceans.png');
player11.poster('//vjs.zencdn.net/v/oceans.png');
player21.poster('//vjs.zencdn.net/v/oceans.png');
setTimeout(function() {
videojs.players.vid06 && videojs.players.vid06.src({src:'./oceans.mp4', type:'video/mp4'});
videojs.players.vid16 && videojs.players.vid16.src({src:'./oceans.mp4', type:'video/mp4'});
videojs.players.vid26 && videojs.players.vid26.src({src:'./oceans.mp4', type:'video/mp4'});
});
</script>
</body>
</html>

View File

@ -366,6 +366,7 @@ class Player extends Component {
// now remove immediately so native controls don't flash.
// May be turned back on by HTML5 tech if nativeControlsForTouch is true
tag.controls = false;
tag.removeAttribute('controls');
/*
* Store the internal state of scrubbing
@ -511,16 +512,37 @@ class Player extends Component {
* The DOM element that gets created.
*/
createEl() {
const tag = this.tag;
let tag = this.tag;
let el;
const playerElIngest = this.playerElIngest_ = tag.parentNode && tag.parentNode.hasAttribute && tag.parentNode.hasAttribute('data-vjs-player');
let playerElIngest = this.playerElIngest_ = tag.parentNode && tag.parentNode.hasAttribute && tag.parentNode.hasAttribute('data-vjs-player');
const divEmbed = this.tag.tagName.toLowerCase() === 'video-js';
if (playerElIngest) {
el = this.el_ = tag.parentNode;
} else {
} else if (!divEmbed) {
el = this.el_ = super.createEl('div');
}
// Copy over all the attributes from the tag, including ID and class
// ID will now reference player box, not the video tag
const attrs = Dom.getAttributes(tag);
if (divEmbed) {
el = this.el_ = tag;
tag = this.tag = document.createElement('video');
while (el.children.length) {
tag.appendChild(el.firstChild);
}
if (!Dom.hasClass(el, 'video-js')) {
Dom.addClass(el, 'video-js');
}
el.appendChild(tag);
playerElIngest = this.playerElIngest_ = el;
}
// set tabindex to -1 so we could focus on the player element
tag.setAttribute('tabindex', '-1');
@ -528,17 +550,21 @@ class Player extends Component {
tag.removeAttribute('width');
tag.removeAttribute('height');
// Copy over all the attributes from the tag, including ID and class
// ID will now reference player box, not the video tag
const attrs = Dom.getAttributes(tag);
Object.getOwnPropertyNames(attrs).forEach(function(attr) {
// 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];
if (divEmbed) {
tag.className += ' ' + attrs[attr];
}
} else {
el.setAttribute(attr, attrs[attr]);
if (divEmbed) {
tag.setAttribute(attr, attrs[attr]);
}
}
});

View File

@ -31,6 +31,7 @@ const autoSetup = function() {
// through each list of elements to build up a new, combined list of elements.
const vids = document.getElementsByTagName('video');
const audios = document.getElementsByTagName('audio');
const divs = document.getElementsByTagName('video-js');
const mediaEls = [];
if (vids && vids.length > 0) {
@ -45,6 +46,12 @@ const autoSetup = function() {
}
}
if (divs && divs.length > 0) {
for (let i = 0, e = divs.length; i < e; i++) {
mediaEls.push(divs[i]);
}
}
// Check if any media elements exist
if (mediaEls && mediaEls.length > 0) {

View File

@ -38,6 +38,7 @@ if (typeof HTMLVideoElement === 'undefined' && Dom.isReal()) {
document.createElement('video');
document.createElement('audio');
document.createElement('track');
document.createElement('video-js');
}
/**

View File

@ -1,6 +1,7 @@
/* eslint-env qunit */
import videojs from '../../src/js/video.js';
import * as Dom from '../../src/js/utils/dom.js';
import * as browser from '../../src/js/utils/browser.js';
import log from '../../src/js/utils/log.js';
import document from 'global/document';
import sinon from 'sinon';
@ -363,3 +364,157 @@ QUnit.test('should create a new tag for movingMediaElementInDOM', function(asser
Html5.isSupported = oldIS;
Html5.nativeSourceHandler.canPlayType = oldCPT;
});
/* **************************************************** *
* div embed tests copied from video emebed tests above *
* **************************************************** */
if (!browser.IS_IE8) {
QUnit.module('video.js video-js embed', {
beforeEach() {
this.clock = sinon.useFakeTimers();
},
afterEach() {
this.clock.restore();
}
});
QUnit.test('should return a video player instance', function(assert) {
const fixture = document.getElementById('qunit-fixture');
fixture.innerHTML += '<video-js id="test_vid_id"></video-js>' +
'<video-js id="test_vid_id2"></video-js>';
const player = videojs('test_vid_id', { techOrder: ['techFaker'] });
assert.ok(player, 'created player from tag');
assert.ok(player.id() === 'test_vid_id');
assert.ok(videojs.getPlayers().test_vid_id === player,
'added player to global reference');
const playerAgain = videojs('test_vid_id');
assert.ok(player === playerAgain, 'did not create a second player from same tag');
assert.equal(player, playerAgain, 'we did not make a new player');
const tag2 = document.getElementById('test_vid_id2');
const player2 = videojs(tag2, { techOrder: ['techFaker'] });
assert.ok(player2.id() === 'test_vid_id2', 'created player from element');
player.dispose();
player2.dispose();
});
QUnit.test('should log about already initalized players if options already passed',
function(assert) {
const origWarnLog = log.warn;
const fixture = document.getElementById('qunit-fixture');
const warnLogs = [];
log.warn = (args) => {
warnLogs.push(args);
};
fixture.innerHTML += '<video-js id="test_vid_id"></video-js>';
const player = videojs('test_vid_id', { techOrder: ['techFaker'] });
assert.ok(player, 'created player from tag');
assert.equal(player.id(), 'test_vid_id', 'player has the right ID');
assert.equal(warnLogs.length, 0, 'no warn logs');
const playerAgain = videojs('test_vid_id');
assert.equal(player, playerAgain, 'did not create a second player from same tag');
assert.equal(warnLogs.length, 0, 'no warn logs');
const playerAgainWithOptions = videojs('test_vid_id', { techOrder: ['techFaker'] });
assert.equal(player,
playerAgainWithOptions,
'did not create a second player from same tag');
assert.equal(warnLogs.length, 1, 'logged a warning');
assert.equal(warnLogs[0],
'Player "test_vid_id" is already initialised. Options will not be applied.',
'logged the right message');
log.warn = origWarnLog;
player.dispose();
});
QUnit.test('should return a video player instance from el html5 tech', function(assert) {
const fixture = document.getElementById('qunit-fixture');
fixture.innerHTML += '<video-js id="test_vid_id"></video-js>' +
'<video-js id="test_vid_id2"></video-js>';
const vid = document.querySelector('#test_vid_id');
const player = videojs(vid);
assert.ok(player, 'created player from tag');
assert.ok(player.id() === 'test_vid_id');
assert.ok(videojs.getPlayers().test_vid_id === player,
'added player to global reference');
const playerAgain = videojs(vid);
assert.ok(player === playerAgain, 'did not create a second player from same tag');
assert.equal(player, playerAgain, 'we did not make a new player');
const tag2 = document.getElementById('test_vid_id2');
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) {
const fixture = document.getElementById('qunit-fixture');
fixture.innerHTML += '<video-js id="test_vid_id"></video-js>' +
'<video-js id="test_vid_id2"></video-js>';
const vid = document.querySelector('#test_vid_id');
const player = videojs(vid, {techOrder: ['techFaker']});
assert.ok(player, 'created player from tag');
assert.ok(player.id() === 'test_vid_id');
assert.ok(videojs.getPlayers().test_vid_id === player,
'added player to global reference');
const playerAgain = videojs(vid);
assert.ok(player === playerAgain, 'did not create a second player from same tag');
assert.equal(player, playerAgain, 'we did not make a new player');
const tag2 = document.getElementById('test_vid_id2');
const player2 = videojs(tag2, { techOrder: ['techFaker'] });
assert.ok(player2.id() === 'test_vid_id2', 'created player from element');
player.dispose();
player2.dispose();
});
QUnit.test('adds video-js class name with the video-js embed', function(assert) {
const fixture = document.getElementById('qunit-fixture');
fixture.innerHTML += '<video-js id="test_vid_id"></video-js>' +
'<video-js class="video-js" id="test_vid_id2"></video-js>';
const vid = document.querySelector('#test_vid_id');
const player = videojs(vid, {techOrder: ['techFaker']});
const tag2 = document.getElementById('test_vid_id2');
const player2 = videojs(tag2, { techOrder: ['techFaker'] });
assert.ok(player.hasClass('video-js'), 'video-js class was added to the first embed');
assert.ok(player2.hasClass('video-js'), 'video-js class was preserved to the second embed');
player.dispose();
player2.dispose();
});
}