/**
* @file plugin.js
*/
import evented de './mixins/evented' ;
import stateful from './mixins/stateful' ;
import * as Events from './utils/events' ;
import log from './utils/log' ;
import Player from './player' ;
/**
* Le nom du plugin de base.
*
* @private
* @constant
* @type {string}
*/
const BASE_PLUGIN_NAME = 'plugin' ;
/**
* Clé sur laquelle est stocké le cache des plugins actifs d'un lecteur.
*
* @private
* @constant
* @type {string}
*/
const PLUGIN_CACHE_KEY = 'activePlugins_' ;
/**
* Stocke les plugins enregistrés dans un espace privé.
*
* @private
* @type {Objet}
*/
const pluginStorage = {} ;
/**
* Indique si un plugin a été enregistré ou non.
*
* @private
* @param {string} name
* Le nom d'un plugin.
*
* @return {boolean}
* Si le plugin a été enregistré ou non.
*/
const pluginExists = (name) => pluginStorage.hasOwnProperty(name) ;
/**
* Obtenir un plugin enregistré par son nom.
*
* @private
* @param {string} name
* Le nom d'un plugin.
*
* @return {Fonction|undefined}
* Le plugin (ou non défini).
*/
const getPlugin = (name) => pluginExists(name) ? pluginStorage[name] : undefined ;
/**
* Marque un plugin comme étant "actif" sur un lecteur.
*
* Il s'assure également que le lecteur dispose d'un objet permettant de suivre les plugins actifs.
*
* @private
* @param {Player} player
* Une instance de lecteur Video.js.
*
* @param {string} name
* Le nom d'un plugin.
*/
const markPluginAsActive = (player, name) => {
player[PLUGIN_CACHE_KEY] = player[PLUGIN_CACHE_KEY] || {} ;
player[PLUGIN_CACHE_KEY][name] = true ;
};
/**
* Déclenche une paire d'événements de configuration du plugin.
*
* @private
* @param {Player} player
* Une instance de lecteur Video.js.
*
* @param {Plugin~PluginEventHash} hash
* Un hachage de l'événement du plugin.
*
* @param {boolean} [before]
* Si vrai, préfixe le nom de l'événement par "before". En d'autres termes,
* à utiliser pour déclencher "beforepluginsetup" au lieu de "pluginsetup".
*/
const triggerSetupEvent = (player, hash, before) => {
const eventName = (before ? 'before' : '') + 'pluginsetup' ;
player.trigger(eventName, hash) ;
player.trigger(eventName + ':' + hash.name, hash) ;
};
/**
* Prend une fonction d'extension de base et renvoie une fonction d'enveloppe qui marque
* sur le lecteur que le plugin a été activé.
*
* @private
* @param {string} name
* Le nom du plugin.
*
* @param {Fonction} plugin
* Le plugin de base.
*
* @return {Fonction}
* Une fonction d'enveloppe pour le plugin donné.
*/
const createBasicPlugin = function(name, plugin) {
const basicPluginWrapper = function() {
// Nous déclenchons les événements "beforepluginsetup" et "pluginsetup" sur le lecteur
// peu importe, mais nous voulons que le hachage soit cohérent avec le hachage fourni
// pour les plugins avancés.
//
// La seule chose potentiellement contre-intuitive ici est la `instance` dans
// l'événement "pluginsetup" est la valeur retournée par la fonction `plugin`.
triggerSetupEvent(this, {name, plugin, instance : null}, true) ;
const instance = plugin.apply(this, arguments) ;
markPluginAsActive(this, name) ;
triggerSetupEvent(this, {name, plugin, instance}) ;
retourner l'instance ;
};
Object.keys(plugin).forEach(function(prop) {
basicPluginWrapper[prop] = plugin[prop] ;
}) ;
return basicPluginWrapper ;
};
/**
* Prend une sous-classe de plugin et renvoie une fonction d'usine pour la génération de
* instances de celui-ci.
*
* Cette fonction d'usine se remplacera elle-même par une instance de la fonction demandée
* sous-classe de Plugin.
*
* @private
* @param {string} name
* Le nom du plugin.
*
* @param {Plugin} Sous-classe de plugin
* Le plugin avancé.
*
* @return {Fonction}
*/
const createPluginFactory = (name, PluginSubClass) => {
// Ajouter une propriété `name` au prototype du plugin pour que chaque plugin puisse
// se référer à lui-même par son nom.
PluginSubClass.prototype.name = name ;
return function(...args) {
triggerSetupEvent(this, {name, plugin : PluginSubClass, instance : null}, true) ;
const instance = new PluginSubClass(...[this, ...args]) ;
// Le plugin est remplacé par une fonction qui renvoie l'instance actuelle.
this[name] = () => instance ;
triggerSetupEvent(this, instance.getEventHash()) ;
retourner l'instance ;
};
};
/**
* Classe mère pour tous les plugins avancés.
*
* @mixes module:evented~EventedMixin
* @mixes module:stateful~StatefulMixin
* @fires Player#beforepluginsetup
* @fires Player#beforepluginsetup:$name
* @fires Player#pluginsetup
* @fires Player#pluginsetup:$name
* @listens Player#dispose
* @throws {Error}
* Si l'on tente d'instancier la classe {@link Plugin} de base
* directement au lieu de passer par une sous-classe.
*/
class Plugin {
/**
* Crée une instance de cette classe.
*
* Les sous-classes doivent appeler `super` pour s'assurer que les plugins sont correctement initialisés.
*
* @param {Player} player
* Une instance de lecteur Video.js.
*/
constructor(player) {
if (this.constructor === Plugin) {
lancer une nouvelle erreur ('Plugin must be sub-classed ; not directly instantiated.') ;
}
this.player = player ;
if (!this.log) {
this.log = this.player.log.createLogger(this.name) ;
}
// Faire de cet objet un objet événementiel, mais supprimer la méthode `trigger` ajoutée pour que nous puissions
// utiliser la version prototype à la place.
evented(this) ;
supprimer ce.trigger ;
stateful(this, this.constructor.defaultState) ;
markPluginAsActive(player, this.name) ;
// Lier automatiquement la méthode dispose afin de pouvoir l'utiliser en tant qu'auditeur et la délier
// il est facile de l'utiliser plus tard.
this.dispose = this.dispose.bind(this) ;
// Si le lecteur est éliminé, éliminez le plugin.
player.on('dispose', this.dispose) ;
}
/**
* Obtenir la version du plugin qui a été définie sur <pluginName>.VERSION
*/
version() {
return this.constructor.VERSION ;
}
/**
* Chaque événement déclenché par les plugins comprend un hachage de données supplémentaires avec
* propriétés conventionnelles.
*
* Elle renvoie cet objet ou modifie un hachage existant.
*
* @param {Objet} [hash={}]
* Un objet à utiliser en tant qu'événement - un hachage d'événement.
*
* @return {Plugin~PluginEventHash}
* Un objet de hachage d'événement avec les propriétés fournies mélangées.
*/
getEventHash(hash = {}) {
hash.name = this.name ;
hash.plugin = this.constructor ;
hash.instance = this ;
return hash ;
}
/**
* Déclenche un événement sur l'objet du plugin et surcharge la fonction
* {@link module:evented~EventedMixin.trigger|EventedMixin.trigger}.
*
* @param {string|Object} event
* Un type d'événement ou un objet avec une propriété de type.
*
* @param {Objet} [hash={}]
* Hachage de données supplémentaires à fusionner avec un
* {@link Plugin~PluginEventHash|PluginEventHash}.
*
* @return {boolean}
* Si le défaut a été évité ou non.
*/
trigger(event, hash = {}) {
return Events.trigger(this.eventBusEl_, event, this.getEventHash(hash)) ;
}
/**
* Gère les événements "statechanged" du plugin. No-op par défaut, surcharge par
* la sous-classification.
*
* @abstract
* @param {Event} e
* Un objet d'événement fourni par un événement "statechanged".
*
* @param {Objet} e.changes
* Un objet décrivant les changements survenus avec le paramètre "statechanged"
* événement.
*/
handleStateChanged(e) {}
/**
* Supprime un plugin.
*
* Les sous-classes peuvent surcharger cette fonction si elles le souhaitent, mais pour des raisons de sécurité,
* il est probablement préférable de s'abonner à l'événement "dispose".
*
* @fires Plugin#dispose
*/
dispose() {
const {nom, joueur} = this ;
/**
* Signale qu'un plugin avancé est sur le point d'être éliminé.
*
* @event Plugin#dispose
* @type {EventTarget~Event}
*/
this.trigger('dispose') ;
this.off() ;
player.off('dispose', this.dispose) ;
// Éliminez toutes les sources possibles de fuites de mémoire en nettoyant les éléments suivants
// les références entre le joueur et l'instance de plugin et les annuler
// l'état du plugin et le remplacement des méthodes par une fonction qui lance.
player[PLUGIN_CACHE_KEY][name] = false ;
this.player = this.state = null ;
// Enfin, remplacer le nom du plugin sur le lecteur par une nouvelle fabrique
// pour que le plugin soit prêt à être réinstallé.
player[name] = createPluginFactory(name, pluginStorage[name]) ;
}
/**
* Détermine si un plugin est un plugin de base (c'est-à-dire qu'il n'est pas une sous-classe de `Plugin`).
*
* @param {string|Function} plugin
* S'il s'agit d'une chaîne, correspond au nom d'un plugin. S'il s'agit d'une fonction, elle sera
* testé directement.
*
* @return {boolean}
* Si un plugin est un plugin de base ou non.
*/
static isBasic(plugin) {
const p = (typeof plugin === 'string') ? getPlugin(plugin) : plugin ;
return typeof p === 'function' && !Plugin.prototype.isPrototypeOf(p.prototype) ;
}
/**
* Enregistrer un plugin Video.js.
*
* @param {string} name
* Le nom du plugin à enregistrer. Doit être une chaîne et
* ne doit pas correspondre à un plugin existant ou à une méthode du `Player`
* prototype.
*
* @param {Fonction} plugin
* Une sous-classe de `Plugin` ou une fonction pour les plugins de base.
*
* @return {Fonction}
* Pour les plugins avancés, une fonction d'usine pour ce plugin. Pour
* les plugins de base, une fonction d'encapsulation qui initialise le plugin.
*/
static registerPlugin(name, plugin) {
if (typeof name !== 'string') {
throw new Error(`Nom de plugin illégal, "${nom}", doit être une chaîne, était ${typeof name}.`) ;
}
if (pluginExists(name)) {
log.warn(`Un plugin nommé "${nom}" existe déjà. Vous voudrez peut-être éviter de réenregistrer les plugins!`) ;
} else if (Player.prototype.hasOwnProperty(name)) {
throw new Error(`Nom de plugin illégal, "${nom}", ne peut pas partager un nom avec une méthode de lecteur existante!`) ;
}
if (typeof plugin !== 'function') {
throw new Error(`Illegal plugin for "${name}", must be a function, was ${typeof plugin}.`) ;
}
pluginStorage[name] = plugin ;
// Ajouter une méthode de prototype de lecteur pour tous les plugins sous-classés (mais pas pour le plugin
// la classe de base du plugin).
if (name !== BASE_PLUGIN_NAME) {
if (Plugin.isBasic(plugin)) {
Player.prototype[name] = createBasicPlugin(name, plugin) ;
} else {
Player.prototype[name] = createPluginFactory(name, plugin) ;
}
}
return plugin ;
}
/**
* Désenregistrement d'un plugin Video.js.
*
* @param {string} name
* Le nom du plugin à désenregistrer. Doit être une chaîne de caractères qui
* correspond à un plugin existant.
*
* @throws {Error}
* En cas de tentative de désenregistrement du plugin de base.
*/
static deregisterPlugin(name) {
if (name === BASE_PLUGIN_NAME) {
lancer une nouvelle erreur ('Cannot de-register base plugin.') ;
}
if (pluginExists(name)) {
supprimer pluginStorage[nom] ;
supprimer Player.prototype[name] ;
}
}
/**
* Obtient un objet contenant plusieurs plugins Video.js.
*
* @param {Array} [names]
* S'il est fourni, il doit s'agir d'un tableau de noms de plugins. La valeur par défaut est _tous_
* noms des plugins.
*
* @return {Objet|non défini}
* Un objet contenant un ou des plugin(s) associé(s) à leur(s) nom(s) ou à leur(s)
* `undefined` si aucun plugin correspondant n'existe).
*/
static getPlugins(names = Object.keys(pluginStorage)) {
laisser le résultat ;
names.forEach(name => {
const plugin = getPlugin(name) ;
if (plugin) {
résultat = résultat || {} ;
result[name] = plugin ;
}
}) ;
retourner le résultat ;
}
/**
* Obtient la version d'un plugin, si elle est disponible
*
* @param {string} name
* Le nom d'un plugin.
*
* @return {string}
* La version du plugin ou une chaîne vide.
*/
static getPluginVersion(name) {
const plugin = getPlugin(name) ;
return plugin && plugin.VERSION || '' ;
}
}
/**
* Récupère un plugin par son nom s'il existe.
*
* @statique
* @method getPlugin
* @memberOf Plugin
* @param {string} name
* Le nom d'un plugin.
*
* @returns {Fonction|undefined}
* Le plugin (ou `undefined`).
*/
Plugin.getPlugin = getPlugin ;
/**
* Le nom de la classe de base du plugin telle qu'elle est enregistrée.
*
* @type {string}
*/
Plugin.BASE_PLUGIN_NAME = BASE_PLUGIN_NAME ;
Plugin.registerPlugin(BASE_PLUGIN_NAME, Plugin) ;
/**
* Documenté dans player.js
*
* @ignore
*/
Player.prototype.usingPlugin = function(name) {
return !!this[PLUGIN_CACHE_KEY] && this[PLUGIN_CACHE_KEY][name] === true ;
};
/**
* Documenté dans player.js
*
* @ignore
*/
Player.prototype.hasPlugin = function(name) {
return !!pluginExists(name) ;
};
exporter le plugin par défaut ;
/**
* Signale qu'un plugin est sur le point d'être installé sur un lecteur.
*
* @event Player#beforepluginsetup
* @type {Plugin~PluginEventHash}
*/
/**
* Signale qu'un plugin est sur le point d'être installé sur un lecteur - par son nom. Le nom
* est le nom du plugin.
*
* @event Player#beforepluginsetup:$name
* @type {Plugin~PluginEventHash}
*/
/**
* Signale qu'un plugin vient d'être installé sur un lecteur.
*
* @event Player#pluginsetup
* @type {Plugin~PluginEventHash}
*/
/**
* Signale qu'un plugin vient d'être installé sur un lecteur - par son nom. Le nom
* est le nom du plugin.
*
* @event Player#pluginsetup:$name
* @type {Plugin~PluginEventHash}
*/
/**
* @typedef {Objet} Plugin~PluginEventHash
*
* @property {string} instance
* Pour les plugins de base, la valeur de retour de la fonction du plugin. Pour
* plugins avancés, l'instance de plugin sur laquelle l'événement est déclenché.
*
* @property {string} name
* Le nom du plugin.
*
* @property {string} plugin
* Pour les plugins de base, la fonction du plugin. Pour les plugins avancés, l'option
* classe/constructeur de plugin.
*/