/**
 * Componente de reproductor: clase base para todos los objetos de la interfaz de usuario
 *
 * @archivo componente.js
 * /
importar ventana desde 'global/window';
importar eventos desde './mixins/evented';
importar con estado desde './mixins/stateful';
importar * como Dom desde './utils/dom.js';
importar * como Fn desde './utils/fn.js';
importar * como Guid desde './utils/guid.js';
importar {toTitleCase, toLowerCase} desde './utils/string-cases.js';
importar mergeOptions desde './utils/merge-options.js';
importar estilo computado desde './utils/computed-style';
importar mapa desde './utils/map.js';
importar conjunto desde './utils/set.js';
importar código clave desde 'código clave';

/**
 * Clase base para todos los componentes de la interfaz de usuario.
 * Los componentes son objetos de interfaz de usuario que representan tanto un objeto javascript como un elemento
 * en el DOM. Pueden ser hijos de otros componentes y pueden tener
 * los propios niños.
 *
 * Los componentes también pueden usar métodos de {@link EventTarget}
 * /
componente de clase {

  /**
   * Una devolución de llamada que se llama cuando un componente está listo. no tiene ninguna
   * Se ignorarán los parámetros y cualquier valor de devolución de llamada.
   *
   * Componente @callback~ReadyCallback
   * @este componente
   * /

  /**
   * Crea una instancia de esta clase.
   *
   * @param {Jugador} jugador
   * El `Jugador` al que se debe adjuntar esta clase.
   *
   * @param {Objeto} [opciones]
   * El almacén de clave/valor de las opciones de componentes.
   *
   * @param {Objeto[]} [opciones.niños]
   * Una matriz de objetos secundarios para inicializar este componente. Los objetos infantiles tienen
   * una propiedad de nombre que se usará si se necesita más de un componente del mismo tipo
   * agregado.
   *
   * @param {cadena} [opciones.className]
   * Una lista de clases separadas por espacios o clases para agregar el componente
   *
   * @param {Componente~ReadyCallback} [listo]
   * Función que se llama cuando el 'Componente' está listo.
   * /
  constructor(jugador, opciones, listo) {

    // El componente podría ser el propio reproductor y no podemos pasar `this` a super
    si (!jugador && este juego) {
      este.jugador_ = jugador = esto; // eslint-disable-line
    } else {
      este.jugador_ = jugador;
    }

    this.isDisposed_ = false;

    // Mantener la referencia al componente principal a través del método `addChild`
    this.parentComponent_ = null;

    // Haga una copia de prototipo.opciones_ para protegerse contra la anulación de valores predeterminados
    this.options_ = mergeOptions({}, this.options_);

    // Opciones actualizadas con opciones suministradas
    opciones = this.options_ = mergeOptions(this.options_, options);

    // Obtener ID de opciones o elemento de opciones si se proporciona uno
    this.id_ = opciones.id || (opciones.el && opciones.el.id);

    // Si no hubo ID de las opciones, generar uno
    si (!este.id_) {
      // No requiere la función de ID de jugador en el caso de jugadores ficticios
      const id = jugador && jugador.id && jugador.id() || 'no_jugador';

      this.id_ = `${id}_component_${Guid.newGUID()}`;
    }

    this.name_ = opciones.nombre || nulo;

    // Crear elemento si no se proporcionó uno en las opciones
    if (opciones.el) {
      this.el_ = opciones.el;
    } else if (opciones.createEl !== falso) {
      esto.el_ = esto.createEl();
    }

    if (opciones.nombreClase && esto.el_) {
      options.className.split(' ').forEach(c => esto.addClass(c));
    }

    // si evented es cualquier cosa menos falso, queremos mezclarlo en evented
    if (opciones.evento !== falso) {
      // Hacer de este un objeto con eventos y usar `el_`, si está disponible, como su bus de eventos
      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);

    esto.niños_ = [];
    this.childIndex_ = {};
    this.childNameIndex_ = {};

    this.setTimeoutIds_ = nuevo Conjunto();
    this.setIntervalIds_ = nuevo Conjunto();
    this.rafIds_ = nuevo Conjunto();
    this.namedRafs_ = nuevo Mapa();
    this.clearingTimersOnDispose_ = falso;

    // Agregue cualquier componente secundario en las opciones
    if (opciones.initChildren !== falso) {
      esto.initChildren();
    }

    // No quiero desencadenar listo aquí o irá antes de que init sea realmente
    // terminado para todos los niños que ejecutan este constructor
    esto.listo(listo);

    if (opciones.reportTouchActivity!== false) {
      this.enableTouchActivity();
    }

  }

  /**
   * Deseche el `Componente` y todos los componentes secundarios.
   *
   * Componente @fires#dispose
   *
   * @param {Objeto} opciones
   * @param {Element} options.originalEl elemento con el que reemplazar el elemento jugador
   * /
  disponer(opciones = {}) {

    // Rescatar si el componente ya ha sido desechado.
    if (this.isDisposed_) {
      devolver;
    }

    if (this.readyQueue_) {
      this.readyQueue_.longitud = 0;
    }

    /**
     * Se activa cuando se desecha un 'Componente'.
     *
     * @event Component#dispose
     * @type {Objetivo del evento~Evento}
     *
     * @propiedad {booleano} [burbujas=falso]
     * establecido en falso para que el evento de eliminación no
     * burbujear
     * /
    this.trigger({tipo: 'desechar', burbujas: falso});

    this.isDisposed_ = true;

    // Deshágase de todos los niños.
    si (esto.niños_) {
      for (sea i = this.children_.length - 1; i > = 0; i--) {
        if (esto.niños_[i].dispose) {
          this.children_[i].dispose();
        }
      }
    }

    // Eliminar referencias secundarias
    esto.niños_ = nulo;
    este.childIndex_ = nulo;
    this.childNameIndex_ = nulo;

    this.parentComponent_ = null;

    si (esto.el_) {
      // Eliminar elemento de DOM
      if (this.el_.parentNode) {
        if (opciones.restoreEl) {
          this.el_.parentNode.replaceChild(options.restoreEl, this.el_);
        } else {
          this.el_.parentNode.removeChild(this.el_);
        }
      }

      esto.el_ = nulo;
    }

    // elimina la referencia al jugador después de deshacerse del elemento
    este.jugador_ = nulo;
  }

  /**
   * Determinar si este componente ha sido desechado o no.
   *
   * @return {booleano}
   * Si el componente ha sido desechado, será `verdadero`. De lo contrario, `falso`.
   * /
  se desecha() {
    return Boolean(this.isDisposed_);
  }

  /**
   * Devolver el {@link Player} al que se adjuntó el `Componente`.
   *
   * @return {Jugador}
   * El jugador al que se ha adjuntado este `Componente`.
   * /
  jugador() {
    devolver este.jugador_;
  }

  /**
   * Fusión profunda de objetos de opciones con nuevas opciones.
   * > Nota: Cuando tanto `obj` como `options` contienen propiedades cuyos valores son objetos.
   * Las dos propiedades se fusionan usando {@link module:mergeOptions}
   *
   * @param {Objeto} obj
   * El objeto que contiene nuevas opciones.
   *
   * @return {Objeto}
   * Un nuevo objeto de `this.options_` y `obj` se fusionaron.
   * /
  opciones (obj) {
    si (! objeto) {
      devuelve esto.opciones_;
    }

    this.options_ = mergeOptions(this.options_, obj);
    devuelve esto.opciones_;
  }

  /**
   * Obtener el elemento DOM del 'Componente'
   *
   * @return {Elemento}
   * El elemento DOM para este `Componente`.
   * /
  el() {
    devolver esto.el_;
  }

  /**
   * Crear el elemento DOM `Component`.
   *
   * @param {cadena} [nombre de etiqueta]
   * Tipo de nodo DOM del elemento. por ejemplo, 'div'
   *
   * @param {Objeto} [propiedades]
   * Un objeto de propiedades que debe establecerse.
   *
   * @param {Objeto} [atributos]
   * Un objeto de atributos que debe establecerse.
   *
   * @return {Elemento}
   * El elemento que se crea.
   * /
  createEl(tagName, propiedades, atributos) {
    return Dom.createEl(tagName, propiedades, atributos);
  }

  /**
   * Localizar una cadena dada la cadena en inglés.
   *
   * Si se proporcionan tokens, intentará ejecutar un reemplazo de token simple en la cadena proporcionada.
   * Los tokens que busca se parecen a `{1}` con el índice indexado en 1 en la matriz de tokens.
   *
   * Si se proporciona un `defaultValue`, lo usará sobre `string`,
   * si no se encuentra un valor en los archivos de idioma proporcionados.
   * Esto es útil si desea tener una clave descriptiva para el reemplazo de tokens
   * pero tiene una cadena localizada sucinta y no requiere que se incluya `en.json`.
   *
   * Actualmente, se utiliza para el tiempo de la barra de progreso.
   * ```js
   * {
   * "tiempo de la barra de progreso: hora actual = {1} duración = {2}": "{1} de {2}"
   * }
   * ```
   * Entonces se usa así:
   * ```js
   * this.localize('tiempo de la barra de progreso: horaActual={1} duración{2}',
   * [este.jugador_.tiempoActual(), este.jugador_.duración()],
   * '{1 de 2}');
   * ```
   *
   * Lo que genera algo como: `01:23 de 24:56`.
   *
   *
   * @param {cadena} cadena
   * La cadena para localizar y la clave para buscar en los archivos de idioma.
   * @param {cadena[]} [tokens]
   * Si el elemento actual tiene reemplazos de fichas, proporcione las fichas aquí.
   * @param {cadena} [valor predeterminado]
   * Por defecto es `cadena`. Puede ser un valor predeterminado para usar para el reemplazo del token
   * si la clave de búsqueda debe estar separada.
   *
   * @return {cadena}
   * La cadena localizada o, si no existe localización, la cadena en inglés.
   * /
  localizar (cadena, tokens, valor predeterminado = cadena) {

    código constante = this.player_.language && este.jugador_.idioma();
    const idiomas = this.player_.languages && este.jugador_.idiomas();
    const idioma = idiomas && idiomas[código];
    const códigoprimario = código && código.split('-')[0];
    const PrimaryLang = idiomas && idiomas[primaryCode];

    let localizadoString = defaultValue;

    si (idioma && idioma[cadena]) {
      cadenaLocalizada = idioma[cadena];
    } más si (primaryLang && idiomaprimario[cadena]) {
      cadenaLocalizada = idiomaPrincipal[cadena];
    }

    si (fichas) {
      cadenaLocalizada = CadenaLocalizada.replace(/\{(\d+)\}/g, función(coincidencia, índice) {
        valor const = fichas [índice - 1];
        sea ret = valor;

        if (tipo de valor === 'indefinido') {
          ret = partido;
        }

        volver ret;
      });
    }

    return cadena localizada;
  }

  /**
   * Maneja el cambio de idioma para el jugador en componentes. Debe ser anulado por subcomponentes.
   *
   * @abstracto
   * /
  manejarcambioIdioma() {}

  /**
   * Devuelve el elemento DOM del 'Componente'. Aquí es donde se insertan los niños.
   * Por lo general, será el mismo que el elemento devuelto en {@link Component#el}.
   *
   * @return {Elemento}
   * El elemento de contenido para este `Componente`.
   * /
  contenidoEl() {
    devolver este.contentEl_ || esto.el_;
  }

  /**
   * Obtener el ID de este 'Componente'
   *
   * @return {cadena}
   * El id de este `Componente`
   * /
  identificación() {
    devolver este.id_;
  }

  /**
   * Obtener el nombre del 'Componente'. El nombre se usa para hacer referencia al `Componente`
   * y se establece durante el registro.
   *
   * @return {cadena}
   * El nombre de este `Componente`.
   * /
  nombre() {
    devuelve este.nombre_;
  }

  /**
   * Obtenga una matriz de todos los componentes secundarios
   *
   * @return {Array}
   * Los niños
   * /
  niños() {
    devolver esto.niños_;
  }

  /**
   * Devuelve el 'Componente' secundario con el 'id' dado.
   *
   * @param {cadena} id
   * La identificación del 'Componente' secundario que se va a obtener.
   *
   * @return {Componente|indefinido}
   * El 'Componente' secundario con el 'id' dado o indefinido.
   * /
  getChildById(id) {
    devolver esto.childIndex_[id];
  }

  /**
   * Devuelve el 'Componente' secundario con el 'nombre' dado.
   *
   * @param {cadena} nombre
   * El nombre del 'Componente' secundario que se va a obtener.
   *
   * @return {Componente|indefinido}
   * El 'Componente' secundario con el 'nombre' dado o indefinido.
   * /
  getChild(nombre) {
    si (! nombre) {
      devolver;
    }

    devuelve this.childNameIndex_[nombre];
  }

  /**
   * Devuelve el 'Componente' descendiente que sigue al dado
   * `nombres` descendientes. Por ejemplo ['foo', 'bar', 'baz'] sería
   * intente obtener 'foo' en el componente actual, 'bar' en 'foo'
   * componente y 'baz' en el componente 'barra' y devuelve indefinido
   * si alguno de esos no existe.
   *
   * @param {...cadena[]|...cadena} nombres
   * El nombre del 'Componente' secundario que se va a obtener.
   *
   * @return {Componente|indefinido}
   * El 'Componente' descendiente que sigue al descendiente dado
   * `nombres` o indefinido.
   * /
  getDescendiente(...nombres) {
    // aplana el argumento de la matriz en la matriz principal
    nombres = nombres.reduce((acc, n) => acc.concat(n), []);

    let currentChild = esto;

    para (sea i = 0; i < nombres.longitud; i++) {
      hijoActual = HijoActual.getChild(nombres[i]);

      if (!niñoactual || !niñoactual.getChild) {
        devolver;
      }
    }

    volver hijo actual;
  }

  /**
   * Agregue un 'Componente' secundario dentro del 'Componente' actual.
   *
   *
   * @param {cadena|Componente} niño
   * El nombre o instancia de un niño para agregar.
   *
   * @param {Objeto} [opciones={}]
   * El almacén de claves/valores de opciones que se pasarán a los hijos de
   * el niño.
   *
   * @param {número} [índice=este.niños_.longitud]
   * El índice para intentar agregar un niño.
   *
   * @return {Componente}
   * El `Componente` que se agrega como hijo. Cuando se usa una cuerda, el
   * `Componente` será creado por este proceso.
   * /
  addChild(child, options = {}, index = this.children_.length) {
    dejar componente;
    let nombreComponente;

    // Si el hijo es una cadena, crea un componente con opciones
    if (tipo de niño === 'cadena') {
      nombreComponente = toTitleCase(hijo);

      const nombreClaseComponente = opciones.claseComponente || Nombre del componente;

      // Establecer nombre a través de opciones
      opciones.nombre = nombreComponente;

      // Crea un nuevo objeto & elemento para este conjunto de controles
      // Si no hay .player_, este es un jugador
      const ComponentClass = Component.getComponent(componentClassName);

      if (!ClaseComponente) {
        throw new Error(`El componente ${componentClassName} no existe`);
      }

      // los datos almacenados directamente en el objeto videojs pueden ser
      // identificado erróneamente como un componente para retener
      // compatibilidad hacia atrás con 4.x. verifique para asegurarse de que
      // la clase de componente puede ser instanciada.
      if (tipo de clase de componente! == 'función') {
        devolver nulo;
      }

      componente = new ComponentClass(this.player_ || this, options);

    // hijo es una instancia de componente
    } else {
      componente = hijo;
    }

    si (componente.parentComponent_) {
      componente.parentComponent_.removeChild(componente);
    }
    this.children_.splice(índice, 0, componente);
    componente.parentComponent_ = esto;

    if (tipo de componente.id === 'función') {
      this.childIndex_[componente.id()] = componente;
    }

    // Si no se usó un nombre para crear el componente, verifique si podemos usar el
    // función de nombre del componente
    nombre del componente = nombre del componente || (Nombre del componente && toTitleCase(componente.nombre()));

    si (nombre del componente) {
      this.childNameIndex_[componentName] = componente;
      this.childNameIndex_[toLowerCase(componentName)] = componente;
    }

    // Agregue el elemento del objeto de la interfaz de usuario al contenedor div (caja)
    // No es necesario tener un elemento
    if (tipodecomponente.el === 'función' && componente.el()) {
      // Si inserta antes de un componente, inserte antes del elemento de ese componente
      let refNode = nulo;

      if (esto.niños_[índice + 1]) {
        // La mayoría de los niños son componentes, pero la tecnología de video es un elemento HTML
        if (esto.niños_[índice + 1].el_) {
          refNode = this.children_[índice + 1].el_;
        } else if (Dom.isEl(this.children_[index + 1])) {
          refNode = this.children_[índice + 1];
        }
      }

      this.contentEl().insertBefore(component.el(), refNode);
    }

    // Regrese para que pueda almacenarse en el objeto principal si lo desea.
    componente de retorno;
  }

  /**
   * Eliminar un 'Componente' secundario de la lista de elementos secundarios de este 'Componente'. también elimina
   * el elemento `Component` secundario de este elemento `Component`.
   *
   * @param {Componente} componente
   * El `Componente` hijo a eliminar.
   * /
  removeChild(componente) {
    if (tipo de componente === 'cadena') {
      componente = this.getChild(componente);
    }

    if (!componente || !este.niños_) {
      devolver;
    }

    let childFound = falso;

    for (sea i = this.children_.length - 1; i > = 0; i--) {
      if (este.niños_[i] === componente) {
        niñoEncontrado = verdadero;
        esto.niños_.empalme(i, 1);
        romper;
      }
    }

    if (!niñoEncontrado) {
      devolver;
    }

    componente.parentComponent_ = nulo;

    this.childIndex_[componente.id()] = nulo;
    this.childNameIndex_[toTitleCase(component.name())] = null;
    this.childNameIndex_[toLowerCase(component.name())] = null;

    const compEl = componente.el();

    si (compEl && compEl.parentNode === this.contentEl()) {
      this.contentEl().removeChild(componente.el());
    }
  }

  /**
   * Agregue e inicialice los 'Componentes' secundarios predeterminados en función de las opciones.
   * /
  initChildren() {
    const niños = esto.opciones_.niños;

    si (hijos) {
      // `este` es `padre`
      const parentOptions = this.options_;

      const handleAdd = (hijo) => {
        const nombre = hijo.nombre;
        let opts = child.opts;

        // Permitir que las opciones para los hijos se establezcan en las opciones de los padres
        // por ejemplo, videojs(id, { barra de control: falso });
        // en lugar de videojs(id, { children: { controlBar: false });
        if (parentOptions[nombre] !== indefinido) {
          opciones = parentOptions[nombre];
        }

        // Permitir la desactivación de componentes predeterminados
        // por ejemplo, options['child']['posterImage'] = false
        si (opta === falso) {
          devolver;
        }

        // Permitir que las opciones se pasen como un booleano simple si no hay configuración
        // es necesario.
        if (opta === verdadero) {
          opciones = {};
        }

        // También queremos pasar las opciones del jugador original
        // a cada componente también para que no necesiten
        // Vuelva a buscar opciones en el reproductor más adelante.
        opts.playerOptions = this.options_.playerOptions;

        // Crear y agregar el componente secundario.
        // Agregue una referencia directa al hijo por nombre en la instancia principal.
        // Si se utilizan dos del mismo componente, se deben proporcionar nombres diferentes
        // para cada
        const newChild = this.addChild(nombre, opciones);

        si (nuevoNiño) {
          este[nombre] = nuevoNiño;
        }
      };

      // Permitir que se pase una serie de detalles secundarios en las opciones
      dejar trabajar a los niños;
      const Tecnología = Componente.getComponent('Tecnología');

      if (Array.isArray(hijos)) {
        niñostrabajadores = niños;
      } else {
        niñostrabajando = Object.keys(niños);
      }

      niños trabajando
      // los niños que están en this.options_ pero también en workingChildren serían
      // danos hijos extra que no queremos. Entonces, queremos filtrarlos.
        .concat(Objeto.claves(this.options_)
          .filter(función(hijo) {
            return !workingChildren.some(function(wchild) {
              if (tipo de wchild === 'cadena') {
                volver niño === niño;
              }
              return child === wchild.nombre;
            });
          }))
        .mapa((niño) => {
          dejar nombre;
          deja que opte;

          if (tipo de niño === 'cadena') {
            nombre = niño;
            opciones = hijos[nombre] || this.options_[nombre] || {};
          } else {
            nombre = hijo.nombre;
            opta = niño;
          }

          volver {nombre, opciones};
        })
        .filter((hijo) => {
        // tenemos que asegurarnos de que child.name no esté en techOrder ya que
        // las tecnologías están registradas como componentes pero no pueden no ser compatibles
        // Ver https://github.com/videojs/video.js/issues/2772
          const c = Componente.getComponent(child.opts.componentClass ||
                                       toTitleCase(niño.nombre));

          volver c && !Tecnología.esTecnología(c);
        })
        .forEach(manejarAñadir);
    }
  }

  /**
   * Construye el nombre de clase DOM predeterminado. Debe ser anulado por subcomponentes.
   *
   * @return {cadena}
   * El nombre de la clase DOM para este objeto.
   *
   * @abstracto
   * /
  construirClaseCSS() {
    // Las clases secundarias pueden incluir una función que hace:
    // devuelve 'NOMBRE DE LA CLASE' + this._super();
    devolver '';
  }

  /**
   * Vincular un oyente al estado listo del componente.
   * Diferente de los oyentes de eventos en que si el evento listo ya sucedió
   * activará la función inmediatamente.
   *
   * @return {Componente}
   * Se devuelve a sí mismo; El método se puede encadenar.
   * /
  listo (fn, sincronizar = falso) {
    si (!fn) {
      devolver;
    }

    si (!esto.estáListo_) {
      this.readyQueue_ = this.readyQueue_ || [];
      this.readyQueue_.push(fn);
      devolver;
    }

    si (sincronizar) {
      fn.llamar(esto);
    } else {
      // Llamar a la función de forma asíncrona por defecto para mantener la coherencia
      esto.setTimeout(fn, 1);
    }
  }

  /**
   * Active todos los oyentes listos para este 'Componente'.
   *
   * @fires Componente#listo
   * /
  activadorListo() {
    esto.estáListo_ = verdadero;

    // Asegurarse de que ready se active de forma asíncrona
    this.setTimeout(función() {
      const readyQueue = this.readyQueue_;

      // Restablecer la cola de listos
      this.readyQueue_ = [];

      si (cola lista && readyQueue.longitud > 0) {
        readyQueue.forEach(función(fn) {
          fn.llamar(esto);
        }, this);
      }

      // Permitir el uso de detectores de eventos también
      /**
       * Activado cuando un 'Componente' está listo.
       *
       * @event Componente#listo
       * @type {Objetivo del evento~Evento}
       * /
      this.trigger('listo');
    }, 1);
  }

  /**
   * Encuentra un solo elemento DOM que coincida con un `selector`. Esto puede estar dentro del `Component`s
   * `contentEl()` u otro contexto personalizado.
   *
   * Selector @param {cadena}
   * Un selector de CSS válido, que se pasará a `querySelector`.
   *
   * @param {Elemento|cadena} [contexto=este.contentEl()]
   * Un elemento DOM dentro del cual consultar. También puede ser una cadena selectora en
   * en cuyo caso el primer elemento coincidente se usará como contexto. Si
   * falta `this.contentEl()` se usa. Si `this.contentEl()` regresa
   * nada, vuelve a `document`.
   *
   * @return {Elemento|null}
   * el elemento dom que se encontró, o nulo
   *
   * @ver [Información sobre los selectores de CSS](https://developer.mozilla.org/en-US/docs/Web/Guide/CSS/Getting_Started/Selectors)
   * /
  $(selector, contexto) {
    return Dom.$(selector, contexto || this.contentEl());
  }

  /**
   * Encuentra todos los elementos DOM que coincidan con un `selector`. Esto puede estar dentro del `Component`s
   * `contentEl()` u otro contexto personalizado.
   *
   * Selector @param {cadena}
   * Un selector de CSS válido, que se pasará a `querySelectorAll`.
   *
   * @param {Elemento|cadena} [contexto=este.contentEl()]
   * Un elemento DOM dentro del cual consultar. También puede ser una cadena selectora en
   * en cuyo caso el primer elemento coincidente se usará como contexto. Si
   * falta `this.contentEl()` se usa. Si `this.contentEl()` regresa
   * nada, vuelve a `document`.
   *
   * @return {Lista de nodos}
   * una lista de elementos dom que se encontraron
   *
   * @ver [Información sobre los selectores de CSS](https://developer.mozilla.org/en-US/docs/Web/Guide/CSS/Getting_Started/Selectors)
   * /
  $$(selector, contexto) {
    return Dom.$$(selector, contexto || this.contentEl());
  }

  /**
   * Comprobar si el elemento de un componente tiene un nombre de clase CSS.
   *
   * @param {cadena} classToCheck
   * Nombre de la clase CSS para comprobar.
   *
   * @return {booleano}
   * - Verdadero si el `Componente` tiene la clase.
   * - Falso si el `Componente` no tiene la clase`
   * /
  tieneClase(claseParaComprobar) {
    return Dom.hasClass(this.el_, classToCheck);
  }

  /**
   * Agregue un nombre de clase CSS al elemento `Component`.
   *
   * @param {cadena} classToAdd
   * Nombre de clase CSS para agregar
   * /
  addClass(classToAdd) {
    Dom.addClass(this.el_, classToAdd);
  }

  /**
   * Eliminar un nombre de clase CSS del elemento `Component`.
   *
   * @param {cadena} classToRemove
   * Nombre de clase CSS para eliminar
   * /
  removeClass(classToRemove) {
    Dom.removeClass(this.el_, classToRemove);
  }

  /**
   * Agregue o elimine un nombre de clase CSS del elemento del componente.
   * - `classToToggle` se agrega cuando {@link Component#hasClass} devuelve falso.
   * - `classToToggle` se elimina cuando {@link Component#hasClass} devuelve verdadero.
   *
   * @param {cadena} classToToggle
   * La clase para agregar o quitar según (@link Component#hasClass}
   *
   * @param {booleano|Dom~predicado} [predicado]
   * Una función {@link Dom~predicate} o un booleano
   * /
  toggleClass(classToToggle, predicado) {
    Dom.toggleClass(this.el_, classToToggle, predicado);
  }

  /**
   * Muestre el elemento `Component` si está oculto eliminando el
   * Nombre de clase 'vjs-hidden'.
   * /
  espectáculo() {
    this.removeClass('vjs-hidden');
  }

  /**
   * Oculte el elemento `Component` si se muestra actualmente agregando el
   * Nombre de clase 'vjs-hidden`.
   * /
  esconder() {
    this.addClass('vjs-hidden');
  }

  /**
   * Bloquee un elemento `Component`s en su estado visible agregando 'vjs-lock-showing'
   * nombre de la clase. Se utiliza durante el fundido de entrada/salida.
   *
   * @privado
   * /
  bloqueoMostrando() {
    this.addClass('vjs-lock-showing');
  }

  /**
   * Desbloquee un elemento `Component`s de su estado visible eliminando el 'vjs-lock-showing'
   * nombre de clase de él. Se utiliza durante el fundido de entrada/salida.
   *
   * @privado
   * /
  desbloquearMostrar() {
    this.removeClass('vjs-lock-showing');
  }

  /**
   * Obtener el valor de un atributo en el elemento `Component`.
   *
   * atributo @param {cadena}
   * Nombre del atributo del que obtener el valor.
   *
   * @return {cadena|null}
   * - El valor del atributo que se solicitó.
   * - Puede ser una cadena vacía en algunos navegadores si el atributo no existe
   * o no tiene valor
   * - La mayoría de los navegadores devolverán un valor nulo si el atributo no existe o tiene
   * sin valor.
   *
   * @ver [API DOM]{@enlace https://developer.mozilla.org/en-US/docs/Web/API/Element/getAttribute}
   * /
  getAttribute(atributo) {
    return Dom.getAttribute(this.el_, atributo);
  }

  /**
   * Establecer el valor de un atributo en el elemento `Componente`
   *
   * atributo @param {cadena}
   * Nombre del atributo a configurar.
   *
   * @param {cadena} valor
   * Valor para establecer el atributo.
   *
   * @ver [API DOM]{@enlace https://developer.mozilla.org/en-US/docs/Web/API/Element/setAttribute}
   * /
  setAttribute(atributo, valor) {
    Dom.setAttribute(this.el_, atributo, valor);
  }

  /**
   * Eliminar un atributo del elemento `Component`.
   *
   * atributo @param {cadena}
   * Nombre del atributo a eliminar.
   *
   * @ver [API DOM]{@enlace https://developer.mozilla.org/en-US/docs/Web/API/Element/removeAttribute}
   * /
  removeAttribute(atributo) {
    Dom.removeAttribute(this.el_, atributo);
  }

  /**
   * Obtener o establecer el ancho del componente según los estilos CSS.
   * Consulte {@link Component#dimension} para obtener información más detallada.
   *
   * @param {número|cadena} [número]
   * El ancho que desea establecer postfijo con '%', 'px' o nada.
   *
   * @param {booleano} [skipListeners]
   * Omitir el activador de evento de cambio de tamaño de componente
   *
   * @return {número|cadena}
   * El ancho al llegar, cero si no hay ancho. Puede ser una cuerda
   * pospixelado con '%' o 'px'.
   * /
  ancho (num, skipListeners) {
    return this.dimension('ancho', num, skipListeners);
  }

  /**
   * Obtenga o establezca la altura del componente en función de los estilos CSS.
   * Consulte {@link Component#dimension} para obtener información más detallada.
   *
   * @param {número|cadena} [número]
   * La altura que desea establecer con el postfijo '%', 'px' o nada.
   *
   * @param {booleano} [skipListeners]
   * Omitir el activador de evento de cambio de tamaño de componente
   *
   * @return {número|cadena}
   * El ancho al llegar, cero si no hay ancho. Puede ser una cuerda
   * pospixelado con '%' o 'px'.
   * /
  altura(num, skipListeners) {
    return this.dimension('altura', num, skipListeners);
  }

  /**
   * Establecer tanto el ancho como la altura del elemento `Componente` al mismo tiempo.
   *
   * @param {número|cadena} ancho
   * Ancho para establecer el elemento `Component`.
   *
   * @param {número|cadena} altura
   * Altura para establecer el elemento `Component`.
   * /
  dimensiones (ancho, alto) {
    // Omita los oyentes de cambio de tamaño de componente en el ancho para la optimización
    this.width(ancho, verdadero);
    esta.altura(altura);
  }

  /**
   * Obtener o establecer el ancho o alto del elemento `Componente`. Este es el código compartido.
   * para {@link Component#width} y {@link Component#height}.
   *
   * Cosas que saber:
   * - Si el ancho o alto en un número, esto devolverá el número con el postfijo 'px'.
   * - Si el ancho/alto es un porcentaje, devolverá el porcentaje con el postfijo '%'
   * - Los elementos ocultos tienen un ancho de 0 con `window.getComputedStyle`. Esta función
   * por defecto es `style.width` del `Component` y vuelve a `window.getComputedStyle`.
   * Ver [este]{@enlace http://www.foliotek.com/devblog/getting-the-width-of-a-hidden-element-with-jquery-using-width/}
   * para más información
   * - Si desea el estilo calculado del componente, use {@link Component#currentWidth}
   * y {@link {Component#currentHeight}
   *
   * @fires Componente#redimensionarcomponente
   *
   * @param {cadena} ancho o alto
   8 'ancho' o 'alto'
   *
   * @param {número|cadena} [número]
   8 Nueva dimensión
   *
   * @param {booleano} [skipListeners]
   * Omitir desencadenador de evento de cambio de tamaño de componente
   *
   * @return {número}
   * La dimensión al obtener o 0 si no se establece
   * /
  dimensión (ancho o alto, número, skipListeners) {
    if (num !== indefinido) {
      // Establecer en cero si es nulo o literalmente NaN (NaN !== NaN)
      if (num === nulo || numero !== numero) {
        número = 0;
      }

      // Comprueba si usas ancho/alto css (% o px) y ajusta
      if (('' + número).indexOf('%') !== -1 || ('' + número).indexOf('px') !== -1) {
        this.el_.style[anchoOAlto] = num;
      } else if (num === 'auto') {
        this.el_.style[anchoOAlto] = '';
      } else {
        this.el_.style[anchoOAlto] = num + 'px';
      }

      // skipListeners nos permite evitar activar el evento de cambio de tamaño al establecer tanto el ancho como el alto
      if (!skipOyentes) {
        /**
         * Se activa cuando se cambia el tamaño de un componente.
         *
         * @event Componente#redimensionarcomponente
         * @type {Objetivo del evento~Evento}
         * /
        this.trigger('componentresize');
      }

      devolver;
    }

    // No establecer un valor, así que obtenerlo
    // Asegurarse de que el elemento existe
    si (!esto.el_) {
      devolver 0;
    }

    // Obtener el valor de la dimensión del estilo
    const val = this.el_.style[anchoOAlto];
    const pxIndex = val.indexOf('px');

    si (índicepx! == -1) {
      // Devuelve el valor del píxel sin 'px'
      return parseInt(val.slice(0, pxIndex), 10);
    }

    // Sin px, por lo que se configuró el uso de % o ningún estilo, por lo que se recurrió a offsetWidth/height
    // Si el componente tiene visualización: ninguna, el desplazamiento devolverá 0
    // TODO: manejar la visualización: ninguno y ningún estilo de dimensión usando px
    return parseInt(this.el_['offset' + toTitleCase(ancho o alto)], 10);
  }

  /**
   * Obtenga el ancho calculado o la altura del elemento del componente.
   *
   * Usa `window.getComputedStyle`.
   *
   * @param {cadena} ancho o alto
   * Una cadena que contiene 'ancho' o 'alto'. Cualquiera que quieras conseguir.
   *
   * @return {número}
   * La dimensión que se solicita o 0 si no se configuró nada
   * para esa dimensión.
   * /
  dimensión actual (ancho o alto) {
    deje calculadoWidthOrHeight = 0;

    if (anchoOAlto !== 'ancho' && ancho o alto !== 'alto') {
      throw new Error('currentDimension solo acepta valores de ancho o alto');
    }

    ancho o alto calculado = estilo calculado (this.el_, ancho o alto);

    // eliminar 'px' de la variable y analizar como entero
    ancho o alto calculado = parseFloat (ancho o alto calculado);

    // si el valor calculado sigue siendo 0, es posible que el navegador esté mintiendo
    // y queremos verificar los valores de compensación.
    // Este código también se ejecuta donde no existe getComputedStyle.
    if (anchura o altura calculada === 0 || isNaN(anchura o altura calculada)) {
      const rule = `offset${toTitleCase(ancho o alto)}`;

      ancho o alto calculado = this.el_[regla];
    }

    volver ancho o alto calculado;
  }

  /**
   * Un objeto que contiene valores de ancho y alto de los 'Componentes'
   * estilo calculado. Utiliza `window.getComputedStyle`.
   *
   * @typedef {Objeto} Componente ~ DimensionObject
   *
   * @property {número} ancho
   * El ancho del estilo calculado del 'Componente'.
   *
   * @propiedad {número} altura
   * La altura del estilo calculado del 'Componente'.
   * /

  /**
   * Obtenga un objeto que contenga valores calculados de ancho y alto del
   * elemento del componente.
   *
   * Usa `window.getComputedStyle`.
   *
   * @return {Componente~ObjetoDimensión}
   * Las dimensiones calculadas del elemento del componente.
   * /
  dimensionesactuales() {
    regreso {
      ancho: this.currentDimension('ancho'),
      altura: this.currentDimension('altura')
    };
  }

  /**
   * Obtenga el ancho calculado del elemento del componente.
   *
   * Usa `window.getComputedStyle`.
   *
   * @return {número}
   * El ancho calculado del elemento del componente.
   * /
  anchoActual() {
    devuelve this.currentDimension('ancho');
  }

  /**
   * Obtener la altura calculada del elemento del componente.
   *
   * Usa `window.getComputedStyle`.
   *
   * @return {número}
   * La altura calculada del elemento del componente.
   * /
  altura actual() {
    devuelve this.currentDimension('altura');
  }

  /**
   * Establecer el foco en este componente
   * /
  enfocar() {
    this.el_.focus();
  }

  /**
   * Eliminar el foco de este componente
   * /
  difuminar() {
    this.el_.blur();
  }

  /**
   * Cuando este Componente recibe un evento `keydown` que no procesa,
   * pasa el evento al jugador para su manejo.
   *
   * @param {EventTarget~Evento} evento
   * El evento `keydown` que hizo que se llamara a esta función.
   * /
  handleKeyDown(evento) {
    si (este.jugador_) {

      // Solo detenemos la propagación aquí porque queremos que caigan los eventos no controlados
      // de vuelta al navegador. Excluir pestaña para reventado de foco.
      if (!keycode.isEventKey(evento, 'Tab')) {
        event.stopPropagation();
      }
      this.player_.handleKeyDown(evento);
    }
  }

  /**
   * Muchos componentes solían tener un método `handleKeyPress`, que no funcionaba bien.
   * nombrado porque escuchó un evento `keydown`. Este nombre de método ahora
   * delegados a `handleKeyDown`. Esto significa que cualquiera que llame `handleKeyPress`
   * no verá que sus llamadas a métodos dejen de funcionar.
   *
   * @param {EventTarget~Evento} evento
   * El evento que provocó la llamada de esta función.
   * /
  handleKeyPress(evento) {
    this.handleKeyDown(evento);
  }

  /**
   * Emite eventos de 'toque' cuando se detecta soporte de eventos táctiles. esto se acostumbra
   * Admite alternar los controles a través de un toque en el video. se habilitan
   * porque, de lo contrario, cada subcomponente tendría una sobrecarga adicional.
   *
   * @privado
   * @fires Componente#tap
   * @escucha Componente#touchstart
   * @escucha Componente#touchmove
   * @escucha Componente#touchleave
   * @escucha Componente#touchcancel
   * @escucha Componente#touchend

   * /
  emitTapEvents() {
    // Seguimiento de la hora de inicio para que podamos determinar cuánto duró el toque
    let touchStart = 0;
    let firstTouch = nulo;

    // El movimiento máximo permitido durante un evento táctil aún se considera un toque
    // Otras bibliotecas populares usan desde 2 (hammer.js) hasta 15,
    // así que 10 parece un buen número redondo.
    const tapMovementThreshold = 10;

    // La duración máxima que puede tener un toque sin dejar de ser considerado un toque
    const touchTimeThreshold = 200;

    vamos podríaBeTap;

    this.on('touchstart', function(evento) {
      // Si hay más de un dedo, no considere tratar esto como un clic
      if (evento.toques.longitud === 1) {
        // Copia páginaX/páginaY del objeto
        primer toque = {
          páginaX: evento.toca[0].páginaX,
          páginaY: evento.toca[0].páginaY
        };
        // Registrar la hora de inicio para que podamos detectar un toque frente a &quot;tocar y mantener&quot;
        touchStart = ventana.rendimiento.ahora();
        // Restablecer el seguimiento de couldBeTap
        podríaBeTap = verdadero;
      }
    });

    this.on('touchmove', function(evento) {
      // Si hay más de un dedo, no considere tratar esto como un clic
      if (evento.toca.longitud > 1) {
        podríaBeTap = false;
      } más si (primer Toque) {
        // Algunos dispositivos lanzarán movimientos táctiles para todos, excepto para los toques más leves.
        // Entonces, si nos moviéramos solo una pequeña distancia, esto aún podría ser un toque
        const xdiff = event.touches[0].pageX - firstTouch.pageX;
        const ydiff = event.touches[0].pageY - firstTouch.pageY;
        const touchDistance = Math.sqrt(xdiff * xdiff + ydiff * ydiff);

        si (distancia táctil > tapMovementThreshold) {
          podríaBeTap = false;
        }
      }
    });

    const noTap = función () {
      podríaBeTap = false;
    };

    // HACER: Escuche el objetivo original. http://youtu.be/DujfpXOKUp8?t=13m8s
    this.on('touchleave', noTap);
    this.on('touchcancel', noTap);

    // Cuando termine el toque, mida cuánto tiempo tomó y active el apropiado
    // evento
    this.on('touchend', función(evento) {
      primerToque = nulo;
      // Continúe solo si el evento touchmove/leave/cancel no sucedió
      if (podríaBeTap === verdadero) {
        // Mide cuánto duró el toque
        const touchTime = ventana.rendimiento.ahora() - touchStart;

        // Asegúrese de que el toque fue menor que el umbral para ser considerado un toque
        si (tiempo de toque < umbral de tiempo táctil) {
          // No permita que el navegador convierta esto en un clic
          event.preventDefault();
          /**
           * Se activa cuando se toca un `Componente`.
           *
           * @componente del evento#tap
           * @type {Objetivo del evento~Evento}
           * /
          this.trigger('tocar');
          // Puede ser bueno copiar el objeto del evento touchend y cambiar el
          // escriba para tocar, si las otras propiedades del evento no son exactas después
          // Eventos.fixEvent se ejecuta (por ejemplo, event.target)
        }
      }
    });
  }

  /**
   * Esta función informa la actividad del usuario cada vez que ocurren eventos táctiles. esto puede conseguir
   * desactivado por cualquier subcomponente que quiera que los eventos táctiles actúen de otra manera.
   *
   * Informar sobre la actividad táctil del usuario cuando se produzcan eventos táctiles. La actividad del usuario se acostumbra
   * determinar cuándo deben mostrarse/ocultarse los controles. Es simple cuando se trata de mouse
   * eventos, porque cualquier evento del mouse debería mostrar los controles. Así que capturamos el ratón.
   * Eventos que surgen al jugador e informan actividad cuando eso sucede.
   * Con eventos táctiles no es tan fácil como `touchstart` y `touchend` toggle player
   * control S. Así que los eventos táctiles tampoco nos pueden ayudar a nivel de jugador.
   *
   * La actividad del usuario se comprueba de forma asíncrona. Entonces, lo que podría pasar es un evento de toque
   * en el video apaga los controles. Luego, el evento `touchend` se dispara hasta
   * el jugador. Que, si informara sobre la actividad del usuario, cambiaría los controles a la derecha
   * de nuevo en. Tampoco queremos bloquear por completo los eventos táctiles para evitar que se burbujeen.
   * Además, un evento `touchmove` y cualquier cosa que no sea un toque, no debe activarse
   * controles de nuevo en.
   *
   * @escucha Componente#touchstart
   * @escucha Componente#touchmove
   * @escucha Componente#touchend
   * @escucha Componente#touchcancel
   * /
  enableTouchActivity() {
    // No continúe si el reproductor raíz no admite informar la actividad del usuario
    if (!este.jugador() || !este.jugador().reportUserActivity) {
      devolver;
    }

    // oyente para informar que el usuario está activo
    informe const = Fn.bind(this.player(), this.player().reportUserActivity);

    dejar tocarSostener;

    this.on('touchstart', function() {
      informe();
      // Mientras estén tocando el dispositivo o tengan el mouse presionado,
      // los consideramos activos incluso si no mueven el dedo o el mouse.
      // Entonces queremos continuar actualizando que están activos
      this.clearInterval(touchHolding);
      // informe en el mismo intervalo que el control de actividad
      touchHolding = this.setInterval(informe, 250);
    });

    const touchEnd = función (evento) {
      informe();
      // detener el intervalo que mantiene la actividad si el toque se mantiene
      this.clearInterval(touchHolding);
    };

    this.on('touchmove', informe);
    this.on('touchend', touchEnd);
    this.on('touchcancel', touchEnd);
  }

  /**
   * Una devolución de llamada que no tiene parámetros y está vinculada al contexto `Component`.
   *
   * Componente @callback~GenericCallback
   * @este componente
   * /

  /**
   * Crea una función que se ejecuta después de un tiempo de espera de `x` milisegundos. Esta función es una
   * envoltorio alrededor de `window.setTimeout`. Hay algunas razones para usar este
   * en cambio, sin embargo:
   * 1. Se borra a través de {@link Component#clearTimeout} cuando
   * Se llama a {@link Component#dispose}.
   * 2. La devolución de llamada de la función se convertirá en un {@link Component~GenericCallback}
   *
   * > Nota: No puede usar `window.clearTimeout` en la identificación devuelta por esta función. Esto
   * ¡hará que su detector de eliminación no se limpie! Por favor use
   * {@link Component#clearTimeout} o {@link Component#dispose} en su lugar.
   *
   * @param {Componente~GenericCallback} fn
   * La función que se ejecutará después de `timeout`.
   *
   * @param {número} tiempo de espera
   * Tiempo de espera en milisegundos para demorar antes de ejecutar la función especificada.
   *
   * @return {número}
   * Devuelve una ID de tiempo de espera que se usa para identificar el tiempo de espera. También puede
   * se usa en {@link Component#clearTimeout} para borrar el tiempo de espera que
   * se estableció.
   *
   * @escucha Componente#dispose
   * @ver [Similar a]{@enlace https://developer.mozilla.org/en-US/docs/Web/API/WindowTimers/setTimeout}
   * /
  establecerTiempo de espera (fn, tiempo de espera) {
    // declarar como variables para que estén correctamente disponibles en la función de tiempo de espera
    // eslint-disable-next-line
    var timeoutId, disponerFn;

    fn = Fn.bind(esto, fn);

    this.clearTimersOnDispose_();

    timeoutId = ventana.setTimeout(() => {
      if (this.setTimeoutIds_.has(timeoutId)) {
        this.setTimeoutIds_.delete(timeoutId);
      }
      fn();
    }, timeout);

    this.setTimeoutIds_.add(timeoutId);

    devuelve timeoutId;
  }

  /**
   * Borra un tiempo de espera que se crea a través de `window.setTimeout` o
   * {@link Component#setTimeout}. Si establece un tiempo de espera a través de {@link Component#setTimeout}
   * use esta función en lugar de `window.clearTimout`. Si no te dispones
   * ¡El oyente no se limpiará hasta {@link Component#dispose}!
   *
   * @param {número} timeoutId
   * La identificación del tiempo de espera para borrar. El valor de retorno de
   * {@link Component#setTimeout} o `window.setTimeout`.
   *
   * @return {número}
   * Devuelve el ID de tiempo de espera que se borró.
   *
   * @ver [Similar a]{@enlace https://developer.mozilla.org/en-US/docs/Web/API/WindowTimers/clearTimeout}
   * /
  clearTimeout(timeoutId) {
    if (this.setTimeoutIds_.has(timeoutId)) {
      this.setTimeoutIds_.delete(timeoutId);
      ventana.clearTimeout(timeoutId);
    }

    devuelve timeoutId;
  }

  /**
   * Crea una función que se ejecuta cada `x` milisegundos. Esta función es un envoltorio
   * alrededor de `window.setInterval`. Sin embargo, hay algunas razones para usar este.
   * 1. Se borra a través de {@link Component#clearInterval} cuando
   * Se llama a {@link Component#dispose}.
   * 2. La devolución de llamada de la función será {@link Component~GenericCallback}
   *
   * @param {Componente~GenericCallback} fn
   * La función para ejecutar cada `x` segundos.
   *
   * @param {número} intervalo
   * Ejecuta la función especificada cada `x` milisegundos.
   *
   * @return {número}
   * Devuelve una identificación que se puede usar para identificar el intervalo. También se puede utilizar en
   * {@link Component#clearInterval} para borrar el intervalo.
   *
   * @escucha Componente#dispose
   * @ver [Similar a]{@enlace https://developer.mozilla.org/en-US/docs/Web/API/WindowTimers/setInterval}
   * /
  establecerIntervalo(fn, intervalo) {
    fn = Fn.bind(esto, fn);

    this.clearTimersOnDispose_();

    const intervaloId = ventana.setInterval(fn, intervalo);

    this.setIntervalIds_.add(intervalId);

    ID de intervalo de retorno;
  }

  /**
   * Borra un intervalo que se crea a través de `window.setInterval` o
   * {@link Component#setInterval}. Si establece un intervalo a través de {@link Component#setInterval}
   * use esta función en lugar de `window.clearInterval`. Si no te dispones
   * ¡El oyente no se limpiará hasta {@link Component#dispose}!
   *
   * @param {número} ID de intervalo
   * El id del intervalo a borrar. El valor de retorno de
   * {@link Component#setInterval} o `window.setInterval`.
   *
   * @return {número}
   * Devuelve el ID de intervalo que se borró.
   *
   * @ver [Similar a]{@enlace https://developer.mozilla.org/en-US/docs/Web/API/WindowTimers/clearInterval}
   * /
  clearInterval(intervalId) {
    if (this.setIntervalIds_.has(intervalId)) {
      this.setIntervalIds_.delete(intervalId);
      ventana.clearInterval(intervalId);
    }

    ID de intervalo de retorno;
  }

  /**
   * Pone en cola una devolución de llamada para pasar a requestAnimationFrame (rAF), pero
   * con algunas bonificaciones adicionales:
   *
   * - Admite navegadores que no admiten rAF recurriendo a
   * {@link Component#setTimeout}.
   *
   * - La devolución de llamada se convierte en un {@link Component~GenericCallback} (es decir,
   * vinculado al componente).
   *
   * - La cancelación automática de la devolución de llamada rAF se maneja si el componente
   * se elimina antes de que se llame.
   *
   * @param {Componente~GenericCallback} fn
   * Una función que se vinculará a este componente y se ejecutará solo
   * antes del próximo repintado del navegador.
   *
   * @return {número}
   * Devuelve un ID de rAF que se utiliza para identificar el tiempo de espera. Puede
   * también se puede usar en {@link Component#cancelAnimationFrame} para cancelar
   * la devolución de llamada del cuadro de animación.
   *
   * @escucha Componente#dispose
   * @ver [Similar a]{@link https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame}
   * /
  solicitudAnimationFrame(fn) {
    // Vuelve a usar un temporizador.
    if (!this.supportsRaf_) {
      devolver esto.setTimeout(fn, 1000/60);
    }

    this.clearTimersOnDispose_();

    // declarar como variables para que estén correctamente disponibles en la función rAF
    // eslint-disable-next-line
    id de var;
    fn = Fn.bind(esto, fn);

    id = ventana.requestAnimationFrame(() => {
      if (this.rafIds_.has(id)) {
        this.rafIds_.delete(id);
      }
      fn();
    });
    this.rafIds_.add(id);

    identificación de retorno;
  }

  /**
   * Solicite un cuadro de animación, pero solo una animación con nombre
   * el marco se pondrá en cola. Nunca se agregará otro hasta que
   * termina el anterior.
   *
   * @param {cadena} nombre
   * El nombre para dar a esta solicitudAnimationFrame
   *
   * @param {Componente~GenericCallback} fn
   * Una función que se vinculará a este componente y se ejecutará solo
   * antes del próximo repintado del navegador.
   * /
  requestNamedAnimationFrame(nombre, fn) {
    if (este.namedRafs_.has(nombre)) {
      devolver;
    }
    this.clearTimersOnDispose_();

    fn = Fn.bind(esto, fn);

    const id = this.requestAnimationFrame(() => {
      fn();
      if (este.namedRafs_.has(nombre)) {
        this.namedRafs_.delete(nombre);
      }
    });

    this.namedRafs_.set(nombre, id);

    devolver nombre;
  }

  /**
   * Cancela un cuadro de animación con nombre actual, si existe.
   *
   * @param {cadena} nombre
   * El nombre de la requestAnimationFrame para cancelar.
   * /
  cancelNamedAnimationFrame(nombre) {
    if (!this.namedRafs_.has(nombre)) {
      devolver;
    }

    this.cancelAnimationFrame(this.namedRafs_.get(nombre));
    this.namedRafs_.delete(nombre);
  }

  /**
   * Cancela una devolución de llamada en cola pasada a {@link Component#requestAnimationFrame}
   * (RAF).
   *
   * Si pone en cola una devolución de llamada de rAF a través de {@link Component#requestAnimationFrame},
   * use esta función en lugar de `window.cancelAnimationFrame`. si no lo haces,
   * ¡Su detector de disposición no se limpiará hasta {@link Component#dispose}!
   *
   * ID de @param {número}
   * El rAF ID para borrar. El valor de retorno de {@link Component#requestAnimationFrame}.
   *
   * @return {número}
   * Devuelve el ID de rAF que se borró.
   *
   * @ver [Similar a]{@enlace https://developer.mozilla.org/en-US/docs/Web/API/window/cancelAnimationFrame}
   * /
  cancelAnimationFrame(id) {
    // Vuelve a usar un temporizador.
    if (!this.supportsRaf_) {
      devolver esto.clearTimeout(id);
    }

    if (this.rafIds_.has(id)) {
      this.rafIds_.delete(id);
      ventana.cancelAnimationFrame(id);
    }

    identificación de retorno;

  }

  /**
   * Una función para configurar `requestAnimationFrame`, `setTimeout`,
   * y `setInterval`, borrando al desechar.
   *
   * > Anteriormente, cada temporizador agregado y eliminado dispone de oyentes por sí solo.
   * Para un mejor rendimiento, se decidió agruparlos a todos y usar `Set`s
   * para realizar un seguimiento de los identificadores de temporizador pendientes.
   *
   * @privado
   * /
  clearTimersOnDispose_() {
    if (this.clearingTimersOnDispose_) {
      devolver;
    }

    this.clearingTimersOnDispose_ = true;
    this.one('dispose', () => {
      [
        ['namedRafs_', 'cancelNamedAnimationFrame'],
        ['rafIds_', 'cancelAnimationFrame'],
        ['setTimeoutIds_', 'clearTimeout'],
        ['setIntervalIds_', 'clearInterval']
      ].forEach(([idNombre, cancelarNombre]) => {
        // para una tecla `Set` será en realidad el valor de nuevo
        // entonces paraCada((val,val) => ` pero para mapas queremos usar
        // la clave.
        este[idNombre].forEach((valor, clave) => this[cancelName](clave));
      });

      this.clearingTimersOnDispose_ = falso;
    });
  }

  /**
   * Registre un `Componente` con `videojs` dado el nombre y el componente.
   *
   * > NOTA: {@link Tech}s no debe registrarse como un 'Componente'. {@link Tech}s
   * debe registrarse usando {@link Tech.registerTech} o
   * {@enlace videojs:videojs.registerTech}.
   *
   * > NOTA: Esta función también se puede ver en videojs como
   * {@enlace videojs:videojs.registerComponent}.
   *
   * @param {cadena} nombre
   * El nombre del `Componente` a registrar.
   *
   * @param {Componente} Componente para registrar
   * La clase `Componente` a registrar.
   *
   * @return {Componente}
   * El `Componente` que fue registrado.
   * /
  Componente de registro estático (nombre, Componente a registro) {
    if (tipo de nombre !== 'cadena' || !nombre) {
      throw new Error(`Nombre de componente ilegal, &quot;${name}&quot;; debe ser una cadena no vacía.`);
    }

    const Tecnología = Componente.getComponent('Tecnología');

    // Necesitamos asegurarnos de que esta verificación solo se realice si Tech se ha registrado.
    const isTech = Tecnología && Tech.isTech(ComponenteParaRegistrar);
    const isComp = Componente === ComponenteParaRegistrar ||
      Componente.prototipo.isPrototypeOf(ComponentToRegister.prototype);

    if (esTec || !isComp) {
      deja razonar;

      si (tecnología) {
        razón = 'los técnicos deben registrarse mediante Tech.registerTech()';
      } else {
        razón = 'debe ser una subclase de Componente';
      }

      throw new Error(`Componente ilegal, &quot;${nombre}&quot;; ${razón}.`);
    }

    nombre = toTitleCase(nombre);

    if (!Componente.componentes_) {
      Componente.componentes_ = {};
    }

    const Jugador = Componente.getComponent('Jugador');

    if (nombre === 'Jugador' && Jugador && jugador.jugadores) {
      const jugadores = Jugador.jugadores;
      const playerNames = Object.keys(players);

      // Si tenemos jugadores que fueron descartados, su nombre seguirá siendo
      // en Jugadores.jugadores. Entonces, debemos recorrer y verificar que el valor
      // para cada elemento no es nulo. Esto permite el registro del componente Player
      // después de que se hayan eliminado todos los jugadores o antes de que se haya creado alguno.
      si (jugadores &&
          nombredeljugador.longitud > 0 &&
          playerNames.map((pname) => jugadores[pname]).every(Boolean)) {
        throw new Error('No se puede registrar el componente del reproductor después de que se haya creado el reproductor');
      }
    }

    Componente.componentes_[nombre] = ComponenteParaRegistrar;
    Component.components_[toLowerCase(nombre)] = ComponentToRegister;

    volver ComponenteParaRegistrar;
  }

  /**
   * Obtener un 'Componente' basado en el nombre con el que fue registrado.
   *
   * @param {cadena} nombre
   * El Nombre del componente a obtener.
   *
   * @return {Componente}
   * El 'Componente' que se registró con el nombre de pila.
   * /
  getComponent estático (nombre) {
    if (!nombre || !Componente.componentes_) {
      devolver;
    }

    return Componente.componentes_[nombre];
  }
}

/**
 * Si este componente admite o no `requestAnimationFrame`.
 *
 * Esto se expone principalmente con fines de prueba.
 *
 * @privado
 * @tipo {Booleano}
 * /
Component.prototype.supportsRaf_ = typeof window.requestAnimationFrame === 'función' &&
  typeof window.cancelAnimationFrame === 'función';

Componente.registerComponent('Componente', Componente);

exportar componente predeterminado;