/**
 * @archivo tech.js
 * /

importar componente desde '../componente';
importar mergeOptions desde '../utils/merge-options.js';
importar * como Fn desde '../utils/fn.js';
importar registro desde '../utils/log.js';
import { createTimeRange } from '../utils/time-ranges.js';
importar {bufferedPercent} desde '../utils/buffer.js';
importar MediaError desde '../media-error.js';
importar ventana desde 'global/window';
importar documento desde 'global/document';
importar {isPlain} desde '../utils/obj';
importar * como TRACK_TYPES desde '../tracks/track-types';
importar {toTitleCase, toLowerCase} desde '../utils/string-cases.js';
importar vtt desde 'videojs-vtt.js';
importar * como Guid desde '../utils/guid.js';

/**
 * Un objeto que contiene una estructura como: `{src: 'url', type: 'mimetype'}` o cadena
 * que solo contiene la url src sola.
 * * `var SourceObject = {src: 'http://ex.com/video.mp4', type: 'video/mp4'};`
   * `var SourceString = 'http://example.com/some-video.mp4';`
 *
 * @typedef {Objeto|cadena} Tech~SourceObject
 *
 * @propiedad {cadena} src
 * La url a la fuente
 *
 * tipo @property {cadena}
 * El tipo mime de la fuente
 * /

/**
 * Una función utilizada por {@link Tech} para crear un nuevo {@link TextTrack}.
 *
 * @privado
 *
 * @param {Tecnología} yo mismo
 * Una instancia de la clase Tech.
 *
 * @param {cadena} tipo
 * Tipo `TextTrack` (subtítulos, leyendas, descripciones, capítulos o metadatos)
 *
 * @param {cadena} [etiqueta]
 * Etiqueta para identificar la pista de texto
 *
 * @param {cadena} [idioma]
 * Abreviatura de idioma de dos letras
 *
 * @param {Objeto} [opciones={}]
 * Un objeto con opciones de seguimiento de texto adicionales
 *
 * @return {Pista de texto}
 * La pista de texto que se creó.
 * /
function createTrackHelper(self, tipo, etiqueta, idioma, opciones = {}) {
  const pistas = self.textTracks();

  opciones.tipo = tipo;

  si (etiqueta) {
    opciones.etiqueta = etiqueta;
  }
  si (idioma) {
    opciones.idioma = idioma;
  }
  opciones.tech = self;

  const track = new TRACK_TYPES.ALL.text.TrackClass(opciones);

  pistas.addTrack(pista);

  pista de retorno;
}

/**
 * Esta es la clase base para los controladores de tecnología de reproducción multimedia, como
 * {@enlace HTML5}
 *
 * Componente @extiende
 * /
class Tech extiende el componente {

  /**
  * Crear una instancia de este Tech.
  *
  * @param {Objeto} [opciones]
  * El almacén de clave/valor de las opciones del jugador.
  *
  * @param {Componente~ReadyCallback} listo
  * Función de devolución de llamada para llamar cuando la tecnología `HTML5` esté lista.
  * /
  constructor(opciones = {}, listo = función() {}) {
    // no queremos que la tecnología informe la actividad del usuario automáticamente.
    // Esto se hace manualmente en addControlsListeners
    opciones.reportTouchActivity = falso;
    super(nulo, opciones, listo);

    this.onDurationChange_ = (e) => this.onDurationChange(e);
    this.trackProgress_ = (e) => este.trackProgress(e);
    this.trackCurrentTime_ = (e) => this.trackCurrentTime(e);
    this.stopTrackingCurrentTime_ = (e) => this.stopTrackingCurrentTime(e);
    this.disposeSourceHandler_ = (e) => this.disposeSourceHandler(e);

    this.queuedHanders_ = new Set();

    // realizar un seguimiento de si la fuente actual se ha reproducido en absoluto para
    // implementar un jugado() muy limitado
    this.hasStarted_ = false;
    this.on('jugando', function() {
      this.hasStarted_ = true;
    });
    this.on('loadstart', function() {
      this.hasStarted_ = false;
    });

    TRACK_TYPES.ALL.names.forEach((nombre) => {
      const props = TRACK_TYPES.ALL[nombre];

      si (opciones && opciones[props.getterName]) {
        this[props.privateName] = options[props.getterName];
      }
    });

    // Realice un seguimiento manual del progreso en los casos en que el navegador/tecnología no lo informe.
    if (!this.featuresProgressEvents) {
      this.manualProgressOn();
    }

    // Realice un seguimiento manual de las actualizaciones de tiempo en los casos en que el navegador/tecnología no lo informe.
    if (!this.featuresTimeupdateEvents) {
      this.manualTimeUpdatesOn();
    }

    ['Texto', 'Audio', 'Video'].forEach((pista) => {
      if (opciones[`native${pista}Pistas`] === falso) {
        this[`featuresNative${track}Tracks`] = false;
      }
    });

    if (opciones.nativeCaptions === false || options.nativeTextTracks === false) {
      this.featuresNativeTextTracks = false;
    } else if (opciones.nativeCaptions === verdadero || opciones.nativeTextTracks === verdadero) {
      this.featuresNativeTextTracks = true;
    }

    if (!this.featuresNativeTextTracks) {
      this.emulateTextTracks();
    }

    this.preloadTextTracks = options.preloadTextTracks !== false;

    this.autoRemoteTextTracks_ = new TRACK_TYPES.ALL.text.ListClass();

    this.initTrackListeners();

    // Active los eventos de pulsación de componentes solo si no utiliza controles nativos
    si (!opciones.nativeControlsForTouch) {
      esto.emitTapEvents();
    }

    if (este.constructor) {
      este.nombre_ = este.constructor.nombre || 'Tecnología desconocida';
    }
  }

  /**
   * Una función especial para activar el conjunto de fuentes de una manera que permitirá al jugador
   * para volver a activar si el jugador o la tecnología aún no están listos.
   *
   * @fuegos Tech#sourceset
   * @param {string} src La cadena de origen en el momento del cambio de fuente.
   * /
  triggerSourceset(origen) {
    si (!esto.estáListo_) {
      // en la preparación inicial, tenemos que activar el conjunto de fuentes
      // 1 ms después de listo para que el jugador pueda verlo.
      this.one('listo', () => this.setTimeout(() => this.triggerSourceset(src), 1));
    }

    /**
     * Activado cuando la fuente está configurada en la tecnología que causa el elemento multimedia
     * para recargar.
     *
     * @ver {@link Player#event:sourceset}
     * @event Tech#sourceset
     * @type {Objetivo del evento~Evento}
     * /
    este.disparador({
      origen,
      tipo: 'conjunto de fuentes'
    });
  }

  /* Reservas para tipos de eventos no admitidos
  ================================================== ==============================*/

  /**
   * Polyfill el evento `progress` para los navegadores que no lo admiten de forma nativa.
   *
   * @ver {@link Tech#trackProgress}
   * /
  manualProgressOn() {
    this.on('cambioduración', this.onDurationChange_);

    este.progresomanual = verdadero;

    // Activar la observación del progreso cuando una fuente comienza a cargarse
    this.one('listo', this.trackProgress_);
  }

  /**
   * Desactivar el polyfill para eventos de `progreso` que se creó en
   * {@link Tech#manualProgressOn}
   * /
  ManualProgressOff() {
    este.progresomanual = falso;
    this.stopTrackingProgress();

    this.off('cambioduración', this.onDurationChange_);
  }

  /**
   * Esto se usa para desencadenar un evento de `progreso` cuando cambia el porcentaje almacenado en el búfer. Él
   * establece una función de intervalo que se llamará cada 500 milisegundos para verificar si el
   * el porcentaje final del búfer ha cambiado.
   *
   * > Esta función es llamada por {@link Tech#manualProgressOn}
   *
   * @param {EventTarget~Evento} evento
   * El evento `ready` que hizo que esto se ejecutara.
   *
   * @escucha Tech#listo
   * @fires Tech#progreso
   * /
  trackProgress(evento) {
    this.stopTrackingProgress();
    this.progressInterval = this.setInterval(Fn.bind(this, function() {
      // No activar a menos que la cantidad almacenada en búfer sea mayor que la última vez

      const numBufferedPercent = this.bufferedPercent();

      if (this.bufferedPercent_ !== numBufferedPercent) {
        /**
         * Ver {@link Player#progress}
         *
         * @event Tech#progreso
         * @type {Objetivo del evento~Evento}
         * /
        this.trigger('progreso');
      }

      this.bufferedPercent_ = numBufferedPercent;

      if (numeroPorcentajeBúfer === 1) {
        this.stopTrackingProgress();
      }
    }), 500);
  }

  /**
   * Actualice nuestra duración interna en un evento `durationchange` llamando
   * {@link Tecnología#duración}.
   *
   * @param {EventTarget~Evento} evento
   * El evento `durationchange` que hizo que esto se ejecutara.
   *
   * @escucha Tech#durationchange
   * /
  onDurationChange(evento) {
    this.duration_ = this.duration();
  }

  /**
   * Obtenga y cree un objeto `TimeRange` para el almacenamiento en búfer.
   *
   * @return {Intervalo de tiempo}
   * El objeto de intervalo de tiempo que se creó.
   * /
  almacenado en búfer () {
    volver crearRangoTiempo(0, 0);
  }

  /**
   * Obtenga el porcentaje del video actual que actualmente está almacenado en búfer.
   *
   * @return {número}
   * Un número del 0 al 1 que representa el porcentaje decimal de la
   * video que está almacenado en búfer.
   *
   * /
  porcentajebuffered() {
    return bufferedPercent(this.buffered(), this.duration_);
  }

  /**
   * Desactivar el polyfill para eventos de `progreso` que se creó en
   * {@link Tech#manualProgressOn}
   * Deje de rastrear manualmente los eventos de progreso borrando el intervalo que se estableció en
   * {@link Tech#trackProgress}.
   * /
  detener el seguimiento del progreso () {
    este.clearInterval(este.progressInterval);
  }

  /**
   * Polyfill el evento `timeupdate` para los navegadores que no lo admiten.
   *
   * @ver {@link Tech#trackCurrentTime}
   * /
  manualTimeUpdatesOn() {
    this.manualTimeUpdates = verdadero;

    this.on('reproducir', this.trackCurrentTime_);
    this.on('pausa', this.stopTrackingCurrentTime_);
  }

  /**
   * Desactivar el polyfill para eventos `timeupdate` que se creó en
   * {@link Tech#manualTimeUpdatesOn}
   * /
  manualTimeUpdatesOff() {
    this.manualTimeUpdates = falso;
    this.stopTrackingCurrentTime();
    this.off('reproducir', this.trackCurrentTime_);
    this.off('pausa', this.stopTrackingCurrentTime_);
  }

  /**
   * Configura una función de intervalo para realizar un seguimiento de la hora actual y activar `timeupdate` cada
   * 250 milisegundos.
   *
   * @escucha Tech#play
   * @triggers Tech#timeupdate
   * /
  seguimientoHoraActual() {
    if (este.intervaloTiempoActual) {
      this.stopTrackingCurrentTime();
    }
    this.currentTimeInterval = this.setInterval(function() {
      /**
       * Activado en un intervalo de 250 ms para indicar que el tiempo está pasando en el video.
       *
       * @event Tech#timeupdate
       * @type {Objetivo del evento~Evento}
       * /
      this.trigger({ type: 'timeupdate', target: this, manualmente Triggered: true });

    // 42 = 24 fps // 250 es lo que usa Webkit // FF usa 15
    }, 250);
  }

  /**
   * Detenga la función de intervalo creada en {@link Tech#trackCurrentTime} para que el
   * El evento `timeupdate` ya no se activa.
   *
   * @escucha {Tecnología#pausa}
   * /
  detener el seguimiento de la hora actual () {
    this.clearInterval(this.currentTimeInterval);

    // #1002 - si el video termina justo antes de que ocurra la próxima actualización,
    // la barra de progreso no llegará hasta el final
    this.trigger({ type: 'timeupdate', target: this, manualmente Triggered: true });
  }

  /**
   * Apague todos los polyfills de eventos, borre la `Tech`s {@link AudioTrackList},
   * {@link VideoTrackList} y {@link TextTrackList}, y deseche esta tecnología.
   *
   * Componente @fires#dispose
   * /
  disponer () {

    // borrar todas las pistas porque no podemos reutilizarlas entre técnicos
    this.clearTracks(TRACK_TYPES.NORMAL.names);

    // Desactiva cualquier progreso manual o seguimiento de actualización de tiempo
    if (este.progresomanual) {
      this.manualProgressOff();
    }

    if (this.manualTimeUpdates) {
      this.manualTimeUpdatesOff();
    }

    super.dispose();
  }

  /**
   * Borrar una sola `TrackList` o una matriz de `TrackLists` dados sus nombres.
   *
   * > Nota: Los técnicos sin controladores de fuentes deberían llamar a esto entre fuentes para `video`
   *         & Pistas de `audio`. ¡No querrás usarlos entre pistas!
   *
   * @param {cadena[]|cadena} tipos
   * Los nombres de TrackList para borrar, los nombres válidos son `video`, `audio` y
   * `texto`.
   * /
  borrar pistas (tipos) {
    tipos = [].concat(tipos);
    // borrar todas las pistas porque no podemos reutilizarlas entre técnicos
    tipos.forEach((tipo) => {
      const list = this[`${type}Pistas`]() || [];
      let i = lista.longitud;

      mientras yo--) {
        const pista = lista[i];

        if (escriba === 'texto') {
          this.removeRemoteTextTrack(pista);
        }
        list.removeTrack(pista);
      }
    });
  }

  /**
   * Elimine cualquier TextTracks agregado a través de addRemoteTextTrack que sea
   * marcado para recolección automática de basura
   * /
  limpiezaAutoTextTracks() {
    const list = this.autoRemoteTextTracks_ || [];
    let i = lista.longitud;

    mientras yo--) {
      const pista = lista[i];

      this.removeRemoteTextTrack(pista);
    }
  }

  /**
   * Restablecer la tecnología, que eliminará todas las fuentes y restablecerá el estado de preparación interno.
   *
   * @abstracto
   * /
  reiniciar() {}

  /**
   * Obtenga el valor de `crossOrigin` de la tecnología.
   *
   * @abstracto
   *
   * @ver {Html5#crossOrigin}
   * /
  origencruz() {}

  /**
   * Establezca el valor de `crossOrigin` en la tecnología.
   *
   * @abstracto
   *
   * @param {string} crossOrigin el valor de crossOrigin
   * @ver {Html5#setCrossOrigin}
   * /
  establecer origen cruzado () {}

  /**
   * Obtener o establecer un error en el Tech.
   *
   * @param {Error de medios} [error]
   * Error al configurar en el Tech
   *
   * @return {Error de Medios|null}
   * El objeto de error actual en la tecnología, o nulo si no lo hay.
   * /
  error (err) {
    si (err !== indefinido) {
      this.error_ = new MediaError(err);
      this.trigger('error');
    }
    devolver este.error_;
  }

  /**
   * Devuelve los `TimeRange`s que se han reproducido para la fuente actual.
   *
   * > NOTA: Esta implementación está incompleta. No rastrea el `TimeRange` reproducido.
   * Solo comprueba si la fuente se ha reproducido o no.
   *
   * @return {Intervalo de tiempo}
   * - Un solo rango de tiempo si este video se ha reproducido
   * - Un conjunto vacío de rangos si no.
   * /
  jugó() {
    if (esto.haComenzado_) {
      volver crearRangoTiempo(0, 0);
    }
    devuelve createTimeRange();
  }

  /**
   * Iniciar reproducción
   *
   * @abstracto
   *
   * @ver {Html5#play}
   * /
  jugar() {}

  /**
   * Establecer si estamos fregando o no
   *
   * @abstracto
   *
   * @ver {Html5#setScrubbing}
   * /
  setScrubbing() {}

  /**
   * Obtener si estamos fregando o no
   *
   * @abstracto
   *
   * @ver {Html5#scrubbing}
   * /
  fregado() {}

  /**
   * Hace que se produzca una actualización manual de la hora si {@link Tech#manualTimeUpdatesOn} fue
   * previamente llamado.
   *
   * @Fire Tech#timeupdate
   * /
  establecerHoraActual() {
    // mejorar la precisión de las actualizaciones de tiempo manuales
    if (this.manualTimeUpdates) {
      /**
       * Un evento manual `timeupdate`.
       *
       * @event Tech#timeupdate
       * @type {Objetivo del evento~Evento}
       * /
      this.trigger({ type: 'timeupdate', target: this, manualmente Triggered: true });
    }
  }

  /**
   * Activar oyentes para {@link VideoTrackList}, {@link {AudioTrackList} y
   * {@link TextTrackList} eventos.
   *
   * Esto agrega {@link EventTarget~EventListeners} para `addtrack` y `removetrack`.
   *
   * @fires Tech#cambio de pista de audio
   * @fires Tech#videotrackchange
   * @fires Tech#texttrackchange
   * /
  initTrackListeners() {
    /**
      * Se activa cuando se agregan o eliminan pistas en Tech {@link AudioTrackList}
      *
      * @event Tech#cambio de pista de audio
      * @type {Objetivo del evento~Evento}
      * /

    /**
      * Se activa cuando se agregan o eliminan pistas en Tech {@link VideoTrackList}
      *
      * @event Tech#videotrackchange
      * @type {Objetivo del evento~Evento}
      * /

    /**
      * Se activa cuando se agregan o eliminan pistas en Tech {@link TextTrackList}
      *
      * @event Tech#texttrackchange
      * @type {Objetivo del evento~Evento}
      * /
    TRACK_TYPES.NORMAL.names.forEach((nombre) => {
      const props = TRACK_TYPES.NORMAL[nombre];
      const trackListChanges = () => {
        this.trigger(`${nombre}cambio de pista`);
      };

      const pistas = this[props.getterName]();

      pistas.addEventListener('removetrack', trackListChanges);
      pistas.addEventListener('addtrack', trackListChanges);

      this.on('dispose', () => {
        pistas.removeEventListener('removetrack', trackListChanges);
        pistas.removeEventListener('addtrack', trackListChanges);
      });
    });
  }

  /**
   * Emular TextTracks usando vtt.js si es necesario
   *
   * @fires Tech#vttjsloaded
   * @fires Tech#vttjserror
   * /
  agregarWebVttScript_() {
    si (ventana.WebVTT) {
      devolver;
    }

    // Inicialmente, Tech.el_ es un elemento secundario de un div ficticio, espere hasta que el sistema de componentes
    // señala que el Tech está listo en cuyo punto Tech.el_ es parte del DOM
    // antes de insertar el script WebVTT
    if (documento.cuerpo.contiene(esto.el())) {

      // cargar a través de require si está disponible y no se pasó la ubicación del script vtt.js
      // como una opción. Las compilaciones novtt convertirán la llamada requerida anterior en un objeto vacío
      // lo que causará esto si la verificación falla siempre.
      if (!this.options_['vtt.js'] && es simple (vtt) && Objeto.claves(vtt).longitud > 0) {
        this.trigger('vttjsloaded');
        devolver;
      }

      // cargar vtt.js a través de la opción de ubicación del script o el cdn sin ubicación fue
      // aprobada en
      const guión = documento.createElement('guión');

      script.src = this.options_['vtt.js'] || 'https://vjs.zencdn.net/vttjs/0.14.1/vtt.min.js';
      script.onload = () => {
        /**
         * Se activa cuando se carga vtt.js.
         *
         * @event Tech#vttjsloaded
         * @type {Objetivo del evento~Evento}
         * /
        this.trigger('vttjsloaded');
      };
      script.onerror = () => {
        /**
         * Activado cuando vtt.js no se cargó debido a un error
         *
         * @event Tech#vttjsloaded
         * @type {Objetivo del evento~Evento}
         * /
        this.trigger('vttjserror');
      };
      this.on('dispose', () => {
        secuencia de comandos.onload = nulo;
        script.onerror = nulo;
      });
      // pero aún no se ha cargado y lo establecemos en verdadero antes de la inyección para que
      // no sobrescribimos la ventana inyectada.WebVTT si se carga de inmediato
      ventana.WebVTT = verdadero;
      this.el().parentNode.appendChild(script);
    } else {
      esto.listo(esto.addWebVttScript_);
    }

  }

  /**
   * Emular pistas de texto
   *
   * /
  emular pistas de texto () {
    const pistas = this.textTracks();
    const remoteTracks = this.remoteTextTracks();
    const handleAddTrack = (e) => pistas.addTrack(e.pista);
    const handleRemoveTrack = (e) => pistas.removeTrack(pista electrónica);

    remoteTracks.on('addtrack', handleAddTrack);
    remoteTracks.on('removetrack', handleRemoveTrack);

    esto.addWebVttScript_();

    const actualizarPantalla = () => this.trigger('texttrackchange');

    const textTracksChanges = () => {
      actualizarPantalla();

      para (sea i = 0; i < pistas.longitud; i++) {
        const pista = pistas[i];

        track.removeEventListener('cuechange', updateDisplay);
        if (track.mode === 'mostrando') {
          track.addEventListener('cuechange', updateDisplay);
        }
      }
    };

    cambios de pistas de texto ();
    tracks.addEventListener('cambio', textTracksChanges);
    tracks.addEventListener('addtrack', textTracksChanges);
    pistas.addEventListener('removetrack', textTracksChanges);

    this.on('dispose', function() {
      remoteTracks.off('addtrack', handleAddTrack);
      remoteTracks.off('removetrack', handleRemoveTrack);
      tracks.removeEventListener('cambio', textTracksChanges);
      tracks.removeEventListener('addtrack', textTracksChanges);
      tracks.removeEventListener('removetrack', textTracksChanges);

      para (sea i = 0; i < pistas.longitud; i++) {
        const pista = pistas[i];

        track.removeEventListener('cuechange', updateDisplay);
      }
    });
  }

  /**
   * Crea y devuelve un objeto {@link TextTrack} remoto.
   *
   * @param {cadena} tipo
   * Tipo `TextTrack` (subtítulos, leyendas, descripciones, capítulos o metadatos)
   *
   * @param {cadena} [etiqueta]
   * Etiqueta para identificar la pista de texto
   *
   * @param {cadena} [idioma]
   * Abreviatura de idioma de dos letras
   *
   * @return {Pista de texto}
   * El TextTrack que se crea.
   * /
  addTextTrack(tipo, etiqueta, idioma) {
    si (! tipo) {
      throw new Error('Se requiere el tipo TextTrack pero no se proporcionó');
    }

    return createTrackHelper(this, kind, label, language);
  }

  /**
   * Cree un TextTrack emulado para que lo use addRemoteTextTrack
   *
   * Esto está destinado a ser anulado por las clases que heredan de
   * Tecnología para crear TextTracks nativos o personalizados.
   *
   * @param {Objeto} opciones
   * El objeto debe contener las opciones para inicializar TextTrack.
   *
   * @param {cadena} [opciones.tipo]
   * Tipo `TextTrack` (subtítulos, leyendas, descripciones, capítulos o metadatos).
   *
   * @param {cadena} [opciones.etiqueta].
   * Etiqueta para identificar la pista de texto
   *
   * @param {cadena} [opciones.idioma]
   * Abreviatura del idioma de dos letras.
   *
   * @return {HTMLTrackElement}
   * El elemento de pista que se crea.
   * /
  createRemoteTextTrack(opciones) {
    const track = mergeOptions(opciones, {
      tecnología: esto
    });

    devuelve nuevos TRACK_TYPES.REMOTE.remoteTextEl.TrackClass(pista);
  }

  /**
   * Crea un objeto de seguimiento de texto remoto y devuelve un elemento de seguimiento html.
   *
   * > Nota: Puede ser un {@link HTMLTrackElement} emulado o uno nativo.
   *
   * @param {Objeto} opciones
   * Consulte {@link Tech#createRemoteTextTrack} para obtener propiedades más detalladas.
   *
   * @param {booleano} [limpieza manual=verdadero]
   * - Cuando es falso: TextTrack se eliminará automáticamente del video
   * elemento cada vez que cambia la fuente
   * - Cuando es cierto: El TextTrack tendrá que limpiarse manualmente
   *
   * @return {HTMLTrackElement}
   * Un elemento de pista Html.
   *
   * @deprecated La funcionalidad predeterminada para esta función será equivalente
   * a &quot;manualCleanup=false&quot; en el futuro. El parámetro manualCleanup
   * también ser eliminado.
   * /
  addRemoteTextTrack(opciones = {}, limpiezamanual) {
    const htmlTrackElement = this.createRemoteTextTrack(opciones);

    if (limpieza manual! == verdadero && limpieza manual !== falso) {
      // advertencia de obsolescencia
      log.warn('Llamar a addRemoteTextTrack sin establecer explícitamente el parámetro &quot;manualCleanup&quot; en `true` está en desuso y está predeterminado en `false` en futuras versiones de video.js');
      limpieza manual = verdadero;
    }

    // almacenar HTMLTrackElement y TextTrack en la lista remota
    this.remoteTextTrackEls().addTrackElement_(htmlTrackElement);
    this.remoteTextTracks().addTrack(htmlTrackElement.track);

    if (limpieza manual! == verdadero) {
      // crea la TextTrackList si no existe
      esto.listo(() => this.autoRemoteTextTracks_.addTrack(htmlTrackElement.track));
    }

    devolver htmlTrackElement;
  }

  /**
   * Elimina una pista de texto remota de la `TextTrackList` remota.
   *
   * @param {TextTrack} pista
   * `TextTrack` para eliminar de `TextTrackList`
   * /
  removeRemoteTextTrack(pista) {
    const trackElement = this.remoteTextTrackEls().getTrackElementByTrack_(pista);

    // eliminar HTMLTrackElement y TextTrack de la lista remota
    this.remoteTextTrackEls().removeTrackElement_(trackElement);
    this.remoteTextTracks().removeTrack(pista);
    this.autoRemoteTextTracks_.removeTrack(pista);
  }

  /**
   * Obtiene métricas de calidad de reproducción de medios disponibles según lo especificado por los medios de W3C
   * API de calidad de reproducción.
   *
   * @ver [Especificaciones]{@enlace https://wicg.github.io/media-playback-quality}
   *
   * @return {Objeto}
   * Un objeto con métricas de calidad de reproducción multimedia compatibles
   *
   * @abstracto
   * /
  getVideoPlaybackQuality() {
    devolver {};
  }

  /**
   * Intente crear una ventana de video flotante siempre encima de otras ventanas
   * para que los usuarios puedan seguir consumiendo medios mientras interactúan con otros
   * sitios de contenido o aplicaciones en su dispositivo.
   *
   * @ver [Especificación]{@enlace https://wicg.github.io/picture-in-picture}
   *
   * @return {Promesa|indefinido}
   * Una promesa con una ventana Picture-in-Picture si el navegador es compatible
   * Promesas (o se pasó una como opción). Devuelve indefinido
   * de lo contrario.
   *
   * @abstracto
   * /
  solicitudImagenEnImagen() {
    const PromiseClass = this.options_.Promise || ventana.Promesa;

    si (PromesaClase) {
      devolver PromiseClass.reject();
    }
  }

  /**
   * Un método para verificar el valor de 'disablePictureInPicture' < video> propiedad.
   * El valor predeterminado es verdadero, ya que debe considerarse deshabilitado si la tecnología no es compatible con pip
   *
   * @abstracto
   * /
  deshabilitarImagenEnImagen() {
    devolver verdadero;
  }

  /**
   * Un método para configurar o desactivar 'disablePictureInPicture' < video> propiedad.
   *
   * @abstracto
   * /
  setDisablePictureInPicture() {}

  /**
   * Una implementación alternativa de requestVideoFrameCallback usando requestAnimationFrame
   *
   * @param {función} cb
   * ID de solicitud de @return {número}
   * /
  solicitudVideoFrameCallback(cb) {
    const id = Guid.nuevoGUID();

    if (!this.isReady_ || this.paused()) {
      this.queuedHanders_.add(id);
      this.one('jugando', () => {
        if (this.queuedHanders_.has(id)) {
          this.queuedHanders_.delete(id);
          cb();
        }
      });
    } else {
      this.requestNamedAnimationFrame(id, cb);
    }

    identificación de retorno;
  }

  /**
   * Una implementación alternativa de cancelVideoFrameCallback
   *
   * @param {number} id id de la devolución de llamada que se cancelará
   * /
  cancelarVideoFrameCallback(id) {
    if (this.queuedHanders_.has(id)) {
      this.queuedHanders_.delete(id);
    } else {
      this.cancelNamedAnimationFrame(id);
    }
  }

  /**
   * Un método para configurar un cartel de un `Tech`.
   *
   * @abstracto
   * /
  establecerPoster() {}

  /**
   * Un método para verificar la presencia de 'playsinline' < video> atributo.
   *
   * @abstracto
   * /
  juegosenlinea() {}

  /**
   * Un método para configurar o desactivar el 'playsinline' < video> atributo.
   *
   * @abstracto
   * /
  setPlaysinline() {}

  /**
   * Intento de forzar la anulación de las pistas de audio nativas.
   *
   * @param {boolean} anular: si se establece en verdadero audio nativo, se anulará,
   * de lo contrario, se utilizará potencialmente el audio nativo.
   *
   * @abstracto
   * /
  anular las pistas de audio nativas () {}

  /**
   * Intento de forzar la anulación de pistas de video nativas.
   *
   * @param {boolean} anular: si se establece en verdadero video nativo, se anulará,
   * de lo contrario, se utilizará potencialmente el video nativo.
   *
   * @abstracto
   * /
  anular las pistas de vídeo nativas () {}

  /*
   * Verifique si la tecnología puede admitir el tipo de mimo dado.
   *
   * La tecnología base no admite ningún tipo, pero los controladores de origen pueden
   * sobrescribir esto.
   *
   * @param {cadena} tipo
   * El tipo MIME para comprobar si hay soporte.
   *
   * @return {cadena}
   * 'probablemente', 'quizás' o cadena vacía
   *
   * @ver [Spec]{@link https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/canPlayType}
   *
   * @abstracto
   * /
  puedePlayType() {
    devolver '';
  }

  /**
   * Compruebe si el tipo es compatible con esta tecnología.
   *
   * La tecnología base no admite ningún tipo, pero los controladores de origen pueden
   * sobrescribir esto.
   *
   * @param {cadena} tipo
   * El tipo de medio para comprobar
   * @return {string} Devuelve la respuesta del elemento de video nativo
   * /
  canPlayType estático () {
    devolver '';
  }

  /**
   * Verifique si la tecnología puede admitir la fuente dada
   *
   * @param {Objeto} srcObj
   * El objeto fuente
   * @param {Objeto} opciones
   * Las opciones pasan a la tecnología
   * @return {cadena} 'probablemente', 'tal vez' o '' (cadena vacía)
   * /
  canPlaySource estático (srcObj, opciones) {
    devuelve Tech.canPlayType(srcObj.type);
  }

  /*
   * Devuelve si el argumento es un Tech o no.
   * Se puede pasar una clase como `Html5` o una instancia como `player.tech_`
   *
   * @param {Objeto} componente
   * El artículo a comprobar
   *
   * @return {booleano}
   * Si es una tecnología o no
   * - Cierto si es una tecnología
   * - Falso si no lo es
   * /
  isTech estático (componente) {
    volver componente.prototipo instancia de Tech ||
           instancia de componente de tecnología ||
           componente === Tecnología;
  }

  /**
   * Registra un `Tech` en una lista compartida para videojs.
   *
   * @param {cadena} nombre
   * Nombre del `Tech` a registrar.
   *
   * @param {Objeto} tecnología
   * La clase `Tech` para registrarse.
   * /
  static registerTech(nombre, tecnología) {
    if (!Tech.techs_) {
      Tech.techs_ = {};
    }

    if (!Tecnología.esTecnología(tecnología)) {
      throw new Error(`Tech ${name} debe ser un Tech`);
    }

    if (!Tech.canPlayType) {
      throw new Error('Los técnicos deben tener un método canPlayType estático');
    }
    si (!Tech.canPlaySource) {
      throw new Error('Los técnicos deben tener un método canPlaySource estático');
    }

    nombre = toTitleCase(nombre);

    Tech.techs_[nombre] = tecnología;
    Tech.techs_[toLowerCase(nombre)] = tecnología;
    if (nombre !== 'Tecnología') {
      // camel case el techName para usar en techOrder
      Tech.defaultTechOrder_.push(nombre);
    }
    tecnología de retorno;
  }

  /**
   * Obtenga un 'Tecnólogo' de la lista compartida por nombre.
   *
   * @param {cadena} nombre
   * `camelCase` o `TitleCase` nombre del Tech para obtener
   *
   * @return {Tecnología|indefinido}
   * El `Tech` o indefinido si no hubo ningún tech con el nombre solicitado.
   * /
  getTech estático (nombre) {
    si (! nombre) {
      devolver;
    }

    si (Tecnología.técnicas_ && Tech.techs_[nombre]) {
      return Tech.techs_[nombre];
    }

    nombre = toTitleCase(nombre);

    si (ventana && ventana.videojs && ventana.videojs[nombre]) {
      log.warn(`La tecnología ${name} se agregó al objeto videojs cuando debería registrarse usando videojs.registerTech(name, tech)`);
      volver ventana.videojs[nombre];
    }
  }
}

/**
 * Obtener la {@link VideoTrackList}
 *
 * @returns {VideoTrackList}
 * @método Tech.prototype.videoTracks
 * /

/**
 * Obtener la {@link AudioTrackList}
 *
 * @returns {Lista de pistas de audio}
 * @método Tech.prototype.audioTracks
 * /

/**
 * Obtén la {@link TextTrackList}
 *
 * @devoluciones {TextTrackList}
 * @método Tech.prototype.textTracks
 * /

/**
 * Obtener el elemento remoto {@link TextTrackList}
 *
 * @devoluciones {TextTrackList}
 * @método Tech.prototype.remoteTextTracks
 * /

/**
 * Obtener el elemento remoto {@link HtmlTrackElementList}
 *
 * @devoluciones {HtmlTrackElementList}
 * @método Tech.prototype.remoteTextTrackEls
 * /

TRACK_TYPES.ALL.names.forEach(función(nombre) {
  const props = TRACK_TYPES.ALL[nombre];

  Tech.prototype[props.getterName] = función() {
    this[props.privateName] = this[props.privateName] || nuevos accesorios.ListClass();
    devolver este [props.privateName];
  };
});

/**
 * Lista de pistas de texto asociadas
 *
 * @escriba {TextTrackList}
 * @privado
 * @property Tech#textTracks_
 * /

/**
 * Lista de pistas de audio asociadas.
 *
 * @escriba {Lista de pistas de audio}
 * @privado
 * @property Tech#audioTracks_
 * /

/**
 * Lista de pistas de video asociadas.
 *
 * @escriba {Lista de pistas de video}
 * @privado
 * @property Tech#videoTracks_
 * /

/**
 * Booleano que indica si `Tech` admite control de volumen.
 *
 * @tipo {booleano}
 * @por defecto
 * /
Tech.prototype.featuresVolumeControl = verdadero;

/**
 * Booleano que indica si `Tech` admite volumen de silenciamiento.
 *
 * @tipo {bolean}
 * @por defecto
 * /
Tech.prototype.featuresMuteControl = verdadero;

/**
 * Booleano que indica si `Tech` admite el control de cambio de tamaño de pantalla completa.
 * Cambiar el tamaño de los complementos mediante la solicitud de pantalla completa vuelve a cargar el complemento
 *
 * @tipo {booleano}
 * @por defecto
 * /
Tech.prototype.featuresFullscreenResize = false;

/**
 * Booleano que indica si `Tech` admite cambiar la velocidad a la que se reproduce el video
 * obras de teatro. Ejemplos:
 * - Establecer jugador para jugar 2x (dos veces) tan rápido
 * - Configure el jugador para que juegue 0.5x (la mitad) más rápido
 *
 * @tipo {booleano}
 * @por defecto
 * /
Tech.prototype.featuresPlaybackRate = falso;

/**
 * Booleano que indica si `Tech` admite el evento `progress`. esto es actualmente
 * no activado por video-js-swf. Esto se utilizará para determinar si
 * Se debe llamar a {@link Tech#manualProgressOn}.
 *
 * @tipo {booleano}
 * @por defecto
 * /
Tech.prototype.featuresProgressEvents = falso;

/**
 * Booleano que indica si `Tech` admite el evento `sourceset`.
 *
 * Un técnico debe establecer esto en &quot;verdadero&quot; y luego usar {@link Tech#triggerSourceset}
 * para activar un {@link Tech#event:sourceset} lo antes posible después de obtener
 * una nueva fuente.
 *
 * @tipo {booleano}
 * @por defecto
 * /
Tech.prototype.featuresSourceset = false;

/**
 * Booleano que indica si `Tech` admite el evento `timeupdate`. esto es actualmente
 * no activado por video-js-swf. Esto se utilizará para determinar si
 * Se debe llamar a {@link Tech#manualTimeUpdates}.
 *
 * @tipo {booleano}
 * @por defecto
 * /
Tech.prototype.featuresTimeupdateEvents = falso;

/**
 * Booleano que indica si `Tech` es compatible con `TextTrack`s nativos.
 * Esto nos ayudará a integrarnos con `TextTrack`s nativos si el navegador los admite.
 *
 * @tipo {booleano}
 * @por defecto
 * /
Tech.prototype.featuresNativeTextTracks = falso;

/**
 * Booleano que indica si `Tech` admite `requestVideoFrameCallback`.
 *
 * @tipo {booleano}
 * @por defecto
 * /
Tech.prototype.featuresVideoFrameCallback = falso;

/**
 * Un mixin funcional para técnicos que quieren usar el patrón Source Handler.
 * Los controladores de origen son scripts para manejar formatos específicos.
 * El patrón de controlador de origen se utiliza para formatos adaptables (HLS, DASH) que
 * Cargue manualmente los datos de video y aliméntelos a un búfer de origen (extensiones de origen de medios)
 * Ejemplo: `Tech.withSourceHandlers.call(MyTech);`
 *
 * @param {Tecnología} _Tecnología
 * La tecnología para agregar funciones de controlador de fuente.
 *
 * @mixes Tech~SourceHandlerAdditions
 * /
Tech.withSourceHandlers = function(_Tech) {

  /**
   * Registrar un controlador de fuente
   *
   * Controlador @param {Función}
   * La clase de controlador de origen
   *
   * @param {número} [índice]
   * Regístralo en el siguiente índice
   * /
  _Tech.registerSourceHandler = función (controlador, índice) {
    dejar manejadores = _Tech.sourceHandlers;

    si (! manejadores) {
      manejadores = _Tech.sourceHandlers = [];
    }

    si (índice === indefinido) {
      // añadir al final de la lista
      índice = manejadores.longitud;
    }

    manejadores.empalme(índice, 0, manejador);
  };

  /**
   * Verifique si la tecnología puede admitir el tipo dado. También comprueba la
   * Técnicos sourceHandlers.
   *
   * @param {cadena} tipo
   * El tipo MIME a comprobar.
   *
   * @return {cadena}
   * 'probablemente', 'tal vez' o '' (cadena vacía)
   * /
  _Tech.canPlayType = función (tipo) {
    manejadores const = _Tech.sourceHandlers || [];
    dejar puede;

    para (sea i = 0; i < manejadores.longitud; i++) {
      can = handlers[i].canPlayType(tipo);

      si puede) {
        lata de retorno;
      }
    }

    devolver '';
  };

  /**
   * Devuelve el primer controlador de fuente que admite la fuente.
   *
   * HACER: Responda a la pregunta: ¿debería priorizarse 'probablemente' sobre 'quizás'?
   *
   * @param {Tech~SourceObject} fuente
   * El objeto fuente
   *
   * @param {Objeto} opciones
   * Las opciones pasan a la tecnología
   *
   * @return {SourceHandler|null}
   * El primer controlador de fuente que admite la fuente o nulo si
   * ningún SourceHandler admite la fuente
   * /
  _Tech.selectSourceHandler = función (fuente, opciones) {
    manejadores const = _Tech.sourceHandlers || [];
    dejar puede;

    para (sea i = 0; i < manejadores.longitud; i++) {
      can = handlers[i].canHandleSource(fuente, opciones);

      si puede) {
        manejadores de devolución [i];
      }
    }

    devolver nulo;
  };

  /**
   * Compruebe si la tecnología puede admitir la fuente dada.
   *
   * @param {Tech~SourceObject} srcObj
   * El objeto fuente
   *
   * @param {Objeto} opciones
   * Las opciones pasan a la tecnología
   *
   * @return {cadena}
   * 'probablemente', 'tal vez' o '' (cadena vacía)
   * /
  _Tech.canPlaySource = function(srcObj, opciones) {
    const sh = _Tech.selectSourceHandler(srcObj, opciones);

    si (sh) {
      volver sh.canHandleSource(srcObj, opciones);
    }

    devolver '';
  };

  /**
   * Cuando utilice un controlador de código fuente, prefiera su implementación de
   * cualquier función proporcionada normalmente por la tecnología.
   * /
  const diferible = [
    'buscable',
    'buscando',
    'duración'
  ];

  /**
   * Un contenedor alrededor de {@link Tech#seekable} que llamará a un `SourceHandler`s seekable
   * función si existe, con un respaldo a la función de búsqueda Techs.
   *
   * @método _Tech.buscable
   * /

  /**
   * Un contenedor alrededor de {@link Tech#duration} que llamará a la duración de un 'SourceHandler'
   * función si existe, de lo contrario, recurrirá a la función de duración de tecnología.
   *
   * @método _Tecnología.duración
   * /

  diferible.forEach(function(fnName) {
    const originalFn = this[fnName];

    if (tipo de originalFn !== 'función') {
      devolver;
    }

    this[fnName] = función () {
      si (este.sourceHandler_ && this.sourceHandler_[fnName]) {
        devuelve this.sourceHandler_[fnName].apply(this.sourceHandler_, argumentos);
      }
      return originalFn.apply(esto, argumentos);
    };
  }, _Tecnología.prototipo);

  /**
   * Crear una función para configurar la fuente usando un objeto fuente
   * y controladores de origen.
   * Nunca debe llamarse a menos que se encuentre un controlador de origen.
   *
   * @param {Tech~SourceObject} fuente
   * Un objeto fuente con src y claves de tipo
   * /
  _Tech.prototype.setSource = función (fuente) {
    let sh = _Tech.selectSourceHandler(fuente, this.options_);

    si (!sh) {
      // Recurra a un manejador de fuente nativo cuando las fuentes no admitidas son
      // establecer deliberadamente
      si (_Tech.nativeSourceHandler) {
        sh = _Tech.nativeSourceHandler;
      } else {
        log.error('No se encontró un controlador de fuente para la fuente actual.');
      }
    }

    // Eliminar cualquier controlador de fuente existente
    this.disposeSourceHandler();
    this.off('dispose', this.disposeSourceHandler_);

    if (sh !== _Tech.nativeSourceHandler) {
      this.currentSource_ = fuente;
    }

    this.sourceHandler_ = sh.handleSource(source, this, this.options_);
    this.one('dispose', this.disposeSourceHandler_);
  };

  /**
   * Limpie cualquier SourceHandlers y oyentes existentes cuando se elimine el Tech.
   *
   * @escucha Tech#dispose
   * /
  _Tech.prototype.disposeSourceHandler = función () {
    // si tenemos una fuente y obtenemos otra
    // entonces estamos cargando algo nuevo
    // que borrar todas nuestras pistas actuales
    if (this.currentSource_) {
      this.clearTracks(['audio', 'video']);
      this.currentSource_ = null;
    }

    // siempre limpia las pistas de texto automático
    this.cleanupAutoTextTracks();

    si (este.sourceHandler_) {

      si (este.sourceHandler_.dispose) {
        este.sourceHandler_.dispose();
      }

      este.sourceHandler_ = nulo;
    }
  };

};

// La clase de tecnología base debe registrarse como un componente. Es el único
// Tecnología que se puede registrar como Componente.
Component.registerComponent('Tecnología', Tecnología);
Tecnología.registrarTecnología('Tecnología', Tecnología);

/**
 * Una lista de tecnologías que deben agregarse a techOrder en Players
 *
 * @privado
 * /
Tech.defaultTechOrder_ = [];

exportar tecnología predeterminada;