- 1 :
/**
- 2 :
* @file plugin.js
- 3 :
*/
- 4 :
import evented from './mixins/evented';
- 5 :
import stateful from './mixins/stateful';
- 6 :
import * as Events from './utils/events';
- 7 :
import log from './utils/log';
- 8 :
import Player from './player';
- 9 :
- 10 :
/**
- 11 :
* The base plugin name.
- 12 :
*
- 13 :
* @private
- 14 :
* @constant
- 15 :
* @type {string}
- 16 :
*/
- 17 :
const BASE_PLUGIN_NAME = 'plugin';
- 18 :
- 19 :
/**
- 20 :
* The key on which a player's active plugins cache is stored.
- 21 :
*
- 22 :
* @private
- 23 :
* @constant
- 24 :
* @type {string}
- 25 :
*/
- 26 :
const PLUGIN_CACHE_KEY = 'activePlugins_';
- 27 :
- 28 :
/**
- 29 :
* Stores registered plugins in a private space.
- 30 :
*
- 31 :
* @private
- 32 :
* @type {Object}
- 33 :
*/
- 34 :
const pluginStorage = {};
- 35 :
- 36 :
/**
- 37 :
* Reports whether or not a plugin has been registered.
- 38 :
*
- 39 :
* @private
- 40 :
* @param {string} name
- 41 :
* The name of a plugin.
- 42 :
*
- 43 :
* @return {boolean}
- 44 :
* Whether or not the plugin has been registered.
- 45 :
*/
- 46 :
const pluginExists = (name) => pluginStorage.hasOwnProperty(name);
- 47 :
- 48 :
/**
- 49 :
* Get a single registered plugin by name.
- 50 :
*
- 51 :
* @private
- 52 :
* @param {string} name
- 53 :
* The name of a plugin.
- 54 :
*
- 55 :
* @return {Function|undefined}
- 56 :
* The plugin (or undefined).
- 57 :
*/
- 58 :
const getPlugin = (name) => pluginExists(name) ? pluginStorage[name] : undefined;
- 59 :
- 60 :
/**
- 61 :
* Marks a plugin as "active" on a player.
- 62 :
*
- 63 :
* Also, ensures that the player has an object for tracking active plugins.
- 64 :
*
- 65 :
* @private
- 66 :
* @param {Player} player
- 67 :
* A Video.js player instance.
- 68 :
*
- 69 :
* @param {string} name
- 70 :
* The name of a plugin.
- 71 :
*/
- 72 :
const markPluginAsActive = (player, name) => {
- 73 :
player[PLUGIN_CACHE_KEY] = player[PLUGIN_CACHE_KEY] || {};
- 74 :
player[PLUGIN_CACHE_KEY][name] = true;
- 75 :
};
- 76 :
- 77 :
/**
- 78 :
* Triggers a pair of plugin setup events.
- 79 :
*
- 80 :
* @private
- 81 :
* @param {Player} player
- 82 :
* A Video.js player instance.
- 83 :
*
- 84 :
* @param {Plugin~PluginEventHash} hash
- 85 :
* A plugin event hash.
- 86 :
*
- 87 :
* @param {boolean} [before]
- 88 :
* If true, prefixes the event name with "before". In other words,
- 89 :
* use this to trigger "beforepluginsetup" instead of "pluginsetup".
- 90 :
*/
- 91 :
const triggerSetupEvent = (player, hash, before) => {
- 92 :
const eventName = (before ? 'before' : '') + 'pluginsetup';
- 93 :
- 94 :
player.trigger(eventName, hash);
- 95 :
player.trigger(eventName + ':' + hash.name, hash);
- 96 :
};
- 97 :
- 98 :
/**
- 99 :
* Takes a basic plugin function and returns a wrapper function which marks
- 100 :
* on the player that the plugin has been activated.
- 101 :
*
- 102 :
* @private
- 103 :
* @param {string} name
- 104 :
* The name of the plugin.
- 105 :
*
- 106 :
* @param {Function} plugin
- 107 :
* The basic plugin.
- 108 :
*
- 109 :
* @return {Function}
- 110 :
* A wrapper function for the given plugin.
- 111 :
*/
- 112 :
const createBasicPlugin = function(name, plugin) {
- 113 :
const basicPluginWrapper = function() {
- 114 :
- 115 :
// We trigger the "beforepluginsetup" and "pluginsetup" events on the player
- 116 :
// regardless, but we want the hash to be consistent with the hash provided
- 117 :
// for advanced plugins.
- 118 :
//
- 119 :
// The only potentially counter-intuitive thing here is the `instance` in
- 120 :
// the "pluginsetup" event is the value returned by the `plugin` function.
- 121 :
triggerSetupEvent(this, {name, plugin, instance: null}, true);
- 122 :
- 123 :
const instance = plugin.apply(this, arguments);
- 124 :
- 125 :
markPluginAsActive(this, name);
- 126 :
triggerSetupEvent(this, {name, plugin, instance});
- 127 :
- 128 :
return instance;
- 129 :
};
- 130 :
- 131 :
Object.keys(plugin).forEach(function(prop) {
- 132 :
basicPluginWrapper[prop] = plugin[prop];
- 133 :
});
- 134 :
- 135 :
return basicPluginWrapper;
- 136 :
};
- 137 :
- 138 :
/**
- 139 :
* Takes a plugin sub-class and returns a factory function for generating
- 140 :
* instances of it.
- 141 :
*
- 142 :
* This factory function will replace itself with an instance of the requested
- 143 :
* sub-class of Plugin.
- 144 :
*
- 145 :
* @private
- 146 :
* @param {string} name
- 147 :
* The name of the plugin.
- 148 :
*
- 149 :
* @param {Plugin} PluginSubClass
- 150 :
* The advanced plugin.
- 151 :
*
- 152 :
* @return {Function}
- 153 :
*/
- 154 :
const createPluginFactory = (name, PluginSubClass) => {
- 155 :
- 156 :
// Add a `name` property to the plugin prototype so that each plugin can
- 157 :
// refer to itself by name.
- 158 :
PluginSubClass.prototype.name = name;
- 159 :
- 160 :
return function(...args) {
- 161 :
triggerSetupEvent(this, {name, plugin: PluginSubClass, instance: null}, true);
- 162 :
- 163 :
const instance = new PluginSubClass(...[this, ...args]);
- 164 :
- 165 :
// The plugin is replaced by a function that returns the current instance.
- 166 :
this[name] = () => instance;
- 167 :
- 168 :
triggerSetupEvent(this, instance.getEventHash());
- 169 :
- 170 :
return instance;
- 171 :
};
- 172 :
};
- 173 :
- 174 :
/**
- 175 :
* Parent class for all advanced plugins.
- 176 :
*
- 177 :
* @mixes module:evented~EventedMixin
- 178 :
* @mixes module:stateful~StatefulMixin
- 179 :
* @fires Player#beforepluginsetup
- 180 :
* @fires Player#beforepluginsetup:$name
- 181 :
* @fires Player#pluginsetup
- 182 :
* @fires Player#pluginsetup:$name
- 183 :
* @listens Player#dispose
- 184 :
* @throws {Error}
- 185 :
* If attempting to instantiate the base {@link Plugin} class
- 186 :
* directly instead of via a sub-class.
- 187 :
*/
- 188 :
class Plugin {
- 189 :
- 190 :
/**
- 191 :
* Creates an instance of this class.
- 192 :
*
- 193 :
* Sub-classes should call `super` to ensure plugins are properly initialized.
- 194 :
*
- 195 :
* @param {Player} player
- 196 :
* A Video.js player instance.
- 197 :
*/
- 198 :
constructor(player) {
- 199 :
if (this.constructor === Plugin) {
- 200 :
throw new Error('Plugin must be sub-classed; not directly instantiated.');
- 201 :
}
- 202 :
- 203 :
this.player = player;
- 204 :
- 205 :
if (!this.log) {
- 206 :
this.log = this.player.log.createLogger(this.name);
- 207 :
}
- 208 :
- 209 :
// Make this object evented, but remove the added `trigger` method so we
- 210 :
// use the prototype version instead.
- 211 :
evented(this);
- 212 :
delete this.trigger;
- 213 :
- 214 :
stateful(this, this.constructor.defaultState);
- 215 :
markPluginAsActive(player, this.name);
- 216 :
- 217 :
// Auto-bind the dispose method so we can use it as a listener and unbind
- 218 :
// it later easily.
- 219 :
this.dispose = this.dispose.bind(this);
- 220 :
- 221 :
// If the player is disposed, dispose the plugin.
- 222 :
player.on('dispose', this.dispose);
- 223 :
}
- 224 :
- 225 :
/**
- 226 :
* Get the version of the plugin that was set on <pluginName>.VERSION
- 227 :
*/
- 228 :
version() {
- 229 :
return this.constructor.VERSION;
- 230 :
}
- 231 :
- 232 :
/**
- 233 :
* Each event triggered by plugins includes a hash of additional data with
- 234 :
* conventional properties.
- 235 :
*
- 236 :
* This returns that object or mutates an existing hash.
- 237 :
*
- 238 :
* @param {Object} [hash={}]
- 239 :
* An object to be used as event an event hash.
- 240 :
*
- 241 :
* @return {Plugin~PluginEventHash}
- 242 :
* An event hash object with provided properties mixed-in.
- 243 :
*/
- 244 :
getEventHash(hash = {}) {
- 245 :
hash.name = this.name;
- 246 :
hash.plugin = this.constructor;
- 247 :
hash.instance = this;
- 248 :
return hash;
- 249 :
}
- 250 :
- 251 :
/**
- 252 :
* Triggers an event on the plugin object and overrides
- 253 :
* {@link module:evented~EventedMixin.trigger|EventedMixin.trigger}.
- 254 :
*
- 255 :
* @param {string|Object} event
- 256 :
* An event type or an object with a type property.
- 257 :
*
- 258 :
* @param {Object} [hash={}]
- 259 :
* Additional data hash to merge with a
- 260 :
* {@link Plugin~PluginEventHash|PluginEventHash}.
- 261 :
*
- 262 :
* @return {boolean}
- 263 :
* Whether or not default was prevented.
- 264 :
*/
- 265 :
trigger(event, hash = {}) {
- 266 :
return Events.trigger(this.eventBusEl_, event, this.getEventHash(hash));
- 267 :
}
- 268 :
- 269 :
/**
- 270 :
* Handles "statechanged" events on the plugin. No-op by default, override by
- 271 :
* subclassing.
- 272 :
*
- 273 :
* @abstract
- 274 :
* @param {Event} e
- 275 :
* An event object provided by a "statechanged" event.
- 276 :
*
- 277 :
* @param {Object} e.changes
- 278 :
* An object describing changes that occurred with the "statechanged"
- 279 :
* event.
- 280 :
*/
- 281 :
handleStateChanged(e) {}
- 282 :
- 283 :
/**
- 284 :
* Disposes a plugin.
- 285 :
*
- 286 :
* Subclasses can override this if they want, but for the sake of safety,
- 287 :
* it's probably best to subscribe the "dispose" event.
- 288 :
*
- 289 :
* @fires Plugin#dispose
- 290 :
*/
- 291 :
dispose() {
- 292 :
const {name, player} = this;
- 293 :
- 294 :
/**
- 295 :
* Signals that a advanced plugin is about to be disposed.
- 296 :
*
- 297 :
* @event Plugin#dispose
- 298 :
* @type {EventTarget~Event}
- 299 :
*/
- 300 :
this.trigger('dispose');
- 301 :
this.off();
- 302 :
player.off('dispose', this.dispose);
- 303 :
- 304 :
// Eliminate any possible sources of leaking memory by clearing up
- 305 :
// references between the player and the plugin instance and nulling out
- 306 :
// the plugin's state and replacing methods with a function that throws.
- 307 :
player[PLUGIN_CACHE_KEY][name] = false;
- 308 :
this.player = this.state = null;
- 309 :
- 310 :
// Finally, replace the plugin name on the player with a new factory
- 311 :
// function, so that the plugin is ready to be set up again.
- 312 :
player[name] = createPluginFactory(name, pluginStorage[name]);
- 313 :
}
- 314 :
- 315 :
/**
- 316 :
* Determines if a plugin is a basic plugin (i.e. not a sub-class of `Plugin`).
- 317 :
*
- 318 :
* @param {string|Function} plugin
- 319 :
* If a string, matches the name of a plugin. If a function, will be
- 320 :
* tested directly.
- 321 :
*
- 322 :
* @return {boolean}
- 323 :
* Whether or not a plugin is a basic plugin.
- 324 :
*/
- 325 :
static isBasic(plugin) {
- 326 :
const p = (typeof plugin === 'string') ? getPlugin(plugin) : plugin;
- 327 :
- 328 :
return typeof p === 'function' && !Plugin.prototype.isPrototypeOf(p.prototype);
- 329 :
}
- 330 :
- 331 :
/**
- 332 :
* Register a Video.js plugin.
- 333 :
*
- 334 :
* @param {string} name
- 335 :
* The name of the plugin to be registered. Must be a string and
- 336 :
* must not match an existing plugin or a method on the `Player`
- 337 :
* prototype.
- 338 :
*
- 339 :
* @param {Function} plugin
- 340 :
* A sub-class of `Plugin` or a function for basic plugins.
- 341 :
*
- 342 :
* @return {Function}
- 343 :
* For advanced plugins, a factory function for that plugin. For
- 344 :
* basic plugins, a wrapper function that initializes the plugin.
- 345 :
*/
- 346 :
static registerPlugin(name, plugin) {
- 347 :
if (typeof name !== 'string') {
- 348 :
throw new Error(`Illegal plugin name, "${name}", must be a string, was ${typeof name}.`);
- 349 :
}
- 350 :
- 351 :
if (pluginExists(name)) {
- 352 :
log.warn(`A plugin named "${name}" already exists. You may want to avoid re-registering plugins!`);
- 353 :
} else if (Player.prototype.hasOwnProperty(name)) {
- 354 :
throw new Error(`Illegal plugin name, "${name}", cannot share a name with an existing player method!`);
- 355 :
}
- 356 :
- 357 :
if (typeof plugin !== 'function') {
- 358 :
throw new Error(`Illegal plugin for "${name}", must be a function, was ${typeof plugin}.`);
- 359 :
}
- 360 :
- 361 :
pluginStorage[name] = plugin;
- 362 :
- 363 :
// Add a player prototype method for all sub-classed plugins (but not for
- 364 :
// the base Plugin class).
- 365 :
if (name !== BASE_PLUGIN_NAME) {
- 366 :
if (Plugin.isBasic(plugin)) {
- 367 :
Player.prototype[name] = createBasicPlugin(name, plugin);
- 368 :
} else {
- 369 :
Player.prototype[name] = createPluginFactory(name, plugin);
- 370 :
}
- 371 :
}
- 372 :
- 373 :
return plugin;
- 374 :
}
- 375 :
- 376 :
/**
- 377 :
* De-register a Video.js plugin.
- 378 :
*
- 379 :
* @param {string} name
- 380 :
* The name of the plugin to be de-registered. Must be a string that
- 381 :
* matches an existing plugin.
- 382 :
*
- 383 :
* @throws {Error}
- 384 :
* If an attempt is made to de-register the base plugin.
- 385 :
*/
- 386 :
static deregisterPlugin(name) {
- 387 :
if (name === BASE_PLUGIN_NAME) {
- 388 :
throw new Error('Cannot de-register base plugin.');
- 389 :
}
- 390 :
if (pluginExists(name)) {
- 391 :
delete pluginStorage[name];
- 392 :
delete Player.prototype[name];
- 393 :
}
- 394 :
}
- 395 :
- 396 :
/**
- 397 :
* Gets an object containing multiple Video.js plugins.
- 398 :
*
- 399 :
* @param {Array} [names]
- 400 :
* If provided, should be an array of plugin names. Defaults to _all_
- 401 :
* plugin names.
- 402 :
*
- 403 :
* @return {Object|undefined}
- 404 :
* An object containing plugin(s) associated with their name(s) or
- 405 :
* `undefined` if no matching plugins exist).
- 406 :
*/
- 407 :
static getPlugins(names = Object.keys(pluginStorage)) {
- 408 :
let result;
- 409 :
- 410 :
names.forEach(name => {
- 411 :
const plugin = getPlugin(name);
- 412 :
- 413 :
if (plugin) {
- 414 :
result = result || {};
- 415 :
result[name] = plugin;
- 416 :
}
- 417 :
});
- 418 :
- 419 :
return result;
- 420 :
}
- 421 :
- 422 :
/**
- 423 :
* Gets a plugin's version, if available
- 424 :
*
- 425 :
* @param {string} name
- 426 :
* The name of a plugin.
- 427 :
*
- 428 :
* @return {string}
- 429 :
* The plugin's version or an empty string.
- 430 :
*/
- 431 :
static getPluginVersion(name) {
- 432 :
const plugin = getPlugin(name);
- 433 :
- 434 :
return plugin && plugin.VERSION || '';
- 435 :
}
- 436 :
}
- 437 :
- 438 :
/**
- 439 :
* Gets a plugin by name if it exists.
- 440 :
*
- 441 :
* @static
- 442 :
* @method getPlugin
- 443 :
* @memberOf Plugin
- 444 :
* @param {string} name
- 445 :
* The name of a plugin.
- 446 :
*
- 447 :
* @returns {Function|undefined}
- 448 :
* The plugin (or `undefined`).
- 449 :
*/
- 450 :
Plugin.getPlugin = getPlugin;
- 451 :
- 452 :
/**
- 453 :
* The name of the base plugin class as it is registered.
- 454 :
*
- 455 :
* @type {string}
- 456 :
*/
- 457 :
Plugin.BASE_PLUGIN_NAME = BASE_PLUGIN_NAME;
- 458 :
- 459 :
Plugin.registerPlugin(BASE_PLUGIN_NAME, Plugin);
- 460 :
- 461 :
/**
- 462 :
* Documented in player.js
- 463 :
*
- 464 :
* @ignore
- 465 :
*/
- 466 :
Player.prototype.usingPlugin = function(name) {
- 467 :
return !!this[PLUGIN_CACHE_KEY] && this[PLUGIN_CACHE_KEY][name] === true;
- 468 :
};
- 469 :
- 470 :
/**
- 471 :
* Documented in player.js
- 472 :
*
- 473 :
* @ignore
- 474 :
*/
- 475 :
Player.prototype.hasPlugin = function(name) {
- 476 :
return !!pluginExists(name);
- 477 :
};
- 478 :
- 479 :
export default Plugin;
- 480 :
- 481 :
/**
- 482 :
* Signals that a plugin is about to be set up on a player.
- 483 :
*
- 484 :
* @event Player#beforepluginsetup
- 485 :
* @type {Plugin~PluginEventHash}
- 486 :
*/
- 487 :
- 488 :
/**
- 489 :
* Signals that a plugin is about to be set up on a player - by name. The name
- 490 :
* is the name of the plugin.
- 491 :
*
- 492 :
* @event Player#beforepluginsetup:$name
- 493 :
* @type {Plugin~PluginEventHash}
- 494 :
*/
- 495 :
- 496 :
/**
- 497 :
* Signals that a plugin has just been set up on a player.
- 498 :
*
- 499 :
* @event Player#pluginsetup
- 500 :
* @type {Plugin~PluginEventHash}
- 501 :
*/
- 502 :
- 503 :
/**
- 504 :
* Signals that a plugin has just been set up on a player - by name. The name
- 505 :
* is the name of the plugin.
- 506 :
*
- 507 :
* @event Player#pluginsetup:$name
- 508 :
* @type {Plugin~PluginEventHash}
- 509 :
*/
- 510 :
- 511 :
/**
- 512 :
* @typedef {Object} Plugin~PluginEventHash
- 513 :
*
- 514 :
* @property {string} instance
- 515 :
* For basic plugins, the return value of the plugin function. For
- 516 :
* advanced plugins, the plugin instance on which the event is fired.
- 517 :
*
- 518 :
* @property {string} name
- 519 :
* The name of the plugin.
- 520 :
*
- 521 :
* @property {string} plugin
- 522 :
* For basic plugins, the plugin function. For advanced plugins, the
- 523 :
* plugin class/constructor.
- 524 :
*/