mirror of
				https://github.com/videojs/video.js.git
				synced 2025-10-31 00:08:01 +02:00 
			
		
		
		
	feat: Add videojs.hookOnce method to allow single-run hooks. (#4672)
This commit is contained in:
		
				
					committed by
					
						 Gary Katsevman
						Gary Katsevman
					
				
			
			
				
	
			
			
			
						parent
						
							ee2a49c18c
						
					
				
				
					commit
					85fe685696
				
			| @@ -1,6 +1,6 @@ | ||||
| # 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 | ||||
|  | ||||
| @@ -18,129 +18,153 @@ Currently, the following hooks are available: | ||||
|  | ||||
| ### 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 dom video element that will be used for the player | ||||
| * 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 that will be created. | ||||
|  | ||||
| `beforesetup` hook functions should: | ||||
|  | ||||
| * take two arguments | ||||
|   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 | ||||
| * return options that will merge and override options that Video.js with intialized with | ||||
| * Take two arguments: | ||||
|   1. `videoEl`: DOM `<video>` element that Video.js is going to use to create a player. | ||||
|   1. `options`: The options object that Video.js was called with and will be passed to the player during creation. | ||||
| * Return an options object that will be merged with the originally provided options. | ||||
|  | ||||
| Example: adding beforesetup hook | ||||
| #### Example | ||||
|  | ||||
| ```js | ||||
| var beforeSetup = function(videoEl, options) { | ||||
|   // videoEl.id will be some-id here, since that is what Video.js | ||||
|   // was created with | ||||
| videojs.hook('beforesetup', function(videoEl, options) { | ||||
|  | ||||
|   // 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'; | ||||
|  | ||||
|   // autoplay will be true here, since we passed in as such | ||||
|   (options.autoplay) { | ||||
|   // autoplay will be true here, since we passed it as such. | ||||
|   if (options.autoplay) { | ||||
|     options.autoplay = false | ||||
|   } | ||||
|  | ||||
|   // options that are returned here will be merged with old options | ||||
|   // in this example options will now be | ||||
|   // {autoplay: false, controls: true} | ||||
|   // Options that are returned here will be merged with old options. | ||||
|   // | ||||
|   // 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; | ||||
| }; | ||||
| }); | ||||
|  | ||||
| videojs.hook('beforesetup', beforeSetup); | ||||
| // Create a new player. | ||||
| videojs('some-id', {autoplay: true, controls: true}); | ||||
| ``` | ||||
|  | ||||
| ### 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 | ||||
| * changes to the player object itself | ||||
| * Plugins or other custom functionality to initialize on the player. | ||||
| * Changes to the player object itself. | ||||
|  | ||||
| `setup` hook functions: | ||||
|  | ||||
| * Take one argument | ||||
|   * player: the player that Video.js created | ||||
| * Take one argument: | ||||
|   1. `player`: the player that Video.js created | ||||
| * Don't have to return anything | ||||
|  | ||||
| Example: adding a setup hook | ||||
| #### Example | ||||
|  | ||||
| ```js | ||||
|     var setup = function(player) { | ||||
|         // initialize the foo plugin | ||||
|         player.foo(); | ||||
|     }; | ||||
|     var foo = function() {}; | ||||
| videojs.registerPlugin('foo', function() { | ||||
|  | ||||
|     videojs.registerPlugin('foo', foo); | ||||
|     videojs.hook('setup', setup); | ||||
|     var player = videojs('some-id', {autoplay: true, controls: true}); | ||||
|   // This basic plugin will add the "some-super-class" class to a player. | ||||
|   this.addClass('some-super-class'); | ||||
| }); | ||||
|  | ||||
| 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 | ||||
|  | ||||
| ### 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 | ||||
| videojs.hook('beforesetup', function(videoEl, options) { | ||||
|   // videoEl will be the element with id=vid1 | ||||
|   // options will contain {autoplay: false} | ||||
| }); | ||||
| videojs.hook('setup', function(player) { | ||||
|   // player will be the same player that is defined below | ||||
|   // as `var player` | ||||
|   // This hook will be called twice. Once for "vid1" and once for "vid2". | ||||
|   // The options will match what is passed to videojs() for each of them. | ||||
| }); | ||||
|  | ||||
| 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 | ||||
|  | ||||
| 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 | ||||
|  | ||||
| // Get an array of all the 'beforesetup' hooks. | ||||
| var beforeSetupHooks = videojs.hooks('beforesetup'); | ||||
|  | ||||
| // Get an array of all the 'setup' hooks. | ||||
| var setupHooks = videojs.hooks('setup'); | ||||
| ``` | ||||
|  | ||||
| ### 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 | ||||
| var beforeSetup = function(videoEl, options) {}; | ||||
|  | ||||
| // add the hook | ||||
| // Add the hook. | ||||
| videojs.hook('beforesetup', beforeSetup); | ||||
|  | ||||
| // remove that same hook | ||||
| // Remove the same hook. | ||||
| 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]); | ||||
| ``` | ||||
|   | ||||
| @@ -140,8 +140,8 @@ videojs.hooks_ = {}; | ||||
|  * @param {string} type | ||||
|  *        the lifecyle to get hooks from | ||||
|  * | ||||
|  * @param {Function} [fn] | ||||
|  *        Optionally add a hook to the lifecycle that your are getting. | ||||
|  * @param {Function|Function[]} [fn] | ||||
|  *        Optionally add a hook (or hooks) to the lifecycle that your are getting. | ||||
|  * | ||||
|  * @return {Array} | ||||
|  *         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); | ||||
| }; | ||||
|  | ||||
| /** | ||||
|  * 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. | ||||
|  * | ||||
|   | ||||
| @@ -1,6 +1,7 @@ | ||||
| /* eslint-env qunit */ | ||||
| import videojs from '../../src/js/video.js'; | ||||
| import document from 'global/document'; | ||||
| import sinon from 'sinon'; | ||||
| import log from '../../src/js/utils/log.js'; | ||||
|  | ||||
| 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_.foo.length, 1, 'should have 1 foo hook'); | ||||
|  | ||||
|   videojs.hook('foo', function() {}); | ||||
|   videojs.hook('foo', function() {}); | ||||
|   videojs.hook('foo', function() {}); | ||||
|   videojs.hook('foo', [function() {}, function() {}, function() {}]); | ||||
|   assert.equal(videojs.hooks_.foo.length, 4, 'should have 4 foo 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_'); | ||||
| }); | ||||
|  | ||||
| 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) { | ||||
|   const vjsOptions = {techOrder: ['techFaker']}; | ||||
|   let setupCalled = false; | ||||
|   | ||||
		Reference in New Issue
	
	Block a user