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

feat: Add videojs.hookOnce method to allow single-run hooks. (#4672)

This commit is contained in:
Pat O'Neill 2017-10-30 17:56:21 -04:00 committed by Gary Katsevman
parent ee2a49c18c
commit 85fe685696
3 changed files with 131 additions and 68 deletions

View File

@ -1,6 +1,6 @@
# Hooks # Hooks
Hooks exist so that users can "hook" on to certain Video.js player lifecycle Hooks exist so that users can globally hook into certain Video.js lifecycle moments.
## Table of Contents ## Table of Contents
@ -18,129 +18,153 @@ Currently, the following hooks are available:
### beforesetup ### beforesetup
`beforesetup` is called just before the player is created. This allows: `beforesetup` occurs just before a player is created. This allows:
* modification of the options passed to the Video.js function (`videojs('some-id, options)`) * Modification of the options passed to the Video.js function (e.g., `videojs('some-id, options)`).
* modification of the dom video element that will be used for the player * Modification of the DOM video element that will be used for the player that will be created.
`beforesetup` hook functions should: `beforesetup` hook functions should:
* take two arguments * Take two arguments:
1. videoEl: dom video element that Video.js is going to use to create a player 1. `videoEl`: DOM `<video>` element that Video.js is going to use to create a player.
1. options: options that Video.js was intialized with and will later pass to the player during creation 1. `options`: The options object that Video.js was called with and will be passed to the player during creation.
* return options that will merge and override options that Video.js with intialized with * Return an options object that will be merged with the originally provided options.
Example: adding beforesetup hook #### Example
```js ```js
var beforeSetup = function(videoEl, options) { videojs.hook('beforesetup', function(videoEl, options) {
// videoEl.id will be some-id here, since that is what Video.js
// was created with // videoEl will be the video element with id="some-id" since that
// gets passed to videojs() below. On subsequent calls, it will be
// different.
videoEl.className += ' some-super-class'; videoEl.className += ' some-super-class';
// autoplay will be true here, since we passed in as such // autoplay will be true here, since we passed it as such.
(options.autoplay) { if (options.autoplay) {
options.autoplay = false options.autoplay = false
} }
// options that are returned here will be merged with old options // Options that are returned here will be merged with old options.
// in this example options will now be //
// {autoplay: false, controls: true} // In this example options will now be:
// {autoplay: false, controls: true}
//
// This has the practical effect of always disabling autoplay no matter
// what options are passed to videojs().
return options; return options;
}; });
videojs.hook('beforesetup', beforeSetup); // Create a new player.
videojs('some-id', {autoplay: true, controls: true}); videojs('some-id', {autoplay: true, controls: true});
``` ```
### setup ### setup
`setup` is called just after the player is created. This allows: `setup` occurs just after a player is created. This allows:
* plugin or custom functionality to intialize on the player * Plugins or other custom functionality to initialize on the player.
* changes to the player object itself * Changes to the player object itself.
`setup` hook functions: `setup` hook functions:
* Take one argument * Take one argument:
* player: the player that Video.js created 1. `player`: the player that Video.js created
* Don't have to return anything * Don't have to return anything
Example: adding a setup hook #### Example
```js ```js
var setup = function(player) { videojs.registerPlugin('foo', function() {
// initialize the foo plugin
player.foo();
};
var foo = function() {};
videojs.registerPlugin('foo', foo); // This basic plugin will add the "some-super-class" class to a player.
videojs.hook('setup', setup); this.addClass('some-super-class');
var player = videojs('some-id', {autoplay: true, controls: true}); });
videojs.hook('setup', function(player) {
// Initialize the foo plugin after any player is created.
player.foo();
});
// Create a new player.
videojs('some-id', {autoplay: true, controls: true});
``` ```
## Usage ## Usage
### Adding ### Adding
In order to use hooks you must first include Video.js in the page or script that you are using. Then you add hooks using `videojs.hook(<name>, function)` before running the `videojs()` function. Hooks can be added using `videojs.hook(<name>, function)` before running the `videojs()` function.
Example: adding hooks #### Example
```js ```js
videojs.hook('beforesetup', function(videoEl, options) { videojs.hook('beforesetup', function(videoEl, options) {
// videoEl will be the element with id=vid1 // This hook will be called twice. Once for "vid1" and once for "vid2".
// options will contain {autoplay: false} // The options will match what is passed to videojs() for each of them.
});
videojs.hook('setup', function(player) {
// player will be the same player that is defined below
// as `var player`
}); });
var player = videojs('vid1', {autoplay: false}); videojs.hook('setup', function(player) {
// This hook will be called twice. Once for "vid1" and once for "vid2".
// The player value will be the player that is created for each element.
});
videojs('vid1', {autoplay: false});
videojs('vid2', {autoplay: true});
``` ```
After adding your hooks they will automatically be run at the correct time in the Video.js lifecycle. After adding your hooks, they will automatically be run at the correct time in the Video.js lifecycle.
### Adding Once
In some cases, you may only want your hook to run once. In these cases, use `videojs.hookOnce(<name>, function)` before running the `videojs()` function.
#### Example
```js
videojs.hookOnce('beforesetup', function(videoEl, options) {
// This hook will be called once for "vid1", but not for "vid2".
// The options will match what is passed to videojs().
});
videojs.hookOnce('setup', function(player) {
// This hook will be called once for "vid1", but not for "vid2".
// The player value will be the player that is created for each element.
});
videojs('vid1', {autoplay: false});
videojs('vid2', {autoplay: true});
```
### Getting ### Getting
To access the array of hooks that currently exists and will be run on the Video.js object you can use the `videojs.hooks` function. To access the array of functions that currently exists for any hook, use the `videojs.hooks` function.
Example: getting all hooks attached to Video.js #### Example
```js ```js
// Get an array of all the 'beforesetup' hooks.
var beforeSetupHooks = videojs.hooks('beforesetup'); var beforeSetupHooks = videojs.hooks('beforesetup');
// Get an array of all the 'setup' hooks.
var setupHooks = videojs.hooks('setup'); var setupHooks = videojs.hooks('setup');
``` ```
### Removing ### Removing
To stop hooks from being executed during the Video.js lifecycle you will remove them using `videojs.removeHook`. To stop hooks from being executed during any future Video.js lifecycles you can remove them using `videojs.removeHook`.
Example: remove a hook that was defined by you #### Example
```js ```js
var beforeSetup = function(videoEl, options) {}; var beforeSetup = function(videoEl, options) {};
// add the hook // Add the hook.
videojs.hook('beforesetup', beforeSetup); videojs.hook('beforesetup', beforeSetup);
// remove that same hook // Remove the same hook.
videojs.removeHook('beforesetup', beforeSetup); videojs.removeHook('beforesetup', beforeSetup);
``` ```
You can also use `videojs.hooks` in conjunction with `videojs.removeHook` but it may have unexpected results if used during an asynchronous callbacks as other plugins/functionality may have added hooks.
Example: using `videojs.hooks` and `videojs.removeHook` to remove a hook
```js
// add the hook
videojs.hook('setup', function(videoEl, options) {});
var setupHooks = videojs.hooks('setup');
// remove the hook you just added
videojs.removeHook('setup', setupHooks[setupHooks.length - 1]);
```

View File

@ -140,8 +140,8 @@ videojs.hooks_ = {};
* @param {string} type * @param {string} type
* the lifecyle to get hooks from * the lifecyle to get hooks from
* *
* @param {Function} [fn] * @param {Function|Function[]} [fn]
* Optionally add a hook to the lifecycle that your are getting. * Optionally add a hook (or hooks) to the lifecycle that your are getting.
* *
* @return {Array} * @return {Array}
* an array of hooks, or an empty array if there are none. * an array of hooks, or an empty array if there are none.
@ -167,6 +167,26 @@ videojs.hook = function(type, fn) {
videojs.hooks(type, fn); videojs.hooks(type, fn);
}; };
/**
* Add a function hook that will only run once to a specific videojs lifecycle.
*
* @param {string} type
* the lifecycle to hook the function to.
*
* @param {Function|Function[]}
* The function or array of functions to attach.
*/
videojs.hookOnce = function(type, fn) {
videojs.hooks(type, [].concat(fn).map(original => {
const wrapper = (...args) => {
videojs.removeHook(type, wrapper);
original(...args);
};
return wrapper;
}));
};
/** /**
* Remove a hook from a specific videojs lifecycle. * Remove a hook from a specific videojs lifecycle.
* *

View File

@ -1,6 +1,7 @@
/* eslint-env qunit */ /* eslint-env qunit */
import videojs from '../../src/js/video.js'; import videojs from '../../src/js/video.js';
import document from 'global/document'; import document from 'global/document';
import sinon from 'sinon';
import log from '../../src/js/utils/log.js'; import log from '../../src/js/utils/log.js';
QUnit.module('video.js:hooks ', { QUnit.module('video.js:hooks ', {
@ -23,9 +24,7 @@ QUnit.test('should be able to add a hook', function(assert) {
assert.equal(videojs.hooks_.bar.length, 2, 'should have 2 bar hooks'); assert.equal(videojs.hooks_.bar.length, 2, 'should have 2 bar hooks');
assert.equal(videojs.hooks_.foo.length, 1, 'should have 1 foo hook'); assert.equal(videojs.hooks_.foo.length, 1, 'should have 1 foo hook');
videojs.hook('foo', function() {}); videojs.hook('foo', [function() {}, function() {}, function() {}]);
videojs.hook('foo', function() {});
videojs.hook('foo', function() {});
assert.equal(videojs.hooks_.foo.length, 4, 'should have 4 foo hooks'); assert.equal(videojs.hooks_.foo.length, 4, 'should have 4 foo hooks');
assert.equal(videojs.hooks_.bar.length, 2, 'should have 2 bar hooks'); assert.equal(videojs.hooks_.bar.length, 2, 'should have 2 bar hooks');
}); });
@ -104,6 +103,26 @@ QUnit.test('should be get all hooks for a type and add at the same time', functi
assert.deepEqual(videojs.hooks_.bar, barHooks, 'should return the exact bar list from videojs.hooks_'); assert.deepEqual(videojs.hooks_.bar, barHooks, 'should return the exact bar list from videojs.hooks_');
}); });
QUnit.test('should be able to add a hook that runs once', function(assert) {
const spies = [
sinon.spy(),
sinon.spy(),
sinon.spy()
];
videojs.hookOnce('foo', spies);
assert.equal(videojs.hooks_.foo.length, 3, 'should have 3 foo hooks');
videojs.hooks('foo').forEach(fn => fn());
spies.forEach((spy, i) => {
assert.ok(spy.calledOnce, `spy #${i + 1} was called`);
});
assert.equal(videojs.hooks_.foo.length, 0, 'should have 0 foo hooks');
});
QUnit.test('should trigger beforesetup and setup during videojs setup', function(assert) { QUnit.test('should trigger beforesetup and setup during videojs setup', function(assert) {
const vjsOptions = {techOrder: ['techFaker']}; const vjsOptions = {techOrder: ['techFaker']};
let setupCalled = false; let setupCalled = false;