/**
 * @archivo reproductor.js
 * /
// Componente de subclases
importar componente desde './component.js';

importar {versión} desde '../../package.json';
importar documento desde 'global/document';
importar ventana desde 'global/window';
importar eventos desde './mixins/evented';
importar {isEvented, addEventedCallback} desde './mixins/evented';
importar * como Eventos desde './utils/events.js';
importar * como Dom desde './utils/dom.js';
importar * como Fn desde './utils/fn.js';
importar * como Guid desde './utils/guid.js';
importar * como navegador desde './utils/browser.js';
importar {IE_VERSION, IS_CHROME, IS_WINDOWS} desde './utils/browser.js';
registro de importación, { createLogger } desde './utils/log.js';
importar {toTitleCase, titleCaseEquals} desde './utils/string-cases.js';
importar { createTimeRange } desde './utils/time-ranges.js';
importar {bufferedPercent} desde './utils/buffer.js';
importar * como hoja de estilo desde './utils/stylesheet.js';
importar FullscreenApi desde './fullscreen-api.js';
importar MediaError desde './media-error.js';
importar safeParseTuple desde 'safe-json-parse/tuple';
importar {asignar} desde './utils/obj';
importar mergeOptions desde './utils/merge-options.js';
importar {silencePromise, isPromise} desde './utils/promise';
importar textTrackConverter desde './tracks/text-track-list-converter.js';
importar ModalDialog desde './modal-dialog';
importar tecnología desde './tech/tech.js';
importar * como middleware desde './tech/middleware.js';
importar {TODO como TRACK_TYPES} desde './tracks/track-types';
importar filterSource desde './utils/filter-source';
importar {getMimetype, findMimetype} desde './utils/mimetypes';
importar {hooks} desde './utils/hooks';
importar {isObject} desde './utils/obj';
importar código clave desde 'código clave';

// Las siguientes importaciones se utilizan solo para garantizar que los módulos correspondientes
// siempre se incluyen en el paquete video.js. La importación de los módulos
// ejecútelos y se registrarán solos con video.js.
importar './tech/loader.js';
importar './imagen-del-cartel.js';
import './tracks/text-track-display.js';
importar './loading-spinner.js';
importar './gran-play-button.js';
importar './cerrar-boton.js';
importar './barra-de-control/barra-de-control.js';
importar './error-display.js';
import './tracks/text-track-settings.js';
importar './resize-manager.js';
importar './live-tracker.js';

// Importar tecnología Html5, al menos para desechar la etiqueta de video original.
importar './tech/html5.js';

// Los siguientes eventos tecnológicos simplemente se reactivan
// en el jugador cuando suceden
constante TECH_EVENTS_RETRIGGER = [
  /**
   * Activado mientras el agente de usuario está descargando datos multimedia.
   *
   * @event Jugador#progreso
   * @type {Objetivo del evento~Evento}
   * /
  /**
   * Vuelva a activar el evento "progreso" que activó {@link Tech}.
   *
   * @privado
   * @método Player#handleTechProgress_
   * @fires Player#progreso
   * @escucha tecnología#progreso
   * /
  'progreso',

  /**
   * Se dispara cuando se cancela la carga de un audio/video.
   *
   * @reproductor de eventos#abortar
   * @type {Objetivo del evento~Evento}
   * /
  /**
   * Vuelva a activar el evento `abortar` que fue activado por {@link Tech}.
   *
   * @privado
   * @método Player#handleTechAbort_
   * @fires Player#abortar
   * @escucha Tech#abort
   * /
  'abortar',

  /**
   * Se activa cuando el navegador intencionalmente no obtiene datos multimedia.
   *
   * @reproductor de eventos#suspender
   * @type {Objetivo del evento~Evento}
   * /
  /**
   * Vuelva a activar el evento `suspender` que fue activado por {@link Tech}.
   *
   * @privado
   * @método Player#handleTechSuspend_
   * @despedir jugador#suspender
   * @escucha Tech#suspender
   * /
  'suspender',

  /**
   * Se activa cuando la lista de reproducción actual está vacía.
   *
   * @event Jugador #vaciado
   * @type {Objetivo del evento~Evento}
   * /
  /**
   * Vuelva a activar el evento "vaciado" que activó {@link Tech}.
   *
   * @privado
   * @method Player#handleTechEmptied_
   * @fires Jugador #vaciado
   * @escucha Tech#emptied
   * /
  'vaciado',
  /**
   * Se activa cuando el navegador intenta obtener datos multimedia, pero los datos no están disponibles.
   *
   * @event jugador # estancado
   * @type {Objetivo del evento~Evento}
   * /
  /**
   * Vuelva a activar el evento `stalled` que fue activado por {@link Tech}.
   *
   * @privado
   * @method Player#handleTechStalled_
   * @fires Player#stalled
   * @escucha Tech#stalled
   * /
  'estancado',

  /**
   * Se dispara cuando el navegador ha cargado metadatos para el audio/video.
   *
   * @reproductor de eventos#metadatos cargados
   * @type {Objetivo del evento~Evento}
   * /
  /**
   * Vuelva a activar el evento `loadedmetadata` que fue activado por {@link Tech}.
   *
   * @privado
   * @method Player#handleTechLoadedmetadata_
   * @fires Player#metadatos cargados
   * @escucha Tech#loadedmetadata
   * /
  'metadatos cargados',

  /**
   * Se dispara cuando el navegador ha cargado el cuadro actual del audio/video.
   *
   * @reproductor del evento#datos cargados
   * @tipo {evento}
   * /
  /**
   * Vuelva a activar el evento `loadeddata` que fue activado por {@link Tech}.
   *
   * @privado
   * @método Player#handleTechLoaddeddata_
   * @fires Player#loadeddata
   * @escucha Tech#loadeddata
   * /
  'datos cargados',

  /**
   * Se dispara cuando la posición de reproducción actual ha cambiado.
   *
   * @event Player#timeupdate
   * @tipo {evento}
   * /
  /**
   * Vuelva a activar el evento `timeupdate` que fue activado por {@link Tech}.
   *
   * @privado
   * @método Player#handleTechTimeUpdate_
   * @fires jugador #actualización de tiempo
   * @escucha Tech#timeupdate
   * /
  'actualización de tiempo',

  /**
   * Se dispara cuando cambian las dimensiones intrínsecas del video
   *
   * @reproductor de eventos#redimensionar
   * @tipo {evento}
   * /
  /**
   * Vuelva a activar el evento de "cambio de tamaño" que activó {@link Tech}.
   *
   * @privado
   * @method Player#handleTechResize_
   * @fires jugador #redimensionar
   * @escucha Tech#redimensionar
   * /
  'redimensionar',

  /**
   * Se dispara cuando se ha cambiado el volumen
   *
   * @reproductor de eventos#cambio de volumen
   * @tipo {evento}
   * /
  /**
   * Vuelva a activar el evento `cambio de volumen` que fue activado por {@link Tech}.
   *
   * @privado
   * @method Player#handleTechVolumechange_
   * @fires Player#cambio de volumen
   * @escucha Tech#cambio de volumen
   * /
  'cambio de volumen',

  /**
   * Se dispara cuando se ha cambiado la pista de texto
   *
   * @reproductor de eventos#texttrackchange
   * @tipo {evento}
   * /
  /**
   * Vuelva a activar el evento `texttrackchange` que fue activado por {@link Tech}.
   *
   * @privado
   * @method Player#handleTechTexttrackchange_
   * @fires Player#texttrackchange
   * @escucha Tech#texttrackchange
   * /
  'cambio de pista de texto'
];

// eventos en cola cuando la tasa de reproducción es cero
// esto es un hash con el único propósito de mapear nombres de eventos que no están en mayúsculas y minúsculas
// a nombres de funciones en mayúsculas y minúsculas
constante TECH_EVENTS_QUEUE = {
  Poder jugar: 'Poder jugar',
  puede jugar a través de: 'Puede reproducir',
  jugando: 'Jugando',
  buscado: 'buscado'
};

constante BREAKPOINT_ORDER = [
  'diminuto',
  'xpequeño',
  'pequeño',
  'medio',
  'grande',
  'grande',
  'enorme'
];

const BREAKPOINT_CLASSES = {};

// grep: vjs-layout-tiny
// grep: vjs-layout-x-pequeño
// grep: vjs-layout-pequeño
// grep: vjs-layout-medium
// grep: vjs-layout-grande
// grep: vjs-layout-x-grande
// grep: vjs-layout-enorme
BREAKPOINT_ORDER.forEach(k => {
  const v = k.charAt(0) === 'x' ? `x-${k.subcadena(1)}` : k;

  BREAKPOINT_CLASSES[k] = `vjs-layout-${v}`;
});

constante DEFAULT_BREAKPOINTS = {
  diminuto: 210,
  xpequeño: 320,
  pequeño: 425,
  medio: 768,
  grande: 1440,
  extra grande: 2560,
  enorme: Infinidad
};

/**
 * Se crea una instancia de la clase `Player` cuando cualquiera de los métodos de configuración de Video.js
 * se utilizan para inicializar un video.
 *
 * Una vez que se ha creado una instancia, se puede acceder a ella globalmente de dos maneras:
 * 1. Llamando a `videojs('example_video_1');`
 * 2. Usándolo directamente a través de `videojs.players.example_video_1;`
 *
 * Componente @extiende
 * /
El jugador de clase extiende el componente {

  /**
   * Crear una instancia de esta clase.
   *
   * Etiqueta @param {Elemento}
   * El elemento DOM de video original utilizado para configurar las opciones.
   *
   * @param {Objeto} [opciones]
   * Objeto de nombres y valores de opciones.
   *
   * @param {Componente~ReadyCallback} [listo]
   * Función de devolución de llamada lista.
   * /
  constructor(etiqueta, opciones, listo) {
    // Asegúrese de que existe la identificación de la etiqueta
    etiqueta.id = etiqueta.id || opciones.id || `vjs_video_${Guid.nuevoGUID()}`;

    // Establecer opciones
    // El argumento de opciones anula las opciones establecidas en la etiqueta de video
    // que anula las opciones establecidas globalmente.
    // Esta última parte coincide con el orden de carga
    // (la etiqueta debe existir antes que Player)
    opciones = asignar (Player.getTagSettings (etiqueta), opciones);

    // Retrasar la inicialización de los niños porque necesitamos configurar
    // primero las propiedades del jugador, y no puede usar `this` antes de `super()`
    opciones.initChildren = falso;

    // Lo mismo con la creación del elemento
    opciones.createEl = false;

    // no mezclar automáticamente la mezcla con eventos
    options.evented = false;

    // no queremos que el reproductor informe de la actividad táctil sobre sí mismo
    // ver enableTouchActivity en Componente
    opciones.reportTouchActivity = falso;

    // Si el idioma no está configurado, obtenga el atributo lang más cercano
    if (!opciones.idioma) {
      if (tipo de etiqueta.más cercano === 'función') {
        const más cercano = etiqueta.más cercano('[idioma]');

        si (más cercano && más cercano.getAttribute) {
          opciones.idioma = más cercano.getAttribute('idioma');
        }
      } else {
        let elemento = etiqueta;

        mientras (elemento && elemento.tiponodo === 1) {
          if (Dom.getAttributes(elemento).hasOwnProperty('lang')) {
            opciones.idioma = elemento.getAttribute('idioma');
            romper;
          }
          elemento = elemento.NodoPadre;
        }
      }
    }

    // Ejecutar la inicialización del componente base con nuevas opciones
    super(nulo, opciones, listo);

    // Crear métodos enlazados para detectores de documentos.
    this.boundDocumentFullscreenChange_ = (e) => this.documentFullscreenChange_(e);
    this.boundFullWindowOnEscKey_ = (e) => this.fullWindowOnEscKey(e);

    this.boundUpdateStyleEl_ = (e) => this.updateStyleEl_(e);
    this.boundApplyInitTime_ = (e) => this.applyInitTime_(e);
    this.boundUpdateCurrentBreakpoint_ = (e) => this.updateCurrentBreakpoint_(e);

    this.boundHandleTechClick_ = (e) => this.handleTechClick_(e);
    this.boundHandleTechDoubleClick_ = (e) => this.handleTechDoubleClick_(e);
    this.boundHandleTechTouchStart_ = (e) => this.handleTechTouchStart_(e);
    this.boundHandleTechTouchMove_ = (e) => this.handleTechTouchMove_(e);
    this.boundHandleTechTouchEnd_ = (e) => this.handleTechTouchEnd_(e);
    this.boundHandleTechTap_ = (e) => este.handleTechTap_(e);

    // por defecto isFullscreen_ a falso
    this.isFullscreen_ = falso;

    // crear registrador
    este.log = createLogger(este.id_);

    // Mantener nuestra propia referencia a la API de pantalla completa para que se pueda simular en las pruebas
    esto.fsApi_ = FullscreenApi;

    // Realiza un seguimiento cuando un técnico cambia el cartel
    this.isPosterFromTech_ = falso;

    // Contiene información de devolución de llamada que se pone en cola cuando la tasa de reproducción es cero
    // y está ocurriendo una búsqueda
    this.queuedCallbacks_ = [];

    // Desactive el acceso a la API porque estamos cargando una nueva tecnología que podría cargarse de forma asíncrona
    esto.estáListo_ = falso;

    // Estado inicial hasStarted_
    this.hasStarted_ = false;

    // Estado inicial userActive_
    este.usuarioActivo_ = falso;

    // Inicialización de depuración habilitada_
    this.debugEnabled_ = falso;

    // Estado de inicio audioOnlyMode_
    this.audioOnlyMode_ = falso;

    // Estado de inicio audioPosterMode_
    este.audioPosterMode_ = falso;

    // Estado inicial audioOnlyCache_
    this.audioOnlyCache_ = {
      altura del jugador: nulo,
      niños ocultos: []
    };

    // si el objeto de la opción global fue volado accidentalmente por
    // alguien, fianza temprano con un error informativo
    if (!estas.opciones_ ||
        !this.options_.techOrder ||
        !this.options_.techOrder.longitud) {
      throw new Error('No se especificó orden de tecnología. Sobrescribiste ' +
                      'videojs.options en lugar de simplemente cambiar el ' +
                      '¿propiedades que desea anular?');
    }

    // Almacenar la etiqueta original utilizada para establecer opciones
    esta.etiqueta = etiqueta;

    // Almacenar los atributos de la etiqueta utilizados para restaurar el elemento html5
    this.tagAttributes = etiqueta && Dom.getAttributes(etiqueta);

    // Actualizar idioma actual
    este.idioma(esta.opciones_.idioma);

    // Actualizar idiomas admitidos
    if (opciones.idiomas) {
      // Normalizar los idiomas de las opciones del jugador a minúsculas
      const idiomasABajar = {};

      Object.getOwnPropertyNames(opciones.idiomas).forEach(función(nombre) {
        languagesToLower[nombre.toLowerCase()] = opciones.languages[nombre];
      });
      this.languages_ = languagesToLower;
    } else {
      this.languages_ = Player.prototype.options_.languages;
    }

    esto.resetCache_();

    // Establecer cartel
    this.poster_ = opciones.poster || '';

    // Establecer controles
    this.controls_ = !!opciones.controles;

    // Configuraciones de etiquetas originales almacenadas en opciones
    // ahora elimine inmediatamente para que los controles nativos no parpadeen.
    // La tecnología HTML5 puede volver a activarlo si nativeControlsForTouch es verdadero
    etiqueta.controles = falso;
    etiqueta.removeAttribute('controles');

    este.cambiandoSrc_ = falso;
    this.playCallbacks_ = [];
    this.playTerpressedQueue_ = [];

    // el atributo anula la opción
    if (etiqueta.hasAttribute('reproducción automática')) {
      this.reproducción automática (verdadero);
    } else {
      // de lo contrario, use el setter para validar y
      // establecer el valor correcto.
      esta.reproducción automática(esta.opciones_.reproducción automática);
    }

    // comprobar complementos
    si (opciones.complementos) {
      Object.keys(options.plugins).forEach((nombre) => {
        if (tipo de este [nombre] !== 'función') {
          lanzar un nuevo error (`el complemento "${name}" no existe`);
        }
      });
    }

    /*
     * Almacenar el estado interno de fregado
     *
     * @privado
     * @return {Boolean} True si el usuario está limpiando
     * /
    esto.fregar_ = falso;

    esto.el_ = esto.createEl();

    // Convierta esto en un objeto con eventos y use `el_` como su bus de eventos.
    evento(esto, {eventBusKey: 'el_'});

    // escuchar los controladores de cambio de pantalla completa del documento y del reproductor para que recibamos esos eventos
    // antes de que un usuario pueda recibirlos para que podamos actualizar isFullscreen adecuadamente.
    // asegúrese de escuchar los eventos de cambio de pantalla completa antes que todo lo demás para asegurarse de que
    // nuestro método isFullscreen se actualiza correctamente tanto para los componentes internos como para los externos.
    si (esto.fsApi_.requestFullscreen) {
      Events.on(document, this.fsApi_.fullscreenchange, this.boundDocumentFullscreenChange_);
      this.on(this.fsApi_.fullscreenchange, this.boundDocumentFullscreenChange_);
    }

    si (este.fluido_) {
      this.on(['playerreset', 'resize'], this.boundUpdateStyleEl_);
    }
    // También queremos pasar las opciones del reproductor original a cada componente y complemento
    // también para que no necesiten volver a buscar opciones en el reproductor más adelante.
    // También necesitamos hacer otra copia de this.options_ para no terminar con
    // un bucle infinito.
    const playerOptionsCopy = mergeOptions(this.options_);

    // Cargar complementos
    si (opciones.complementos) {
      Object.keys(options.plugins).forEach((nombre) => {
        este[nombre](opciones.complementos[nombre]);
      });
    }

    // Habilite el modo de depuración para activar el evento de depuración para todos los complementos.
    si (opciones.depuración) {
      esto. depurar (verdadero);
    }

    this.options_.playerOptions = playerOptionsCopy;

    este.middleware_ = [];

    this.playbackRates(opciones.playbackRates);

    esto.initChildren();

    // Establecer isAudio en función de si se utilizó o no una etiqueta de audio
    this.isAudio(tag.nodeName.toLowerCase() === 'audio');

    // Actualizar controles className. No se puede hacer esto cuando los controles están inicialmente
    // establecido porque el elemento aún no existe.
    if (esto.controles()) {
      this.addClass('vjs-controls-enabled');
    } else {
      this.addClass('vjs-controls-disabled');
    }

    // Establecer la etiqueta ARIA y el rol de la región según el tipo de jugador
    this.el_.setAttribute('rol', 'región');
    si (esto.esAudio()) {
      this.el_.setAttribute('aria-label', this.localize('Reproductor de audio'));
    } else {
      this.el_.setAttribute('aria-label', this.localize('Video Player'));
    }

    si (esto.esAudio()) {
      this.addClass('vjs-audio');
    }

    si (esto.flexNotSupported_()) {
      this.addClass('vjs-no-flex');
    }

    // HACER: Haz esto más inteligente. Cambiar el estado del usuario entre tocar/mouse
    // usar eventos, ya que los dispositivos pueden tener eventos táctiles y de mouse.
    // HACER: Haga que esta verificación se realice nuevamente cuando la ventana cambie entre monitores
    // (Ver https://github.com/videojs/video.js/issues/5683)
    si (navegador.TOUCH_ENABLED) {
      this.addClass('vjs-touch-enabled');
    }

    // iOS Safari ha roto el manejo de desplazamiento
    si (!navegador.IS_IOS) {
      this.addClass('vjs-workinghover');
    }

    // Hacer que el jugador sea fácilmente localizable por ID
    Player.players[this.id_] = esto;

    // Agregue una clase de versión principal para ayudar a css en los complementos
    const majorVersion = version.split('.')[0];

    this.addClass(`vjs-v${versión principal}`);

    // Cuando el reproductor se inicializa por primera vez, active la actividad para que los componentes
    // como la barra de control se muestra si es necesario
    este.usuarioActivo(verdadero);
    this.reportUserActivity();

    this.one('reproducir', (e) => this.listenForUserActivity_(e));
    this.on('clic en el escenario', (e) => this.handleStageClick_(e));
    this.on('teclado abajo', (e) => this.handleKeyDown(e));
    this.on('cambio de idioma', (e) => this.handleLanguagechange(e));

    this.breakpoints(this.options_.breakpoints);
    this.responsive(this.options_.responsive);

    // Llamar a ambos métodos de modo de audio después de que el reproductor esté completamente
    // configuración para poder escuchar los eventos desencadenados por ellos
    this.on('listo', () => {
      // Llamar primero al método audioPosterMode para que
      // audioOnlyMode puede tener prioridad cuando ambas opciones se establecen en verdadero
      this.audioPosterMode(this.options_.audioPosterMode);
      this.audioOnlyMode(this.options_.audioOnlyMode);
    });
  }

  /**
   * Destruye el reproductor de video y realiza cualquier limpieza necesaria.
   *
   * Esto es especialmente útil si está agregando y eliminando videos dinámicamente
   * hacia/desde el DOM.
   *
   * @fires jugador #dispose
   * /
  disponer () {
    /**
     * Llamado cuando el jugador está siendo eliminado.
     *
     * @reproductor de eventos#dispose
     * @type {Objetivo del evento~Evento}
     * /
    this.trigger('dispose');
    // evita que se llame dos veces a dispose
    this.off('dispose');

    // Asegúrese de que todos los detectores de documentos específicos del reproductor no estén vinculados. Esto es
    Events.off(document, this.fsApi_.fullscreenchange, this.boundDocumentFullscreenChange_);
    Events.off(document, 'keydown', this.boundFullWindowOnEscKey_);

    if (this.styleEl_ && this.styleEl_.parentNode) {
      this.styleEl_.parentNode.removeChild(this.styleEl_);
      este.estiloEl_ = nulo;
    }

    // Elimina la referencia a este jugador
    Player.players[this.id_] = null;

    si (esta.etiqueta && este.etiqueta.jugador) {
      esta.etiqueta.jugador = nulo;
    }

    si (esto.el_ && este.el_.jugador) {
      this.el_.jugador = nulo;
    }

    si (esta.tecnología_) {
      this.tech_.dispose();
      this.isPosterFromTech_ = falso;
      este.poster_ = '';
    }

    if (this.playerElIngest_) {
      this.playerElIngest_ = null;
    }

    si (esta.etiqueta) {
      esta.etiqueta = nulo;
    }

    middleware.clearCacheForPlayer(esto);

    // eliminar todos los controladores de eventos para las listas de pistas
    // todas las pistas y los oyentes de pista se eliminan en
    // desechar tecnología
    TRACK_TYPES.names.forEach((nombre) => {
      const props = TRACK_TYPES[nombre];
      const list = this[props.getterName]();

      // si no es una lista nativa
      // tenemos que eliminar manualmente los detectores de eventos
      si (lista && lista.off) {
        lista.off();
      }
    });

    // el .el_ real se elimina aquí, o se reemplaza si
    super.dispose({restoreEl: this.options_.restoreEl});
  }

  /**
   * Crea el elemento DOM del `Player`.
   *
   * @return {Elemento}
   * El elemento DOM que se crea.
   * /
  crearEl() {
    let etiqueta = this.tag;
    deja que el;
    let playerElIngest = this.playerElIngest_ = tag.parentNode && etiqueta.parentNode.hasAttribute && tag.parentNode.hasAttribute('datos-vjs-jugador');
    const divEmbed = this.tag.tagName.toLowerCase() === 'video-js';

    if (jugadorElIngesto) {
      el = this.el_ = etiqueta.parentNode;
    } si no (!divEmbed) {
      el = esto.el_ = super.createEl('div');
    }

    // Copia todos los atributos de la etiqueta, incluidos el ID y la clase
    // ID ahora hará referencia al cuadro del reproductor, no a la etiqueta de video
    const attrs = Dom.getAttributes(etiqueta);

    si (divEmbed) {
      el = esto.el_ = etiqueta;
      etiqueta = esto.etiqueta = documento.createElement('video');
      while (el.niños.longitud) {
        etiqueta.appendChild(el.firstChild);
      }

      if (!Dom.hasClass(el, 'video-js')) {
        Dom.addClass(el, 'video-js');
      }

      el.appendChild(etiqueta);

      playerElIngest = this.playerElIngest_ = el;
      // mueve las propiedades desde nuestro elemento `video-js` personalizado
      // a nuestro nuevo elemento `video`. Esto moverá cosas como
      // `src` o `controls` que se configuraron a través de js antes del jugador
      // fue inicializado.
      Objeto.claves(el).forEach((k) => {
        intentar {
          etiqueta[k] = el[k];
        } catch (e) {
          // tenemos una propiedad como HTML externo que en realidad no podemos copiar, ignórela
        }
      });
    }

    // establece tabindex en -1 para eliminar el elemento de video del orden de enfoque
    etiqueta.setAttribute('tabindex', '-1');
    atributos.tabindex = '-1';

    // Solución alternativa para #4583 (JAWS+IE no anuncia BPB ni botón de reproducción), y
    // por el mismo problema con Chrome (en Windows) con JAWS.
    // Consulte https://github.com/FreedomScientific/VFO-standards-support/issues/78
    // Tenga en cuenta que no podemos detectar si se está utilizando JAWS, pero este atributo ARIA
    // no cambia el comportamiento de IE11 o Chrome si no se usa JAWS
    si (IE_VERSION || (IS_CHROME && ES_WINDOWS)) {
      tag.setAttribute('rol', 'aplicación');
      attrs.role = 'aplicación';
    }

    // Elimina los atributos de ancho/alto de la etiqueta para que CSS pueda hacerlo 100% ancho/alto
    etiqueta.removeAttribute('ancho');
    etiqueta.removeAttribute('altura');

    if ('ancho' en atributos) {
      eliminar atributos.ancho;
    }
    if ('altura' en atributos) {
      eliminar attrs.height;
    }

    Object.getOwnPropertyNames(attrs).forEach(function(attr) {
      // no copiar sobre el atributo de clase al elemento del jugador cuando estamos en un div incrustado
      // la clase ya está configurada correctamente en el caso de divEmbed
      // y queremos asegurarnos de que la clase `video-js` no se pierda
      si (!(divIncrustar && atributo === 'clase')) {
        el.setAttribute(atributo, atributos[atributo]);
      }

      si (divEmbed) {
        etiqueta.setAttribute(atributo, atributos[atributo]);
      }
    });

    // Actualizar id/clase de etiqueta para usar como tecnología de reproducción de HTML5
    // Podría pensar que deberíamos hacer esto después de incrustarlo en el contenedor, por lo que la clase .vjs-tech
    // no parpadea al 100% de ancho/alto, pero la clase solo se aplica con el padre .video-js
    etiqueta.playerId = etiqueta.id;
    etiqueta.id += '_html5_api';
    etiqueta.className = 'vjs-tech';

    // Hacer que el jugador se pueda encontrar en los elementos
    tag.jugador = el.jugador = esto;
    // El estado predeterminado del video está en pausa
    this.addClass('vjs-paused');

    // Agregue un elemento de estilo en el reproductor que usaremos para establecer el ancho/alto
    // del jugador de una manera que CSS aún puede anular, al igual que el
    // elemento de vídeo
    si (ventana.VIDEOJS_NO_DYNAMIC_STYLE! == verdadero) {
      this.styleEl_ = stylesheet.createStyleElement('vjs-styles-dimensions');
      const defaultsStyleEl = Dom.$('.vjs-styles-defaults');
      const cabeza = Dom.$('cabeza');

      head.insertBefore(this.styleEl_, defaultsStyleEl ? defaultsStyleEl.nextSibling : head.firstChild);
    }

    esto.llenar_ = falso;
    este.fluido_ = falso;

    // Pase las opciones de ancho/alto/relación de aspecto que actualizarán el estilo el
    este.ancho(esta.opciones_.ancho);
    esta.altura(esta.opciones_.altura);
    this.fill(this.options_.fill);
    este.fluido(esta.opciones_.fluido);
    this.aspectRatio(this.options_.aspectRatio);
    // admite crossOrigin y crossorigin para reducir la confusión y los problemas relacionados con el nombre
    this.crossOrigin(this.options_.crossOrigin || this.options_.crossorigin);

    // Ocultar cualquier enlace dentro de la etiqueta de video/audio,
    // porque IE no los oculta completamente de los lectores de pantalla.
    const enlaces = etiqueta.getElementsByTagName('a');

    para (sea i = 0; i < enlaces.longitud; i++) {
      const linkEl = enlaces.item(i);

      Dom.addClass(linkEl, 'vjs-hidden');
      linkEl.setAttribute('oculto', 'oculto');
    }

    // insertElFirst parece hacer que el estado de la red parpadee de 3 a 2, por lo que
    // realizar un seguimiento del original para más adelante para que podamos saber si la fuente falló originalmente
    etiqueta.initNetworkState_ = etiqueta.networkState;

    // Envolver la etiqueta de video en el contenedor div (el/box)
    if (etiqueta.nodopadre && !playerElIngest) {
      etiqueta.parentNode.insertBefore(el, etiqueta);
    }

    // inserta la etiqueta como el primer hijo del elemento jugador
    // luego agréguelo manualmente a la matriz de niños para que this.addChild
    // funcionará correctamente para otros componentes
    //
    // Rompe el iPhone, corregido en la configuración de HTML5.
    Dom.prependTo(etiqueta, el);
    this.children_.unshift(etiqueta);

    // Establezca lang attr en el reproductor para garantizar que CSS :lang() sea coherente con el reproductor
    // si se ha configurado en algo diferente al documento
    this.el_.setAttribute('lang', this.language_);

    this.el_.setAttribute('traducir', 'no');

    esto.el_ = el;

    volver el;
  }

  /**
   * Obtener o establecer la opción crossOrigin del `Player`. Para el reproductor HTML5, este
   * establece la propiedad `crossOrigin` en `< video> ` etiqueta para controlar el CORS
   * comportamiento.
   *
   * @ver [Atributos de elemento de video]{@enlace https://developer.mozilla.org/en-US/docs/Web/HTML/Element/video#attr-crossorigin}
   *
   * @param {cadena} [valor]
   * El valor para establecer el crossOrigin del `Player`. Si un argumento es
   * proporcionado, debe ser uno de `anonymous` o `use-credentials`.
   *
   * @return {cadena|indefinido}
   * - El valor crossOrigin actual del `Player` al obtener.
   * - indefinido al configurar
   * /
  origencruz(valor) {
    si (! valor) {
      devuelve esto.techGet_('crossOrigin');
    }

    if (valor !== 'anonimo' && valor !== 'usar-credenciales') {
      log.warn(`crossOrigin debe ser &quot;anónimo&quot; o &quot;usar-credenciales&quot;, dado &quot;${valor}&quot;`);
      devolver;
    }

    this.techCall_('setCrossOrigin', valor);

    devolver;
  }

  /**
   * Un getter/setter para el ancho del `Jugador`. Devuelve el valor configurado del jugador.
   * Para obtener el ancho actual, use `currentWidth()`.
   *
   * @param {número} [valor]
   * El valor para establecer el ancho del `Jugador`.
   *
   * @return {número}
   * El ancho actual del `Player` al obtener.
   * /
  ancho (valor) {
    return this.dimension('ancho', valor);
  }

  /**
   * Un getter/setter para la altura del `Jugador`. Devuelve el valor configurado del jugador.
   * Para obtener la altura actual, use `currentheight ()`.
   *
   * @param {número} [valor]
   * El valor para establecer la altura del `Jugador`.
   *
   * @return {número}
   * La altura actual del `Jugador` al obtener.
   * /
  altura (valor) {
    return this.dimension('altura', valor);
  }

  /**
   * Un getter/setter para el ancho del `Jugador` & altura.
   *
   * @param {cadena} dimensión
   * Esta cadena puede ser:
   * - 'ancho'
   * - 'altura'
   *
   * @param {número} [valor]
   * Valor de la dimensión especificada en el primer argumento.
   *
   * @return {número}
   * El valor de los argumentos de dimensión al obtener (ancho/alto).
   * /
  dimensión(dimensión, valor) {
    const privDimension = dimensión + '_';

    si (valor === indefinido) {
      devuelve esta[dimensiónprivada] || 0;
    }

    if (valor === '' || valor === 'auto') {
      // Si se da una cadena vacía, restablece la dimensión para que sea automática
      esta[dimensiónprivada] = indefinido;
      this.updateStyleEl_();
      devolver;
    }

    const parsedVal = parseFloat(valor);

    if (esNaN(valor analizado)) {
      log.error(`Valor incorrecto &quot;${valor}&quot; suministrado para ${dimensión}`);
      devolver;
    }

    this[privDimension] = parsedVal;
    this.updateStyleEl_();
  }

  /**
   * Un getter/setter/toggler para vjs-fluid `className` en el `Player`.
   *
   * Activar esto desactivará el modo de llenado.
   *
   * @param {booleano} [booleano]
   * - Un valor de true agrega la clase.
   * - Un valor de falso elimina la clase.
   * - Ningún valor será captador.
   *
   * @return {booleano|indefinido}
   * - El valor del fluido al llegar.
   * - `indefinido` al configurar.
   * /
  fluido(bool) {
    si (bool === indefinido) {
      volver !!este.fluido_;
    }

    este.fluido_ = !!bool;

    si (es un evento (esto)) {
      this.off(['playerreset', 'resize'], this.boundUpdateStyleEl_);
    }
    si (bool) {
      this.addClass('vjs-fluido');
      esto.llenar(falso);
      addEventedCallback(esto, () => {
        this.on(['playerreset', 'resize'], this.boundUpdateStyleEl_);
      });
    } else {
      this.removeClass('vjs-fluido');
    }

    this.updateStyleEl_();
  }

  /**
   * Un getter/setter/toggler para vjs-fill `className` en el `Player`.
   *
   * Activar esto desactivará el modo fluido.
   *
   * @param {booleano} [booleano]
   * - Un valor de true agrega la clase.
   * - Un valor de falso elimina la clase.
   * - Ningún valor será captador.
   *
   * @return {booleano|indefinido}
   * - El valor del fluido al llegar.
   * - `indefinido` al configurar.
   * /
  llenar (bool) {
    si (bool === indefinido) {
      volver !!esto.llenar_;
    }

    this.fill_ = !!bool;

    si (bool) {
      this.addClass('vjs-fill');
      este.fluido(falso);
    } else {
      this.removeClass('vjs-fill');
    }
  }

  /**
   * Obtener/Establecer la relación de aspecto
   *
   * @param {cadena} [proporción]
   * Relación de aspecto para el jugador
   *
   * @return {cadena|indefinido}
   * devuelve la relación de aspecto actual al obtener
   * /

  /**
   * Un getter/setter para la relación de aspecto del `Player`.
   *
   * @param {cadena} [proporción]
   * El valor para establecer la relación de aspecto del 'Reproductor'.
   *
   * @return {cadena|indefinido}
   * - La relación de aspecto actual del `Player` al obtener.
   * - indefinido al configurar
   * /
  relación de aspecto (relación) {
    si (proporción === indefinido) {
      devuelve esto.aspectRatio_;
    }

    // Comprobar el formato ancho:alto
    si (!(/^\d+\:\d+$/).prueba(proporción)) {
      throw new Error('Valor incorrecto proporcionado para la relación de aspecto. El formato debe ser ancho:alto, por ejemplo 16:9.');
    }
    this.aspectRatio_ = relación;

    // Suponemos que si establece una relación de aspecto desea el modo fluido,
    // porque en modo fijo podrías calcular el ancho y la altura tú mismo.
    este.fluido(verdadero);

    this.updateStyleEl_();
  }

  /**
   * Actualizar estilos del elemento `Player` (alto, ancho y relación de aspecto).
   *
   * @privado
   * @escucha Tech#loadedmetadata
   * /
  actualizarEstiloEl_() {
    si (ventana.VIDEOJS_NO_DYNAMIC_STYLE === verdadero) {
      const ancho = tipo de esto.ancho_ === 'número' ? este.ancho_ : este.opciones_.ancho;
      altura const = tipo de this.height_ === 'número'? esta.altura_ : esta.opciones_.altura;
      const techEl = this.tech_ && esto.tech_.el();

      si (tecnología) {
        si (ancho > = 0) {
          techEl.ancho = ancho;
        }
        si (altura > = 0) {
          techEl.altura = altura;
        }
      }

      devolver;
    }

    dejar ancho;
    dejar altura;
    dejar relación de aspecto;
    dejar idClase;

    // La relación de aspecto se usa directamente o para calcular el ancho y la altura.
    if (this.aspectRatio_ !== indefinido && this.aspectRatio_ !== 'auto') {
      // Usar cualquier relación de aspecto que se haya establecido específicamente
      relación de aspecto = this.aspectRatio_;
    } más si (this.videoWidth() > 0) {
      // De lo contrario, intente obtener la relación de aspecto de los metadatos del video
      relación de aspecto = this.videoWidth() + ':' + this.videoHeight();
    } else {
      // O use un valor predeterminado. El elemento de video es 2:1, pero 16:9 es más común.
      relación de aspecto = '16:9';
    }

    // Obtenga la relación como un decimal que podemos usar para calcular dimensiones
    const ratioParts = aspectRatio.split(':');
    const ratioMultiplier = ratioParts[1] / ratioParts[0];

    if (this.width_ !== indefinido) {
      // Usa cualquier ancho que se haya establecido específicamente
      ancho = este.ancho_;
    } else if (this.height_ !== indefinido) {
      // O calcule el ancho a partir de la relación de aspecto si se ha establecido una altura
      ancho = this.height_ / ratioMultiplier;
    } else {
      // O use los metadatos del video, o use el valor predeterminado de video el de 300
      ancho = this.videoWidth() || 300;
    }

    if (this.height_ !== indefinido) {
      // Usar cualquier altura que se haya establecido específicamente
      altura = esta.altura_;
    } else {
      // De lo contrario, calcule la altura a partir de la relación y el ancho
      alto = ancho * ratioMultiplier;
    }

    // Asegúrese de que la clase CSS sea válida comenzando con un carácter alfabético
    if ((/^[^a-zA-Z]/).test(this.id())) {
      idClass = 'dimensiones-' + this.id();
    } else {
      idClass = this.id() + '-dimensiones';
    }

    // Asegúrese de que la clase correcta aún esté en el reproductor para el elemento de estilo
    this.addClass(idClass);

    hoja de estilo.setTextContent(this.styleEl_, `
      .${idClase} {
        ancho: ${ancho}px;
        altura: ${altura}px;
      }

      .${idClass}.vjs-fluid:not(.vjs-audio-only-mode) {
        padding-top: ${ratioMultiplier * 100}%;
      }
    `);
  }

  /**
   * Cargar/Crear una instancia de reproducción {@link Tech} incluido el elemento
   * y métodos API. Luego agregue el elemento `Tech` en `Player` como un elemento secundario.
   *
   * @param {string} techName
   * nombre de la tecnología de reproducción
   *
   * @param {cadena} fuente
   * fuente de vídeo
   *
   * @privado
   * /
  loadTech_(nombreTecnología, fuente) {

    // Pausa y elimina la tecnología de reproducción actual
    si (esta.tecnología_) {
      esto.descargaTecnología_();
    }

    const titleTechName = toTitleCase(techName);
    const camelTechName = techName.charAt(0).toLowerCase() + techName.slice(1);

    // deshacerse de la etiqueta de video HTML5 tan pronto como estemos usando otra tecnología
    if (titleTechName !== 'Html5' && esta.etiqueta) {
      Tech.getTech('Html5').disposeMediaElement(this.tag);
      esta.etiqueta.jugador = nulo;
      esta.etiqueta = nulo;
    }

    this.techName_ = titleTechName;

    // Desactive el acceso a la API porque estamos cargando una nueva tecnología que podría cargarse de forma asíncrona
    esto.estáListo_ = falso;

    let reproducción automática = this.autoplay();

    // si la reproducción automática es una cadena (o `true` con normalizeAutoplay: true) le pasamos falso a la tecnología
    // porque el reproductor manejará la reproducción automática en `loadstart`
    if (tipo de this.autoplay() === 'cadena' || this.autoplay() === verdadero && this.options_.normalizeReproducción automática) {
      reproducción automática = falso;
    }

    // Obtenga opciones específicas de tecnología de las opciones del reproductor y agregue la fuente y el elemento principal para usar.
    const techOptions = {
      fuente,
      auto-reproducción,
      'nativeControlsForTouch': this.options_.nativeControlsForTouch,
      'IdJugador': this.id(),
      'techId': `${this.id()}_${camelTechName}_api`,
      'playsinline': this.options_.playsinline,
      'precargar': this.options_.preload,
      'bucle': this.options_.loop,
      'disablePictureInPicture': this.options_.disablePictureInPicture,
      'silenciado': this.options_.muted,
      'cartel': este.cartel(),
      'idioma': este.idioma(),
      'playerElIngest': this.playerElIngest_ || FALSO,
      'vtt.js': this.options_['vtt.js'],
      'canOverridePoster': !!this.options_.techCanOverridePoster,
      'enableSourceset': this.options_.enableSourceset,
      'Promesa': this.options_.Promise
    };

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

      techOptions[props.getterName] = this[props.privateName];
    });

    asignar(techOptions, this.options_[titleTechName]);
    asignar(techOptions, this.options_[camelTechName]);
    asignar(opcionestecnológicas, this.options_[techName.toLowerCase()]);

    si (esta.etiqueta) {
      techOptions.tag = this.tag;
    }

    si (fuente && fuente.src === this.cache_.src && this.cache_.currentTime > 0) {
      techOptions.startTime = this.cache_.currentTime;
    }

    // Inicializar instancia de tecnología
    const TechClass = Tech.getTech(techName);

    if (!ClaseTecnológica) {
      throw new Error(`No existe ninguna tecnología llamada '${titleTechName}'! '${titleTechName}' debe registrarse usando videojs.registerTech()'`);
    }

    this.tech_ = new TechClass(techOptions);

    // player.triggerReady siempre es asíncrono, así que no es necesario que sea asíncrono
    this.tech_.ready(Fn.bind(this, this.handleTechReady_), true);

    textTrackConverter.jsonToTextTracks(this.textTracksJson_ || [], this.tech_);

    // Escuche todos los eventos definidos por HTML5 y actívelos en el reproductor
    TECH_EVENTS_RETRIGGER.forEach((evento) => {
      this.on(this.tech_, event, (e) => this[`handleTech${toTitleCase(evento)}_`](e));
    });

    Objeto.keys(TECH_EVENTS_QUEUE).forEach((evento) => {
      this.on(this.tech_, event, (eventObj) => {
        if (this.tech_.playbackRate() === 0 && esta.tecnología_.buscando()) {
          this.queuedCallbacks_.push({
            devolución de llamada: this[`handleTech${TECH_EVENTS_QUEUE[event]}_`].bind(this),
            evento: eventObj
          });
          devolver;
        }
        this[`handleTech${TECH_EVENTS_QUEUE[evento]}_`](eventObj);
      });
    });

    this.on(this.tech_, 'loadstart', (e) => this.handleTechLoadStart_(e));
    this.on(this.tech_, 'sourceset', (e) => this.handleTechSourceset_(e));
    this.on(this.tech_, 'esperando', (e) => this.handleTechWaiting_(e));
    this.on(this.tech_, 'terminado', (e) => this.handleTechEnded_(e));
    this.on(this.tech_, 'buscando', (e) => this.handleTechSeeking_(e));
    this.on(this.tech_, 'play', (e) => this.handleTechPlay_(e));
    this.on(this.tech_, 'firstplay', (e) => this.handleTechFirstPlay_(e));
    this.on(this.tech_, 'pause', (e) => this.handleTechPause_(e));
    this.on(this.tech_, 'durationchange', (e) => this.handleTechDurationChange_(e));
    this.on(this.tech_, 'cambio de pantalla completa', (e, data) => this.handleTechFullscreenChange_(e, datos));
    this.on(this.tech_, 'error de pantalla completa', (e, err) => this.handleTechFullscreenError_(e, err));
    this.on(this.tech_, 'enterpictureinpicture', (e) => this.handleTechEnterPictureInPicture_(e));
    this.on(this.tech_, 'dejar imagen en imagen', (e) => this.handleTechLeavePictureInPicture_(e));
    this.on(this.tech_, 'error', (e) => this.handleTechError_(e));
    this.on(this.tech_, 'posterchange', (e) => this.handleTechPosterChange_(e));
    this.on(this.tech_, 'textdata', (e) => this.handleTechTextData_(e));
    this.on(this.tech_, 'ratechange', (e) => this.handleTechRateChange_(e));
    this.on(this.tech_, 'loadedmetadata', this.boundUpdateStyleEl_);

    this.usingNativeControls(this.techGet_('controls'));

    si (esto.controles() && !this.usingNativeControls()) {
      this.addTechControlsListeners_();
    }

    // Agregue el elemento tecnológico en el DOM si aún no estaba allí
    // Asegúrese de no insertar el elemento de video original si usa Html5
    if (this.tech_.el().parentNode !== this.el() && (titleTechName !== 'Html5' || !this.tag)) {
      Dom.prependTo(this.tech_.el(), this.el());
    }

    // Deshágase de la referencia de la etiqueta de video original después de cargar la primera tecnología
    si (esta.etiqueta) {
      esta.etiqueta.jugador = nulo;
      esta.etiqueta = nulo;
    }
  }

  /**
   * Descargue y elimine la reproducción actual {@link Tech}.
   *
   * @privado
   * /
  descargarTecnología_() {
    // Guarde las pistas de texto actuales para que podamos reutilizar las mismas pistas de texto con la siguiente tecnología
    TRACK_TYPES.names.forEach((nombre) => {
      const props = TRACK_TYPES[nombre];

      this[props.privateName] = this[props.getterName]();
    });
    this.textTracksJson_ = textTrackConverter.textTracksToJson(this.tech_);

    esto.estáListo_ = falso;

    this.tech_.dispose();

    this.tech_ = false;

    si (este.esPosterFromTech_) {
      este.poster_ = '';
      this.trigger('cambio de cartel');
    }

    this.isPosterFromTech_ = falso;
  }

  /**
   * Devolver una referencia al {@link Tech} actual.
   * Imprimirá una advertencia por defecto sobre el peligro de usar la tecnología directamente
   * pero cualquier argumento que se pase silenciará la advertencia.
   *
   * @param {*} [seguridad]
   * Cualquier cosa pasó para silenciar la advertencia.
   *
   * @return {Tecnología}
   * La tecnología
   * /
  tecnología (seguridad) {
    if (seguridad === indefinido) {
      log.warn('Usar la tecnología directamente puede ser peligroso. Espero que sepas lo que estás haciendo.\n' +
        'Consulte https://github.com/videojs/video.js/issues/2617 para obtener más información.\n');
    }

    devolver esto.tech_;
  }

  /**
   * Configure oyentes de clic y toque para el elemento de reproducción
   *
   * - En computadoras de escritorio: un clic en el video cambiará la reproducción
   * - En dispositivos móviles: un clic en el video alterna los controles
   * que se hace alternando el estado del usuario entre activo y
   * inactivo
   * - Un toque puede indicar que un usuario se ha vuelto activo o se ha vuelto inactivo
   * Por ejemplo, un toque rápido en una película de iPhone debería revelar los controles. Otro
   * el toque rápido debería ocultarlos nuevamente (lo que indica que el usuario está inactivo)
   * estado de visualización)
   * - Además de esto, todavía queremos que el usuario sea considerado inactivo después de
   * unos segundos de inactividad.
   *
   * > Nota: la única parte de la interacción de iOS que no podemos imitar con esta configuración
   * es mantener presionado el elemento de video que cuenta como actividad para
   * mantenga los controles visibles, pero eso no debería ser un problema. Un toque y espera
   * en cualquier control aún mantendrá al usuario activo
   *
   * @privado
   * /
  addTechControlsListeners_() {
    // Asegúrese de eliminar todos los oyentes anteriores en caso de que nos llamen varias veces.
    this.removeTechControlsListeners_();

    this.on(this.tech_, 'click', this.boundHandleTechClick_);
    this.on(this.tech_, 'dblclick', this.boundHandleTechDoubleClick_);

    // Si los controles estaban ocultos, no queremos que eso cambie sin un evento de toque
    // así que comprobaremos si los controles ya se mostraban antes de informar al usuario
    // actividad
    this.on(this.tech_, 'touchstart', this.boundHandleTechTouchStart_);
    this.on(this.tech_, 'touchmove', this.boundHandleTechTouchMove_);
    this.on(this.tech_, 'touchend', this.boundHandleTechTouchEnd_);

    // El oyente del toque debe ir después del oyente del extremo táctil porque el oyente del toque
    // el oyente cancela cualquier actividad de usuario informada al configurar userActive (falso)
    this.on(this.tech_, 'toque', this.boundHandleTechTap_);
  }

  /**
   * Elimine los oyentes utilizados para los controles de clic y toque. Esto es necesario para
   * alternar a los controles deshabilitados, donde un toque/toque no debería hacer nada.
   *
   * @privado
   * /
  removeTechControlsListeners_() {
    // No queremos usar simplemente `this.off()` porque podría haber otros necesarios
    // oyentes agregados por técnicos que amplían esto.
    this.off(this.tech_, 'toque', this.boundHandleTechTap_);
    this.off(this.tech_, 'touchstart', this.boundHandleTechTouchStart_);
    this.off(this.tech_, 'touchmove', this.boundHandleTechTouchMove_);
    this.off(this.tech_, 'touchend', this.boundHandleTechTouchEnd_);
    this.off(this.tech_, 'click', this.boundHandleTechClick_);
    this.off(this.tech_, 'dblclick', this.boundHandleTechDoubleClick_);
  }

  /**
   * El jugador espera a que la tecnología esté lista
   *
   * @privado
   * /
  manejarTechReady_() {
    this.triggerReady();

    // Mantener el mismo volumen que antes
    if (este.caché_.volumen) {
      this.techCall_('setVolume', this.cache_.volume);
    }

    // Mire si el técnico encontró un póster de mayor resolución mientras cargaba
    this.handleTechPosterChange_();

    // Actualizar la duración si está disponible
    this.handleTechDurationChange_();
  }

  /**
   * Vuelva a activar el evento `loadstart` que fue activado por {@link Tech}. Esto
   * la función también activará {@link Player#firstplay} si es el primer inicio de carga
   * para un vídeo.
   *
   * @fires Player#loadstart
   * @fires jugador #firstplay
   * @escucha Tech#loadstart
   * @privado
   * /
  handleTechLoadStart_() {
    // HACER: Actualice para usar el evento 'vaciado' en su lugar. Ver #1277.

    this.removeClass('vjs-terminado');
    this.removeClass('búsqueda de vjs');

    // restablecer el estado de error
    este.error(nulo);

    // Actualizar la duración
    this.handleTechDurationChange_();

    // Si ya se está reproduciendo, queremos activar un evento firstplay ahora.
    // El evento firstplay se basa en los eventos play y loadstart
    // que puede ocurrir en cualquier orden para una nueva fuente
    if (!this.paused()) {
      /**
       * Activado cuando el agente de usuario comienza a buscar datos de medios
       *
       * @reproductor de eventos#loadstart
       * @type {Objetivo del evento~Evento}
       * /
      this.trigger('loadstart');
      this.trigger('primera reproducción');
    } else {
      // restablecer el estado hasStarted
      this.hasStarted(false);
      this.trigger('loadstart');
    }

    // la reproducción automática ocurre después del inicio de carga para el navegador,
    // entonces imitamos ese comportamiento
    this.manualAutoplay_(this.autoplay() === verdadero && this.options_.normalizeReproducción automática? 'reproducir': this.autoplay());
  }

  /**
   * Manejar valores de cadena de reproducción automática, en lugar del típico booleano
   * valores que debe manejar el técnico. Tenga en cuenta que esto no es
   * parte de cualquier especificación. Los valores válidos y lo que hacen pueden ser
   * encontrado en el captador de reproducción automática en Player#autoplay()
   * /
  manualAutoplay_(tipo) {
    if (!this.tech_ || tipo de tipo !== 'cadena') {
      devolver;
    }

    // Guarde el valor silenciado() original, establezca silenciado en verdadero e intente reproducir().
    // Al rechazar la promesa, restaurar silenciado desde el valor guardado
    const resolverSilenciado = () => {
      const previamente silenciado = this.muted();

      esto.silenciado(verdadero);

      const restaurar silenciado = () => {
        this.muted(anteriormente silenciado);
      };

      // restaurar silenciado al terminar la reproducción
      this.playTerpressedQueue_.push(restoreMuted);

      const mutedPromise = this.play();

      if (!isPromise(mutedPromise)) {
        devolver;
      }

      return Promesa silenciada.catch(err => {
        restaurarSilenciado();
        throw new Error(`Rechazo en manualAutoplay. Restauración del valor silenciado. ${err? error: ''}`);
      });
    };

    dejar prometer;

    // si está silenciado, el valor predeterminado es verdadero
    // lo unico que podemos hacer es llamar al play
    si (escriba === 'cualquiera' && !esto.silenciado()) {
      promesa = this.play();

      si (esPromesa(promesa)) {
        promesa = promesa.catch(resolveMuted);
      }
    } else if (escriba === 'silenciado' && !esto.silenciado()) {
      promesa = resolverSilenciado();
    } else {
      promesa = this.play();
    }

    if (!isPromise(promesa)) {
      devolver;
    }

    volver promesa.entonces(() => {
      this.trigger({tipo: 'reproducción automática-éxito', reproducción automática: tipo});
    }).atrapar(() => {
      this.trigger({tipo: 'autoplay-failure', autoplay: tipo});
    });
  }

  /**
   * Actualizar las cachés de fuentes internas para que devolvamos la fuente correcta de
   * `src()`, `currentSource()` y `currentSources()`.
   *
   * > Nota: `currentSources` no se actualizará si la fuente que se pasa existe
   * en el caché `currentSources` actual.
   *
   *
   * @param {Tech~SourceObject} srcObj
   * Una fuente de cadena u objeto para actualizar nuestros cachés.
   * /
  updateSourceCaches_(srcObj = '') {

    let src = srcObj;
    dejar tipo = '';

    if (tipo de src !== 'cadena') {
      src = srcObj.src;
      tipo = srcObj.tipo;
    }

    // asegúrese de que todos los cachés estén configurados con los valores predeterminados
    // para evitar la verificación nula
    this.cache_.source = this.cache_.source || {};
    this.cache_.sources = this.cache_.sources || [];

    // intenta obtener el tipo de src que se pasó
    si (fuente && !tipo) {
      tipo = findMimetype(this, src);
    }

    // actualiza siempre la memoria caché `currentSource`
    this.cache_.source = mergeOptions({}, srcObj, {src, type});

    const matchingSources = this.cache_.sources.filter((s) => s.src && s.src === src);
    const fuenteElFuentes = [];
    const sourceEls = this.$$('fuente');
    const coincidenciaSourceEls = [];

    para (sea i = 0; i < fuenteEls.longitud; i++) {
      const sourceObj = Dom.getAttributes(sourceEls[i]);

      sourceElSources.push(sourceObj);

      si (origenObj.src && sourceObj.src === src) {
        haciendo coincidirSourceEls.push(sourceObj.src);
      }
    }

    // si tenemos els fuente coincidente pero fuentes no coincidentes
    // el caché de origen actual no está actualizado
    if (matchingSourceEls.longitud && !fuentescoincidencias.longitud) {
      this.cache_.sources = sourceElSources;
    // si no tenemos una fuente coincidente o una fuente els, establezca el
    // fuentes de la memoria caché a la memoria caché `currentSource`
    } else if (!fuentescoincidencias.longitud) {
      this.cache_.sources = [this.cache_.source];
    }

    // actualizar el caché tecnológico `src`
    this.cache_.src = src;
  }

  /**
   * *EXPERIMENTAL* Se activa cuando la fuente se configura o cambia en {@link Tech}
   * haciendo que el elemento multimedia se vuelva a cargar.
   *
   * Se disparará para la fuente inicial y cada fuente subsiguiente.
   * Este evento es un evento personalizado de Video.js y lo activa {@link Tech}.
   *
   * El objeto de evento para este evento contiene una propiedad `src` que contendrá la fuente
   * que estaba disponible cuando se activó el evento. Esto generalmente solo es necesario si Video.js
   * está cambiando de tecnología mientras se cambiaba la fuente.
   *
   * También se dispara cuando se llama a `cargar` en el reproductor (o elemento multimedia)
   * porque el {@enlace https://html.spec.whatwg.org/multipage/media.html#dom-media-load|especificación para `carga`}
   * dice que el algoritmo de selección de recursos debe cancelarse y reiniciarse.
   * En este caso, es muy probable que la propiedad `src` se establezca en el
   * cadena vacía `&quot;&quot;` para indicar que no sabemos cuál será la fuente pero
   * que está cambiando.
   *
   * *Este evento aún es experimental y puede cambiar en versiones menores.*
   * __Para usar esto, pasa la opción `enableSourceset` al reproductor.__
   *
   * @reproductor de eventos#conjuntofuente
   * @type {Objetivo del evento~Evento}
   * @prop {cadena} src
   * La URL de origen disponible cuando se activó el `sourceset`.
   * Será una cadena vacía si no podemos saber cuál es la fuente
   * pero sepa que la fuente cambiará.
   * /
  /**
   * Vuelva a activar el evento `sourceset` que fue activado por {@link Tech}.
   *
   * @fires Player#sourceset
   * @escucha Tech#sourceset
   * @privado
   * /
  handleTechSourceset_(evento) {
    // solo actualice el caché de origen cuando el origen
    // no se actualizó usando la API del reproductor
    if (!este.cambiandoSrc_) {
      let updateSourceCaches = (src) => this.updateSourceCaches_(src);
      const playerSrc = this.currentSource().src;
      const eventSrc = event.src;

      // si tenemos un playerSrc que no es un blob y un tech src que es un blob
      if (jugadorSrc && !(/^blob:/).test(playerSrc) && (/^blob:/).prueba(eventSrc)) {

        // si tanto la fuente de tecnología como la fuente del jugador se actualizaron, asumimos
        // Algo así como @videojs/http-streaming hizo el conjunto de fuentes y salteó la actualización del caché de fuentes.
        if (!this.lastSource_ || (this.lastSource_.tech !== eventSrc && this.lastSource_.player !== playerSrc)) {
          updateSourceCaches = () => {};
        }
      }

      // actualice la fuente a la fuente inicial de inmediato
      // en algunos casos será una cadena vacía
      updateSourceCaches(eventSrc);

      // si el `sourceset` `src` era una cadena vacía
      // espera un `loadstart` para actualizar el caché a `currentSrc`.
      // Si ocurre un conjunto de fuentes antes de un `loadstart`, restablecemos el estado
      si (!evento.src) {
        this.tech_.any(['sourceset', 'loadstart'], (e) => {
          // si un conjunto de fuentes ocurre antes de un `loadstart` allí
          // no hay nada que hacer como `handleTechSourceset_`
          // se volverá a llamar y esto se manejará allí.
          if (e.type === 'conjunto de fuentes') {
            devolver;
          }

          const techSrc = this.techGet('currentSrc');

          this.lastSource_.tech = techSrc;
          this.updateSourceCaches_(techSrc);
        });
      }
    }
    this.lastSource_ = {jugador: this.currentSource().src, tech: event.src};

    este.disparador({
      src: evento.src,
      tipo: 'conjunto de fuentes'
    });
  }

  /**
   * Agregar/eliminar la clase vjs-has-started
   *
   * @fires jugador #firstplay
   *
   * solicitud @param {booleano}
   * - verdadero: agrega la clase
   * - falso: eliminar la clase
   *
   * @return {booleano}
   * el valor booleano de hasStarted_
   * /
  ha comenzado (solicitud) {
    si (solicitud === indefinido) {
      // actúa como captador, si no tenemos ninguna solicitud de cambio
      return this.hasStarted_;
    }

    if (solicitud === this.hasStarted_) {
      devolver;
    }

    this.hasStarted_ = solicitud;

    if (esto.haComenzado_) {
      this.addClass('vjs-ha-comenzado');
      this.trigger('primera reproducción');
    } else {
      this.removeClass('vjs-ha-comenzado');
    }
  }

  /**
   * Activado cada vez que el medio comienza o reanuda la reproducción
   *
   * @ver [Especificación]{@enlace https://html.spec.whatwg.org/multipage/embedded-content.html#dom-media-play}
   * @fires Jugador#jugar
   * @escucha Tech#play
   * @privado
   * /
  handleTechPlay_() {
    this.removeClass('vjs-terminado');
    this.removeClass('vjs-paused');
    this.addClass('vjs-jugando');

    // oculta el cartel cuando el usuario presiona reproducir
    this.hasStarted(verdadero);
    /**
     * Se activa cada vez que ocurre un evento {@link Tech#play}. Indica que
     * la reproducción ha comenzado o se ha reanudado.
     *
     * @reproductor de eventos#reproducir
     * @type {Objetivo del evento~Evento}
     * /
    this.trigger('reproducir');
  }

  /**
   * Vuelva a activar el evento `ratechange` que fue activado por {@link Tech}.
   *
   * Si había algún evento en cola mientras la velocidad de reproducción era cero, disparar
   * esos eventos ahora.
   *
   * @privado
   * @método Player#handleTechRateChange_
   * @fires Jugador#cambio de tasa
   * @escucha Tech#ratechange
   * /
  handleTechRateChange_() {
    if (this.tech_.playbackRate() > 0 && this.cache_.lastPlaybackRate === 0) {
      this.queuedCallbacks_.forEach((en cola) => en cola.devolución de llamada(en cola.evento));
      this.queuedCallbacks_ = [];
    }
    this.cache_.lastPlaybackRate = this.tech_.playbackRate();
    /**
     * Se dispara cuando se cambia la velocidad de reproducción del audio/video
     *
     * @event Player#ratechange
     * @tipo {evento}
     * /
    this.trigger('cambio de tasa');
  }

  /**
   * Vuelva a activar el evento &quot;en espera&quot; que activó {@link Tech}.
   *
   * @fires Player#esperando
   * @escucha Tech#esperando
   * @privado
   * /
  handleTechWaiting_() {
    this.addClass('vjs-esperando');
    /**
     * Un cambio de readyState en el elemento DOM ha provocado que se detenga la reproducción.
     *
     * @reproductor de eventos#esperando
     * @type {Objetivo del evento~Evento}
     * /
    this.trigger('esperando');

    // Los navegadores pueden emitir un evento de actualización de tiempo después de un evento de espera. A fin de evitar
    // eliminación prematura de la clase de espera, esperar a que cambie el tiempo.
    const timeWhenWaiting = this.currentTime();
    const timeUpdateListener = () => {
      if (timeWhenWaiting !== this.currentTime()) {
        this.removeClass('vjs-esperando');
        this.off('actualización de hora', escucha de actualización de hora);
      }
    };

    this.on('actualización de hora', escucha de actualización de hora);
  }

  /**
   * Vuelva a activar el evento `canplay` que fue activado por {@link Tech}.
   * > Nota: Esto no es consistente entre navegadores. Ver #1351
   *
   * @fires Jugador#puedejugar
   * @escucha Tech#canplay
   * @privado
   * /
  handleTechCanPlay_() {
    this.removeClass('vjs-esperando');
    /**
     * El medio tiene un estado listo de HAVE_FUTURE_DATA o superior.
     *
     * @event Jugador#puedejugar
     * @type {Objetivo del evento~Evento}
     * /
    this.trigger('puede jugar');
  }

  /**
   * Vuelva a activar el evento `canplaythrough` que fue activado por {@link Tech}.
   *
   * @fires Player#canplaythrough
   * @escucha Tech#canplaythrough
   * @privado
   * /
  handleTechCanPlayThrough_() {
    this.removeClass('vjs-esperando');
    /**
     * El medio tiene un estado listo de TENER_ENOUGH_DATA o superior. Esto significa que el
     * El archivo multimedia completo se puede reproducir sin almacenamiento en búfer.
     *
     * @event Player#canplaythrough
     * @type {Objetivo del evento~Evento}
     * /
    this.trigger('puedejugar');
  }

  /**
   * Vuelva a activar el evento `reproduciendo` que fue activado por {@link Tech}.
   *
   * @fires Jugador#jugando
   * @escucha Tech#playing
   * @privado
   * /
  manejarTecnologíaJugando_() {
    this.removeClass('vjs-esperando');
    /**
     * El medio ya no está bloqueado para la reproducción y ha comenzado a reproducirse.
     *
     * @event Jugador#jugando
     * @type {Objetivo del evento~Evento}
     * /
    this.trigger('jugando');
  }

  /**
   * Vuelva a activar el evento `buscando` que fue activado por {@link Tech}.
   *
   * @fires Jugador#buscando
   * @escucha Tecnología#buscando
   * @privado
   * /
  handleTechSeeking_() {
    this.addClass('búsqueda de vjs');
    /**
     * Se dispara cada vez que el jugador salta a un nuevo tiempo
     *
     * @evento Jugador#buscando
     * @type {Objetivo del evento~Evento}
     * /
    this.trigger('buscando');
  }

  /**
   * Vuelva a activar el evento `buscado` que fue activado por {@link Tech}.
   *
   * @fires Player#buscado
   * @escucha Tech#buscado
   * @privado
   * /
  handleTechSeeked_() {
    this.removeClass('búsqueda de vjs');
    this.removeClass('vjs-terminado');
    /**
     * Se dispara cuando el jugador ha terminado de saltar a un nuevo tiempo
     *
     * Jugador de @event#buscado
     * @type {Objetivo del evento~Evento}
     * /
    this.trigger('buscado');
  }

  /**
   * Vuelva a activar el evento `firstplay` que fue activado por {@link Tech}.
   *
   * @fires jugador #firstplay
   * @escucha Tech#firstplay
   * @deprecated A partir de 6.0, el evento firstplay está obsoleto.
   * A partir de la versión 6.0, pasar la opción `starttime` al jugador y el evento firstplay quedan obsoletos.
   * @privado
   * /
  handleTechFirstPlay_() {
    // Si se especifica el primer atributo de hora de inicio
    // luego comenzaremos en el desplazamiento dado en segundos
    if (esta.opciones_.hora de inicio) {
      log.warn('Pasar la opción `starttime` al jugador quedará obsoleto en 6.0');
      this.currentTime(this.options_.starttime);
    }

    this.addClass('vjs-ha-comenzado');
    /**
     * Se activa la primera vez que se reproduce un video. No es parte de la especificación HLS, y esto es
     * Probablemente no sea la mejor implementación todavía, así que utilícelo con moderación. Si no tienes un
     * motivo para evitar la reproducción, use `myPlayer.one('play');` en su lugar.
     *
     * @reproductor del evento#firstplay
     * @deprecated A partir de 6.0, el evento firstplay está obsoleto.
     * @type {Objetivo del evento~Evento}
     * /
    this.trigger('primera reproducción');
  }

  /**
   * Vuelva a activar el evento de &quot;pausa&quot; que activó {@link Tech}.
   *
   * @fires jugador #pausa
   * @escucha Tech#pausa
   * @privado
   * /
  manejarPausaTecnológica_() {
    this.removeClass('vjs-jugando');
    this.addClass('vjs-paused');
    /**
     * Disparado cada vez que los medios han sido pausados
     *
     * @reproductor de eventos#pausa
     * @type {Objetivo del evento~Evento}
     * /
    this.trigger('pausa');
  }

  /**
   * Vuelva a activar el evento `finalizado` que activó {@link Tech}.
   *
   * @fires Player#finalizado
   * @escucha Tech#finalizado
   * @privado
   * /
  handleTechEnded_() {
    this.addClass('vjs-terminado');
    this.removeClass('vjs-esperando');
    if (esta.opciones_.bucle) {
      this.currentTime(0);
      este juego();
    } else if (!this.paused()) {
      esto.pausa();
    }

    /**
     * Activado cuando se alcanza el final del recurso de medios (currentTime == duración)
     *
     * @event Player#finalizado
     * @type {Objetivo del evento~Evento}
     * /
    this.trigger('terminado');
  }

  /**
   * Activado cuando la duración del recurso de medios se conoce o cambia por primera vez
   *
   * @escucha Tech#durationchange
   * @privado
   * /
  handleTechDurationChange_() {
    this.duration(this.techGet_('duration'));
  }

  /**
   * Manejar un clic en el elemento multimedia para reproducir/pausar
   *
   * @param {EventTarget~Evento} evento
   * el evento que provocó que esta función se activara
   *
   * @escucha Tech#click
   * @privado
   * /
  handleTechClick_(evento) {
    // Cuando los controles están deshabilitados, un clic no debería alternar la reproducción porque
    // el clic se considera un control
    if (!this.controls_) {
      devolver;
    }

    si (
      this.options_ === indefinido ||
      this.options_.userActions === indefinido ||
      this.options_.userActions.click === indefinido ||
      this.options_.userActions.click !== false
    ) {

      si (
        this.options_ !== undefined &&
        this.options_.userActions !== undefined &&
        typeof this.options_.userActions.click === 'función'
      ) {

        this.options_.userActions.click.call(este, evento);

      } else if (esto.en pausa()) {
        silencioPromise(this.play());
      } else {
        esto.pausa();
      }
    }
  }

  /**
   * Haz doble clic en el elemento multimedia para entrar/salir de la pantalla completa
   *
   * @param {EventTarget~Evento} evento
   * el evento que provocó que esta función se activara
   *
   * @escucha Tech#dblclick
   * @privado
   * /
  handleTechDoubleClick_(evento) {
    if (!this.controls_) {
      devolver;
    }

    // no queremos alternar el estado de pantalla completa
    // al hacer doble clic dentro de una barra de control o un modal
    const inAllowedEls = Array.prototype.some.call(
      this.$$('.vjs-control-bar, .vjs-modal-dialog'),
      el => el.contains(evento.objetivo)
    );

    if (!inAllowedEls) {
      /*
       * options.userActions.doubleClick
       *
       * Si es `indefinido` o `verdadero`, hacer doble clic cambia a pantalla completa si los controles están presentes
       * Establecer en `falso` para deshabilitar el manejo de doble clic
       * Establecido en una función para sustituir un controlador de doble clic externo
       * /
      si (
        this.options_ === indefinido ||
        this.options_.userActions === indefinido ||
        this.options_.userActions.doubleClick === indefinido ||
        this.options_.userActions.doubleClick !== false
      ) {

        si (
          this.options_ !== undefined &&
          this.options_.userActions !== undefined &&
          typeof this.options_.userActions.doubleClick === 'función'
        ) {

          this.options_.userActions.doubleClick.call(este, evento);

        } más si (this.isFullscreen()) {
          this.exitFullscreen();
        } else {
          this.requestFullscreen();
        }
      }
    }
  }

  /**
   * Manejar un toque en el elemento multimedia. Cambiará al usuario
   * estado de actividad, que oculta y muestra los controles.
   *
   * @escucha Tech#tap
   * @privado
   * /
  handleTechTap_() {
    este.usuarioActivo(!este.usuarioActivo());
  }

  /**
   * Manija táctil para comenzar
   *
   * @escucha Tech#touchstart
   * @privado
   * /
  manejarTechTouchStart_() {
    this.userWasActive = this.userActive();
  }

  /**
   * Manija táctil para mover
   *
   * @escucha Tech#touchmove
   * @privado
   * /
  handleTechTouchMove_() {
    if (este.usuarioEstabaActivo) {
      this.reportUserActivity();
    }
  }

  /**
   * Manejar toque para terminar
   *
   * @param {EventTarget~Evento} evento
   * el evento touchend que activó
   * esta función
   *
   * @escucha Tech#touchend
   * @privado
   * /
  handleTechTouchEnd_(evento) {
    // Impide que los eventos del mouse también sucedan
    if (evento.cancelable) {
      event.preventDefault();
    }
  }

  /**
   * Los eventos de clics nativos en SWF no se activan en IE11, Win8.1RT
   * use eventos de clic de etapa activados desde dentro del SWF en su lugar
   *
   * @privado
   * @escucha stageclick
   * /
  handleStageClick_() {
    this.reportUserActivity();
  }

  /**
   * @privado
   * /
  alternar Clase de pantalla completa_ () {
    si (this.isFullscreen()) {
      this.addClass('vjs-pantalla completa');
    } else {
      this.removeClass('vjs-pantalla completa');
    }
  }

  /**
   * cuando se desencadena el evento fschange del documento, llama a esto
   * /
  documentFullscreenChange_(e) {
    const targetPlayer = e.target.player;

    // si otro jugador estaba en pantalla completa
    // hacer una verificación nula para targetPlayer porque los antiguos firefox pondrían el documento como e.target
    si (jugador objetivo && ¡jugador objetivo! == esto) {
      devolver;
    }

    const el = esto.el();
    let isFs = document[this.fsApi_.fullscreenElement] === el;

    si (!esFs && el.coincidencias) {
      isFs = el.matches(':' + this.fsApi_.fullscreen);
    } más si (!isFs && el.msMatchesSelector) {
      isFs = el.msMatchesSelector(':' + this.fsApi_.fullscreen);
    }

    this.isFullscreen(isFs);
  }

  /**
   * Manejar el cambio de pantalla completa de Tech
   *
   * @param {EventTarget~Evento} evento
   * el evento de cambio de pantalla completa que activó esta función
   *
   * @param {Objeto} datos
   * los datos que se enviaron con el evento
   *
   * @privado
   * @escucha Tech#fullscreenchange
   * @fires Player#cambio de pantalla completa
   * /
  handleTechFullscreenChange_(evento, datos) {
    si (datos) {
      si (data.nativeIOSFullscreen) {
        this.addClass('vjs-ios-native-fs');
        this.tech_.one('webkitendfullscreen', () => {
          this.removeClass('vjs-ios-native-fs');
        });
      }
      this.isFullscreen(datos.isFullscreen);
    }
  }

  handleTechFullscreenError_(evento, error) {
    this.trigger('error de pantalla completa', err);
  }

  /**
   * @privado
   * /
  alternarImagenEnClaseDeImagen_() {
    if (esto.esEnImagenEnImagen()) {
      this.addClass('vjs-imagen-en-imagen');
    } else {
      this.removeClass('vjs-imagen-en-imagen');
    }
  }

  /**
   * Handle Tech Ingrese Picture-in-Picture.
   *
   * @param {EventTarget~Evento} evento
   * el evento enterpictureinpicture que activó esta función
   *
   * @privado
   * @escucha Tech#enterpictureinpicture
   * /
  handleTechEnterPictureInPicture_(evento) {
    esto.isInPictureInPicture(verdadero);
  }

  /**
   * Manejar Tech Leave Picture-in-Picture.
   *
   * @param {EventTarget~Evento} evento
   * el evento LeavePictureInPicture que activó esta función
   *
   * @privado
   * @escucha Tech#dejarimagenenimagen
   * /
  handleTechLeavePictureInPicture_(evento) {
    esto.isInPictureInPicture(falso);
  }

  /**
   * Se dispara cuando ocurre un error durante la carga de un audio/video.
   *
   * @privado
   * @escucha Tech#error
   * /
  handleTechError_() {
    const error = this.tech_.error();

    este.error(error);
  }

  /**
   * Vuelva a activar el evento `textdata` que fue activado por {@link Tech}.
   *
   * @fires Player#textdata
   * @escucha Tech#textdata
   * @privado
   * /
  handleTechTextData_() {
    dejar datos = nulo;

    if (argumentos.longitud > 1) {
      datos = argumentos[1];
    }

    /**
     * Se dispara cuando recibimos un evento de datos de texto de tecnología
     *
     * @reproductor de eventos#datos de texto
     * @type {Objetivo del evento~Evento}
     * /
    this.trigger('datos de texto', datos);
  }

  /**
   * Obtener objeto para valores almacenados en caché.
   *
   * @return {Objeto}
   * obtener el caché de objetos actual
   * /
  obtenerCaché() {
    devolver esto.cache_;
  }

  /**
   * Restablece el objeto de caché interno.
   *
   * El uso de esta función fuera del constructor del jugador o el método de reinicio puede
   * tener efectos secundarios no deseados.
   *
   * @privado
   * /
  resetCache_() {
    esto.cache_ = {

      // En este momento, el tiempo actual no está _realmente_ en caché porque siempre está
      // recuperado de la tecnología (ver: hora actual). Sin embargo, para completar,
      // lo establecemos en cero aquí para asegurarnos de que si comenzamos a almacenar en caché
      // lo reiniciamos junto con todo lo demás.
      tiempo actual: 0,
      tiempo de inicio: 0,
      inactividadTiempo de espera: esto.opciones_.inactividadTiempo de espera,
      duración: Yaya,
      último volumen: 1,
      lastPlaybackRate: this.defaultPlaybackRate(),
      medios: nulo,
      origen: '',
      fuente: {},
      fuentes: [],
      tasas de reproducción: [],
      volumen: 1
    };
  }

  /**
   * Pasar valores a la tecnología de reproducción
   *
   * @param {cadena} [método]
   * el método para llamar
   *
   * @param {Objeto} argumento
   * el argumento para pasar
   *
   * @privado
   * /
  techCall_(método, arg) {
    // Si aún no está listo, llame al método cuando lo esté

    esto.listo(función() {
      if (método en middleware.allowedSetters) {
        return middleware.set(this.middleware_, this.tech_, method, arg);

      } else if (method in middleware.allowedMediators) {
        return middleware.mediate(this.middleware_, this.tech_, method, arg);
      }

      intentar {
        si (esta.tecnología_) {
          this.tech_[método](arg);
        }
      } catch (e) {
        registro (e);
        tirar e;
      }
    }, true);
  }

  /**
   * Recibir llamadas no puede esperar a la tecnología y, a veces, no es necesario.
   *
   * Método @param {cadena}
   * Método tecnológico
   *
   * @return {Función|indefinido}
   * el método o indefinido
   *
   * @privado
   * /
  techGet_(método) {
    if (!esta.tecnología_ || !esta.tecnología_.estáListo_) {
      devolver;
    }

    if (método en middleware.allowedGetters) {
      return middleware.get(this.middleware_, this.tech_, method);

    } else if (method in middleware.allowedMediators) {
      return middleware.mediate(this.middleware_, this.tech_, method);
    }

    // A Flash le gusta morir y recargar cuando lo ocultas o lo cambias de posición.
    // En estos casos, los métodos del objeto desaparecen y obtenemos errores.
    // HACER: ¿Es necesario para otras tecnologías además de Flash?
    // Cuando eso suceda, detectaremos los errores e informaremos al equipo técnico que ya no está listo.
    intentar {
      devuelve this.tech_[método]();
    } catch (e) {

      // Al crear bibliotecas tecnológicas adicionales, es posible que aún no se haya definido un método esperado
      if (this.tech_[método] === indefinido) {
        log(`Video.js: método ${method} no definido para la tecnología de reproducción ${this.techName_}.`, e);
        tirar e;
      }

      // Cuando un método no está disponible en el objeto, arroja un TypeError
      if (e.nombre === 'TypeError') {
        log(`Video.js: ${método} no disponible en el elemento de tecnología de reproducción ${this.techName_}.`, e);
        this.tech_.isReady_ = falso;
        tirar e;
      }

      // Si el error es desconocido, simplemente inicie sesión y lance
      registro (e);
      tirar e;
    }
  }

  /**
   * Intente iniciar la reproducción en la primera oportunidad.
   *
   * @return {Promesa|indefinido}
   * Devuelve una promesa si el navegador admite Promises (o una
   * se pasó como una opción). Esta promesa se resolverá el
   * el valor de retorno del juego. Si esto no está definido, cumplirá con el
   * cadena de promesa de lo contrario, la cadena de promesa se cumplirá cuando
   * se cumple la promesa del juego.
   * /
  jugar() {
    const PromiseClass = this.options_.Promise || ventana.Promesa;

    si (PromesaClase) {
      devolver nueva PromiseClass((resolver) => {
        this.play_(resolver);
      });
    }

    devolver esto.play_();
  }

  /**
   * La lógica real para jugar, toma una devolución de llamada que se resolverá en el
   * valor de retorno del juego. Esto nos permite resolver la promesa de juego si hay
   * es uno en los navegadores modernos.
   *
   * @privado
   * @param {Función} [devolución de llamada]
   * La devolución de llamada que debe llamarse cuando los técnicos juegan realmente se llama
   * /
  play_(devolución de llamada = promesa de silencio) {
    this.playCallbacks_.push(devolución de llamada);

    const isSrcReady = Boolean(!this. ChangingSrc_ && (este.src() || este.currentSrc()));

    // trata las llamadas a play_ algo así como la función de evento `one`
    si (esto.waitToPlay_) {
      this.off(['ready', 'loadstart'], this.waitToPlay_);
      this.waitToPlay_ = null;
    }

    // si el jugador/tecnología no está listo o el src mismo no está listo
    // poner en cola una llamada para jugar en `ready` o `loadstart`
    if (!this.isReady_ || !isSrcReady) {
      this.waitToPlay_ = (e) => {
        este juego_();
      };
      this.one(['ready', 'loadstart'], this.waitToPlay_);

      // si estamos en Safari, existe una gran posibilidad de que loadstart se active después del período de tiempo del gesto
      // en ese caso, necesitamos preparar el elemento de video llamando a cargar para que esté listo a tiempo
      si (! isSrcReady && (navegador.IS_ANY_SAFARI || navegador.IS_IOS)) {
        esto.load();
      }
      devolver;
    }

    // Si el reproductor/técnico está listo y tenemos una fuente, podemos intentar la reproducción.
    const val = this.techGet_('jugar');

    // la reproducción finalizó si el valor devuelto es nulo
    si (val === nulo) {
      this.runPlayTerpressedQueue_();
    } else {
      this.runPlayCallbacks_(val);
    }
  }

  /**
   * Estas funciones se ejecutarán cuando finalice el juego. si jugar
   * runPlayCallbacks_ se ejecuta, esta función no se ejecutará. Esto nos permite
   * para diferenciar entre una jugada terminada y una llamada a jugar real.
   * /
  ejecutarReproducirColaTerminada_() {
    const cola = this.playTerpressedQueue_.slice(0);

    this.playTerpressedQueue_ = [];

    cola.paraCada(función(q) {
      q();
    });
  }

  /**
   * Cuando se retrasa una devolución de llamada para jugar, tenemos que ejecutar estos
   * devoluciones de llamada cuando el juego se llama realmente en la tecnología. Esta función
   * ejecuta las devoluciones de llamada que se retrasaron y acepta el valor devuelto
   * de la tecnología.
   *
   * @param {indefinido|Promesa} val
   * El valor de retorno de la tecnología.
   * /
  ejecutarReproducirCallbacks_(val) {
    const devoluciones de llamada = this.playCallbacks_.slice(0);

    this.playCallbacks_ = [];
    // Limpiar la cola finalizada del juego ya que terminamos un juego real.
    this.playTerpressedQueue_ = [];

    devoluciones de llamada.forEach(función(cb) {
      cb(valor);
    });
  }

  /**
   * Pausa la reproducción del video
   *
   * @return {Jugador}
   * Una referencia al objeto del jugador en el que se invocó esta función
   * /
  pausa() {
    this.techCall_('pausa');
  }

  /**
   * Compruebe si el reproductor está en pausa o aún no ha jugado
   *
   * @return {booleano}
   * - falso: si el medio se está reproduciendo actualmente
   * - verdadero: si los medios no se están reproduciendo actualmente
   * /
  en pausa() {
    // El estado inicial de pausa debe ser verdadero (en Safari es falso)
    return (this.techGet_('paused') === false) ? falso verdadero;
  }

  /**
   * Obtenga un objeto TimeRange que represente los rangos de tiempo actuales que el usuario
   * ha jugado.
   *
   * @return {Intervalo de tiempo}
   * Un objeto de rango de tiempo que representa todos los incrementos de tiempo que han
   * se ha jugado.
   * /
  jugó() {
    devuelve esto.techGet_('reproducido') || crear rango de tiempo (0, 0);
  }

  /**
   * Devuelve si el usuario está &quot;fregando&quot; o no. fregar es
   * cuando el usuario ha hecho clic en el controlador de la barra de progreso y está
   * arrastrándolo a lo largo de la barra de progreso.
   *
   * @param {booleano} [es fregar]
   * si el usuario está o no está fregando
   *
   * @return {booleano}
   * El valor de fregar al obtener
   * /
  fregando(esFregando) {
    if (typeof isScrubbing === 'indefinido') {
      devolver esto.fregar_;
    }
    this.scrubbing_ = !!isScrubbing;
    this.techCall_('setScrubbing', this.scrubbing_);

    si (está fregando) {
      this.addClass('vjs-fregado');
    } else {
      this.removeClass('vjs-scrubbing');
    }
  }

  /**
   * Obtener o establecer la hora actual (en segundos)
   *
   * @param {número|cadena} [segundos]
   * El tiempo para buscar en segundos
   *
   * @return {número}
   * - la hora actual en segundos al obtener
   * /
  horaActual(segundos) {
    if (tipo de segundos !== 'indefinido') {
      si (segundos < 0) {
        segundos = 0;
      }
      if (!this.isReady_ || this. ChangingSrc_ || !this.tech_ || !this.tech_.isReady_) {
        this.cache_.initTime = segundos;
        this.off('canplay', this.boundApplyInitTime_);
        this.one('canplay', this.boundApplyInitTime_);
        devolver;
      }
      this.techCall_('setCurrentTime', segundos);
      this.cache_.initTime = 0;
      devolver;
    }

    // almacena en caché la última hora actual y regresa. por defecto a 0 segundos
    //
    // El almacenamiento en caché de currentTime está destinado a evitar una gran cantidad de lecturas en la tecnología
    // horaActual al realizar la limpieza, pero puede que no proporcione muchos beneficios de rendimiento después de todo.
    // Debe ser probado. También algo tiene que leer la hora actual real o el caché
    // nunca se actualiza.
    this.cache_.currentTime = (this.techGet_('currentTime') || 0);
    devuelve this.cache_.currentTime;
  }

  /**
   * Aplicar el valor de initTime almacenado en caché como currentTime.
   *
   * @privado
   * /
  aplicarTiempoInicio_() {
    this.currentTime(this.cache_.initTime);
  }

  /**
   * Normalmente obtiene la duración del video en segundos;
   * en todos los casos de uso, excepto en los más raros, NO se pasará un argumento al método
   *
   * > **NOTA**: El video debe haber comenzado a cargarse antes de que se pueda determinar la duración.
   * conocido y, según el comportamiento de precarga, es posible que no se conozca hasta que comience el video
   * jugando.
   *
   * @fires Player#cambio de duración
   *
   * @param {número} [segundos]
   * La duración del video a configurar en segundos
   *
   * @return {número}
   * - La duración del video en segundos al obtener
   * /
  duración (segundos) {
    si (segundos === indefinido) {
      // devuelve NaN si no se conoce la duración
      devolver this.cache_.duration !== indefinido ? this.cache_.duration : Yaya;
    }

    segundos = parseFloat(segundos);

    // Estandarizar en Infinity para señalar que el video está en vivo
    si (segundos < 0) {
      segundos = infinito;
    }

    if (segundos! == this.cache_.duration) {
      // Almacenar en caché el último valor establecido para una limpieza optimizada (esp. Destello)
      // HACER: ¿Requerido para tecnologías que no sean Flash?
      this.cache_.duration = segundos;

      si (segundos === infinito) {
        this.addClass('vjs-live');
      } else {
        this.removeClass('vjs-live');
      }
      if (!isNaN(segundos)) {
        // No active el cambio de duración a menos que se conozca el valor de duración.
        // @ver [Spec]{@link https://www.w3.org/TR/2011/WD-html5-20110113/video.html#media-element-load-algorithm}

        /**
         * @event Player#cambio de duración
         * @type {Objetivo del evento~Evento}
         * /
        this.trigger('cambio de duración');
      }
    }
  }

  /**
   * Calcula cuánto tiempo queda en el video. no parte
   * de la API de video nativa.
   *
   * @return {número}
   * El tiempo restante en segundos
   * /
  tiempo restante() {
    devuelve this.duration() - this.currentTime();
  }

  /**
   * Una función de tiempo restante que está destinada a ser utilizada cuando
   * el tiempo debe mostrarse directamente al usuario.
   *
   * @return {número}
   * El tiempo restante redondeado en segundos
   * /
  visualización de tiempo restante () {
    return Math.floor(this.duration()) - Math.floor(this.currentTime());
  }

  //
  // Algo así como una matriz de partes del video que se han descargado.

  /**
   * Obtenga un objeto TimeRange con una matriz de los tiempos del video
   * que han sido descargados. Si sólo desea el porcentaje de la
   * video que ha sido descargado, use bufferedPercent.
   *
   * @ver [Especificación en búfer]{@link http://dev.w3.org/html5/spec/video.html#dom-media-buffered}
   *
   * @return {Intervalo de tiempo}
   * Un objeto TimeRange simulado (siguiendo las especificaciones de HTML)
   * /
  almacenado en búfer () {
    let buffered = this.techGet_('buffered');

    if (!buffered || !buffered.length) {
      almacenado en búfer = createTimeRange(0, 0);
    }

    retorno amortiguado;
  }

  /**
   * Obtenga el porcentaje (como decimal) del video que se descargó.
   * Este método no forma parte de la API de video HTML nativo.
   *
   * @return {número}
   * Un decimal entre 0 y 1 que representa el porcentaje
   * que está almacenado en búfer 0 siendo 0% y 1 siendo 100%
   * /
  porcentajebuffered() {
    return bufferedPercent(this.buffered(), this.duration());
  }

  /**
   * Obtenga la hora de finalización del último rango de tiempo almacenado en búfer
   * Esto se usa en la barra de progreso para encapsular todos los rangos de tiempo.
   *
   * @return {número}
   * El final del último rango de tiempo almacenado en el búfer
   * /
  bufferedEnd() {
    const buffered = this.buffered();
    const duracion = this.duration();
    let end = buffered.end(buffered.length - 1);

    si (fin > duración) {
      fin = duración;
    }

    fin de retorno;
  }

  /**
   * Obtener o establecer el volumen actual de los medios
   *
   * @param {número} [porcentaje como decimal]
   * El nuevo volumen como porcentaje decimal:
   * - 0 está silenciado/0%/apagado
   * - 1.0 es 100%/lleno
   * - 0.5 es la mitad del volumen o 50%
   *
   * @return {número}
   * El volumen actual como porcentaje al obtener
   * /
  volumen (porcentaje como decimal) {
    vamos vol;

    if (porcentaje como decimal !== indefinido) {
      // Forzar valor entre 0 y 1
      vol = Math.max(0, Math.min(1, parseFloat(percentAsDecimal)));
      this.cache_.volume = vol;
      this.techCall_('setVolume', vol);

      si (vol. > 0) {
        this.lastVolume_(vol);
      }

      devolver;
    }

    // Predeterminado a 1 al devolver el volumen actual.
    vol = parseFloat(this.techGet_('volumen'));
    volver (isNaN(vol)) ? 1 : volumen;
  }

  /**
   * Obtenga el estado silenciado actual, o active o desactive el silencio
   *
   * @param {booleano} [silenciado]
   * - verdadero para silenciar
   * - falso para desactivar el silencio
   *
   * @return {booleano}
   * - verdadero si el silencio está activado y se está activando
   * - falso si el silencio está desactivado y se activa
   * /
  silenciado (silenciado) {
    si (silenciado! == indefinido) {
      this.techCall_('setMuted', silenciado);
      devolver;
    }
    devuelve esto.techGet_('silenciado') || FALSO;
  }

  /**
   * Obtener el estado de silenciado predeterminado actual, o activar o desactivar el silenciado predeterminado. por defecto silenciado
   * indica el estado de silencio en la reproducción inicial.
   *
   * ```js
   * var myPlayer = videojs('algún-id-del-jugador');
   *
   * myPlayer.src(&quot;http://www.example.com/path/to/video.mp4&quot;);
   *
   * // obtener, debe ser falso
   * console.log(myPlayer.defaultMuted());
   * // establecido en verdadero
   * myPlayer.defaultMuted(true);
   * // obtener debe ser verdadero
   * console.log(myPlayer.defaultMuted());
   * ```
   *
   * @param {booleano} [predeterminado silenciado]
   * - verdadero para silenciar
   * - falso para desactivar el silencio
   *
   * @return {booleano|Jugador}
   * - verdadero si defaultMuted está activado y obteniendo
   * - falso si defaultMuted está desactivado y obteniendo
   * - Una referencia al reproductor actual al configurar
   * /
  predeterminado silenciado (predeterminado silenciado) {
    if (predeterminado silenciado !== indefinido) {
      devuelve this.techCall_('setDefaultMuted', defaultMuted);
    }
    devuelve esto.techGet_('defaultMuted') || FALSO;
  }

  /**
   * Obtenga el último volumen, o configúrelo
   *
   * @param {número} [porcentaje como decimal]
   * El nuevo último volumen como porcentaje decimal:
   * - 0 está silenciado/0%/apagado
   * - 1.0 es 100%/lleno
   * - 0.5 es la mitad del volumen o 50%
   *
   * @return {número}
   * el valor actual de lastVolume como porcentaje al obtener
   *
   * @privado
   * /
  lastVolume_(percentAsDecimal) {
    if (porcentaje como decimal !== indefinido && porcentaje como decimal !== 0) {
      this.cache_.lastVolume = percentAsDecimal;
      devolver;
    }
    devuelve this.cache_.lastVolume;
  }

  /**
   * Comprobar si la tecnología actual admite pantalla completa nativa
   * (por ejemplo, con controles integrados como iOS)
   *
   * @return {booleano}
   * si se admite la pantalla completa nativa
   * /
  admite pantalla completa () {
    devuelve esto.techGet_('supportsFullScreen') || FALSO;
  }

  /**
   * Compruebe si el jugador está en modo de pantalla completa o dígale al jugador que
   * está o no está en modo de pantalla completa.
   *
   * > NOTA: A partir de la última especificación de HTML5, isFullscreen ya no es oficial
   * propiedad y en su lugar se utiliza document.fullscreenElement. Pero isFullscreen es
   * sigue siendo una propiedad valiosa para el funcionamiento interno del jugador.
   *
   * @param {booleano} [isFS]
   * Establecer el estado actual de pantalla completa de los jugadores
   *
   * @return {booleano}
   * - verdadero si la pantalla completa está activada y obteniendo
   * - falso si la pantalla completa está desactivada y se está poniendo
   * /
  esPantalla completa(isFS) {
    if (isFS !== indefinido) {
      const oldValue = this.isFullscreen_;

      this.isFullscreen_ = Boolean(isFS);

      // si cambiamos el estado de pantalla completa y estamos en modo prefijado, active el cambio de pantalla completa
      // este es el único lugar donde activamos eventos de cambio de pantalla completa para navegadores más antiguos
      // el modo de ventana completa se trata como un evento con prefijo y también obtendrá un evento de cambio de pantalla completa
      if (this.isFullscreen_ !== valorAntiguo && this.fsApi_.prefijado) {
        /**
           * @reproductor de eventos#cambio de pantalla completa
           * @type {Objetivo del evento~Evento}
           * /
        this.trigger('cambio de pantalla completa');
      }

      this.toggleFullscreenClass_();
      devolver;
    }
    devuelve esto.isFullscreen_;
  }

  /**
   * Aumentar el tamaño del video a pantalla completa
   * En algunos navegadores, la pantalla completa no es compatible de forma nativa, por lo que ingresa
   * &quot;modo de ventana completa&quot;, donde el video llena la ventana del navegador.
   * En navegadores y dispositivos que admiten pantalla completa nativa, a veces el
   * Se mostrarán los controles predeterminados del navegador y no el aspecto personalizado de Video.js.
   * Esto incluye la mayoría de los dispositivos móviles (iOS, Android) y versiones anteriores de
   * Safari.
   *
   * @param {Objeto} [opciones de pantalla completa]
   * Anular las opciones de pantalla completa del jugador
   *
   * @fires Player#cambio de pantalla completa
   * /
  solicitud de pantalla completa (opciones de pantalla completa) {
    const PromiseClass = this.options_.Promise || ventana.Promesa;

    si (PromesaClase) {
      const self = esto;

      return new PromiseClass((resolver, rechazar) => {
        función offHandler() {
          self.off('error de pantalla completa', manejador de errores);
          self.off('cambio de pantalla completa', controlador de cambios);
        }
        función changeHandler() {
          offHandler();
          resolver();
        }
        función manejador de errores (e, err) {
          offHandler();
          rechazar (error);
        }

        self.one('cambio de pantalla completa', changeHandler);
        self.one('error de pantalla completa', manejador de errores);

        const promesa = self.requestFullscreenHelper_(fullscreenOptions);

        si (promesa) {
          promesa.entonces(offHandler, offHandler);
          prometer.entonces(resolver, rechazar);
        }
      });
    }

    devolver esto.requestFullscreenHelper_();
  }

  requestFullscreenHelper_(opciones de pantalla completa) {
    dejar fsOptions;

    // Solo pase las opciones de pantalla completa a requestFullscreen en navegadores que cumplan con las especificaciones.
    // Use la opción predeterminada o configurada por el jugador a menos que se pase directamente a este método.
    if (!this.fsApi_.prefijado) {
      fsOptions = this.options_.pantalla completa && this.options_.fullscreen.options || {};
      if (opciones de pantalla completa! == indefinido) {
        fsOptions = opciones de pantalla completa;
      }
    }

    // Este método funciona de la siguiente manera:
    // 1. si hay una API de pantalla completa disponible, utilícela
    // 1. solicitud de llamada Pantalla completa con opciones potenciales
    // 2. si recibimos una promesa de arriba, utilícela para actualizar isFullscreen()
    // 2. De lo contrario, si la tecnología admite pantalla completa, llame a `enterFullScreen`.
    // Esto se usa particularmente para iPhone, iPads más antiguos y navegadores que no son safari en iOS.
    // 3. de lo contrario, use el modo &quot;ventana completa&quot;
    si (esto.fsApi_.requestFullscreen) {
      const promesa = this.el_[this.fsApi_.requestFullscreen](fsOptions);

      si (promesa) {
        promesa.entonces(() => this.isFullscreen(verdadero), () => this.isFullscreen(falso));
      }

      promesa de devolución;
    } else if (this.tech_.supportsFullScreen() && !this.options_.preferFullWindow === true) {
      // no podemos tomar los controles de video.js a pantalla completa pero podemos ir a pantalla completa
      // con controles nativos
      this.techCall_('ingresar a pantalla completa');
    } else {
      // la pantalla completa no es compatible, por lo que estiraremos el elemento de video para
      // llenar la ventana gráfica
      this.enterFullWindow();
    }
  }

  /**
   * Devolver el video a su tamaño normal luego de haber estado en modo pantalla completa
   *
   * @fires Player#cambio de pantalla completa
   * /
  salir de pantalla completa() {
    const PromiseClass = this.options_.Promise || ventana.Promesa;

    si (PromesaClase) {
      const self = esto;

      return new PromiseClass((resolver, rechazar) => {
        función offHandler() {
          self.off('error de pantalla completa', manejador de errores);
          self.off('cambio de pantalla completa', controlador de cambios);
        }
        función changeHandler() {
          offHandler();
          resolver();
        }
        función manejador de errores (e, err) {
          offHandler();
          rechazar (error);
        }

        self.one('cambio de pantalla completa', changeHandler);
        self.one('error de pantalla completa', manejador de errores);

        const promesa = self.exitFullscreenHelper_();

        si (promesa) {
          promesa.entonces(offHandler, offHandler);
          // asigna la promesa a nuestros métodos de resolución/rechazo
          prometer.entonces(resolver, rechazar);
        }
      });
    }

    devuelve esto.exitFullscreenHelper_();
  }

  exitFullscreenHelper_() {
    si (esto.fsApi_.requestFullscreen) {
      const promesa = documento[this.fsApi_.exitFullscreen]();

      si (promesa) {
        // estamos dividiendo la promesa aquí, así que queremos atrapar el
        // error potencial para que esta cadena no tenga errores no controlados
        silencioPromesa(promesa.entonces(() => this.isFullscreen(falso)));
      }

      promesa de devolución;
    } else if (this.tech_.supportsFullScreen() && !this.options_.preferFullWindow === true) {
      this.techCall_('salir de pantalla completa');
    } else {
      this.exitFullWindow();
    }
  }

  /**
   * Cuando no se admite la pantalla completa, podemos estirar la
   * contenedor de video tan ancho como el navegador nos lo permita.
   *
   * @fires Player#enterFullWindow
   * /
  ingresarVentanaCompleta() {
    this.isFullscreen(verdadero);
    this.isFullWindow = verdadero;

    // Almacenar el valor de desbordamiento del documento original para volver cuando la pantalla completa está desactivada
    this.docOrigOverflow = document.documentElement.style.overflow;

    // Agregar oyente para la tecla esc para salir de pantalla completa
    Events.on(document, 'keydown', this.boundFullWindowOnEscKey_);

    // Ocultar las barras de desplazamiento
    document.documentElement.style.overflow = 'oculto';

    // Aplicar estilos de pantalla completa
    Dom.addClass(document.body, 'vjs-full-window');

    /**
     * @reproductor de eventos#enterFullWindow
     * @type {Objetivo del evento~Evento}
     * /
    this.trigger('enterFullWindow');
  }

  /**
   * Verifique la llamada para salir de la ventana completa o
   * pantalla completa en la tecla ESC
   *
   * evento @param {cadena}
   * Evento para verificar si se presiona una tecla
   * /
  fullWindowOnEscKey(evento) {
    if (keycode.isEventKey(evento, 'Esc')) {
      if (this.isFullscreen() === verdadero) {
        si (!esta.esVentanaCompleta) {
          this.exitFullscreen();
        } else {
          this.exitFullWindow();
        }
      }
    }
  }

  /**
   * Salir de la ventana completa
   *
   * @fires Player#exitFullWindow
   * /
  salirVentanaCompleta() {
    this.isFullscreen(falso);
    this.isFullWindow = falso;
    Events.off(document, 'keydown', this.boundFullWindowOnEscKey_);

    // Mostrar barras de desplazamiento.
    document.documentElement.style.overflow = this.docOrigOverflow;

    // Eliminar estilos de pantalla completa
    Dom.removeClass(document.body, 'vjs-full-window');

    // Cambiar el tamaño de la caja, el controlador y el póster a los tamaños originales
    // this.positionAll();
    /**
     * @reproductor de eventos#exitFullWindow
     * @type {Objetivo del evento~Evento}
     * /
    this.trigger('exitFullWindow');
  }

  /**
   * Deshabilitar el modo de imagen en imagen.
   *
   * valor @param {booleano}
   * - true desactivará el modo Picture-in-Picture
   * - false habilitará el modo Picture-in-Picture
   * /
  deshabilitarImagenEnImagen(valor) {
    si (valor === indefinido) {
      devuelve esto.techGet_('disablePictureInPicture');
    }
    this.techCall_('setDisablePictureInPicture', valor);
    this.options_.disablePictureInPicture = valor;
    this.trigger('deshabilitar imagen en imagen cambiada');
  }

  /**
   * Verifique si el reproductor está en modo Picture-in-Picture o dígale al reproductor que
   * está o no está en el modo Picture-in-Picture.
   *
   * @param {booleano} [isPiP]
   * Establecer el estado actual de Picture-in-Picture de los jugadores
   *
   * @return {booleano}
   * - verdadero si Picture-in-Picture está activado y recibiendo
   * - falso si Picture-in-Picture está desactivado y
   * /
  esEnImagenEnImagen(isPiP) {
    if (esPiP !== indefinido) {
      this.isInPictureInPicture_ = !!isPiP;
      this.togglePictureInPictureClass_();
      devolver;
    }
    volver !!this.isInPictureInPicture_;
  }

  /**
   * Cree una ventana de video flotante siempre encima de otras ventanas para que los usuarios puedan
   * continuar 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}
   *
   * @fires Player#enterpictureinpicture
   *
   * @return {Promesa}
   * Una promesa con una ventana Picture-in-Picture.
   * /
  solicitudImagenEnImagen() {
    if ('imagenEnImagenHabilitada' en el documento && this.disablePictureInPicture() === false) {
      /**
       * Este evento se dispara cuando el jugador entra en el modo de imagen en imagen
       *
       * @reproductor de eventos#enterpictureinpicture
       * @type {Objetivo del evento~Evento}
       * /
      devuelve this.techGet_('requestPictureInPicture');
    }
  }

  /**
   * Salga del modo Imagen en imagen.
   *
   * @ver [Especificación]{@enlace https://wicg.github.io/picture-in-picture}
   *
   * @fires Player#dejarimagenenimagen
   *
   * @return {Promesa}
   * Una promesa.
   * /
  salirImagenEnImagen() {
    if ('imagenEnImagenHabilitada' en el documento) {
      /**
       * Este evento se activa cuando el jugador deja la imagen en modo imagen
       *
       * @reproductor de eventos#dejarimagenenimagen
       * @type {Objetivo del evento~Evento}
       * /
      volver documento.exitPictureInPicture();
    }
  }

  /**
   * Llamado cuando este jugador tiene el foco y se presiona una tecla, o cuando
   * cualquier Componente de este reproductor recibe una pulsación de tecla que no maneja.
   * Esto permite teclas de acceso rápido para todo el jugador (ya sea como se define a continuación, u opcionalmente
   * por una función externa).
   *
   * @param {EventTarget~Evento} evento
   * El evento `keydown` que hizo que se llamara a esta función.
   *
   * @escucha tecla abajo
   * /
  handleKeyDown(evento) {
    const {userActions} = this.options_;

    // Rescatar si las teclas de acceso rápido no están configuradas.
    if (! acciones de usuario || ! acciones de usuario. teclas rápidas) {
      devolver;
    }

    // Función que determina si excluir o no un elemento de
    // manejo de hotkeys.
    const excluirElemento = (el) => {
      const tagName = el.tagName.toLowerCase();

      // La primera y más sencilla prueba es para elementos `contenidos`.
      si (el.isContentEditable) {
        devolver verdadero;
      }

      // Las entradas que coincidan con estos tipos aún activarán el manejo de teclas rápidas como
      // no son entradas de texto.
      const tipos de entrada permitidos = [
        'botón',
        'caja',
        'oculto',
        'radio',
        'reiniciar',
        'entregar'
      ];

      if (etiquetaNombre === 'entrada') {
        return allowInputTypes.indexOf(el.type) === -1;
      }

      // La prueba final es por nombre de etiqueta. Estas etiquetas se excluirán por completo.
      const etiquetasexcluidas = ['textarea'];

      devuelve etiquetasexcluidas.indexOf(tagName) !== -1;
    };

    // Rescatar si el usuario se centra en un elemento de formulario interactivo.
    if (excludeElement(this.el_.ownerDocument.activeElement)) {
      devolver;
    }

    if (typeof userActions.hotkeys === 'función') {
      userActions.hotkeys.call(este, evento);
    } else {
      this.handleHotkeys(evento);
    }
  }

  /**
   * Llamado cuando este reproductor recibe un evento de tecla de acceso directo.
   * Las teclas de acceso rápido compatibles con todo el jugador son:
   *
   * f - alternar pantalla completa
   * m - alternar silencio
   * k o Espacio - alternar reproducción/pausa
   *
   * @param {EventTarget~Evento} evento
   * El evento `keydown` que hizo que se llamara a esta función.
   * /
  handleHotkeys(evento) {
    const hotkeys = this.options_.userActions ? this.options_.userActions.hotkeys: {};

    // establece fullscreenKey, muteKey, playPauseKey desde `hotkeys`, usa los valores predeterminados si no está configurado
    constante {
      tecla de pantalla completa = keydownEvent => keycode.isEventKey(keydownEvent, 'f'),
      muteKey = keydownEvent => keycode.isEventKey(keydownEvent, 'm'),
      playPauseKey = keydownEvent => (keycode.isEventKey(keydownEvent, 'k') || keycode.isEventKey(keydownEvent, 'Space'))
    } = teclas de acceso rápido;

    if (fullscreenKey.call(este, evento)) {
      event.preventDefault();
      event.stopPropagation();

      const FSToggle = Component.getComponent('FullscreenToggle');

      if (document[this.fsApi_.fullscreenEnabled] !== false) {
        FSToggle.prototype.handleClick.call(este, evento);
      }

    } else if (muteKey.call(este, evento)) {
      event.preventDefault();
      event.stopPropagation();

      const MuteToggle = Component.getComponent('MuteToggle');

      MuteToggle.prototype.handleClick.call(este, evento);

    } else if (playPauseKey.call(this, event)) {
      event.preventDefault();
      event.stopPropagation();

      const PlayToggle = Component.getComponent('PlayToggle');

      PlayToggle.prototype.handleClick.call(este, evento);
    }
  }

  /**
   * Comprueba si el jugador puede jugar un tipo de mimo dado
   *
   * @ver https://www.w3.org/TR/2011/WD-html5-20110113/video.html#dom-navigator-canplaytype
   *
   * @param {cadena} tipo
   * El tipo MIME a comprobar
   *
   * @return {cadena}
   * 'probablemente', 'tal vez' o '' (cadena vacía)
   * /
  canPlayType(tipo) {
    dejar puede;

    // Recorre cada tecnología de reproducción en el orden de las opciones
    for (sea i = 0, j = this.options_.techOrder; i < j.longitud; i++) {
      const techName = j[i];
      let tech = Tech.getTech(techName);

      // Admite el comportamiento anterior de los técnicos que se registran como componentes.
      // Eliminar una vez que se elimine ese comportamiento obsoleto.
      si (! tecnología) {
        tech = Component.getComponent(techName);
      }

      // Comprobar si la tecnología actual está definida antes de continuar
      si (! tecnología) {
        log.error(`La tecnología "${techName}" no está definida. Se omitió la verificación de soporte del navegador para esa tecnología.`);
        continuar;
      }

      // Comprobar si el navegador soporta esta tecnología
      si (tech.isSupported()) {
        can = tech.canPlayType(tipo);

        si puede) {
          lata de retorno;
        }
      }
    }

    devolver '';
  }

  /**
   * Seleccione la fuente según el pedido tecnológico o el pedido de la fuente
   * Utiliza la selección de orden de origen si `options.sourceOrder` es veraz. De lo contrario,
   * por defecto a la selección de orden de tecnología
   *
   * @param {Array} fuentes
   * Las fuentes de un recurso multimedia
   *
   * @return {Objeto|booleano}
   * Objeto de origen y orden técnico o falso
   * /
  seleccionarFuente(fuentes) {
    // Obtener solo las tecnologías especificadas en `techOrder` que existen y son compatibles con el
    // plataforma actual
    tecnicos constantes =
      this.options_.techOrder
        .map((nombreTecnológico) => {
          return [NombreTecnología, Tech.getTech(NombreTecnología)];
        })
        .filter(([nombreTecnología, tecnología]) => {
          // Comprobar si la tecnología actual está definida antes de continuar
          si (tecnología) {
            // Comprobar si el navegador soporta esta tecnología
            volver tech.isSupported();
          }

          log.error(`La tecnología "${techName}" no está definida. Se omitió la verificación de soporte del navegador para esa tecnología.`);
          falso retorno;
        });

    // Iterar sobre cada elemento `innerArray` una vez por elemento `outerArray` y ejecutar
    // `probador` con ambos. Si `tester` devuelve un valor no falso, salga temprano y regrese
    // ese valor.
    const findFirstPassingTechSourcePair = function(outerArray, innerArray, tester) {
      dejar encontrado;

      matrizexterna.algunos((elecciónexterna) => {
        return matrizInterior.algunos((ElecciónInterior) => {
          encontrado = probador(elecciónexterna, eleccióninterna);

          si se encuentra) {
            devolver verdadero;
          }
        });
      });

      devolución encontrada;
    };

    let foundSourceAndTech;
    volteo constante = (fn) => (a, b) => fn(b,a);
    buscador constante = ([techName, tech], fuente) => {
      if (tech.canPlaySource(fuente, this.options_[techName.toLowerCase()])) {
        return {fuente, tecnología: techName};
      }
    };

    // Dependiendo de la veracidad de `options.sourceOrder`, intercambiamos el orden de las tecnologías y las fuentes
    // para seleccionar de ellos en función de su prioridad.
    if (this.options_.sourceOrder) {
      // Ordenar primero la fuente
      foundSourceAndTech = findFirstPassingTechSourcePair(sources, techs, flip(finder));
    } else {
      // Ordenar primero en tecnología
      foundSourceAndTech = findFirstPassingTechSourcePair(tecnologías, fuentes, buscador);
    }

    volver encontradoSourceAndTech || FALSO;
  }

  /**
   * Ejecuta la configuración de la fuente y obtiene la lógica
   *
   * @param {Tech~SourceObject|Tech~SourceObject[]|cadena} [fuente]
   * Un SourceObject, una matriz de SourceObjects o una cadena de referencia
   * una URL a una fuente de medios. Es _muy recomendable_ que un objeto
   * o matriz de objetos se utiliza aquí, por lo que la selección de fuente
   * los algoritmos pueden tener en cuenta el `tipo`.
   *
   * Si no se proporciona, este método actúa como captador.
   * @param {booleano} esReintentar
   * Indica si se está llamando internamente como resultado de un reintento
   *
   * @return {cadena|indefinido}
   * Si falta el argumento `fuente`, devuelve la fuente actual
   * URL. De lo contrario, no devuelve nada o indefinido.
   * /
  handleSrc_(fuente, esReintentar) {
    // uso del captador
    if (tipo de fuente === 'indefinido') {
      devolver this.cache_.src || '';
    }

    // Restablece el comportamiento de reintento para la nueva fuente
    si (esto.resetRetryOnError_) {
      this.resetRetryOnError_();
    }

    // filtrar las fuentes inválidas y convertir nuestra fuente en
    // una matriz de objetos de origen
    const fuentes = filterSource(fuente);

    // si se pasó una fuente, entonces no es válida porque
    // se filtró a una matriz de longitud cero. entonces tenemos que
    // mostrar un error
    if (!fuentes.longitud) {
      this.setTimeout(función() {
        este.error({ código: 4, mensaje: this.options_.notSupportedMessage });
      }, 0);
      devolver;
    }

    // fuentes iniciales
    este.cambiandoSrc_ = verdadero;

    // Solo actualice la lista de fuentes en caché si no estamos volviendo a intentar una nueva fuente después de un error,
    // ya que en ese caso queremos incluir las fuentes fallidas en el caché
    si (! esReintentar) {
      this.cache_.sources = fuentes;
    }

    this.updateSourceCaches_(fuentes[0]);

    // middlewareSource es la fuente después de haber sido modificada por middleware
    middleware.setSource(esto, fuentes[0], (middlewareSource, mws) => {
      este.middleware_ = mws;

      // dado que sourceSet es asíncrono, tenemos que actualizar el caché nuevamente después de seleccionar una fuente ya que
      // la fuente que se selecciona podría estar fuera de servicio debido a la actualización de caché por encima de esta devolución de llamada.
      si (! esReintentar) {
        this.cache_.sources = fuentes;
      }

      this.updateSourceCaches_(middlewareSource);

      const err = this.src_(middlewareSource);

      si (err) {
        if (fuentes.longitud > 1) {
          devuelve this.handleSrc_(sources.slice(1));
        }

        este.cambiandoSrc_ = falso;

        // Necesitamos envolver esto en un tiempo de espera para darle a la gente la oportunidad de agregar controladores de eventos de error
        this.setTimeout(función() {
          este.error({ código: 4, mensaje: this.options_.notSupportedMessage });
        }, 0);

        // no pudimos encontrar una tecnología apropiada, pero aún notifiquemos al delegado que esto es todo
        // esto necesita un mejor comentario sobre por qué es necesario
        this.triggerReady();

        devolver;
      }

      middleware.setTech(mws, this.tech_);
    });

    // Pruebe con otra fuente disponible si esta falla antes de la reproducción.
    if (this.options_.retryOnError && fuentes.longitud > 1) {
      constante reintento = () => {
        // Eliminar el modal de error
        este.error(nulo);
        this.handleSrc_(sources.slice(1), true);
      };

      const dejar de escuchar errores = () => {
        this.off('error', reintentar);
      };

      this.one('error', reintentar);
      this.one('playing', stopListeningForErrors);

      this.resetRetryOnError_ = () => {
        this.off('error', reintentar);
        this.off('reproduciendo', dejar de escuchar errores);
      };
    }
  }

  /**
   * Obtener o establecer la fuente de video.
   *
   * @param {Tech~SourceObject|Tech~SourceObject[]|cadena} [fuente]
   * Un SourceObject, una matriz de SourceObjects o una cadena de referencia
   * una URL a una fuente de medios. Es _muy recomendable_ que un objeto
   * o matriz de objetos se utiliza aquí, por lo que la selección de fuente
   * los algoritmos pueden tener en cuenta el `tipo`.
   *
   * Si no se proporciona, este método actúa como captador.
   *
   * @return {cadena|indefinido}
   * Si falta el argumento `fuente`, devuelve la fuente actual
   * URL. De lo contrario, no devuelve nada o indefinido.
   * /
  src(fuente) {
    devuelve esto.handleSrc_(fuente, falso);
  }

  /**
   * Establecer el objeto de origen en la tecnología, devuelve un valor booleano que indica si
   * hay una tecnología que puede reproducir la fuente o no
   *
   * @param {Tech~SourceObject} fuente
   * El objeto de origen para establecer en el Tech
   *
   * @return {booleano}
   * - Verdadero si no hay tecnología para reproducir esta fuente
   * - Falso en caso contrario
   *
   * @privado
   * /
  src_(fuente) {
    const sourceTech = this.selectSource([fuente]);

    si (!fuenteTecnología) {
      devolver verdadero;
    }

    if (!titleCaseEquals(sourceTech.tech, this.techName_)) {
      este.cambiandoSrc_ = verdadero;
      // carga esta tecnología con la fuente elegida
      this.loadTech_(sourceTech.tech, sourceTech.source);
      esto.tech_.ready(() => {
        este.cambiandoSrc_ = falso;
      });
      falso retorno;
    }

    // esperar hasta que el técnico esté listo para establecer la fuente
    // y configurarlo sincrónicamente si es posible (#2326)
    esto.listo(función() {

      // El método tecnológico setSource se agregó con controladores de origen
      // para que los técnicos más antiguos no lo admitan
      // Necesitamos verificar el prototipo directo para el caso donde las subclases
      // de la tecnología no admite controladores de origen
      if (this.tech_.constructor.prototype.hasOwnProperty('setSource')) {
        this.techCall_('establecerFuente', fuente);
      } else {
        this.techCall_('src', source.src);
      }

      este.cambiandoSrc_ = falso;
    }, true);

    falso retorno;
  }

  /**
   * Comience a cargar los datos src.
   * /
  carga() {
    this.techCall_('cargar');
  }

  /**
   * Restablecer el reproductor. Carga la primera tecnología en el techOrder,
   * elimina todas las pistas de texto en la `tecnología` existente,
   * y llama a `reset` en `tech`.
   * /
  reiniciar() {
    const PromiseClass = this.options_.Promise || ventana.Promesa;

    if (this.paused() || !PromiseClass) {
      esto.doReset_();
    } else {
      const playPromise = this.play();

      silencioPromesa(reproducirPromesa.entonces(() => esto.doReset_()));
    }
  }

  hacerReset_() {
    si (esta.tecnología_) {
      this.tech_.clearTracks('texto');
    }
    esto.resetCache_();
    este.cartel('');
    this.loadTech_(this.options_.techOrder[0], nulo);
    this.techCall_('restablecer');
    esto.resetControlBarUI_();
    si (es un evento (esto)) {
      this.trigger('reinicio del jugador');
    }
  }

  /**
   * Reinicie la interfaz de usuario de la barra de control llamando a submétodos que reinician
   * todos los componentes de la barra de control
   * /
  resetControlBarUI_() {
    esto.resetProgressBar_();
    this.resetPlaybackRate_();
    this.resetVolumeBar_();
  }

  /**
   * Restablecer el progreso de la tecnología para que la barra de progreso se restablezca en la interfaz de usuario
   * /
  resetProgressBar_() {
    this.currentTime(0);

    const { visualización de duración, visualización de tiempo restante } = this.controlBar || {};

    if (visualización de duración) {
      duraciónDisplay.updateContent();
    }

    si (visualización de tiempo restante) {
      restanteTimeDisplay.updateContent();
    }
  }

  /**
   * Restablecer relación de reproducción
   * /
  resetPlaybackRate_() {
    this.playbackRate(this.defaultPlaybackRate());
    this.handleTechRateChange_();
  }

  /**
   * Restablecer barra de volumen
   * /
  resetVolumeBar_() {
    este.volumen(1.0);
    this.trigger('cambio de volumen');
  }

  /**
   * Devuelve todos los objetos de origen actuales.
   *
   * @return {Tecnología~ObjetoOrigen[]}
   * Los objetos fuente actuales
   * /
  fuentesactuales() {
    const fuente = this.currentSource();
    const fuentes = [];

    // asumir `{}` o `{ src }`
    if (Objeto.claves(origen).longitud !== 0) {
      fuentes.push(fuente);
    }

    devolver this.cache_.sources || fuentes;
  }

  /**
   * Devuelve el objeto fuente actual.
   *
   * @return {Tecnología~ObjetoOrigen}
   * El objeto fuente actual
   * /
  fuente actual() {
    devolver this.cache_.source || {};
  }

  /**
   * Devuelve la URL completa del valor de origen actual, por ejemplo, http://mysite.com/video.mp4
   * Se puede usar junto con `currentType` para ayudar a reconstruir el objeto de origen actual.
   *
   * @return {cadena}
   * La fuente actual
   * /
  fuenteActual() {
    devolver this.currentSource() && this.currentSource().src || '';
  }

  /**
   * Obtener el tipo de fuente actual, por ejemplo, video/mp4
   * Esto puede permitirle reconstruir el objeto de origen actual para que pueda cargar el mismo
   * fuente y tecnología más tarde
   *
   * @return {cadena}
   * El tipo MIME de origen
   * /
  tipoactual() {
    devolver this.currentSource() && this.currentSource().type || '';
  }

  /**
   * Obtener o establecer el atributo de precarga
   *
   * @param {booleano} [valor]
   * - verdadero significa que debemos precargar
   * - falso significa que no debemos precargar
   *
   * @return {cadena}
   * El valor del atributo de precarga al obtener
   * /
  precarga(valor) {
    si (valor! == indefinido) {
      this.techCall_('setPreload', valor);
      this.options_.preload = valor;
      devolver;
    }
    devuelve this.techGet_('preload');
  }

  /**
   * Obtener o configurar la opción de reproducción automática. Cuando esto es un valor booleano, lo hará
   * modificar el atributo en la tecnología. Cuando se trata de una cadena, el atributo en
   * la tecnología se eliminará y `Player` se encargará de la reproducción automática en los inicios de carga.
   *
   * @param {booleano|cadena} [valor]
   * - verdadero: reproducción automática usando el comportamiento del navegador
   * - falso: no reproducir automáticamente
   * - 'reproducir': llamar a reproducir () en cada inicio de carga
   * - 'silenciado': llama a muted() y luego a play() en cada inicio de carga
   * - 'cualquiera': llama a play() en cada inicio de carga. si eso falla, llama a muted() y luego a play().
   * - *: los valores que no sean los enumerados aquí se establecerán como &quot;reproducción automática&quot; en verdadero
   *
   * @return {booleano|cadena}
   * El valor actual de reproducción automática al obtener
   * /
  reproducción automática (valor) {
    // uso del captador
    si (valor === indefinido) {
      devolver esto.opciones_.reproducción automática || FALSO;
    }

    deja que techAutoplay;

    // si el valor es una cadena válida, ajústelo a eso, o normalice `true` a 'play', si es necesario
    if (tipo de valor === 'cadena' && (/(cualquiera|reproducir|silenciado)/).prueba(valor) || valor === cierto && this.options_.normalizeReproducción automática) {
      this.options_.autoplay = valor;
      this.manualAutoplay_(typeof value === 'string' ? value : 'play');
      techAutoplay = false;

    // cualquier valor falso establece la reproducción automática en falso en el navegador,
    // hagamos lo mismo
    } si no (! valor) {
      this.options_.autoplay = falso;

    // cualquier otro valor (es decir, verdadero) establece la reproducción automática en verdadero
    } else {
      this.options_.autoplay = true;
    }

    techAutoplay = tipo de techAutoplay === 'indefinido' ? this.options_.autoplay : techAutoplay;

    // si no tenemos un técnico entonces no hacemos cola
    // una llamada setAutoplay en tecnología lista. Hacemos esto porque el
    // la opción de reproducción automática se pasará en el constructor y nosotros
    // no es necesario configurarlo dos veces
    si (esta.tecnología_) {
      this.techCall_('setAutoplay', techAutoplay);
    }
  }

  /**
   * Establecer o deshabilitar el atributo playsinline.
   * Playsinline le dice al navegador que prefiere la reproducción que no sea a pantalla completa.
   *
   * @param {booleano} [valor]
   * - verdadero significa que deberíamos intentar jugar en línea por defecto
   * - falso significa que debemos usar el modo de reproducción predeterminado del navegador,
   * que en la mayoría de los casos está en línea. iOS Safari es una excepción notable
   * y se reproduce a pantalla completa de forma predeterminada.
   *
   * @return {cadena|Jugador}
   * - el valor actual de playsinline
   * - el reproductor al configurar
   *
   * @ver [Especificación]{@enlace https://html.spec.whatwg.org/#attr-video-playsinline}
   * /
  juegosenlinea(valor) {
    si (valor! == indefinido) {
      this.techCall_('setPlaysinline', value);
      this.options_.playsinline = valor;
      devolver esto;
    }
    devuelve this.techGet_('playsinline');
  }

  /**
   * Obtener o establecer el atributo de bucle en el elemento de video.
   *
   * @param {booleano} [valor]
   * - verdadero significa que debemos repetir el video
   * - falso significa que no debemos repetir el video
   *
   * @return {booleano}
   * El valor actual del bucle al obtener
   * /
  bucle (valor) {
    si (valor! == indefinido) {
      this.techCall_('setLoop', valor);
      this.options_.loop = valor;
      devolver;
    }
    devuelve esto.techGet_('bucle');
  }

  /**
   * Obtenga o configure la URL de origen de la imagen del póster
   *
   * @fires Player#posterchange
   *
   * @param {cadena} [origen]
   * URL de origen de la imagen del póster
   *
   * @return {cadena}
   * El valor actual del cartel al obtener
   * /
  cartel (origen) {
    si (origen === indefinido) {
      devolver este.poster_;
    }

    // La forma correcta de eliminar un póster es configurarlo como una cadena vacía
    // otros valores falsos arrojarán errores
    si (!origen) {
      origen = '';
    }

    if (src === este.poster_) {
      devolver;
    }

    // actualiza la variable del cartel interno
    este.poster_ = src;

    // actualiza el cartel de la tecnología
    this.techCall_('setPoster', src);

    this.isPosterFromTech_ = falso;

    // alerta a los componentes de que se ha configurado el cartel
    /**
     * Este evento se activa cuando se cambia la imagen del póster en el reproductor.
     *
     * @reproductor del evento#cambio de cartel
     * @type {Objetivo del evento~Evento}
     * /
    this.trigger('cambio de cartel');
  }

  /**
   * Algunas tecnologías (por ejemplo, YouTube) pueden proporcionar una fuente de póster en un
   * manera asíncrona. Queremos que el componente de póster use este
   * fuente del cartel para que cubra los controles de la tecnología.
   * (Botón de reproducción de YouTube). Sin embargo, solo queremos usar esto
   * fuente si el usuario del reproductor no ha configurado un póster a través de
   * las API normales.
   *
   * @fires Player#posterchange
   * @escucha Tech#posterchange
   * @privado
   * /
  handleTechPosterChange_() {
    if ((!this.poster_ || this.options_.techCanOverridePoster) && esta.tecnología_ && este.tech_.poster) {
      const nuevoPoster = this.tech_.poster() || '';

      if (nuevoPoster !== this.poster_) {
        this.poster_ = newPoster;
        this.isPosterFromTech_ = verdadero;

        // Informar a los componentes que el cartel ha cambiado
        this.trigger('cambio de cartel');
      }
    }
  }

  /**
   * Obtener o establecer si se muestran o no los controles.
   *
   * @fires Player#controlenabled
   *
   * @param {booleano} [booleano]
   * - true para activar los controles
   * - falso para apagar los controles
   *
   * @return {booleano}
   * El valor actual de los controles al obtener
   * /
  controles(bool) {
    si (bool === indefinido) {
      return !!this.controls_;
    }

    bool = !! bool;

    // No active un evento de cambio a menos que realmente haya cambiado
    if (this.controls_ === bool) {
      devolver;
    }

    esto.controles_ = bool;

    if (esto.usandoNativeControls()) {
      this.techCall_('setControls', bool);
    }

    if (esto.controles_) {
      this.removeClass('vjs-controls-disabled');
      this.addClass('vjs-controls-enabled');
      /**
       * @reproductor de eventos#controles habilitados
       * @type {Objetivo del evento~Evento}
       * /
      this.trigger('controles habilitados');
      if (!this.usingNativeControls()) {
        this.addTechControlsListeners_();
      }
    } else {
      this.removeClass('vjs-controls-enabled');
      this.addClass('vjs-controls-disabled');
      /**
       * @event Player#controlsdisabled
       * @type {Objetivo del evento~Evento}
       * /
      this.trigger('controles deshabilitados');
      if (!this.usingNativeControls()) {
        this.removeTechControlsListeners_();
      }
    }
  }

  /**
   * Activar/desactivar los controles nativos. Los controles nativos son los controles integrados en
   * dispositivos (por ejemplo, controles de iPhone predeterminados) u otras tecnologías
   * (por ejemplo, controles de Vimeo)
   * **Esto solo debe ser configurado por la tecnología actual, porque solo la tecnología sabe
   * si puede admitir controles nativos **
   *
   * @fires Player#usingnativecontrols
   * @dispara al jugador#usandocontrolespersonalizados
   *
   * @param {booleano} [booleano]
   * - true para activar los controles nativos
   * - falso para desactivar los controles nativos
   *
   * @return {booleano}
   * El valor actual de los controles nativos al obtener
   * /
  usandoNativeControls(bool) {
    si (bool === indefinido) {
      devuelve !!this.usingNativeControls_;
    }

    bool = !! bool;

    // No active un evento de cambio a menos que realmente haya cambiado
    if (this.usingNativeControls_ === bool) {
      devolver;
    }

    this.usingNativeControls_ = bool;

    if (esto.usandoNativeControls_) {
      this.addClass('vjs-using-native-controls');

      /**
       * el jugador está usando los controles nativos del dispositivo
       *
       * @reproductor de eventos#usandocontrolesnativos
       * @type {Objetivo del evento~Evento}
       * /
      this.trigger('usando controles nativos');
    } else {
      this.removeClass('vjs-using-native-controls');

      /**
       * el jugador está usando los controles HTML personalizados
       *
       * @reproductor de eventos#usandocontrolespersonalizados
       * @type {Objetivo del evento~Evento}
       * /
      this.trigger('usando controles personalizados');
    }
  }

  /**
   * Establecer u obtener el MediaError actual
   *
   * @fires Player#error
   *
   * @param {Error de Medios|cadena|número} [err]
   * Un MediaError o una cadena/número para convertir
   * en un MediaError
   *
   * @return {Error de Medios|null}
   * El MediaError actual al obtener (o nulo)
   * /
  error (err) {
    si (err === indefinido) {
      devolver este.error_ || nulo;
    }

    // permite que los ganchos modifiquen el objeto de error
    ganchos('antes del error').forEach((funcióngancho) => {
      const newErr = hookFunction(this, err);

      si (!(
        (esObjeto(nuevoErr) && !Array.isArray(nuevoErr)) ||
        typeof newErr === 'cadena' ||
        typeof newErr === 'número' ||
        nuevoErr === nulo
      )) {
        this.log.error('Por favor, devuelva un valor que MediaError espera en los ganchos beforeerror');
        devolver;
      }

      err = newErr;
    });

    // Suprimir el primer mensaje de error de fuente no compatible hasta
    // la interacción del usuario
    si (this.options_.suppressNotSupportedError &&
        errar && err.código === 4
    ) {
      const triggerSuppressedError = function() {
        este.error(err);
      };

      this.options_.suppressNotSupportedError = falso;
      this.any(['click', 'touchstart'], triggerSuppressedError);
      this.one('loadstart', function() {
        this.off(['click', 'touchstart'], triggerSuppressedError);
      });
      devolver;
    }

    // restaurar a los valores predeterminados
    si (err === nulo) {
      this.error_ = err;
      this.removeClass('vjs-error');
      if (esta.visualización de error) {
        this.errorDisplay.close();
      }
      devolver;
    }

    this.error_ = new MediaError(err);

    // agrega el nombre de clase vjs-error al jugador
    this.addClass('vjs-error');

    // registrar el nombre del tipo de error y cualquier mensaje
    // IE11 registra &quot;[objeto objeto]&quot; y requiere que expanda el mensaje para ver el objeto de error
    log.error(`(CÓDIGO:${this.error_.code} ${MediaError.errorTypes[this.error_.code]})`, this.error_.message, this.error_);

    /**
     * @event Player#error
     * @type {Objetivo del evento~Evento}
     * /
    this.trigger('error');

    // notifica a los ganchos del error por jugador
    ganchos('error').forEach((funcióngancho) => hookFunction(esto, esto.error_));

    devolver;
  }

  /**
   * Informar de la actividad del usuario
   *
   * evento @param {Objeto}
   * Objeto de evento
   * /
  reportUserActivity(evento) {
    this.userActivity_ = verdadero;
  }

  /**
   * Obtener/establecer si el usuario está activo
   *
   * @fires Jugador#usuarioactivo
   * @fires Player#userinactive
   *
   * @param {booleano} [booleano]
   * - verdadero si el usuario está activo
   * - falso si el usuario está inactivo
   *
   * @return {booleano}
   * El valor actual de userActive al obtener
   * /
  usuarioActivo(bool) {
    si (bool === indefinido) {
      devolver este.userActive_;
    }

    bool = !! bool;

    if (bool === this.userActive_) {
      devolver;
    }

    este.usuarioActivo_ = bool;

    si (este.usuarioActivo_) {
      this.userActivity_ = verdadero;
      this.removeClass('vjs-usuario-inactivo');
      this.addClass('vjs-usuario-activo');
      /**
       * @event Jugador#usuarioactivo
       * @type {Objetivo del evento~Evento}
       * /
      this.trigger('usuario activo');
      devolver;
    }

    // Chrome/Safari/IE tienen errores donde cuando cambias el cursor puede
    // activa un evento de movimiento del ratón. Esto causa un problema cuando te escondes.
    // el cursor cuando el usuario está inactivo, y un movimiento del mouse señala al usuario
    // actividad. Haciendo imposible pasar al modo inactivo. Específicamente
    // esto sucede en pantalla completa cuando realmente necesitamos ocultar el cursor.
    //
    // Cuando esto se resuelva en TODOS los navegadores, se puede eliminar
    // https://code.google.com/p/chromium/issues/detail?id=103041
    si (esta.tecnología_) {
      this.tech_.one('movimiento del ratón', function(e) {
        e.detener la propagación();
        e.preventDefault();
      });
    }

    this.userActivity_ = false;
    this.removeClass('vjs-user-active');
    this.addClass('vjs-usuario-inactivo');
    /**
     * @event Jugador#usuarioinactivo
     * @type {Objetivo del evento~Evento}
     * /
    this.trigger('usuarioinactivo');
  }

  /**
   * Escuche la actividad del usuario según el valor del tiempo de espera
   *
   * @privado
   * /
  escucharActividadDeUsuario_() {
    dejar mouseInProgress;
    let ultimoMovimientoX;
    let lastMoveY;
    const handleActivity = Fn.bind(this, this.reportUserActivity);

    const manejarMouseMouse = function(e) {
      // #1068 - Prevenir el spam de movimiento del mouse
      // Error de Chrome: https://code.google.com/p/chromium/issues/detail?id=366970
      if (e.screenX !== lastMoveX || e.screenY !== lastMoveY) {
        ultimaMovidaX = e.pantallaX;
        ultimoMovimientoY = e.pantallaY;
        manejarActividad();
      }
    };

    const manejarMouseDown = function() {
      manejarActividad();
      // 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(mouseInProgress);
      // Establecer userActivity=true ahora y establecer el intervalo al mismo tiempo
      // ya que el intervalo de verificación de actividad (250) debería garantizar que nunca perdamos el
      // próxima actividadComprobar
      mouseInProgress = this.setInterval(handleActivity, 250);
    };

    const handleMouseUpAndMouseLeave = función (evento) {
      manejarActividad();
      // Detener el intervalo que mantiene la actividad si el mouse/toque está presionado
      this.clearInterval(mouseInProgress);
    };

    // Cualquier movimiento del mouse se considerará actividad del usuario
    this.on('mousedown', handleMouseDown);
    this.on('movemouse', handleMouseMove);
    this.on('mouseup', handleMouseUpAndMouseLeave);
    this.on('mouseleave', handleMouseUpAndMouseLeave);

    const controlBar = this.getChild('controlBar');

    // Corrige el error en Android & iOS donde al tocar la barra de progreso (cuando se muestra la barra de control)
    // la barra de control ya no estaría oculta por el tiempo de espera predeterminado.
    si (barra de control && !navegador.IS_IOS && !navegador.IS_ANDROID) {

      controlBar.on('mouseenter', function(evento) {
        if (este.jugador().opciones_.inactividadTiempo de espera!== 0) {
          this.player().cache_.inactivityTimeout = this.player().options_.inactivityTimeout;
        }
        este.jugador().opciones_.inactividadTiempo de espera = 0;
      });

      controlBar.on('mouseleave', function(evento) {
        this.player().options_.inactivityTimeout = this.player().cache_.inactivityTimeout;
      });

    }

    // Escuche la navegación del teclado
    // No debería necesitar usar el intervalo inProgress debido a la repetición de la tecla
    this.on('keydown', handleActivity);
    this.on('keyup', handleActivity);

    // Ejecutar un intervalo cada 250 milisegundos en lugar de meter todo en
    // la función mousemove/touchmove en sí, para evitar la degradación del rendimiento.
    // `this.reportUserActivity` simplemente establece this.userActivity_ en verdadero, lo que
    // luego es recogido por este ciclo
    // http://ejohn.org/blog/aprendiendo-de-twitter/
    dejar inactividadTiempo de espera;

    este.setInterval(función() {
      // Comprobar para ver si ha ocurrido actividad de ratón/táctil
      if (!this.userActivity_) {
        devolver;
      }

      // Restablecer el rastreador de actividad
      this.userActivity_ = false;

      // Si el estado del usuario estaba inactivo, establezca el estado en activo
      este.usuarioActivo(verdadero);

      // Borrar cualquier tiempo de espera de inactividad existente para volver a iniciar el temporizador
      this.clearTimeout(inactividadTiempo de espera);

      const timeout = this.options_.inactivityTimeout;

      si (tiempo de espera < = 0) {
        devolver;
      }

      // En < se acabó el tiempo> milisegundos, si no ha ocurrido más actividad, el
      // el usuario será considerado inactivo
      inactividadTiempo de espera = this.setTimeout (función () {
        // Proteger contra el caso en el que inactiveTimeout puede desencadenar solo
        // antes de que el bucle de verificación de actividad detecte la siguiente actividad del usuario
        // causando un parpadeo
        if (!this.userActivity_) {
          este.usuarioActivo(falso);
        }
      }, timeout);

    }, 250);
  }

  /**
   * Obtiene o establece la velocidad de reproducción actual. Una tasa de reproducción de
   * 1,0 representa la velocidad normal y 0,5 indicaría la mitad de la velocidad
   * reproducción, por ejemplo.
   *
   * @ver https://html.spec.whatwg.org/multipage/embedded-content.html#dom-media-playbackrate
   *
   * @param {número} [tasa]
   * Nueva tasa de reproducción para establecer.
   *
   * @return {número}
   * La tasa de reproducción actual al obtener o 1.0
   * /
  tasa de reproducción (tasa) {
    if (tasa !== indefinido) {
      // NOTA: this.cache_.lastPlaybackRate se establece desde el controlador técnico
      // que esta registrado arriba
      this.techCall_('setPlaybackRate', rate);
      devolver;
    }

    si (esta.tecnología_ && this.tech_.featuresRate de reproducción) {
      devolver this.cache_.lastPlaybackRate || this.techGet_('tasa de reproducción');
    }
    volver 1.0;
  }

  /**
   * Obtiene o establece la velocidad de reproducción predeterminada actual. Una tasa de reproducción predeterminada de
   * 1,0 representa la velocidad normal y 0,5 indicaría reproducción a media velocidad, por ejemplo.
   * defaultPlaybackRate solo representará cuál era la tasa de reproducción inicial de un video, no
   * no es la tasa de reproducción actual.
   *
   * @ver https://html.spec.whatwg.org/multipage/embedded-content.html#dom-media-defaultplaybackrate
   *
   * @param {número} [tasa]
   * Nueva tasa de reproducción predeterminada para establecer.
   *
   * @return {número|Jugador}
   * - La velocidad de reproducción predeterminada al obtener o 1.0
   * - el reproductor al configurar
   * /
  tasa de reproducción predeterminada (tasa) {
    if (tasa !== indefinido) {
      devuelve this.techCall_('setDefaultPlaybackRate', rate);
    }

    si (esta.tecnología_ && this.tech_.featuresRate de reproducción) {
      devuelve this.techGet_('defaultPlaybackRate');
    }
    volver 1.0;
  }

  /**
   * Obtiene o establece la bandera de audio
   *
   * @param {booleano} booleano
   * - señales verdaderas de que se trata de un reproductor de audio
   * - señales falsas de que esto no es un reproductor de audio
   *
   * @return {booleano}
   * El valor actual de isAudio al obtener
   * /
  esAudio(bool) {
    si (booleano! == indefinido) {
      this.isAudio_ = !!bool;
      devolver;
    }

    volver !!this.isAudio_;
  }

  enableAudioOnlyUI_() {
    // Actualice el estilo inmediatamente para mostrar la barra de control para que podamos obtener su altura
    this.addClass('vjs-audio-only-mode');

    const jugadorNiños = esto.niños();
    const controlBar = this.getChild('ControlBar');
    const controlBarHeight = controlBar && controlBar.currentHeight();

    // Ocultar todos los componentes del reproductor excepto la barra de control. Componentes de la barra de control
    // necesarios solo para video están ocultos con CSS
    jugadorNiños.paraCada(niño => {
      if (hijo === barra de control) {
        devolver;
      }

      si (niño.el_ && !child.hasClass('vjs-hidden')) {
        niño.hide();

        this.audioOnlyCache_.hiddenChildren.push(child);
      }
    });

    this.audioOnlyCache_.playerHeight = this.currentHeight();

    // Establecer la altura del jugador igual que la barra de control
    this.height(controlBarHeight);
    this.trigger('audioonlymodechange');
  }

  deshabilitarAudioOnlyUI_() {
    this.removeClass('vjs-audio-only-mode');

    // Mostrar los componentes del reproductor que antes estaban ocultos
    this.audioOnlyCache_.hiddenChildren.forEach(child => niño.mostrar());

    // Restablecer la altura del jugador
    esta.altura(esta.audioOnlyCache_.playerHeight);
    this.trigger('audioonlymodechange');
  }

  /**
   * Obtenga el estado actual de audioOnlyMode o establezca audioOnlyMode en verdadero o falso.
   *
   * Establecer esto en `true` ocultará todos los componentes del reproductor excepto la barra de control,
   * así como los componentes de la barra de control necesarios solo para video.
   *
   * @param {booleano} [valor]
   * El valor para configurar audioOnlyMode.
   *
   * @return {Promesa|booleano}
   * Se devuelve una promesa al establecer el estado y un valor booleano al obtener
   * el estado actual
   * /
  AudioOnlyMode(valor) {
    if (typeof value !== 'boolean' || value === this.audioOnlyMode_) {
      devolver este.audioOnlyMode_;
    }

    this.audioOnlyMode_ = valor;

    const PromiseClass = this.options_.Promise || ventana.Promesa;

    si (PromesaClase) {
      // Habilitar el modo de solo audio
      si (valor) {
        const exitPromises = [];

        // Pantalla completa y PiP no son compatibles con audioOnlyMode, así que salga si es necesario.
        if (esto.esEnImagenEnImagen()) {
          exitPromises.push(this.exitPictureInPicture());
        }

        si (this.isFullscreen()) {
          exitPromises.push(this.exitFullscreen());
        }

        if (this.audioPosterMode()) {
          exitPromises.push(this.audioPosterMode(false));
        }

        return PromiseClass.all(exitPromises).then(() => this.enableAudioOnlyUI_());
      }

      // Deshabilitar el modo de solo audio
      devuelve PromiseClass.resolve().then(() => this.disableAudioOnlyUI_());
    }

    si (valor) {
      if (esto.esEnImagenEnImagen()) {
        this.exitPictureInPicture();
      }

      si (this.isFullscreen()) {
        this.exitFullscreen();
      }

      this.enableAudioOnlyUI_();
    } else {
      this.disableAudioOnlyUI_();
    }
  }

  enablePosterModeUI_() {
    // Oculte el elemento de video y muestre la imagen del póster para habilitar posterModeUI
    const tech = this.tech_ && esta.tecnología_;

    tech.hide();
    this.addClass('vjs-audio-poster-mode');
    this.trigger('cambio de modo de póster de audio');
  }

  desactivarPosterModeUI_() {
    // Mostrar el elemento de video y ocultar la imagen del póster para deshabilitar posterModeUI
    const tech = this.tech_ && esta.tecnología_;

    tech.show();
    this.removeClass('vjs-audio-poster-mode');
    this.trigger('cambio de modo de póster de audio');
  }

  /**
   * Obtenga el estado actual de audioPosterMode o establezca audioPosterMode en verdadero o falso
   *
   * @param {booleano} [valor]
   * El valor para configurar audioPosterMode.
   *
   * @return {Promesa|booleano}
   * Se devuelve una promesa al establecer el estado y un valor booleano al obtener
   * el estado actual
   * /
  audioPosterMode(valor) {

    if (typeof value !== 'boolean' || value === this.audioPosterMode_) {
      devolver este.audioPosterMode_;
    }

    this.audioPosterMode_ = valor;

    const PromiseClass = this.options_.Promise || ventana.Promesa;

    si (PromesaClase) {

      si (valor) {

        if (este.audioOnlyMode()) {
          const audioOnlyModePromise = this.audioOnlyMode(false);

          devolver audioOnlyModePromise.then(() => {
            // habilite el modo de póster de audio después de que el modo de solo audio esté deshabilitado
            this.enablePosterModeUI_();
          });
        }

        devuelve PromiseClass.resolve().then(() => {
          // habilitar el modo de cartel de audio
          this.enablePosterModeUI_();
        });
      }

      devuelve PromiseClass.resolve().then(() => {
        // deshabilitar el modo de cartel de audio
        this.disablePosterModeUI_();
      });
    }

    si (valor) {

      if (este.audioOnlyMode()) {
        este.audioOnlyMode(falso);
      }

      this.enablePosterModeUI_();
      devolver;
    }

    this.disablePosterModeUI_();
  }

  /**
   * Un método auxiliar para agregar un {@link TextTrack} a nuestro
   * {@enlace TextTrackList}.
   *
   * Además de la configuración de W3C, permitimos agregar información adicional a través de opciones.
   *
   * @ver http://www.w3.org/html/wg/drafts/html/master/embedded-content-0.html#dom-media-addtexttrack
   *
   * @param {cadena} [tipo]
   * el tipo de TextTrack que está agregando
   *
   * @param {cadena} [etiqueta]
   * la etiqueta para dar la etiqueta TextTrack
   *
   * @param {cadena} [idioma]
   * el idioma a configurar en el TextTrack
   *
   * @return {TextTrack|indefinido}
   * el TextTrack que se agregó o no se definió
   * si no hay tecnología
   * /
  addTextTrack(tipo, etiqueta, idioma) {
    si (esta.tecnología_) {
      devuelve this.tech_.addTextTrack(tipo, etiqueta, idioma);
    }
  }

  /**
   * Cree un {@link TextTrack} remoto y un {@link HTMLTrackElement}.
   * Cuando manualCleanup se establece en falso, la pista se eliminará automáticamente
   * sobre cambios de fuente.
   *
   * @param {Objeto} opciones
   * Opciones para pasar a {@link HTMLTrackElement} durante la creación. Ver
   * {@link HTMLTrackElement} para las propiedades del objeto que debe usar.
   *
   * @param {boolean} [manualCleanup=true] si se establece en false, TextTrack será
   * eliminado en un cambio de fuente
   *
   * @return {HtmlTrackElement}
   * el HTMLTrackElement que se creó y agregó
   * al HtmlTrackElementList y al control remoto
   * Lista de pistas de texto
   *
   * @deprecated El valor predeterminado del parámetro "manualCleanup" será predeterminado
   * a &quot;falso&quot; en las próximas versiones de Video.js
   * /
  addRemoteTextTrack(opciones, limpiezamanual) {
    si (esta.tecnología_) {
      devuelve this.tech_.addRemoteTextTrack(opciones, manualCleanup);
    }
  }

  /**
   * Eliminar un {@link TextTrack} remoto del respectivo
   * {@link TextTrackList} y {@link HtmlTrackElementList}.
   *
   * @param {Objeto} pista
   * Remoto {@link TextTrack} para eliminar
   *
   * @return {indefinido}
   * no devuelve nada
   * /
  removeRemoteTextTrack(obj = {}) {
    let {pista} = obj;

    si (!pista) {
      pista = obj;
    }

    // desestructurar la entrada en un objeto con un argumento de seguimiento, por defecto a arguments[0]
    // predeterminado todo el argumento a un objeto vacío si no se pasó nada en

    si (esta.tecnología_) {
      devuelve this.tech_.removeRemoteTextTrack(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|indefinido}
   * Un objeto con métricas de calidad de reproducción de medios admitidas o indefinido si hay
   * no es tecnología o la tecnología no es compatible.
   * /
  getVideoPlaybackQuality() {
    devuelve this.techGet_('getVideoPlaybackQuality');
  }

  /**
   * Obtener ancho de video
   *
   * @return {número}
   * ancho de video actual
   * /
  ancho de video() {
    devolver esto.tech_ && este.tech_.videoWidth && this.tech_.videoWidth() || 0;
  }

  /**
   * Obtener altura de video
   *
   * @return {número}
   * altura actual del video
   * /
  altura del video() {
    devolver esto.tech_ && esta.tecnología_.videoHeight && esta.tecnología_.videoHeight() || 0;
  }

  /**
   * El código de idioma del jugador.
   *
   * Cambiar el idioma activará
   * [cambio de idioma]{@link Player#event:cambio de idioma}
   * qué componentes pueden usar para actualizar el texto de control.
   * ClickableComponent actualizará su texto de control de forma predeterminada en
   * [cambio de idioma]{@link Player#event:cambio de idioma}.
   *
   * @fires Jugador#cambiodeidioma
   *
   * @param {cadena} [código]
   * el código de idioma para configurar el reproductor
   *
   * @return {cadena}
   * El código de idioma actual al obtener
   * /
  Código de lenguaje) {
    si (código === indefinido) {
      devuelve este.idioma_;
    }

    if (this.language_ !== String(code).toLowerCase()) {
      this.language_ = String(code).toLowerCase();

      // durante la primera inicialización, es posible que algunas cosas no se realicen
      si (es un evento (esto)) {
        /**
        * se activa cuando cambia el idioma del reproductor
        *
        * @event Player#cambio de idioma
        * @type {Objetivo del evento~Evento}
        * /
        this.trigger('cambio de idioma');
      }
    }
  }

  /**
   * Obtenga el diccionario de idioma del jugador
   * Combinar cada vez, porque un complemento recién agregado puede llamar a videojs.addLanguage() en cualquier momento
   * Los idiomas especificados directamente en las opciones del reproductor tienen prioridad
   *
   * @return {Array}
   * Una variedad de idiomas admitidos
   * /
  idiomas() {
    return mergeOptions(Player.prototype.options_.languages, this.languages_);
  }

  /**
   * devuelve un objeto JavaScript que representa la pista actual
   * información. **NO lo devuelve como JSON**
   *
   * @return {Objeto}
   * Objeto que representa la información actual de la pista
   * /
  aJSON() {
    const opciones = mergeOptions(this.options_);
    const pistas = opciones.pistas;

    opciones.pistas = [];

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

      // fusión profunda de pistas y anulación del reproductor para que no haya referencias circulares
      pista = mergeOptions(pista);
      pista.jugador = indefinido;
      opciones.pistas[i] = pista;
    }

    opciones de devolución;
  }

  /**
   * Crea un cuadro de diálogo modal simple (una instancia de {@link ModalDialog}
   * componente) que inmediatamente superpone al jugador con arbitrario
   * contenido y se elimina cuando se cierra.
   *
   * @param {cadena|Función|Elemento|Array|null} contenido
   * Igual que el parámetro del mismo nombre de {@link ModalDialog#content}.
   * El uso más sencillo es proporcionar una cadena o DOM
   * elemento.
   *
   * @param {Objeto} [opciones]
   * Opciones adicionales que se pasarán al {@link ModalDialog}.
   *
   * @return {ModalDialog}
   * el {@link ModalDialog} que se creó
   * /
  createModal(contenido, opciones) {
    opciones = opciones || {};
    opciones.contenido = contenido || '';

    const modal = new ModalDialog(esto, opciones);

    this.addChild(modal);
    modal.on('disponer', () => {
      this.removeChild(modal);
    });

    modal.open();
    retorno modal;
  }

  /**
   * Cambiar las clases de punto de interrupción cuando el jugador cambia de tamaño.
   *
   * @privado
   * /
  actualizarCurrentBreakpoint_() {
    if (!this.responsive()) {
      devolver;
    }

    const currentBreakpoint = this.currentBreakpoint();
    const currentWidth = this.currentWidth();

    para (sea i = 0; i < BREAKPOINT_ORDER.longitud; i++) {
      const candidatoBreakpoint = BREAKPOINT_ORDER[i];
      const maxWidth = this.breakpoints_[candidateBreakpoint];

      si (ancho actual < = ancho máximo) {

        // El punto de interrupción actual no cambió, no hay nada que hacer.
        if (punto de ruptura actual === punto de ruptura candidato) {
          devolver;
        }

        // Solo elimina una clase si hay un punto de interrupción actual.
        si (punto de interrupción actual) {
          this.removeClass(BREAKPOINT_CLASSES[currentBreakpoint]);
        }

        this.addClass(BREAKPOINT_CLASSES[candidateBreakpoint]);
        this.breakpoint_ = candidatoBreakpoint;
        romper;
      }
    }
  }

  /**
   * Elimina el punto de interrupción actual.
   *
   * @privado
   * /
  removeCurrentBreakpoint_() {
    const className = this.currentBreakpointClass();

    this.breakpoint_ = '';

    si (nombre de la clase) {
      this.removeClass(nombreClase);
    }
  }

  /**
   * Obtener o establecer puntos de interrupción en el reproductor.
   *
   * Llamar a este método con un objeto o `true` eliminará cualquier anterior
   * Puntos de interrupción personalizados y comenzar de nuevo desde los valores predeterminados.
   *
   * @param {Objeto|booleano} [puntos de interrupción]
   * Si se da un objeto, se puede utilizar para proporcionar personalizado
   * puntos de ruptura. Si se da `true`, establecerá puntos de interrupción predeterminados.
   * Si no se proporciona este argumento, simplemente devolverá el actual
   * puntos de ruptura.
   *
   * @param {número} [puntos de interrupción.pequeño]
   * El ancho máximo para la clase &quot;vjs-layout-tiny&quot;.
   *
   * @param {número} [puntos de interrupción.xsmall]
   * El ancho máximo para la clase &quot;vjs-layout-x-small&quot;.
   *
   * @param {número} [puntos de interrupción.pequeño]
   * El ancho máximo para la clase &quot;vjs-layout-small&quot;.
   *
   * @param {número} [puntos de interrupción.medio]
   * El ancho máximo para la clase &quot;vjs-layout-medium&quot;.
   *
   * @param {número} [puntos de interrupción.grande]
   * El ancho máximo para la clase &quot;vjs-layout-large&quot;.
   *
   * @param {número} [puntos de interrupción.xlarge]
   * El ancho máximo para la clase &quot;vjs-layout-x-large&quot;.
   *
   * @param {número} [puntos de ruptura.enormes]
   * El ancho máximo para la clase &quot;vjs-layout-huge&quot;.
   *
   * @return {Objeto}
   * Un objeto que asigna nombres de puntos de interrupción a valores de ancho máximo.
   * /
  puntos de interrupción (puntos de interrupción) {

    // Usado como captador.
    if (puntos de ruptura === indefinido) {
      volver asignar (this.breakpoints_);
    }

    this.breakpoint_ = '';
    this.breakpoints_ = asignar({}, DEFAULT_BREAKPOINTS, breakpoints);

    // Cuando cambian las definiciones de punto de interrupción, necesitamos actualizar el actual
    // punto de interrupción seleccionado.
    this.updateCurrentBreakpoint_();

    // Clona los puntos de interrupción antes de regresar.
    volver asignar (this.breakpoints_);
  }

  /**
   * Obtenga o establezca una bandera que indique si este jugador debe o no ajustar
   * su interfaz de usuario basada en sus dimensiones.
   *
   * valor @param {booleano}
   * Debería ser &quot;verdadero&quot; si el jugador debe ajustar su interfaz de usuario en función de su
   * dimensiones; de lo contrario, debería ser `falso`.
   *
   * @return {booleano}
   * Será &quot;verdadero&quot; si este jugador debe ajustar su interfaz de usuario en función de su
   * dimensiones; de lo contrario, será `falso`.
   * /
  sensible (valor) {

    // Usado como captador.
    si (valor === indefinido) {
      devuelve esto.responsive_;
    }

    valor = booleano(valor);
    constante actual = this.responsive_;

    // Nada ha cambiado.
    if (valor === actual) {
      devolver;
    }

    // El valor realmente cambió, configúrelo.
    this.responsive_ = valor;

    // Comenzar a escuchar los puntos de interrupción y establecer el punto de interrupción inicial si el
    // el reproductor ahora responde.
    si (valor) {
      this.on('playerresize', this.boundUpdateCurrentBreakpoint_);
      this.updateCurrentBreakpoint_();

    // Deja de escuchar los puntos de interrupción si el reproductor ya no responde.
    } else {
      this.off('playerresize', this.boundUpdateCurrentBreakpoint_);
      this.removeCurrentBreakpoint_();
    }

    valor de retorno;
  }

  /**
   * Obtenga el nombre del punto de interrupción actual, si corresponde.
   *
   * @return {cadena}
   * Si actualmente hay un punto de interrupción establecido, devuelve la clave del
   * Objeto de puntos de interrupción que lo empareja. De lo contrario, devuelve una cadena vacía.
   * /
  punto de interrupción actual () {
    devolver este.breakpoint_;
  }

  /**
   * Obtener el nombre de la clase de punto de interrupción actual.
   *
   * @return {cadena}
   * El nombre de la clase coincidente (por ejemplo, `&quot;vjs-layout-tiny&quot;` o
   * `&quot;vjs-layout-large&quot;`) para el punto de interrupción actual. Cadena vacía si
   * no hay ningún punto de interrupción actual.
   * /
  claseBreakpointActual() {
    devuelve BREAKPOINT_CLASSES[this.breakpoint_] || '';
  }

  /**
   * Un objeto que describe una sola pieza de medios.
   *
   * Se conservarán las propiedades que no formen parte de la descripción de este tipo; entonces,
   * esto también puede verse como un mecanismo genérico de almacenamiento de metadatos.
   *
   * @ver {@enlace https://wicg.github.io/mediasession/#the-mediametadata-interface}
   * @typedef {Objeto} Reproductor ~ MediaObject
   *
   * @propiedad {cadena} [álbum]
   * Sin usar, excepto si este objeto se pasa a `MediaSession`
   *API.
   *
   * @propiedad {cadena} [artista]
   * Sin usar, excepto si este objeto se pasa a `MediaSession`
   *API.
   *
   * @propiedad {Objeto[]} [obra de arte]
   * Sin usar, excepto si este objeto se pasa a `MediaSession`
   *API. Si no se especifica, se completará a través del `cartel`, si
   * disponible.
   *
   * @propiedad {cadena} [póster]
   * URL de una imagen que se mostrará antes de la reproducción.
   *
   * @property {Tech~SourceObject|Tech~SourceObject[]|string} [src]
   * Un solo objeto de origen, una matriz de objetos de origen o una cadena
   * hacer referencia a una URL a una fuente de medios. Es _muy recomendable_
   * que aquí se usa un objeto o una matriz de objetos, por lo que la fuente
   * los algoritmos de selección pueden tener en cuenta el `tipo`.
   *
   * @propiedad {cadena} [título]
   * Sin usar, excepto si este objeto se pasa a `MediaSession`
   *API.
   *
   * @propiedad {Objeto[]} [pistas de texto]
   * Una matriz de objetos que se utilizarán para crear pistas de texto, siguiendo
   * el {@enlace https://www.w3.org/TR/html50/embedded-content-0.html#the-track-element|formato de elemento de pista nativo}.
   * Para facilitar su eliminación, se crearán como texto &quot;remoto&quot;
   * rastrea y configura para limpiar automáticamente los cambios de fuente.
   *
   * Estos objetos pueden tener propiedades como `src`, `kind`, `label`,
   * y `idioma`, consulte {@link Tech#createRemoteTextTrack}.
   * /

  /**
   * Rellene el reproductor con {@link Player~MediaObject|MediaObject}.
   *
   * @param {Reproductor~MediaObject} medios
   * Un objeto mediático.
   *
   * @param {Función} lista
   * Una devolución de llamada para ser llamada cuando el jugador esté listo.
   * /
  loadMedia(medios, listo) {
    if (! medios || tipo de medios! == 'objeto') {
      devolver;
    }

    esto.reset();

    // Clonar el objeto de medios para que no se pueda mutar desde el exterior.
    this.cache_.media = mergeOptions(media);

    const {arte, poster, src, textTracks} = this.cache_.media;

    // Si no se proporciona `artwork`, créelo usando `poster`.
    si (! ilustraciones && cartel) {
      this.cache_.media.artwork = [{
        origen: cartel,
        tipo: getMimetype(póster)
      }];
    }

    si (fuente) {
      this.src(src);
    }

    si (cartel) {
      este.cartel(cartel);
    }

    if (Array.isArray(textTracks)) {
      Pistas de texto.forEach(tt => this.addRemoteTextTrack(tt, false));
    }

    esto.listo(listo);
  }

  /**
   * Obtén un clon del {@link Player~MediaObject} actual para este reproductor.
   *
   * Si no se ha utilizado el método `loadMedia`, intentará devolver un
   * {@link Player~MediaObject} según el estado actual del reproductor.
   *
   * @return {Reproductor~MediaObject}
   * /
  obtenerMedios() {
    if (!this.cache_.media) {
      const poster = this.poster();
      const src = this.currentSources();
      const textTracks = Array.prototype.map.call(this.remoteTextTracks(), (tt) => ({
        amable: tt. amable,
        etiqueta: tt.etiqueta,
        idioma: tt.idioma,
        src: tt.src
      }));

      const media = {origen, pistas de texto};

      si (cartel) {
        media.poster = cartel;
        medios.arte = [{
          src: media.poster,
          tipo: getMimetype(media.poster)
        }];
      }

      medios de retorno;
    }

    devuelve mergeOptions(this.cache_.media);
  }

  /**
   * Obtiene la configuración de la etiqueta
   *
   * Etiqueta @param {Elemento}
   * La etiqueta del jugador
   *
   * @return {Objeto}
   * Un objeto que contiene todas las configuraciones
   * para una etiqueta de jugador
   * /
  getTagSettings estáticos (etiqueta) {
    const baseOptions = {
      fuentes: [],
      pistas: []
    };

    const tagOptions = Dom.getAttributes(tag);
    const dataSetup = tagOptions['data-setup'];

    if (Dom.hasClass(etiqueta, 'vjs-fill')) {
      tagOptions.fill = verdadero;
    }
    if (Dom.hasClass(etiqueta, 'vjs-fluido')) {
      tagOptions.fluido = verdadero;
    }

    // Comprobar si existe el atributo de configuración de datos.
    si (configuración de datos! == nulo) {
      // Opciones de análisis JSON
      // Si es una cadena vacía, conviértala en un objeto json analizable.
      const [err, datos] = safeParseTuple(dataSetup || '{}');

      si (err) {
        registro.error(err);
      }
      asignar (opciones de etiqueta, datos);
    }

    asignar (opciones base, opciones de etiqueta);

    // Obtener la configuración de los hijos de la etiqueta
    si (etiqueta.hasChildNodes()) {
      const niños = etiqueta.childNodes;

      for (sea i = 0, j = niños.longitud; i < j; i++) {
        const hijo = hijos[i];
        // Cambiar mayúsculas y minúsculas: http://ejohn.org/blog/nodename-case-sensitivity/
        const childName = child.nodeName.toLowerCase();

        if (childName === 'fuente') {
          baseOptions.sources.push(Dom.getAttributes(child));
        } else if (childName === 'pista') {
          baseOptions.tracks.push(Dom.getAttributes(child));
        }
      }
    }

    volver opciones base;
  }

  /**
   * Determinar si flexbox es compatible o no
   *
   * @return {booleano}
   * - verdadero si se admite flexbox
   * - falso si flexbox no es compatible
   * /
  flexNotSupported_() {
    const elem = documento.createElement('i');

    // Nota: En realidad, no usamos flexBasis (o flexOrder), pero es uno de los más
    // características comunes de flexión en las que podemos confiar cuando comprobamos la compatibilidad con flexión.
    volver! ('base flexible' en elem.style ||
            'webkitFlexBasis' en elem.style ||
            'mozFlexBasis' en elem.style ||
            'msFlexBasis' en elem.style ||
            // Específico de IE10 (2012 flex spec), disponible para completar
            'msFlexOrder' en elem.estilo);
  }

  /**
   * Establecer el modo de depuración para habilitar/deshabilitar los registros a nivel de información.
   *
   * @param {booleano} habilitado
   * @fires jugador#debugon
   * @fires Player#debugoff
   * /
  depurar (habilitado) {
    si (habilitado === indefinido) {
      devolver esto.debugEnabled_;
    }
    si (habilitado) {
      this.trigger('debugon');
      this.previousLogLevel_ = this.log.level;
      this.log.level('depurar');
      this.debugEnabled_ = verdadero;
    } else {
      this.trigger('debugoff');
      este.log.level(this.anteriorLogLevel_);
      this.previousLogLevel_ = indefinido;
      this.debugEnabled_ = falso;
    }

  }

  /**
   * Establecer u obtener tasas de reproducción actuales.
   * Toma una matriz y actualiza el menú de tasas de reproducción con los nuevos elementos.
   * Pase una matriz vacía para ocultar el menú.
   * Los valores que no sean matrices se ignoran.
   *
   * @fires Player#playbackrateschange
   * @param {número[]} nuevasTasas
   * Las nuevas tasas a las que debería actualizarse el menú de tasas de reproducción.
   * Una matriz vacía ocultará el menú
   * @return {number[]} Cuando se usa como captador, devolverá las tasas de reproducción actuales
   * /
  tasas de reproducción (tasas nuevas) {
    if (nuevasTasas === indefinido) {
      devolver this.cache_.playbackRates;
    }

    // ignora cualquier valor que no sea una matriz
    if (!Array.isArray(nuevasTasas)) {
      devolver;
    }

    // ignora cualquier matriz que no solo contenga números
    if (!nuevasTarifas.cada((tarifa) => tipo de tasa === 'número')) {
      devolver;
    }

    this.cache_.playbackRates = newRates;

    /**
    * se dispara cuando se cambian las tasas de reproducción en un reproductor
    *
    * @event Jugador#cambio de tasas de reproducción
    * @type {Objetivo del evento~Evento}
    * /
    this.trigger('cambio de tasas de reproducción');
  }
}

/**
 * Obtener la {@link VideoTrackList}
 * @enlace https://html.spec.whatwg.org/multipage/embedded-content.html#videotracklist
 *
 * @return {Lista de pistas de vídeo}
 * la lista de pistas de video actual
 *
 * @método Player.prototype.videoTracks
 * /

/**
 * Obtener la {@link AudioTrackList}
 * @enlace https://html.spec.whatwg.org/multipage/embedded-content.html#audiotracklist
 *
 * @return {Lista de pistas de audio}
 * la lista de pistas de audio actual
 *
 * @método Player.prototype.audioTracks
 * /

/**
 * Obtén la {@link TextTrackList}
 *
 * @enlace http://www.w3.org/html/wg/drafts/html/master/embedded-content-0.html#dom-media-texttracks
 *
 * @return {TextTrackList}
 * la lista de pistas de texto actual
 *
 * @método Player.prototype.textTracks
 * /

/**
 * Obtener el {@link TextTrackList} remoto
 *
 * @return {TextTrackList}
 * La lista de pistas de texto remoto actual
 *
 * @método Player.prototype.remoteTextTracks
 * /

/**
 * Obtener las pistas remotas {@link HtmlTrackElementList}.
 *
 * @return {HtmlTrackElementList}
 * La lista de elementos de pista de texto remoto actual
 *
 * @método Player.prototype.remoteTextTrackEls
 * /

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

  Player.prototype[props.getterName] = function() {
    si (esta.tecnología_) {
      devuelve this.tech_[props.getterName]();
    }

    // si aún no tenemos loadTech_, creamos {video,audio,text}Tracks_
    // estos se pasarán al técnico durante la carga
    this[props.privateName] = this[props.privateName] || nuevos accesorios.ListClass();
    devolver este [props.privateName];
  };
});

/**
 * Obtener o establecer la opción de origen cruzado del `Jugador`. Para el reproductor HTML5, este
 * establece la propiedad `crossOrigin` en `< video> ` etiqueta para controlar el CORS
 * comportamiento.
 *
 * @ver [Atributos de elemento de video]{@enlace https://developer.mozilla.org/en-US/docs/Web/HTML/Element/video#attr-crossorigin}
 *
 * @param {cadena} [valor]
 * El valor para establecer el origen cruzado del `Jugador`. Si un argumento es
 * proporcionado, debe ser uno de `anonymous` o `use-credentials`.
 *
 * @return {cadena|indefinido}
 * - El valor de origen cruzado actual del `Jugador` al obtener.
 * - indefinido al configurar
 * /
Player.prototype.crossorigin = Player.prototype.crossOrigin;

/**
 * Enumeración global de jugadores.
 *
 * Las claves son los ID de los jugadores y los valores son {@link Player}
 * instancia o `null` para jugadores desechados.
 *
 * @type {Objeto}
 * /
Jugador.jugadores = {};

const navegador = ventana.navegador;

/*
 * Opciones de instancia de jugador, surgieron usando opciones
 * opciones = Jugador.prototipo.opciones_
 * Hacer cambios en opciones, no aquí.
 *
 * @type {Objeto}
 * @privado
 * /
Jugador.prototipo.opciones_ = {
  // Orden predeterminado de la tecnología de respaldo
  Orden técnico: Tech.defaultTechOrder_,

  html5: {},

  // tiempo de espera de inactividad predeterminado
  tiempo de inactividad: 2000,

  // tasas de reproducción predeterminadas
  tasas de reproducción: [],
  // Agregar selección de tasa de reproducción agregando tasas
  // 'tasas de reproducción': [0.5, 1, 1.5, 2],
  liveui: falso,

  // Conjuntos de control incluidos
  niños: [
    'cargador de medios',
    'imagen del cartel',
    'textTrackDisplay',
    'cargandoSpinner',
    'botón de reproducción grande',
    'rastreador en vivo',
    'barra de control',
    'visualización de errores',
    'Configuración de pista de texto',
    'resizeManager'
  ],

  idioma: navegador && (navegador.idiomas && navegador.idiomas[0] || navegador.idiomausuario || navegador.idioma) || 'en',

  // locales y sus traducciones de idiomas
  idiomas: {},

  // Mensaje predeterminado para mostrar cuando no se puede reproducir un video.
  Mensaje no admitido: 'No se encontró una fuente compatible para este medio.',

  normalizarReproducción automática: falso,

  pantalla completa: {
    opciones: {
      UI de navegación: 'ocultar'
    }
  },

  puntos de interrupción: {},
  responsivo: falso,
  AudioOnlyMode: falso,
  audioPosterMode: falso
};

[
  /**
   * Devuelve si el jugador está o no en el estado &quot;terminado&quot;.
   *
   * @return {Boolean} Verdadero si el jugador está en el estado finalizado, falso en caso contrario.
   * @método Player#finalizado
   * /
  'terminado',
  /**
   * Devuelve si el jugador está o no en el estado de &quot;búsqueda&quot;.
   *
   * @return {Boolean} Verdadero si el jugador está en el estado de búsqueda, falso si no.
   * @método Jugador#buscando
   * /
  'buscando',
  /**
   * Devuelve los TimeRanges de los medios que están disponibles actualmente
   * por querer.
   *
   * @return {TimeRanges} los intervalos buscables de la línea de tiempo de los medios
   * @método Player#buscable
   * /
  'buscable',
  /**
   * Devuelve el estado actual de la actividad de la red para el elemento, desde
   * los códigos en la lista a continuación.
   * - NETWORK_EMPTY (valor numérico 0)
   * El elemento aún no ha sido inicializado. Todos los atributos están en
   * sus estados iniciales.
   * - NETWORK_IDLE (valor numérico 1)
   * El algoritmo de selección de recursos del elemento está activo y tiene
   * seleccionó un recurso, pero en realidad no está usando la red en
   * esta vez.
   * - NETWORK_LOADING (valor numérico 2)
   * El agente de usuario está tratando activamente de descargar datos.
   * - NETWORK_NO_SOURCE (valor numérico 3)
   * El algoritmo de selección de recursos del elemento está activo, pero tiene
   * aún no se ha encontrado un recurso para usar.
   *
   * @ver https://html.spec.whatwg.org/multipage/embedded-content.html#network-states
   * @return {number} el estado actual de la actividad de la red
   * @método jugador#estado de la red
   * /
  'estado de la red',
  /**
   * Devuelve un valor que expresa el estado actual del elemento
   * con respecto a la representación de la posición de reproducción actual, desde el
   * códigos en la lista a continuación.
   * - NO TENER_NADA (valor numérico 0)
   * No hay información disponible sobre el recurso de los medios.
   * - TENER_METADATA (valor numérico 1)
   * Se ha obtenido suficiente del recurso que la duración de la
   * el recurso está disponible.
   * - TENER_DATOS_ACTUALES (valor numérico 2)
   * Los datos para la posición de reproducción actual inmediata están disponibles.
   * - TENER_DATOS_FUTUROS (valor numérico 3)
   * Los datos para la posición de reproducción actual inmediata están disponibles, como
   * así como datos suficientes para que el agente de usuario avance el actual
   * posición de reproducción en la dirección de reproducción.
   * - TENER_SUFICIENTE_DATOS (valor numérico 4)
   * El agente de usuario estima que hay suficientes datos disponibles para
   * reproducción para continuar sin interrupciones.
   *
   * @ver https://html.spec.whatwg.org/multipage/embedded-content.html#dom-media-readystate
   * @return {number} el estado actual de reproducción de reproducción
   * @method Player#readyState
   * /
  'estadolisto'
].forEach(función(fn) {
  Jugador.prototipo[fn] = función() {
    devolver esto.techGet_(fn);
  };
});

TECH_EVENTS_RETRIGGER.forEach(función(evento) {
  Player.prototype[`handleTech${toTitleCase(evento)}_`] = función() {
    devuelve este disparador (evento);
  };
});

/**
 * Activado cuando el jugador tiene información inicial de duración y dimensión
 *
 * @reproductor de eventos#metadatos cargados
 * @type {Objetivo del evento~Evento}
 * /

/**
 * Activado cuando el reproductor ha descargado datos en la posición de reproducción actual
 *
 * @reproductor del evento#datos cargados
 * @type {Objetivo del evento~Evento}
 * /

/**
 * Se dispara cuando la posición de reproducción actual ha cambiado *
 * Durante la reproducción, se dispara cada 15-250 milisegundos, según el
 * tecnología de reproducción en uso.
 *
 * @event Player#timeupdate
 * @type {Objetivo del evento~Evento}
 * /

/**
 * Disparado cuando cambia el volumen
 *
 * @reproductor de eventos#cambio de volumen
 * @type {Objetivo del evento~Evento}
 * /

/**
 * Informa si un jugador tiene o no un complemento disponible.
 *
 * Esto no informa si el complemento se ha inicializado alguna vez o no.
 * en este reproductor. Para eso, [usingPlugin]{@link Player#usingPlugin}.
 *
 * @método Player#hasPlugin
 * @param {cadena} nombre
 * El nombre de un complemento.
 *
 * @return {booleano}
 * Si este reproductor tiene disponible o no el complemento solicitado.
 * /

/**
 * Informa si un jugador está usando o no un complemento por nombre.
 *
 * Para complementos básicos, esto solo informa si el complemento ha sido _alguna vez_
 * inicializado en este reproductor.
 *
 * @método Player#usingPlugin
 * @param {cadena} nombre
 * El nombre de un complemento.
 *
 * @return {booleano}
 * Si este reproductor está utilizando o no el complemento solicitado.
 * /

Component.registerComponent('Jugador', Jugador);
exportar reproductor predeterminado;