/**
* Composant du lecteur - Classe de base pour tous les objets de l'interface utilisateur
*
* @file component.js
*/
import window from 'global/window' ;
import evented de './mixins/evented' ;
import stateful from './mixins/stateful' ;
import * as Dom from './utils/dom.js' ;
import * as Fn from './utils/fn.js' ;
import * as Guid from './utils/guid.js' ;
import {toTitleCase, toLowerCase} from './utils/string-cases.js' ;
import mergeOptions from './utils/merge-options.js' ;
import computedStyle de './utils/computed-style' ;
import Map from './utils/map.js' ;
import Set from './utils/set.js' ;
import keycode from 'keycode' ;
/**
* Classe de base pour tous les composants de l'interface utilisateur.
* Les composants sont des objets de l'interface utilisateur qui représentent à la fois un objet javascript et un élément
* dans le DOM. Ils peuvent être des enfants d'autres composants et peuvent avoir
* les enfants eux-mêmes.
*
* Les composants peuvent également utiliser des méthodes de {@link EventTarget}
*/
classe Composant {
/**
* Un rappel qui est appelé lorsqu'un composant est prêt. N'a pas de
* et toute valeur de rappel seront ignorées.
*
* @callback Composant~ReadyCallback
* @this Component
*/
/**
* Crée une instance de cette classe.
*
* @param {Player} player
* Le `Player` auquel cette classe doit être attachée.
*
* @param {Objet} [options]
* Le magasin clé/valeur des options des composants.
*
* @param {Objet[]} [options.children]
* Un tableau d'objets enfants avec lesquels initialiser ce composant. Les objets enfants ont
* une propriété de nom qui sera utilisée si plus d'un composant du même type doit être
* ajouté.
*
* @param {string} [options.className]
* Une classe ou une liste de classes séparées par des espaces pour ajouter le composant
*
* @param {Component~ReadyCallback} [ready]
* Fonction qui est appelée lorsque le `Composant` est prêt.
*/
constructor(player, options, ready) {
// Le composant peut être le joueur lui-même et nous ne pouvons pas passer `this` à super
if (!player && this.play) {
this.player_ = player = this ; // eslint-disable-line
} else {
this.player_ = player ;
}
this.isDisposed_ = false ;
// Maintient la référence au composant parent via la méthode `addChild`
this.parentComponent_ = null ;
// Faire une copie de prototype.options_ pour se prémunir contre l'écrasement des valeurs par défaut
this.options_ = mergeOptions({}, this.options_) ;
// Mise à jour des options avec les options fournies
options = this.options_ = mergeOptions(this.options_, options) ;
// Obtenir l'identifiant à partir des options ou de l'élément d'options s'il en est fourni un
this.id_ = options.id || (options.el && options.el.id) ;
// S'il n'y a pas d'identifiant dans les options, en générer un
if (!this.id_) {
// Ne pas exiger la fonction d'identification du joueur dans le cas de joueurs fictifs
const id = player && player.id && player.id() || 'no_player' ;
this.id_ = `${id}_component_${Guid.newGUID()}` ;
}
this.name_ = options.name || null ;
// Créer un élément s'il n'a pas été fourni dans les options
if (options.el) {
this.el_ = options.el ;
} else if (options.createEl !== false) {
this.el_ = this.createEl() ;
}
if (options.className && this.el_) {
options.className.split(' ').forEach(c => this.addClass(c)) ;
}
// si evented est autre chose que false, nous voulons mixer dans evented
if (options.evented !== false) {
// Faire de cet objet un objet événementiel et utiliser `el_`, s'il est disponible, comme bus événementiel
evented(this, {eventBusKey : this.el_ ? 'el_' : null}) ;
this.handleLanguagechange = this.handleLanguagechange.bind(this) ;
this.on(this.player_, 'languagechange', this.handleLanguagechange) ;
}
stateful(this, this.constructor.defaultState) ;
this.children_ = [] ;
this.childIndex_ = {} ;
this.childNameIndex_ = {} ;
this.setTimeoutIds_ = new Set() ;
this.setIntervalIds_ = new Set() ;
this.rafIds_ = new Set() ;
this.namedRafs_ = new Map() ;
this.clearingTimersOnDispose_ = false ;
// Ajouter tout composant enfant dans les options
if (options.initChildren !== false) {
this.initChildren() ;
}
// Il ne faut pas déclencher ready ici, sinon il sera lancé avant que init ne soit réellement exécuté
// terminé pour tous les enfants qui exécutent ce constructeur
this.ready(ready) ;
if (options.reportTouchActivity !== false) {
this.enableTouchActivity() ;
}
}
/**
* Se débarrasse du `Composant` et de tous ses composants enfants.
*
* @fires Composant#dispose
*
* @param {Objet} options
* @param {Element} options.originalEl élément avec lequel remplacer l'élément du lecteur
*/
dispose(options = {}) {
// S'en sortir si le composant a déjà été éliminé.
if (this.isDisposed_) {
retour ;
}
if (this.readyQueue_) {
this.readyQueue_.length = 0 ;
}
/**
* Déclenché lorsqu'un `Composant` est disposé.
*
* @event Composant#dispose
* @type {EventTarget~Event}
*
* @property {boolean} [bubbles=false]
* est fixé à false pour que l'événement dispose ne se produise pas
* bulle d'air
*/
this.trigger({type : 'dispose', bubbles : false}) ;
this.isDisposed_ = true ;
// Se débarrasser de tous les enfants.
if (this.children_) {
for (let i = this.children_.length - 1 ; i >= 0 ; i--) {
if (this.children_[i].dispose) {
this.children_[i].dispose() ;
}
}
}
// Supprimer les références des enfants
this.children_ = null ;
this.childIndex_ = null ;
this.childNameIndex_ = null ;
this.parentComponent_ = null ;
if (this.el_) {
// Retirer l'élément du DOM
if (this.el_.parentNode) {
if (options.restoreEl) {
this.el_.parentNode.replaceChild(options.restoreEl, this.el_) ;
} else {
this.el_.parentNode.removeChild(this.el_) ;
}
}
this.el_ = null ;
}
// supprimer la référence au lecteur après avoir éliminé l'élément
ce.joueur_ = null ;
}
/**
* Déterminer si ce composant a été éliminé ou non.
*
* @return {boolean}
* Si le composant a été éliminé, la valeur sera `true`. Sinon, `false`.
*/
isDisposed() {
return Boolean(this.isDisposed_) ;
}
/**
* Retourne le {@link Player} auquel le `Component` s'est attaché.
*
* @return {Player}
* Le lecteur auquel ce `Component` est attaché.
*/
player() {
return this.player_ ;
}
/**
* Fusion en profondeur d'objets d'options avec de nouvelles options.
* > Remarque : Lorsque `obj` et `options` contiennent des propriétés dont les valeurs sont des objets.
* Les deux propriétés sont fusionnées en utilisant {@link module:mergeOptions}
*
* @param {Objet} obj
* L'objet qui contient les nouvelles options.
*
* @return {Object}
* Un nouvel objet de `this.options_` et `obj` fusionnés ensemble.
*/
options(obj) {
if (!obj) {
return this.options_ ;
}
this.options_ = mergeOptions(this.options_, obj) ;
return this.options_ ;
}
/**
* Obtenir l'élément DOM `Component`s
*
* @return {Element}
* L'élément DOM de ce `Component`.
*/
el() {
return this.el_ ;
}
/**
* Créez l'élément DOM `Component`.
*
* @param {string} [tagName]
* Type de nœud DOM de l'élément, par exemple "div"
*
* @param {Objet} [propriétés]
* Un objet de propriétés à définir.
*
* @param {Objet} [attributs]
* Un objet d'attributs à définir.
*
* @return {Element}
* L'élément qui est créé.
*/
createEl(tagName, properties, attributes) {
return Dom.createEl(tagName, properties, attributes) ;
}
/**
* Localiser une chaîne de caractères à partir de la chaîne en anglais.
*
* Si des jetons sont fournis, il essaiera d'effectuer un simple remplacement de jeton sur la chaîne fournie.
* Les mots-clés recherchés ressemblent à `{1}`, l'index étant indexé à 1 dans le tableau des mots-clés.
*
* Si une `valeur par défaut` est fournie, elle sera utilisée à la place de `string`,
* si une valeur n'est pas trouvée dans les fichiers de langue fournis.
* Cette option est utile si vous souhaitez disposer d'une clé descriptive pour le remplacement des jetons
* mais avoir une chaîne localisée succincte et ne pas exiger que `en.json` soit inclus.
*
* Actuellement, il est utilisé pour la synchronisation de la barre de progression.
* ``js
* {
* "Barre de progression : currentTime={1} duration={2}" : "{1} de {2}"
* }
* ```
* Il est ensuite utilisé comme suit :
* ``js
* this.localize('progress bar timing : currentTime={1} duration{2}',
* [this.player_.currentTime(), this.player_.duration()],
* '{1} de {2}') ;
* ```
*
* Ce qui donne quelque chose comme : `01:23 of 24:56`.
*
*
* @param {string} string
* La chaîne à localiser et la clé à rechercher dans les fichiers de langue.
* @param {string[]} [tokens]
* Si l'élément en cours a des remplacements de jetons, fournir les jetons ici.
* @param {string} [defaultValue]
* La valeur par défaut est `string`. Peut être une valeur par défaut à utiliser pour le remplacement du jeton
* si la clé de recherche doit être séparée.
*
* @return {string}
* La chaîne localisée ou, si aucune localisation n'existe, la chaîne anglaise.
*/
localize(string, tokens, defaultValue = string) {
const code = this.player_.language && this.player_.language() ;
const languages = this.player_.languages && this.player_.languages() ;
const language = languages && languages[code] ;
const primaryCode = code && code.split('-')[0] ;
const primaryLang = languages && languages[primaryCode] ;
let localizedString = defaultValue ;
if (language && language[string]) {
localizedString = language[string] ;
} else if (primaryLang && primaryLang[string]) {
localizedString = primaryLang[string] ;
}
if (tokens) {
localizedString = localizedString.replace(/\{(\d+)\}/g, function(match, index) {
const value = tokens[index - 1] ;
let ret = valeur ;
if (typeof value === 'undefined') {
ret = match ;
}
retour ret ;
}) ;
}
return localizedString ;
}
/**
* Gère le changement de langue pour le lecteur dans les composants. Doive être remplacée par des sous-composants.
*
* @abstract
*/
handleLanguagechange() {}
/**
* Retourne l'élément DOM `Component`. C'est ici que les enfants sont insérés.
* Ce sera généralement le même que l'élément renvoyé dans {@link Component#el}.
*
* @return {Element}
* L'élément de contenu de ce `Component`.
*/
contentEl() {
return this.contentEl_ || this.el_ ;
}
/**
* Obtenir l'ID de ce composant
*
* @return {string}
* L'identifiant de ce `Composant`
*/
id() {
return this.id_ ;
}
/**
* Obtenir le nom du composant. Le nom est utilisé pour référencer le `Composant`
* et est défini lors de l'enregistrement.
*
* @return {string}
* Le nom de ce `Composant`.
*/
name() {
return this.name_ ;
}
/**
* Obtenir un tableau de tous les composants enfants
*
* @return {Array}
* Les enfants
*/
children() {
return this.children_ ;
}
/**
* Retourne l'enfant `Composant` avec l'`id` donné.
*
* @param {string} id
* L'identifiant du `Composant` enfant à obtenir.
*
* @return {Component|undefined}
* L'enfant `Component` avec l'`id` donné ou non défini.
*/
getChildById(id) {
return this.childIndex_[id] ;
}
/**
* Retourne l'enfant `Composant` avec le `nom` donné.
*
* @param {string} name
* Le nom du `Composant` enfant à récupérer.
*
* @return {Component|undefined}
* L'enfant `Composant` avec le `nom` donné ou non défini.
*/
getChild(name) {
if (!name) {
retour ;
}
return this.childNameIndex_[name] ;
}
/**
* Retourne le descendant `Composant` qui suit l'élément donné
* descendant `names`. Par exemple, ['foo', 'bar', 'baz'] serait
* essayer d'obtenir 'foo' sur le composant actuel, 'bar' sur le composant 'foo'
* et "baz" sur le composant "bar" et renvoie une valeur non définie
* si l'un d'entre eux n'existe pas.
*
* @param {...string[]|...string} names
* Le nom du `Composant` enfant à récupérer.
*
* @return {Component|undefined}
* Le descendant `Composant` suivant le descendant donné
* `names` ou non défini.
*/
getDescendant(...names) {
// aplatir l'argument du tableau dans le tableau principal
names = names.reduce((acc, n) => acc.concat(n), []) ;
let currentChild = this ;
for (let i = 0 ; i < names.length ; i++) {
currentChild = currentChild.getChild(names[i]) ;
if (!currentChild || !currentChild.getChild) {
retour ;
}
}
return currentChild ;
}
/**
* Ajoute un `Composant` enfant à l'intérieur du `Composant` actuel.
*
*
* @param {string|Composant} child
* Le nom ou l'instance d'un enfant à ajouter.
*
* @param {Objet} [options={}]
* Le magasin clé/valeur des options qui seront transmises aux enfants de
* l'enfant.
*
* @param {number} [index=this.children_.length]
* L'index dans lequel il faut tenter d'ajouter un enfant.
*
* @return {Component}
* Le `Composant` qui est ajouté en tant qu'enfant. Lors de l'utilisation d'une chaîne de caractères, l'option
* le composant sera créé par ce processus.
*/
addChild(child, options = {}, index = this.children_.length) {
let component ;
let componentName ;
// Si l'enfant est une chaîne, créer un composant avec des options
if (typeof child === 'string') {
componentName = toTitleCase(child) ;
const componentClassName = options.componentClass || componentName ;
// Définir le nom par le biais des options
options.name = componentName ;
// Créer un nouvel objet & pour cet ensemble de contrôles
// S'il n'y a pas de .player_, il s'agit d'un joueur
const ComponentClass = Component.getComponent(componentClassName) ;
if (!ComponentClass) {
throw new Error(`Le composant ${componentClassName} n'existe pas`) ;
}
// les données stockées directement sur l'objet videojs peuvent être
// identifié à tort comme un composant à conserver
// rétrocompatibilité avec la version 4.x. vérifier que le fichier
// la classe de composant peut être instanciée.
if (typeof ComponentClass !== 'function') {
retourner null ;
}
component = new ComponentClass(this.player_ || this, options) ;
// l'enfant est une instance de composant
} else {
composant = enfant ;
}
if (component.parentComponent_) {
component.parentComponent_.removeChild(component) ;
}
this.children_.splice(index, 0, component) ;
component.parentComponent_ = this ;
if (typeof component.id === 'function') {
this.childIndex_[component.id()] = component ;
}
// Si aucun nom n'a été utilisé pour créer le composant, vérifiez si nous pouvons utiliser l'attribut
// nom de la fonction du composant
componentName = componentName || (component.name && toTitleCase(component.name())) ;
if (componentName) {
this.childNameIndex_[componentName] = component ;
this.childNameIndex_[toLowerCase(componentName)] = component ;
}
// Ajouter l'élément de l'objet de l'interface utilisateur au conteneur div (box)
// Il n'est pas nécessaire d'avoir un élément
if (typeof component.el === 'function' && component.el()) {
// Si l'insertion se fait avant un composant, l'insertion se fait avant l'élément de ce composant
let refNode = null ;
if (this.children_[index + 1]) {
// La plupart des enfants sont des composants, mais la technologie vidéo est un élément HTML
if (this.children_[index + 1].el_) {
refNode = this.children_[index + 1].el_ ;
} else if (Dom.isEl(this.children_[index + 1])) {
refNode = this.children_[index + 1] ;
}
}
this.contentEl().insertBefore(component.el(), refNode) ;
}
// Retourner pour qu'il puisse être stocké sur l'objet parent si désiré.
retourner le composant ;
}
/**
* Supprime un `Composant` enfant de la liste des enfants de ce `Composant`. Supprime également
* l'élément `Composant` enfant de cet élément `Composant`.
*
* @param {Composant} component
* Le `Composant` enfant à supprimer.
*/
removeChild(component) {
if (typeof component === 'string') {
component = this.getChild(component) ;
}
if (!component || !this.children_) {
retour ;
}
let childFound = false ;
for (let i = this.children_.length - 1 ; i >= 0 ; i--) {
if (this.children_[i] === component) {
childFound = true ;
this.children_.splice(i, 1) ;
pause ;
}
}
if (!childFound) {
retour ;
}
component.parentComponent_ = null ;
this.childIndex_[component.id()] = null ;
this.childNameIndex_[toTitleCase(component.name())] = null ;
this.childNameIndex_[toLowerCase(component.name())] = null ;
const compEl = component.el() ;
if (compEl && compEl.parentNode === this.contentEl()) {
this.contentEl().removeChild(component.el()) ;
}
}
/**
* Ajouter et initialiser un composant enfant par défaut en fonction des options.
*/
initChildren() {
const children = this.options_.children ;
if (children) {
// `this` est `parent`
const parentOptions = this.options_ ;
const handleAdd = (child) => {
const name = child.name ;
let opts = child.opts ;
// Permettre aux options des enfants d'être définies au niveau des options du parent
// par exemple, videojs(id, { controlBar : false }) ;
// au lieu de videojs(id, { children : { controlBar : false }) ;
if (parentOptions[name] !== undefined) {
opts = parentOptions[name] ;
}
// Permet de désactiver les composants par défaut
// par exemple, options['children']['posterImage'] = false
if (opts === false) {
retour ;
}
// Permettre aux options d'être transmises sous la forme d'un simple booléen s'il n'y a pas de configuration
// est nécessaire.
if (opts === true) {
opts = {} ;
}
// Nous voulons également transmettre les options du joueur d'origine
// à chaque composant afin qu'ils n'aient pas besoin d'en faire autant
// se replonger dans le lecteur pour des options ultérieures.
opts.playerOptions = this.options_.playerOptions ;
// Créer et ajouter le composant enfant.
// Ajouter une référence directe à l'enfant par son nom sur l'instance parent.
// Si deux composants identiques sont utilisés, des noms différents doivent être fournis
// pour chaque
const newChild = this.addChild(name, opts) ;
if (newChild) {
this[name] = newChild ;
}
};
// Permet de passer un tableau de détails sur les enfants dans les options
let workingChildren ;
const Tech = Component.getComponent('Tech') ;
if (Array.isArray(children)) {
workingChildren = children ;
} else {
workingChildren = Object.keys(children) ;
}
enfants travailleurs
// les enfants qui sont dans this.options_ mais aussi dans workingChildren seraient
// nous donnent des enfants supplémentaires dont nous ne voulons pas. Nous voulons donc les filtrer.
.concat(Object.keys(this.options_)
.filter(function(child) {
return !workingChildren.some(function(wchild) {
if (typeof wchild === 'string') {
return child === wchild ;
}
return child === wchild.name ;
}) ;
}))
.map((child) => {
laisser le nom ;
let opts ;
if (typeof child === 'string') {
nom = enfant ;
opts = children[name] || this.options_[name] || {} ;
} else {
nom = nom de l'enfant ;
opts = child ;
}
return {name, opts} ;
})
.filter((child) => {
// nous devons nous assurer que child.name n'est pas dans le techOrder puisque
// les techniciens sont enregistrés comme composants mais ne sont pas compatibles
// Voir https://github.com/videojs/video.js/issues/2772
const c = Component.getComponent(child.opts.componentClass ||
toTitleCase(child.name)) ;
return c && !Tech.isTech(c) ;
})
.forEach(handleAdd) ;
}
}
/**
* Construit le nom de la classe DOM par défaut. Doive être remplacée par des sous-composants.
*
* @return {string}
* Le nom de la classe DOM pour cet objet.
*
* @abstract
*/
buildCSSClass() {
// Les classes enfantines peuvent inclure une fonction qui le fait :
// return 'NOM DE CLASSE' + this._super() ;
retourner '' ;
}
/**
* Lier un écouteur à l'état de préparation du composant.
* Différent des auditeurs d'événements en ce sens que si l'événement "ready" s'est déjà produit
* il déclenchera la fonction immédiatement.
*
* @return {Component}
* Se renvoie lui-même ; la méthode peut être enchaînée.
*/
ready(fn, sync = false) {
if (!fn) {
retour ;
}
if (!this.isReady_) {
this.readyQueue_ = this.readyQueue_ || [] ;
this.readyQueue_.push(fn) ;
retour ;
}
if (sync) {
fn.call(this) ;
} else {
// Appeler la fonction de manière asynchrone par défaut pour des raisons de cohérence
this.setTimeout(fn, 1) ;
}
}
/**
* Déclenche tous les récepteurs prêts pour ce `Composant`.
*
* @fires Composant#ready
*/
triggerReady() {
this.isReady_ = true ;
// S'assurer que l'état prêt est déclenché de manière asynchrone
this.setTimeout(function() {
const readyQueue = this.readyQueue_ ;
// Réinitialisation de la file d'attente
this.readyQueue_ = [] ;
if (readyQueue && readyQueue.length > 0) {
readyQueue.forEach(function(fn) {
fn.call(this) ;
}, this) ;
}
// Permet également d'utiliser des récepteurs d'événements
/**
* Déclenché lorsqu'un `Composant` est prêt.
*
* @event Composant#ready
* @type {EventTarget~Event}
*/
this.trigger('ready') ;
}, 1) ;
}
/**
* Trouver un seul élément DOM correspondant à un `sélecteur`. Cela peut se faire à l'intérieur de la rubrique "Composants"
* `contentEl()` ou un autre contexte personnalisé.
*
* @param {string} selector
* Un sélecteur CSS valide, qui sera passé à `querySelector`.
*
* @param {Element|string} [context=this.contentEl()]
* Un élément du DOM à l'intérieur duquel la requête doit être effectuée. Peut également être une chaîne de sélection dans
* auquel cas le premier élément correspondant sera utilisé comme contexte. Si
* manquant `this.contentEl()` est utilisé. Si `this.contentEl()` renvoie
* rien, il se rabat sur `document`.
*
* @return {Element|null}
* l'élément dom qui a été trouvé, ou null
*
* @voir [Information sur les sélecteurs CSS] (https://developer.mozilla.org/en-US/docs/Web/Guide/CSS/Getting_Started/Selectors)
*/
$(selector, context) {
return Dom.$(selector, context || this.contentEl()) ;
}
/**
* Trouve tous les éléments du DOM correspondant à un `sélecteur`. Cela peut se faire à l'intérieur de la rubrique "Composants"
* `contentEl()` ou un autre contexte personnalisé.
*
* @param {string} selector
* Un sélecteur CSS valide, qui sera passé à `querySelectorAll`.
*
* @param {Element|string} [context=this.contentEl()]
* Un élément du DOM à l'intérieur duquel la requête doit être effectuée. Peut également être une chaîne de sélection dans
* auquel cas le premier élément correspondant sera utilisé comme contexte. Si
* manquant `this.contentEl()` est utilisé. Si `this.contentEl()` renvoie
* rien, il se rabat sur `document`.
*
* @return {NodeList}
* une liste des éléments dom trouvés
*
* @voir [Information sur les sélecteurs CSS] (https://developer.mozilla.org/en-US/docs/Web/Guide/CSS/Getting_Started/Selectors)
*/
$$(selector, context) {
return Dom.$$(selector, context || this.contentEl()) ;
}
/**
* Vérifie si l'élément d'un composant possède un nom de classe CSS.
*
* @param {string} classToCheck
* Nom de la classe CSS à vérifier.
*
* @return {boolean}
* - True si le `Composant` possède la classe.
* - Faux si le `Composant` n'a pas la classe`
*/
hasClass(classToCheck) {
return Dom.hasClass(this.el_, classToCheck) ;
}
/**
* Ajouter un nom de classe CSS à l'élément `Component`.
*
* @param {string} classToAdd
* Nom de la classe CSS à ajouter
*/
addClass(classToAdd) {
Dom.addClass(this.el_, classToAdd) ;
}
/**
* Supprime le nom d'une classe CSS de l'élément `Component`.
*
* @param {string} classToRemove
* Nom de la classe CSS à supprimer
*/
removeClass(classToRemove) {
Dom.removeClass(this.el_, classToRemove) ;
}
/**
* Ajouter ou supprimer un nom de classe CSS de l'élément du composant.
* - `classToToggle` est ajouté lorsque {@link Component#hasClass} renvoie false.
* - `classToToggle` est supprimé lorsque {@link Component#hasClass} renvoie true.
*
* @param {string} classToToggle
* La classe à ajouter ou à supprimer en fonction de (@link Component#hasClass}
*
* @param {boolean|Dom~predicate} [predicate]
* Une fonction {@link Dom~predicate} ou un booléen
*/
toggleClass(classToToggle, predicate) {
Dom.toggleClass(this.el_, classToToggle, predicate) ;
}
/**
* Afficher l'élément `Component` s'il est caché en supprimant l'élément
* le nom de la classe "vjs-hidden" en est extrait.
*/
show() {
this.removeClass('vjs-hidden') ;
}
/**
* Cachez l'élément `Component` s'il est actuellement affiché en ajoutant la balise
* 'vjs-hidden` pour le nom de la classe.
*/
hide() {
this.addClass('vjs-hidden') ;
}
/**
* Verrouiller un élément `Component` dans son état visible en ajoutant l'option 'vjs-lock-showing'
* à son nom de classe. Utilisé pendant les fondus entrant et sortant.
*
* @private
*/
lockShowing() {
this.addClass('vjs-lock-showing') ;
}
/**
* Déverrouiller un élément `Component` de son état visible en supprimant l'option 'vjs-lock-showing'
* de la classe. Utilisé pendant les fondus entrant et sortant.
*
* @private
*/
unlockShowing() {
this.removeClass('vjs-lock-showing') ;
}
/**
* Obtenir la valeur d'un attribut de l'élément `Component`.
*
* @param {string} attribut
* Nom de l'attribut à partir duquel la valeur doit être obtenue.
*
* @return {string|null}
* - La valeur de l'attribut demandé.
* - Peut être une chaîne vide sur certains navigateurs si l'attribut n'existe pas
* ou n'a pas de valeur
* - La plupart des navigateurs renvoient la valeur null si l'attribut n'existe pas ou s'il n'a pas été créé
* aucune valeur.
*
* @see [DOM API]{@link https://developer.mozilla.org/en-US/docs/Web/API/Element/getAttribute}
*/
getAttribute(attribute) {
return Dom.getAttribute(this.el_, attribute) ;
}
/**
* Fixer la valeur d'un attribut sur l'élément `Component`
*
* @param {string} attribut
* Nom de l'attribut à définir.
*
* @param {string} value
* Valeur à attribuer à l'attribut.
*
* @see [DOM API]{@link https://developer.mozilla.org/en-US/docs/Web/API/Element/setAttribute}
*/
setAttribute(attribute, value) {
Dom.setAttribute(this.el_, attribute, value) ;
}
/**
* Supprime un attribut de l'élément `Component`.
*
* @param {string} attribut
* Nom de l'attribut à supprimer.
*
* @see [DOM API]{@link https://developer.mozilla.org/en-US/docs/Web/API/Element/removeAttribute}
*/
removeAttribute(attribute) {
Dom.removeAttribute(this.el_, attribute) ;
}
/**
* Permet d'obtenir ou de définir la largeur du composant en fonction des styles CSS.
* Voir {@link Component#dimension} pour plus d'informations.
*
* @param {number|string} [num]
* La largeur que vous souhaitez définir après avoir fixé '%', 'px' ou rien du tout.
*
* @param {boolean} [skipListeners]
* Sauter le déclenchement de l'événement de redimensionnement des composants
*
* @return {number|string}
* La largeur lors de l'obtention, zéro s'il n'y a pas de largeur. Il peut s'agir d'une chaîne
* postposé avec "%" ou "px".
*/
width(num, skipListeners) {
return this.dimension('width', num, skipListeners) ;
}
/**
* Permet d'obtenir ou de définir la hauteur du composant en fonction des styles CSS.
* Voir {@link Component#dimension} pour plus d'informations.
*
* @param {number|string} [num]
* La hauteur que vous souhaitez définir après avoir fixé '%', 'px' ou rien du tout.
*
* @param {boolean} [skipListeners]
* Sauter le déclenchement de l'événement de redimensionnement des composants
*
* @return {number|string}
* La largeur lors de l'obtention, zéro s'il n'y a pas de largeur. Il peut s'agir d'une chaîne
* postposé avec "%" ou "px".
*/
height(num, skipListeners) {
return this.dimension('height', num, skipListeners) ;
}
/**
* Définit simultanément la largeur et la hauteur de l'élément `Component`.
*
* @param {nombre|chaîne} largeur
* Largeur de l'élément `Component`.
*
* @param {nombre|chaîne} hauteur
* Hauteur de l'élément `Component`.
*/
dimensions(width, height) {
// Sauter les auditeurs de redimensionnement des composants en fonction de la largeur pour des raisons d'optimisation
this.width(width, true) ;
this.height(height) ;
}
/**
* Permet d'obtenir ou de définir la largeur ou la hauteur de l'élément `Component`. Voici le code partagé
* pour les {@link Component#width} et {@link Component#height}.
*
* Ce qu'il faut savoir :
* - Si la largeur ou la hauteur est un nombre, il renvoie le nombre postfixé par "px".
* - Si la largeur/hauteur est un pourcentage, cette option renvoie le pourcentage postfixé par "%"
* - Les éléments cachés ont une largeur de 0 avec `window.getComputedStyle`. Cette fonction
* prend par défaut le `style.width` du `Composant` et retombe sur `window.getComputedStyle`.
* Voir [this]{@link http://www.foliotek.com/devblog/getting-the-width-of-a-hidden-element-with-jquery-using-width/}
* pour plus d'informations
* - Si vous voulez le style calculé du composant, utilisez {@link Component#currentWidth}
* et {@link {Component#currentHeight}
*
* @fires Component#componentresize
*
* @param {string} widthOrHeight
8 "largeur" ou "hauteur
*
* @param {number|string} [num]
8 Nouvelle dimension
*
* @param {boolean} [skipListeners]
* Sauter le déclenchement de l'événement componentresize
*
* @return {number}
* La dimension lorsqu'elle est obtenue ou 0 si elle n'est pas définie
*/
dimension(widthOrHeight, num, skipListeners) {
if (num !== undefined) {
// Fixe à zéro si null ou littéralement NaN (NaN !== NaN)
if (num === null || num !== num) {
num = 0 ;
}
// Vérifier si l'on utilise la largeur/hauteur css (% ou px) et l'ajuster
if (('' + num).indexOf('%') !== -1 || ('' + num).indexOf('px') !== -1) {
this.el_.style[widthOrHeight] = num ;
else if (num === 'auto') {
this.el_.style[widthOrHeight] = '' ;
} else {
this.el_.style[widthOrHeight] = num + 'px' ;
}
// skipListeners nous permet d'éviter de déclencher l'événement de redimensionnement lorsque l'on définit à la fois la largeur et la hauteur
if (!skipListeners) {
/**
* Déclenché lorsqu'un composant est redimensionné.
*
* @event Component#componentresize (Redimensionnement du composant)
* @type {EventTarget~Event}
*/
this.trigger('componentresize') ;
}
retour ;
}
// La valeur n'est pas définie, elle est donc récupérée
// S'assurer que l'élément existe
if (!this.el_) {
retour 0 ;
}
// Obtenir la valeur de la dimension à partir du style
const val = this.el_.style[widthOrHeight] ;
const pxIndex = val.indexOf('px') ;
if (pxIndex !== -1) {
// Retourne la valeur du pixel sans 'px'
return parseInt(val.slice(0, pxIndex), 10) ;
}
// Pas de px donc utilisation de % ou aucun style n'a été défini, donc retour à offsetWidth/height
// Si le composant a un affichage:none, l'offset renverra 0
// TODO : gérer le style display:none et no dimension en utilisant px
return parseInt(this.el_['offset' + toTitleCase(widthOrHeight)], 10) ;
}
/**
* Obtenir la largeur ou la hauteur calculée de l'élément du composant.
*
* Utilise `window.getComputedStyle`.
*
* @param {string} widthOrHeight
* Une chaîne contenant "width" ou "height". Celui que vous voulez obtenir.
*
* @return {number}
* La dimension demandée ou 0 si rien n'a été défini
* pour cette dimension.
*/
currentDimension(widthOrHeight) {
let computedWidthOrHeight = 0 ;
if (widthOrHeight !== 'width' && widthOrHeight !== 'height') {
lancer une nouvelle erreur ('currentDimension n'accepte que la valeur de la largeur ou de la hauteur') ;
}
computedWidthOrHeight = computedStyle(this.el_, widthOrHeight) ;
// supprimer "px" de la variable et l'analyser comme un entier
computedWidthOrHeight = parseFloat(computedWidthOrHeight) ;
// si la valeur calculée est toujours 0, il est possible que le navigateur mente
// et nous voulons vérifier les valeurs de décalage.
// Ce code s'exécute également lorsque getComputedStyle n'existe pas.
if (computedWidthOrHeight === 0 || isNaN(computedWidthOrHeight)) {
const rule = `offset${toTitleCase(widthOrHeight)}` ;
computedWidthOrHeight = this.el_[rule] ;
}
return computedWidthOrHeight ;
}
/**
* Un objet qui contient les valeurs de largeur et de hauteur du `Composant`
* style calculé. Utilise `window.getComputedStyle`.
*
* @typedef {Objet} Composant~DimensionObjet
*
* @property {number} width
* La largeur du style calculé du `Composant`.
*
* @property {number} height
* La hauteur du style calculé du `Composant`.
*/
/**
* Obtention d'un objet contenant les valeurs calculées de la largeur et de la hauteur de l'image
* l'élément du composant.
*
* Utilise `window.getComputedStyle`.
*
* @return {Component~DimensionObject}
* Les dimensions calculées de l'élément du composant.
*/
currentDimensions() {
retour {
width : this.currentDimension('width'),
height : this.currentDimension('height')
};
}
/**
* Obtenir la largeur calculée de l'élément du composant.
*
* Utilise `window.getComputedStyle`.
*
* @return {number}
* La largeur calculée de l'élément du composant.
*/
currentWidth() {
return this.currentDimension('width') ;
}
/**
* Obtenir la hauteur calculée de l'élément du composant.
*
* Utilise `window.getComputedStyle`.
*
* @return {number}
* La hauteur calculée de l'élément du composant.
*/
currentHeight() {
return this.currentDimension('height') ;
}
/**
* Mettre l'accent sur ce composant
*/
focus() {
this.el_.focus() ;
}
/**
* Retirer le focus de ce composant
*/
blur() {
this.el_.blur() ;
}
/**
* Lorsque ce composant reçoit un événement `keydown` qu'il ne traite pas,
* il transmet l'événement au lecteur pour qu'il le traite.
*
* @param {EventTarget~Event} event
* L'événement `keydown` qui a provoqué l'appel de cette fonction.
*/
handleKeyDown(event) {
if (this.player_) {
// Nous n'arrêtons la propagation qu'ici car nous voulons que les événements non gérés tombent
// vers le navigateur. Exclure l'onglet pour la capture de l'attention.
if (!keycode.isEventKey(event, 'Tab')) {
event.stopPropagation() ;
}
this.player_.handleKeyDown(event) ;
}
}
/**
* De nombreux composants avaient une méthode `handleKeyPress`, qui était mal utilisée
* nommé parce qu'il a écouté un événement `keydown`. Le nom de cette méthode est désormais
* est délégué à `handleKeyDown`. Cela signifie que toute personne appelant `handleKeyPress`
* ne verront pas leurs appels de méthode cesser de fonctionner.
*
* @param {EventTarget~Event} event
* L'événement qui a provoqué l'appel de cette fonction.
*/
handleKeyPress(event) {
this.handleKeyDown(event) ;
}
/**
* Émettre un événement "tap" lorsque la prise en charge d'un événement tactile est détectée. Il est utilisé pour
* permettent de basculer les commandes en tapant sur la vidéo. Ils sont activés
* parce que chaque sous-composant aurait des frais généraux supplémentaires dans le cas contraire.
*
* @private
* composant @fires#tap
* @listens Composant#touchstart
* @listens Composant#touchmove
* @listens Composant#touchleave
* @listens Component#touchcancel
* @listens Component#touchend
*/
emitTapEvents() {
// Suivre l'heure de début afin de pouvoir déterminer la durée du contact
let touchStart = 0 ;
let firstTouch = null ;
// Mouvement maximal autorisé lors d'un événement tactile pour être considéré comme un tapotement
// D'autres librairies populaires utilisent entre 2 (hammer.js) et 15,
// donc 10 semble être un chiffre rond et agréable.
const tapMovementThreshold = 10 ;
// La longueur maximale d'une touche peut être considérée comme une prise
const touchTimeThreshold = 200 ;
laisser couldBeTap ;
this.on('touchstart', function(event) {
// S'il y a plus d'un doigt, ne considérez pas cela comme un clic
if (event.touches.length === 1) {
// Copie de la pageX/pageY de l'objet
firstTouch = {
pageX : event.touches[0].pageX,
pageY : event.touches[0].pageY
};
// Enregistrer l'heure de début afin de pouvoir détecter un tapotement par rapport à un "touch and hold" (toucher et maintenir)
touchStart = window.performance.now() ;
// Réinitialiser le suivi de couldBeTap
couldBeTap = true ;
}
}) ;
this.on('touchmove', function(event) {
// S'il y a plus d'un doigt, ne considérez pas cela comme un clic
if (event.touches.length > 1) {
couldBeTap = false ;
} else if (firstTouch) {
// Certains appareils refusent les mouvements tactiles, sauf s'il s'agit d'une légère pression.
// Ainsi, si nous ne nous déplaçons que sur une petite distance, il peut toujours s'agir d'un robinet
const xdiff = event.touches[0].pageX - firstTouch.pageX ;
const ydiff = event.touches[0].pageY - firstTouch.pageY ;
const touchDistance = Math.sqrt(xdiff * xdiff + ydiff * ydiff) ;
if (touchDistance > tapMovementThreshold) {
couldBeTap = false ;
}
}
}) ;
const noTap = function() {
couldBeTap = false ;
};
// TODO : Écouter la cible originale. http://youtu.be/DujfpXOKUp8?t=13m8s
this.on('touchleave', noTap) ;
this.on('touchcancel', noTap) ;
// Lorsque le contact se termine, mesurez la durée du contact et déclenchez l'action appropriée
// événement
this.on('touchend', function(event) {
firstTouch = null ;
// Ne procéder que si l'événement touchmove/leave/cancel ne s'est pas produit
if (couldBeTap === true) {
// Mesure la durée du contact
const touchTime = window.performance.now() - touchStart ;
// S'assurer que le toucher était inférieur au seuil pour être considéré comme un tapotement
if (touchTime < touchTimeThreshold) {
// Ne laissez pas le navigateur transformer ceci en un clic
event.preventDefault() ;
/**
* Déclenché lorsqu'un `Composant` est touché.
*
* @event Composant#tap
* @type {EventTarget~Event}
*/
this.trigger('tap') ;
// Il peut être utile de copier l'objet de l'événement touchend et de modifier l'objet de l'événement touchend
// type de tapotement, si les autres propriétés de l'événement ne sont pas exactes après
// Events.fixEvent s'exécute (par exemple event.target)
}
}
}) ;
}
/**
* Cette fonction signale l'activité de l'utilisateur lorsque des événements tactiles se produisent. Cela peut se traduire par
* désactivé par tout sous-composant qui souhaite que les événements tactiles agissent d'une autre manière.
*
* Signaler l'activité tactile de l'utilisateur lorsque des événements tactiles se produisent. L'activité de l'utilisateur est utilisée pour
* déterminent quand les contrôles doivent être affichés/masqués. C'est simple quand il s'agit de la souris
* car tout événement de souris devrait afficher les contrôles. Nous capturons donc la souris
* qui remontent jusqu'au joueur et signalent l'activité lorsque cela se produit.
* Avec les événements tactiles, ce n'est pas aussi simple que de faire basculer le lecteur de `touchstart` et de `touchend`
* des contrôles. Les événements tactiles ne peuvent donc pas non plus nous aider au niveau du joueur.
*
* L'activité de l'utilisateur est vérifiée de manière asynchrone. Ce qui pourrait donc se produire, c'est un événement de robinetterie
* sur la vidéo désactive les commandes. Ensuite, l'événement `touchend` fait boule de neige jusqu'à
* le joueur. Qui, s'il signalait l'activité de l'utilisateur, ferait basculer les contrôles à droite
* retourner à la page d'accueil. Nous ne voulons pas non plus empêcher complètement les événements tactiles de bouillonner.
* En outre, un événement `touchmove` et toute autre chose qu'un tap, ne devrait pas tourner
* les commandes sont remises en place.
*
* @listens Composant#touchstart
* @listens Composant#touchmove
* @listens Component#touchend
* @listens Component#touchcancel
*/
enableTouchActivity() {
// Ne pas poursuivre si le lecteur racine ne prend pas en charge le signalement de l'activité de l'utilisateur
if (!this.player() || !this.player().reportUserActivity) {
retour ;
}
// listener pour signaler que l'utilisateur est actif
const report = Fn.bind(this.player(), this.player().reportUserActivity) ;
let touchHolding ;
this.on('touchstart', function() {
rapport() ;
// Tant qu'ils touchent l'appareil ou qu'ils ont la souris enfoncée,
// nous les considérons comme actifs même s'ils ne bougent pas le doigt ou la souris.
// Nous voulons donc continuer à mettre à jour qu'ils sont actifs
this.clearInterval(touchHolding) ;
// rapport au même intervalle que activityCheck
touchHolding = this.setInterval(report, 250) ;
}) ;
const touchEnd = function(event) {
rapport() ;
// arrêter l'intervalle qui maintient l'activité si la touche est maintenue
this.clearInterval(touchHolding) ;
};
this.on('touchmove', report) ;
this.on('touchend', touchEnd) ;
this.on('touchcancel', touchEnd) ;
}
/**
* Un callback qui n'a pas de paramètres et qui est lié au contexte du `Composant`.
*
* @callback Component~GenericCallback
* @this Component
*/
/**
* Crée une fonction qui s'exécute après un délai de `x` millisecondes. Cette fonction est une
* enveloppe autour de `window.setTimeout`. Il y a plusieurs raisons de l'utiliser
* à la place :
* 1. Il est effacé par {@link Component#clearTimeout} lorsque
* {@link Component#dispose} est appelé.
* 2. La fonction callback sera transformée en {@link Component~GenericCallback}
*
* > Remarque : Vous ne pouvez pas utiliser `window.clearTimeout` sur l'identifiant renvoyé par cette fonction. Cette
* fera en sorte que l'auditeur de la mise au rebut ne soit pas nettoyé ! Veuillez utiliser
* {@link Component#clearTimeout} ou {@link Component#dispose} à la place.
*
* @param {Composant~GenericCallback} fn
* La fonction qui sera exécutée après `timeout`.
*
* @param {number} timeout
* Délai d'attente en millisecondes avant l'exécution de la fonction spécifiée.
*
* @return {number}
* Renvoie un identifiant de délai d'attente qui est utilisé pour identifier le délai d'attente. Il peut également
* est utilisé dans {@link Component#clearTimeout} pour effacer le délai d'attente que les
* a été fixé.
*
* @listens Component#dispose
* @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/WindowTimers/setTimeout}
*/
setTimeout(fn, timeout) {
// les déclarer comme variables afin qu'elles soient correctement disponibles dans la fonction timeout
// eslint-disable-next-line
var timeoutId, disposeFn ;
fn = Fn.bind(this, fn) ;
this.clearTimersOnDispose_() ;
timeoutId = window.setTimeout(() => {
if (this.setTimeoutIds_.has(timeoutId)) {
this.setTimeoutIds_.delete(timeoutId) ;
}
fn() ;
}, timeout) ;
this.setTimeoutIds_.add(timeoutId) ;
return timeoutId ;
}
/**
* Efface un timeout créé via `window.setTimeout` ou
* {@link Component#setTimeout}. Si vous définissez un délai d'attente via {@link Component#setTimeout}
* utiliser cette fonction au lieu de `window.clearTimout`. Si vous ne vous débarrassez pas de votre
* ne sera pas nettoyé avant {@link Component#dispose} !
*
* @param {number} timeoutId
* L'identifiant du délai d'attente à effacer. La valeur de retour de
* {@link Component#setTimeout} ou `window.setTimeout`.
*
* @return {number}
* Renvoie l'identifiant du délai d'attente qui a été supprimé.
*
* @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/WindowTimers/clearTimeout}
*/
clearTimeout(timeoutId) {
if (this.setTimeoutIds_.has(timeoutId)) {
this.setTimeoutIds_.delete(timeoutId) ;
window.clearTimeout(timeoutId) ;
}
return timeoutId ;
}
/**
* Crée une fonction qui s'exécute toutes les `x` millisecondes. Cette fonction est une enveloppe
* autour de `window.setInterval`. Il y a cependant quelques raisons d'utiliser celle-ci à la place.
* 1. Il est effacé par {@link Component#clearInterval} lorsque
* {@link Component#dispose} est appelé.
* 2. La fonction callback sera un {@link Component~GenericCallback}
*
* @param {Composant~GenericCallback} fn
* La fonction à exécuter toutes les `x` secondes.
*
* @param {number} interval
* Exécute la fonction spécifiée toutes les `x` millisecondes.
*
* @return {number}
* Renvoie un identifiant qui peut être utilisé pour identifier l'intervalle. Il peut également être utilisé dans
* {@link Component#clearInterval} pour effacer l'intervalle.
*
* @listens Component#dispose
* @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/WindowTimers/setInterval}
*/
setInterval(fn, interval) {
fn = Fn.bind(this, fn) ;
this.clearTimersOnDispose_() ;
const intervalId = window.setInterval(fn, interval) ;
this.setIntervalIds_.add(intervalId) ;
return intervalId ;
}
/**
* Efface un intervalle créé via `window.setInterval` ou
* {@link Component#setInterval}. Si vous définissez une valeur entière via {@link Component#setInterval}
* utiliser cette fonction au lieu de `window.clearInterval`. Si vous ne vous débarrassez pas de votre
* ne sera pas nettoyé avant {@link Component#dispose} !
*
* @param {number} intervalId
* L'identifiant de l'intervalle à effacer. La valeur de retour de
* {@link Component#setInterval} ou `window.setInterval`.
*
* @return {number}
* Renvoie l'identifiant de l'intervalle qui a été effacé.
*
* @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/WindowTimers/clearInterval}
*/
clearInterval(intervalId) {
if (this.setIntervalIds_.has(intervalId)) {
this.setIntervalIds_.delete(intervalId) ;
window.clearInterval(intervalId) ;
}
return intervalId ;
}
/**
* Met en file d'attente un rappel à transmettre à requestAnimationFrame (rAF), mais
* avec quelques bonus supplémentaires :
*
* - Les navigateurs qui ne prennent pas en charge rAF sont pris en charge par le système
* {@link Component#setTimeout}.
*
* - Le rappel est transformé en un {@link Component~GenericCallback} (c'est-à-dire
* lié au composant).
*
* - L'annulation automatique du rappel rAF est gérée si le composant
* est éliminé avant d'être appelé.
*
* @param {Composant~GenericCallback} fn
* Une fonction qui sera liée à ce composant et exécutée simplement
* avant le prochain repeint du navigateur.
*
* @return {number}
* Renvoie un ID rAF qui est utilisé pour identifier le délai d'attente. Il peut
* peut également être utilisé dans {@link Component#cancelAnimationFrame} pour annuler une animation
* le rappel de l'image d'animation.
*
* @listens Component#dispose
* @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame}
*/
requestAnimationFrame(fn) {
// Revenir à l'utilisation d'une minuterie.
if (!this.supportsRaf_) {
return this.setTimeout(fn, 1000 / 60) ;
}
this.clearTimersOnDispose_() ;
// les déclarer en tant que variables afin qu'elles soient correctement disponibles dans la fonction rAF
// eslint-disable-next-line
var id ;
fn = Fn.bind(this, fn) ;
id = window.requestAnimationFrame(() => {
if (this.rafIds_.has(id)) {
this.rafIds_.delete(id) ;
}
fn() ;
}) ;
this.rafIds_.add(id) ;
retourner l'id ;
}
/**
* Demande une image d'animation, mais seulement une animation nommée
* sera mise en file d'attente. Il n'y en aura jamais d'autre jusqu'à ce que
* le précédent se termine.
*
* @param {string} name
* Le nom à donner à ce cadre d'animation
*
* @param {Composant~GenericCallback} fn
* Une fonction qui sera liée à ce composant et exécutée simplement
* avant le prochain repeint du navigateur.
*/
requestNamedAnimationFrame(name, fn) {
if (this.namedRafs_.has(name)) {
retour ;
}
this.clearTimersOnDispose_() ;
fn = Fn.bind(this, fn) ;
const id = this.requestAnimationFrame(() => {
fn() ;
if (this.namedRafs_.has(name)) {
this.namedRafs_.delete(name) ;
}
}) ;
this.namedRafs_.set(name, id) ;
nom de retour ;
}
/**
* Annule une image d'animation nommée en cours, si elle existe.
*
* @param {string} name
* Le nom de la fenêtre d'animation à annuler.
*/
cancelNamedAnimationFrame(name) {
if (!this.namedRafs_.has(name)) {
retour ;
}
this.cancelAnimationFrame(this.namedRafs_.get(name)) ;
this.namedRafs_.delete(name) ;
}
/**
* Annule un rappel en file d'attente passé à {@link Component#requestAnimationFrame}
* (rAF).
*
* Si vous mettez en file d'attente un rappel rAF via {@link Component#requestAnimationFrame},
* utiliser cette fonction au lieu de `window.cancelAnimationFrame`. Si ce n'est pas le cas,
* votre auditeur dispose ne sera pas nettoyé avant {@link Component#dispose} !
*
* @param {number} id
* Le rAF ID à dégager. La valeur de retour de {@link Component#requestAnimationFrame}.
*
* @return {number}
* Renvoie l'identifiant rAF qui a été effacé.
*
* @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/window/cancelAnimationFrame}
*/
cancelAnimationFrame(id) {
// Revenir à l'utilisation d'une minuterie.
if (!this.supportsRaf_) {
return this.clearTimeout(id) ;
}
if (this.rafIds_.has(id)) {
this.rafIds_.delete(id) ;
window.cancelAnimationFrame(id) ;
}
retourner l'id ;
}
/**
* Une fonction pour configurer `requestAnimationFrame`, `setTimeout`,
* et `setInterval`, qui s'efface lors de l'élimination.
*
* > Auparavant, chaque minuterie ajoutait et supprimait des récepteurs de mise au rebut de son propre chef.
* Pour de meilleures performances, il a été décidé de les regrouper et d'utiliser `Set`s
* pour suivre les identifiants de temporisation en suspens.
*
* @private
*/
clearTimersOnDispose_() {
if (this.clearingTimersOnDispose_) {
retour ;
}
this.clearingTimersOnDispose_ = true ;
this.one('dispose', () => {
[
['namedRafs_', 'cancelNamedAnimationFrame'],
['rafIds_', 'cancelAnimationFrame'],
['setTimeoutIds_', 'clearTimeout'],
['setIntervalIds_', 'clearInterval']
].forEach(([idName, cancelName]) => {
// pour un `Set` la clé sera en fait la valeur à nouveau
// donc forEach((val, val) =>` mais pour les cartes nous voulons utiliser
// la clé.
this[idName].forEach((val, key) => this[cancelName](key)) ;
}) ;
this.clearingTimersOnDispose_ = false ;
}) ;
}
/**
* Enregistrer un `Composant` avec `videojs` en donnant le nom et le composant.
*
* > NOTE : {@link Tech}s ne doit pas être enregistré en tant que `Component`. {@link Tech}s
* doit être enregistré en utilisant {@link Tech.registerTech} ou
* {@link videojs:videojs.registerTech}.
*
* > NOTE : Cette fonction est également visible sur videojs sous la forme suivante
* {@link videojs:videojs.registerComponent}.
*
* @param {string} name
* Le nom du `Composant` à enregistrer.
*
* @param {Composant} Composant à enregistrer
* La classe `Component` à enregistrer.
*
* @return {Component}
* Le `Composant` qui a été enregistré.
*/
static registerComponent(name, ComponentToRegister) {
if (typeof name !== 'string' || !name) {
throw new Error(`Nom de composant illégal, "${nom}" ; doit être une chaîne non vide.`) ;
}
const Tech = Component.getComponent('Tech') ;
// Nous devons nous assurer que cette vérification n'est effectuée que si Tech a été enregistré.
const isTech = Tech && Tech.isTech(ComponentToRegister) ;
const isComp = Component === ComponentToRegister ||
Component.prototype.isPrototypeOf(ComponentToRegister.prototype) ;
if (isTech || !isComp) {
laisser la raison ;
if (isTech) {
reason = 'techs must be registered using Tech.registerTech()' ;
} else {
reason = "must be a Component subclass" ;
}
lancer une nouvelle erreur (`Composant illégal, "${nom}" ; ${motif}.`) ;
}
nom = toTitleCase(nom) ;
if (!Component.components_) {
Component.components_ = {} ;
}
const Player = Component.getComponent('Player') ;
if (name === 'Player' && Player && Player.players) {
const players = Player.players ;
const playerNames = Object.keys(players) ;
// Si nous avons des joueurs qui ont été éliminés, leur nom sera toujours
// dans Players.players. Nous devons donc faire une boucle et vérifier que la valeur
// pour chaque élément n'est pas nul. Cela permet d'enregistrer le composant Player
// après que tous les joueurs ont été éliminés ou avant qu'aucun n'ait été créé.
si (joueurs &&
playerNames.length > 0 &&
playerNames.map((pname) => players[pname]).every(Boolean)) {
lance une nouvelle erreur ('Impossible d'enregistrer le composant Player après la création du joueur.') ;
}
}
Component.components_[name] = ComponentToRegister ;
Component.components_[toLowerCase(name)] = ComponentToRegister ;
return ComponentToRegister ;
}
/**
* Récupère un `Composant` en se basant sur le nom avec lequel il a été enregistré.
*
* @param {string} name
* Le nom du composant à obtenir.
*
* @return {Component}
* Le `Composant` qui a été enregistré sous le nom donné.
*/
static getComponent(name) {
if (!name || !Component.components_) {
retour ;
}
return Component.components_[name] ;
}
}
/**
* Si ce composant supporte ou non `requestAnimationFrame`.
*
* Elle est exposée principalement à des fins de test.
*
* @private
* @type {Booléen}
*/
Component.prototype.supportsRaf_ = typeof window.requestAnimationFrame === 'function' &&
typeof window.cancelAnimationFrame === 'function' ;
Component.registerComponent('Component', Component) ;
exporter le composant par défaut ;