/**
 * @file player.js
 * /
//Komponente „Unterklassen“
importiere Komponente aus './component.js';

importiere {version} von '../../package.json';
dokument aus 'global/document' importieren;
import window from 'global/window';
importiere evented von './mixins/evented';
importiere {isEvented, addEventedCallback} aus '. /mixins/event ';
import * as Events aus './utils/events.js';
importiere * as Dom aus './utils/dom.js';
import * as Fn from './utils/fn.js';
importiere * als Guid aus './utils/guid.js';
importiere * as browser aus './utils/browser.js';
importiere {IE_VERSION, IS_CHROME, IS_WINDOWS} aus '. /utils/browser.js ';
import log, { createLogger } from './utils/log.js';
importiere {toTitleCase, TitleCaseEquals} aus '. /utils/string-cases.js ';
importiere {createTimeRange} aus '. /utils/time-ranges.js ';
importiere {bufferedPercent} aus '. /utils/buffer.js ';
importiere * als Stylesheet aus './utils/stylesheet.js';
importiere FullScreenAPI aus '. /fullscreen-api.js ';
importiere MediaError aus '. /media-error.js ';
importiere SafeParseTuple aus 'safe-json-parse/tuple';
importiere {assign} von './utils/obj';
importiere mergeOptions aus './utils/merge-options.js';
importiere {silencePromise, isPromise} aus '. /utils/promise ';
importiere TextTrackConverter aus '. /tracks/text-track-list-converter.js ';
import ModalDialog from './modal-dialog';
importiere Tech aus './tech/tech.js';
importiere * als Middleware aus '. /tech/middleware.js ';
importiere {ALL as TRACK_TYPES} aus '. /tracks/track-Typen ';
importiere FilterSource aus '. /utils/filter-source ';
importiere {getMimeType, findMimeType} aus '. /utils/mimetypes ';
importiere {hooks} aus '. /utils/hooks ';
importiere {isObject} von './utils/obj';
import keycode from 'keycode';

//Die folgenden Importe werden nur verwendet, um sicherzustellen, dass die entsprechenden Module
//sind immer im Paket video.js enthalten. Das Importieren der Module wird
//führe sie aus und sie registrieren sich bei video.js.
importieren '. /tech/loader.js ';
importieren '. /poster-image.js ';
importieren '. /tracks/text-track-display.js ';
importieren '. /loading-spinner.js ';
importieren '. /big-play-button.js ';
importieren '. /close-button.js ';
importieren '. /control-bar/control-bar.js ';
importieren '. /error-display.js ';
importieren '. /tracks/text-track-settings.js ';
importieren '. /resize-manager.js ';
importieren '. /live-tracker.js ';

//Importiere Html5-Technologie, zumindest um das ursprüngliche Video-Tag zu löschen.
importieren '. /tech/html5.js ';

//Die folgenden Tech-Events werden einfach erneut ausgelöst
//auf den Spieler, wenn sie passieren
const TECH_EVENTS_RETRIGGER = [
  /**
   * Wird ausgelöst, während der Benutzeragent Mediendaten herunterlädt.
   *
   * @event Spieler #progress
   * @Typ {EventTarget~Event}
   * /
  /**
   * Das `Progress`-Ereignis, das vom {@link Tech} ausgelöst wurde, erneut auslösen.
   *
   * @privat
   * @method Spieler #handleTechProgress_
   * @fires Spieler #progress
   * @listens Technik #progress
   * /
  fortschritt",

  /**
   * Wird ausgelöst, wenn das Laden eines Audio/Videos abgebrochen wird.
   *
   * @event Spieler #abort
   * @Typ {EventTarget~Event}
   * /
  /**
   * Das `abort`-Ereignis, das vom {@link Tech} ausgelöst wurde, erneut auslösen.
   *
   * @privat
   * @method Spieler #handleTechAbort_
   * @fires Spieler #abort
   * @listens Technik #abort
   * /
  abbruch",

  /**
   * Wird ausgelöst, wenn der Browser absichtlich keine Mediendaten abruft.
   *
   * @event Spieler #suspend
   * @Typ {EventTarget~Event}
   * /
  /**
   * Das `suspend`-Ereignis, das vom {@link Tech} ausgelöst wurde, erneut auslösen.
   *
   * @privat
   * @method Spieler #handleTechSuspend_
   * @fires Spieler #suspend
   * @listens Technik #suspend
   * /
  aussetzen',

  /**
   * Wird ausgelöst, wenn die aktuelle Playlist leer ist.
   *
   * @event Spieler #emptied
   * @Typ {EventTarget~Event}
   * /
  /**
   * Löse das `emptied`-Ereignis erneut aus, das vom {@link Tech} ausgelöst wurde.
   *
   * @privat
   * @method Spieler #handleTechEmptied_
   * @fires Spieler #emptied
   * @listens Technik #emptied
   * /
  geleert",
  /**
   * Wird ausgelöst, wenn der Browser versucht, Mediendaten abzurufen, Daten jedoch nicht verfügbar sind.
   *
   * @event Spieler #stalled
   * @Typ {EventTarget~Event}
   * /
  /**
   * Das `stalled`-Ereignis, das vom {@link Tech} ausgelöst wurde, erneut auslösen.
   *
   * @privat
   * @method Spieler #handleTechStalled_
   * @fires Spieler #stalled
   * @listens Technik #stalled
   * /
  abgewürgt",

  /**
   * Wird ausgelöst, wenn der Browser Metadaten für das Audio/Video geladen hat.
   *
   * @Ereignis Player#geladenMetadaten
   * @Typ {EventTarget~Event}
   * /
  /**
   * Das `loadedmetadata`-Ereignis, das vom {@link Tech} ausgelöst wurde, erneut auslösen.
   *
   * @privat
   * @method Spieler #handleTechLoadedmetadata_
   * @fires Spieler #loadedmetadata
   * @listens Tech#loadedmetadata
   * /
  'loadedmetadata',

  /**
   * Wird ausgelöst, wenn der Browser den aktuellen Frame des Audio/Videos geladen hat.
   *
   * @event Player#loadeddata
   * @Typ {Ereignis}
   * /
  /**
   * Das `loadeddata`-Ereignis, das vom {@link Tech} ausgelöst wurde, erneut auslösen.
   *
   * @privat
   * @method Spieler #handleTechLoaddeddata_
   * @fires Spieler #loadeddata
   * @listens Technik #loadeddata
   * /
  'Geladene Daten',

  /**
   * Wird ausgelöst, wenn sich die aktuelle Wiedergabeposition geändert hat.
   *
   * @event Player#timeupdate
   * @Typ {Ereignis}
   * /
  /**
   * Das `timeupdate`-Ereignis, das vom {@link Tech} ausgelöst wurde, erneut auslösen.
   *
   * @privat
   * @method Spieler #handleTechTimeUpdate_
   * @fires Spieler #timeupdate
   * @listens Technik #timeupdate
   * /
  timeupdate',

  /**
   * Wird ausgelöst, wenn sich die intrinsischen Abmessungen des Videos ändern
   *
   * @event Spieler #resize
   * @Typ {Ereignis}
   * /
  /**
   * Das `resize`-Ereignis, das vom {@link Tech} ausgelöst wurde, erneut auslösen.
   *
   * @privat
   * @method Spieler #handleTechResize_
   * @fires Spieler #resize
   * @listens Technik #resize
   * /
  größe ändern",

  /**
   * Wird ausgelöst, wenn die Lautstärke geändert wurde
   *
   * @event Player#Lautstärkeänderung
   * @Typ {Ereignis}
   * /
  /**
   * Das `volumechange`-Ereignis, das vom {@link Tech} ausgelöst wurde, erneut auslösen.
   *
   * @privat
   * @method Spieler #handleTechVolumechange_
   * @fires Spieler #volumechange
   * @listens Technik #volumechange
   * /
  'Volumenänderung',

  /**
   * Wird ausgelöst, wenn der Texttrack geändert wurde
   *
   * @event Spieler #texttrackchange
   * @Typ {Ereignis}
   * /
  /**
   * Das `texttrackchange`-Ereignis, das vom {@link Tech} ausgelöst wurde, erneut auslösen.
   *
   * @privat
   * @method Spieler #handleTechTexttrackchange_
   * @fires Spieler #texttrackchange
   * @listens Technik #texttrackchange
   * /
  'Texttrack-Änderung'
];

//Ereignisse, die in die Warteschlange gestellt werden, wenn die Wiedergabegeschwindigkeit Null ist
//Dies ist ein Hash für den alleinigen Zweck der Zuordnung von Ereignisnamen ohne Kamele
//zu Funktionsnamen in Kamele
const TECH_EVENTS_QUEUE = {
  kann spielen: 'Kann spielen',
  kann durchspielen: 'Kann durchspielen',
  spielend: 'Spielen',
  gesucht: 'Gesucht'
};

const BREAKPOINT_ORDER = [
  'winzig',
  'klein',
  'klein',
  'mittel',
  'groß',
  'groß',
  'riesig'
];

const BREAKPOINT_CLASSES = {};

//grep: vjs-layout-tiny
//grep: vjs-layout-x-small
//grep: vjs-layout-small
//grep: vjs-layout-medium
//grep: vjs-layout-large
//grep: vjs-layout-x-large
//grep: vjs-layout-huge
breakPoint_Order.forEach (k) => {
  const v = k.charat (0) === 'x'? `x-$ {k.substring (1)} `: k;

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

const DEFAULT_BREAKPOINTS = {
  winzig: 210,
  klein: 320,
  klein: 425,
  mittel: 768,
  groß: 1440,
  xgroß: 2560,
  riesig: Unendlichkeit
};

/**
 * Eine Instanz der Klasse `Player` wird erstellt, wenn eine der Methoden von Video.js eingerichtet wird
 * werden verwendet, um ein Video zu initialisieren.
 *
 * Nachdem eine Instanz erstellt wurde, kann auf zwei Arten global darauf zugegriffen werden:
 * 1. Durch das Aufrufen von `videojs ('example_video_1'); `
 * 2. Indem du es direkt über `videojs.players.example_video_1; `benutzt
 *
 * @erweitert Komponente
 * /
class Player erweitert Komponente {

  /**
   * Erstellen Sie eine Instanz dieser Klasse.
   *
   * @param {Element} tag
   * Das ursprüngliche Video-DOM-Element, das für die Konfiguration von Optionen verwendet wurde.
   *
   * @param {Object} [Optionen]
   * Objekt der Optionsnamen und -werte.
   *
   * @param {Component~ReadyCallback} [ready]
   * Bereite Rückruffunktion.
   * /
  Konstruktor (Tag, Optionen, bereit) {
    //Stellen Sie sicher, dass die Tag-ID existiert
    tag.id = tag.id || options.id || `vjs_video_$ {guid.newGUID ()} `;

    //Optionen festlegen
    //Das Optionsargument überschreibt die im Video-Tag gesetzten Optionen
    //was global gesetzte Optionen überschreibt.
    //Dieser letzte Teil stimmt mit der Ladereihenfolge überein
    //(Tag muss vor Player existieren)
    Optionen = assign (player.getTagSettings (Tag), Optionen);

    //Verzögern Sie die Initialisierung von Kindern, weil wir etwas einrichten müssen
    //Spielereigenschaften zuerst und kann `this` nicht vor `super () `verwenden
    options.initChildren = falsch;

    //Das Gleiche gilt für das Erstellen des Elements
    options.createEl = falsch;

    //Mischt das Event-Mixing nicht automatisch
    options.evented = falsch;

    //wir wollen nicht, dass der Spieler Berührungsaktivitäten an sich selbst meldet
    //siehe enableTouchActivity in der Komponente
    options.reportTouchActivity = false;

    //Wenn die Sprache nicht gesetzt ist, hol dir das nächstgelegene lang-Attribut
    wenn (! optionen.sprache) {
      if (typeof tag.closest === 'Funktion') {
        const closest = tag.closest ('[lang]');

        if (close && closest.getAttribute) {
          options.language = closest.getAttribute ('lang');
        }
      } sonst {
        sei Element = Tag;

        während (element && element.nodeType === 1) {
          wenn (dom.getAttributes (element) .hasOwnProperty ('lang')) {
            options.language = element.getAttribute ('lang');
            pause;
          }
          element = element.ParentNode;
        }
      }
    }

    //Initialisierung der Basiskomponente mit neuen Optionen ausführen
    super(null, options, ready);

    //Erstelle gebundene Methoden für Dokument-Listener.
    this.boundDocumentFullScreenChange_ = (e) => this.documentFullScreenChange_ (e);
    this.boundFullWindowOnescKey_ = (e) => this.fullWindowOnescKey (e);

    this.boundUpdateStyleel_ = (e) => this.updateStyleL_ (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) => this.HandletTechTap_ (e);

    //Die Standardeinstellung ist FullScreen_ auf false
    this.isFullScreen_ = falsch;

    //Logger erstellen
    this.log = createLogger (this.id_);

    //Behalte unseren eigenen Verweis auf die Vollbild-API, damit sie in Tests verspottet werden kann
    this.fsApi_ = VollbildAPI;

    //Verfolgt, wann ein Techniker das Poster ändert
    this.isPosterFromTech_ = false;

    //Enthält Rückrufinformationen, die in die Warteschlange gestellt werden, wenn die Wiedergabegeschwindigkeit Null ist
    //und eine Suche findet statt
    this.queuedCallbacks_ = [];

    // Schalten Sie den API-Zugriff aus, da wir eine neue Technologie laden, die möglicherweise asynchron geladen wird
    this.isReady_ = false;

    //Initialstatus HasStarted_
    this.hasStarted_ = false;

    //Initialisiert den Status UserActive_
    this.userActive_ = falsch;

    //DebugEnabled_ initiieren
    this.debugEnabled_ = false;

    //Initialstatus AudioOnlyMode_
    this.AudioOnlyMode_ = falsch;

    //Initialstatus AudioPosterMode_
    this.AudioPosterMode_ = falsch;

    //Initialstatus AudioOnlyCache_
    this.audioOnlyCache_ = {
      Spielerhöhe: null,
      Versteckte Kinder: []
    };

    //wenn das globale Optionsobjekt versehentlich weggeblasen wurde von
    //jemand, mit einem informativen Fehler vorzeitig aussteigen
    wenn (! diese.optionen_ |
        ! this.options_.TechOrder ||
        ! this.options_.techOrder.length) {
      löst einen neuen Fehler aus ('Es wurde kein TechOrder angegeben. Hast du '+ überschrieben?
                      'videojs.options statt nur das '+ zu ändern
                      „Eigenschaften, die Sie überschreiben möchten? ');
    }

    //Speichert das ursprüngliche Tag, das zum Festlegen von Optionen verwendet wurde
    this.tag = Tag;

    //Speichert die Tag-Attribute, die zum Wiederherstellen des HTML5-Elements verwendet wurden
    this.tagAttributes = tag && dom.getAttributes (Tag);

    //Aktuelle Sprache aktualisieren
    this.language (this.options_.language);

    //Unterstützte Sprachen aktualisieren
    wenn (Options.Sprachen) {
      //Normalisiere die Sprachen der Player-Optionen auf Kleinbuchstaben
      const languageStoLower = {};

      object.getOwnPropertyNames (options.languages) .forEach (Funktion (Name) {
        languageToLower [name.toLowerCase ()] = optionen.languages [Name];
      });
      this.languages_ = Sprachen, die es zu senken gilt;
    } sonst {
      this.languages_ = player.prototype.options_.languages;
    }

    this.resetCache_();

    //Plakat setzen
    this.poster_ = optionen.poster || „;

    //Steuerung einstellen
    this.controls_ =!! Optionen. Steuerelemente;

    //Ursprüngliche Tag-Einstellungen werden in den Optionen gespeichert
    //jetzt sofort entfernen, damit die nativen Steuerelemente nicht blinken.
    //Kann von der HTML5-Technologie wieder aktiviert werden, wenn NativeControlsForTouch wahr ist
    tag.controls = falsch;
    tag.removeAttribute ('Kontrollen');

    this.changingSrc_ = false;
    this.playCallbacks_ = [];
    this.playTerminatedQueue_ = [];

    //das Attribut überschreibt die Option
    wenn (tag.hasAttribute ('Autoplay')) {
      this.autoplay (wahr);
    } sonst {
      //andernfalls verwende den Setter zur Validierung und
      //setze den richtigen Wert.
      this.autoplay (this.options_.autoplay);
    }

    //Plugins überprüfen
    if (options.plugins) {
      Object.keys(options.plugins).forEach((name) => {
        if (tippe diesen [Name] ein! == 'Funktion') {
          einen neuen Fehler auslösen (`Plugin „$ {name}“ existiert nicht`);
        }
      });
    }

    /*
     * Speichern Sie den internen Zustand des Schrubbens
     *
     * @privat
     * @return {Boolean} Wahr, wenn der Benutzer scrubbt
     * /
    this.scrubbing_ = falsch;

    this.el_ = this.createEl();

    //Mache daraus ein Event-Objekt und verwende `el_` als Event-Bus.
    evented (this, {eventBusKey: 'el_'});

    //Hör dir die FullscreenChange-Handler von Document und Player an, damit wir diese Ereignisse erhalten
    //bevor ein Benutzer sie erhalten kann, damit wir isFullScreen entsprechend aktualisieren können.
    //stellen Sie sicher, dass wir uns die Fullscreenchange-Ereignisse vor allem anderen anhören, um sicherzustellen, dass
    //Unsere isFullScreen-Methode wird sowohl für interne als auch für externe Komponenten ordnungsgemäß aktualisiert.
    if (this.fsApi_.requestFullscreen) {
      events.on (Dokument, this.fsapi_.fullScreenChange, this.boundDocumentFullScreenChange_);
      this.on (this.fsapi_.fullScreenChange, this.boundDocumentFullScreenChange_);
    }

    wenn (this.fluid_) {
      this.on(['playerreset', 'resize'], this.boundUpdateStyleEl_);
    }
    //Wir wollen auch die ursprünglichen Player-Optionen an jede Komponente und jedes Plugin weitergeben
    //auch, damit sie später nicht erneut in den Player greifen müssen, um Optionen zu erhalten.
    //Wir müssen auch eine weitere Kopie von this.options_ erstellen, damit wir nicht mit
    //eine unendliche Schleife.
    const playerOptionsCopy = mergeOptions (this.options_);

    //Plugins laden
    if (options.plugins) {
      Object.keys(options.plugins).forEach((name) => {
        this [Name] (options.plugins [Name]);
      });
    }

    //Aktiviere den Debug-Modus, um das Debugon-Ereignis für alle Plugins auszulösen.
    wenn (options.debug) {
      this.debug (wahr);
    }

    this.options_.playerOptions = PlayerOptionsCopy;

    this.middleware_ = [];

    this.PlaybackRates (Optionen.PlaybackRates);

    this.initChildren();

    //Setze isAudio basierend darauf, ob ein Audio-Tag verwendet wurde oder nicht
    this.isAudio (tag.nodeName.toLowerCase () === 'audio');

    //Aktualisiere die Steuerelemente ClassName. Dies ist nicht möglich, wenn die Steuerung anfänglich aktiviert ist
    //gesetzt, weil das Element noch nicht existiert.
    wenn (this.controls ()) {
      this.addClass('vjs-controls-enabled');
    } sonst {
      this.addClass('vjs-controls-disabled');
    }

    //Lege das ARIA-Label und die Regionsrolle je nach Spielertyp fest
    this.el_.setAttribute ('Rolle', 'Region');
    if (this.isAudio()) {
      this.el_.setAttribute ('aria-label', this.localize ('Audioplayer'));
    } sonst {
      this.el_.setAttribute ('aria-label', this.localize ('Videoplayer'));
    }

    if (this.isAudio()) {
      this.addClass ('vjs-audio');
    }

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

    // TODO: Machen Sie das schlauer. Benutzerstatus zwischen Berühren/Mausklick umschalten
    //Ereignisse verwenden, da Geräte sowohl Berührungs- als auch Mausereignisse haben können.
    // TODO: Stellen Sie sicher, dass diese Überprüfung erneut ausgeführt wird, wenn das Fenster zwischen Monitoren wechselt
    //(Siehe https://github.com/videojs/video.js/issues/5683)
    wenn (browser.TOUCH_ENABLED) {
      this.addClass ('vjs-touch-enabled');
    }

    //iOS Safari hat die Hover-Behandlung unterbrochen
    wenn (! Browser.is_iOS) {
      this.addClass ('vjs-workinghover');
    }

    //Mach den Spieler anhand seiner ID leicht auffindbar
    player.players [this.id_] = das;

    //Füge eine Hauptversionsklasse hinzu, um CSS in Plugins zu unterstützen
    const majorVersion = version.split ('.') [0];

    this.addClass (`vjs-v$ {majorVersion} `);

    //Wenn der Player zum ersten Mal initialisiert wird, aktiviere Aktivität, also Komponenten
    //wie die Kontrollleisten zeigen sich bei Bedarf
    this.userActive(true);
    this.reportUserActivity();

    this.one ('play', (e) => this.listenForUserActivity_ (e));
    this.on ('stageclick', (e) => this.handleStageClick_ (e));
    this.on('keydown', (e) => this.handleKeyDown(e));
    this.on ('languagechange', (e) => this.handleLanguageChange (e));

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

    //Beide Audiomodusmethoden aufrufen, nachdem der Player vollständig ist
    //Setup, um die von ihnen ausgelösten Ereignisse abhören zu können
    this.on ('bereit', () => {
      //Zuerst die Methode AudioPosterMode aufrufen, damit
      //der AudioOnlyMode kann Vorrang haben, wenn beide Optionen auf true gesetzt sind
      this.AudioPosterMode (this.options_.AudioPosterMode);
      this.AudioOnlyMode (this.options_.AudioOnlyMode);
    });
  }

  /**
   * Zerstört den Videoplayer und führt alle notwendigen Aufräumarbeiten durch.
   *
   * Dies ist besonders hilfreich, wenn Sie Videos dynamisch hinzufügen und entfernen
   * zum/vom DOM.
   *
   * @fires Spieler #dispose
   * /
  dispose() {
    /**
     * Wird aufgerufen, wenn der Spieler entsorgt wird.
     *
     * @event Spieler #dispose
     * @Typ {EventTarget~Event}
     * /
    this.trigger('dispose');
    //verhindere, dass dispose zweimal aufgerufen wird
    this.off ('entsorgen');

    //Vergewissere dich, dass alle spielerspezifischen Dokumentenzuhörer ungebunden sind. Das ist
    events.OFF (Dokument, this.fsapi_.fullScreenChange, this.boundDocumentFullScreenChange_);
    Events.off(document, 'keydown', this.boundFullWindowOnEscKey_);

    wenn (this.styleEL_ && this.styleEL_.parentNode) {
      this.styleEL_.parentNode.removeChild (this.styleEL_);
      this.styleEL_ = null;
    }

    //Verweis auf diesen Spieler töten
    player.players [this.id_] = null;

    wenn (this.tag && this.tag.player) {
      this.tag.player = null;
    }

    wenn (this.el_ && this.el_.player) {
      this.el_.player = null;
    }

    if (this.tech_) {
      this.tech_.dispose();
      this.isPosterFromTech_ = false;
      this.poster_ = '';
    }

    wenn (this.playerELINGEST_) {
      this.playerELINGEST_ = null;
    }

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

    Middleware.clearcacheForPlayer (this);

    //entferne alle Event-Handler für Tracklisten
    //alle Tracks und Track-Listener werden entfernt am
    //technische Entsorgung
    TRACK_TYPES.names.forEach((name) => {
      const props = TRACK_TYPES[name];
      const list = this [props.getterName] ();

      //wenn es keine native Liste ist
      //wir müssen die Event-Listener manuell entfernen
      wenn (list && list.off) {
        list.off ();
      }
    });

    //das eigentliche .el_ wird hier entfernt oder ersetzt, wenn
    super.dispose ({restoReel: this.options_.restoReel});
  }

  /**
   * Erstelle das DOM-Element des `Player`.
   *
   * @return {Element}
   *         Das DOM-Element, das erstellt wird.
   * /
  createEl() {
    sei tag = this.tag;
    lass es sein;
    lass playerelingest = this.playerelingest_ = tag.parentNode && tag.parentNode.hasAttribute && tag.parentNode.hasAttribute ('data-vjs-player');
    const divEmbed = this.tag.tagName.toLowerCase () === 'video-js';

    wenn (playerRelingest) {
      el = this.el_ = Tag.parentNode;
    } sonst wenn (! Dive Embed) {
      el = this.el_ = super.createEl ('div');
    }

    //Kopiere alle Attribute aus dem Tag, einschließlich ID und Klasse
    //Die ID bezieht sich jetzt auf die Player-Box, nicht auf das Video-Tag
    const attrs = dom.getAttributes (Tag);

    wenn (divEmbed) {
      el = this.el_ = Tag;
      tag = this.tag = document.createElement ('video');
      während (el.children.length) {
        tag.appendChild (el.FirstChild);
      }

      wenn (! dom.hasClass (de, 'video-js') {
        dom.addClass (de, 'video-js');
      }

      el.appendChild (Schlagwort);

      playerElingest = this.playerelingest_ = el;
      //Eigenschaften aus unserem benutzerdefinierten `video-js`-Element verschieben
      //zu unserem neuen `Video`-Element. Das wird Dinge bewegen wie
      //`src` oder `controls`, die vor dem Player über js gesetzt wurden
      //wurde initialisiert.
      object.keys (de) .forEach (k) => {
        versuche {
          tag [k] = el [k];
        } catch (e) {
          //wir haben eine Eigenschaft wie outerHTML, die wir nicht wirklich kopieren können, ignoriere sie
        }
      });
    }

    //setze tabindex auf -1, um das Videoelement aus der Fokusreihenfolge zu entfernen
    tag.setAttribute ('tabindex', '-1');
    attrs.tabindex = '-1';

    //Problemumgehung für #4583 (JAWS+IE kündigt weder BPB noch Play-Button an) und
    //für das gleiche Problem mit Chrome (unter Windows) mit JAWS.
    //Siehe https://github.com/FreedomScientific/VFO-standards-support/issues/78
    //Beachten Sie, dass wir nicht erkennen können, ob JAWS verwendet wird, sondern dieses ARIA-Attribut
    //ändert das Verhalten von IE11 oder Chrome nicht, wenn JAWS nicht verwendet wird
    wenn (IE_VERSION | (IS_CHROME & IS_WINDOWS)) {
      tag.setAttribute ('Rolle', 'Anwendung');
      attrs.role = 'Anwendung';
    }

    //Entferne die Breite/Höhen-Attribute aus dem Tag, damit CSS es zu 100% Breite/Höhe machen kann
    tag.removeAttribute ('Breite');
    tag.removeAttribute ('Höhe');

    if ('Breite' in Buchstaben) {
      lösche attrs.width;
    }
    if ('Höhe' in Zeichen) {
      lösche attrs.height;
    }

    object.getOwnPropertyNames (attrs) .forEach (function (attr) {
      //kopiere das Klassenattribut nicht in das Player-Element, wenn wir uns in einer div-Einbettung befinden
      //Die Klasse ist im DivEmbed-Fall bereits richtig eingerichtet
      //und wir wollen sicherstellen, dass die Klasse `video-js` nicht verloren geht
      wenn (! (divEmbed && attr === 'Klasse') {
        el.setAttribute (attr, attrs [attr]);
      }

      wenn (divEmbed) {
        tag.setAttribute (attr, attrs [attr]);
      }
    });

    //Tag-ID/Klasse für die Verwendung als HTML5-Wiedergabetechnologie aktualisieren
    //Ich denke vielleicht, wir sollten das nach dem Einbetten in den Container tun, also die Klasse .vjs-tech
    //blinkt nicht zu 100% Breite/Höhe, aber die Klasse gilt nur für das übergeordnete Objekt .video-js
    tag.playerId = tag.id;
    tag.id += '_html5_api';
    tag.className = 'vjs-tech';

    //Spieler auf Elementen auffindbar machen
    tag.player = el.player = das;
    //Der Standardstatus des Videos ist angehalten
    this.addClass('vjs-paused');

    //Füge ein Style-Element im Player hinzu, mit dem wir die Breite/Höhe festlegen
    //des Players auf eine Weise, die immer noch durch CSS überschrieben werden kann, genau wie der
    //Videoelement
    wenn (window.videoJS_NO_DYNAMIC_STYLE! == wahr) {
      this.styleEl_ = styleSheet.createStyleElement ('vjs-styles-dimensions');
      const defaultsStyleEl = Dom.$ ('.vjs-styles-defaults');
      const head = Dom.$('head');

      head.insertBefore (this.styleEL_, defaultsStyleEL? defaultsStyleEl.NextSibling: head.FirstChild);
    }

    this.fill_ = falsch;
    this.fluid_ = falsch;

    //Übergeben Sie die Optionen width/height/aspectRatio, die den Stil el aktualisieren
    this.width (this.options_.width);
    this.height (this.options_.height);
    this.fill (this.options_.fill);
    this.fluid (this.options_.fluid);
    this.AspectRatio (this.Options_.AspectRatio);
    //unterstützt sowohl CrossOrigin als auch Crossorigin, um Verwirrung und Probleme rund um den Namen zu vermeiden
    this.crossOrigin (this.options_.crossOrigin || this.options_.crossOrigin);

    //Verstecke alle Links innerhalb des Video-/Audio-Tags,
    //weil IE sie nicht vollständig vor Screenreadern versteckt.
    const links = tag.getElementsByTagName ('a');

    für (sei i = 0; i < links.length; i++) {
      const linkEL = links.item (i);

      dom.addClass (LinkEl, 'vjs-hidden');
      linkel.setAttribute ('versteckt', 'versteckt');
    }

    //insertElFirst scheint den NetworkState von 3 auf 2 flackern zu lassen, also
    //Behalte das Original für später im Auge, damit wir wissen, ob die Quelle ursprünglich ausgefallen ist
    tag.initNetworkState_ = Tag.NetworkState;

    //Wickeln Sie das Videotag in den Container div (el/box)
    wenn (Tag.parentNode &&! Spieler Elingest) {
      tag.parentNode.insertBefore (el, tag);
    }

    //füge das Tag als erstes untergeordnetes Element des Player-Elements ein
    //dann füge es manuell zum Children-Array hinzu, sodass this.addChild
    //funktioniert einwandfrei für andere Komponenten
    // //
    //Das iPhone ist kaputt, das wurde im HTML5-Setup behoben.
    dom.prependTo (tag, el);
    this.children_.unshift (Tag);

    //Setze lang attr auf Player, um sicherzustellen, dass css:lang () mit dem Player übereinstimmt
    //wenn es auf etwas anderes als das Dokument eingestellt wurde
    this.el_.setAttribute ('lang', this.language_);

    this.el_.setAttribute ('translate', 'nein');

    this.el_ = el;

    zurück el;
  }

  /**
   * Ruft die CrossOrigin-Option des `Player` ab oder legt sie fest. Für den HTML5-Player bedeutet dies
   * setzt die Eigenschaft "crossOrigin" auf den Tag "<video>", um den CORS zu steuern
   * verhalten.
   *
   * @see [Video Element Attributes]{@link https://developer.mozilla.org/en-US/docs/Web/HTML/Element/video#attr-crossorigin}
   *
   * @param {string} [Wert]
   * Der Wert, auf den der CrossOrigin des `Player` gesetzt werden soll. Wenn ein Argument
   *        angegeben wird, muss eine der Optionen "anonymous" oder "use-credentials" sein.
   *
   * @return {string|undefined}
   * - Der aktuelle CrossOrigin-Wert des `Player` beim Abrufen.
   *         - undefiniert bei der Einstellung
   * /
  CrossOrigin (Wert) {
    if (!Wert) {
      gib this.techGet_ ('crossOrigin') zurück;
    }

    wenn (Wert! == 'anonym' && Wert! == 'Anmeldeinformationen') {
      log.warn (`crossOrigin muss „anonymous“ oder „use-credentials“ sein, vorausgesetzt „$ {value}“ `);
      rückkehr;
    }

    this.techCall_ ('setCrossOrigin', Wert);

    rückkehr;
  }

  /**
   * Ein Getter/Setter für die Breite des `Player`. Gibt den konfigurierten Wert des Spielers zurück.
   * Um die aktuelle Breite zu ermitteln, benutze `currentWidth () `.
   *
   * @param {Zahl} [Wert]
   * Der Wert, auf den die Breite des `Player` eingestellt werden soll.
   *
   * @return {number}
   * Die aktuelle Breite des `Player` beim Abrufen.
   * /
  Breite (Wert) {
    gib this.dimension ('Breite', Wert) zurück;
  }

  /**
   * Ein Getter/Setter für die Größe des `Spieler`. Gibt den konfigurierten Wert des Spielers zurück.
   * Um die aktuelle Höhe zu ermitteln, verwende `currenttheight () `.
   *
   * @param {Zahl} [Wert]
   * Der Wert, auf den die Größe des `Player` eingestellt werden soll.
   *
   * @return {number}
   * Die aktuelle Höhe des `Player` beim Abrufen.
   * /
  Höhe (Wert) {
    gib this.dimension ('Höhe', Wert) zurück;
  }

  /**
   * Ein Getter/Setter für die Breite und Höhe des `Player`.
   *
   * Abmessung @param {string}
   * Diese Zeichenfolge kann sein:
   * - 'Breite'
   * - 'Höhe'
   *
   * @param {Zahl} [Wert]
   * Wert für die im ersten Argument angegebene Dimension.
   *
   * @return {number}
   * Der Wert der Dimensionsargumente beim Abrufen von (Breite/Höhe).
   * /
  Dimension (Dimension, Wert) {
    const privDimension = Abmessung + '_';

    if (Wert === undefiniert) {
      gib das zurück [PrivDimension] || 0;
    }

    if (Wert === "|| Wert === 'auto') {
      //Wenn eine leere Zeichenfolge angegeben wird, setzen Sie die Dimension auf automatisch zurück
      this [PrivDimension] = undefiniert;
      this.updateStyleEl_();
      rückkehr;
    }

    const parsedVal = parseFloat (Wert);

    wenn (isNaN (parsedVal)) {
      log.error (`Ungültiger Wert „$ {value}“ angegeben für $ {dimension} `);
      rückkehr;
    }

    diese [PrivDimension] = parsedVal;
    this.updateStyleEl_();
  }

  /**
   * Ein Getter/Setter/Toggler für das vjs-fluid `className` auf dem `Player`.
   *
   * Wenn Sie diese Option einschalten, wird der Füllmodus deaktiviert.
   *
   * @param {boolean} [bool]
   *        - Bei einem Wert von true wird die Klasse hinzugefügt.
   *        - Bei einem Wert von false wird die Klasse entfernt.
   *        - Kein Wert wird ein Getter sein.
   *
   * @return {boolean|undefined}
   *         - Der Wert der Flüssigkeit bei der Beschaffung.
   *         - bei der Einstellung "unbestimmt".
   * /
  flüssig (bool) {
    if (bool === undefiniert) {
      kehre zurück!! diese.flüssigkeit_;
    }

    this.fluid_ =!! boolen;

    if (isEvented(this)) {
      this.off (['playerreset', 'Größe ändern'], this.boundUpdateStyleel_);
    }
    if (bool) {
      this.addClass ('vjs-fluid');
      this.fill (falsch);
      addEventedCallback (this, () => {
        this.on(['playerreset', 'resize'], this.boundUpdateStyleEl_);
      });
    } sonst {
      this.removeClass ('vjs-fluid');
    }

    this.updateStyleEl_();
  }

  /**
   * Ein Getter/Setter/Toggler für das vjs-fill `className` auf dem `Player`.
   *
   * Wenn Sie diese Option einschalten, wird der Flüssigkeitsmodus ausgeschaltet.
   *
   * @param {boolean} [bool]
   *        - Bei einem Wert von true wird die Klasse hinzugefügt.
   *        - Bei einem Wert von false wird die Klasse entfernt.
   *        - Kein Wert wird ein Getter sein.
   *
   * @return {boolean|undefined}
   *         - Der Wert der Flüssigkeit bei der Beschaffung.
   *         - bei der Einstellung "unbestimmt".
   * /
  fill (bool) {
    if (bool === undefiniert) {
      kehre zurück!! dieses.fill_;
    }

    this.fill_ =!! boolen;

    if (bool) {
      this.addClass ('vjs-fill');
      this.fluid (falsch);
    } sonst {
      this.removeClass ('vjs-fill');
    }
  }

  /**
   * Seitenverhältnis abrufen/einstellen
   *
   * @param {string} [Verhältnis]
   * Seitenverhältnis für den Spieler
   *
   * @return {string|undefined}
   * gibt das aktuelle Seitenverhältnis beim Abrufen zurück
   * /

  /**
   * Ein Getter/Setter für das Seitenverhältnis des `Player`.
   *
   * @param {string} [Verhältnis]
   * Der Wert, auf den das Seitenverhältnis des `Player` eingestellt werden soll.
   *
   * @return {string|undefined}
   * - Das aktuelle Seitenverhältnis des `Player` beim Abrufen.
   *         - undefiniert bei der Einstellung
   * /
  Seitenverhältnis (Verhältnis) {
    if (Verhältnis === undefiniert) {
      gib this.AspectRatio_ zurück;
    }

    //Prüfe das Format Width:Height
    wenn (! (/^\ d+\:\ d+$/) .test (Verhältnis)) {
      löst einen neuen Fehler aus ('Falscher Wert für das Seitenverhältnis angegeben. Das Format sollte breit:höhe sein, zum Beispiel 16:9. ');
    }
    this.AspectRatio_ = Verhältnis;

    //Wir gehen davon aus, dass du, wenn du ein Seitenverhältnis einstellst, den Fluidmodus verwenden möchtest,
    //weil du im festen Modus Breite und Höhe selbst berechnen könntest.
    this.fluid (wahr);

    this.updateStyleEl_();
  }

  /**
   * Aktualisiere die Stile des `Player`-Elements (Höhe, Breite und Seitenverhältnis).
   *
   * @privat
   * @listens Tech#loadedmetadata
   * /
  updateStyleL_ () {
    wenn (window.videoJS_NO_DYNAMIC_STYLE === wahr) {
      const width = typeofthis.width_ === 'Zahl'? this.width_: this.options_.width;
      const height = typeofthis.height_ === 'Zahl'? this.height_: this.options_.height;
      const techEL = this.tech_ && this.tech_.el ();

      wenn (TechEL) {
        wenn (Breite >= 0) {
          techEL.Width = Breite;
        }
        wenn (Höhe >= 0) {
          techEL.Height = Höhe;
        }
      }

      rückkehr;
    }

    Breite links;
    Höhe des Fahrzeuges;
    lass AspecRatio;
    lass IDClass;

    //Das Seitenverhältnis wird entweder direkt oder zur Berechnung von Breite und Höhe verwendet.
    wenn (this.AspectRatio_! = undefiniert & this.AspectRatio_! == 'automatisch') {
      //Verwende ein beliebiges AspectRatio, das speziell festgelegt wurde
      AspectRatio = this.AspectRatio_;
    } sonst wenn (this.videoWidth () > 0) {
      //Andernfalls versuche das Seitenverhältnis aus den Video-Metadaten zu ermitteln
      AspectRatio = this.videoWidth () + ':' + this.videoHeight ();
    } sonst {
      //Oder verwende eine Standardeinstellung. Das Videoelement ist 2:1, aber 16:9 ist üblicher.
      Seitenverhältnis = '16:9';
    }

    //Holen Sie sich das Verhältnis als Dezimalzahl, mit der wir Dimensionen berechnen können
    const RatioParts = Seitenverhältnis.split (':');
    const ratioMultiplier = Verhältnis Teile [1]/Verhältnis Teile [0];

    wenn (this.width_! == undefiniert) {
      //Benutze jede Breite, die speziell festgelegt wurde
      breite = this.width_;
    } sonst wenn (this.height_! == undefiniert) {
      //Oder berechne die Breite aus dem Seitenverhältnis, falls eine Höhe eingestellt wurde
      width = this.height_/ratioMultiplier;
    } sonst {
      //Oder verwende die Metadaten des Videos oder verwende die Standardeinstellung des Video-El von 300
      breite = this.videoWidth () || 300;
    }

    wenn (this.height_! == undefiniert) {
      //Verwenden Sie eine beliebige Höhe, die speziell festgelegt wurde
      Höhe = this.height_;
    } sonst {
      //Andernfalls berechne die Höhe aus dem Verhältnis und der Breite
      Höhe = Breite * RatioMultiplier;
    }

    //Stellen Sie sicher, dass die CSS-Klasse gültig ist, indem Sie mit einem Alpha-Zeichen beginnen
    wenn (/^ [^a-Za-Z]/) .test (this.id ())) {
      idClass = 'Dimensionen-' + this.id ();
    } sonst {
      idClass = this.id () + '-Dimensionen';
    }

    //Stelle sicher, dass immer noch die richtige Klasse für das Style-Element auf dem Player ist
    this.addClass (IDClass);

    styleSheet.setTextContent (this.styleEL_, `
      . $ {idClass} {
        Breite: $ {width} px;
        Höhe: $ {height} px;
      }

      . $ {idClass} .vjs-fluid:nicht (.vjs-Nur-Audio-Modus) {
        obere Polsterung: $ {ratioMultiplier * 100}%;
      }
    `);
  }

  /**
   * Laden/Erstellen Sie eine Instanz von playback {@link Tech} inklusive Element
   * und API-Methoden. Hängen Sie dann als Kind das `Tech`-Element in `Player` an.
   *
   * @param {string} TechName
   * Name der Wiedergabe-Technologie
   *
   * @param {string} Quelle
   * Videoquelle
   *
   * @privat
   * /
  LoadTech_ (TechName, Quelle) {

    //Aktuelle Wiedergabe-Technologie anhalten und entfernen
    if (this.tech_) {
      Das.unloadTech_ ();
    }

    const titleTechName = toTitleCase (TechName);
    const camelTechName = techname.charat (0) .toLowerCase () + techname.Slice (1);

    //entferne das HTML5-Video-Tag, sobald wir eine andere Technologie verwenden
    if (Titel/Techname! == 'Html5' && this.tag) {
      tech.getTech ('Html5') .disposeMediaElement (this.tag);
      this.tag.player = null;
      this.tag = null;
    }

    this.techName_ = TitelTechName;

    // Schalten Sie den API-Zugriff aus, da wir eine neue Technologie laden, die möglicherweise asynchron geladen wird
    this.isReady_ = false;

    sei autoplay = this.autoplay ();

    //wenn Autoplay eine Zeichenfolge ist (oder `true` mit normalizeAutoplay: true) geben wir false an den Techniker weiter
    //weil der Player das Autoplay bei `loadstart` übernehmen wird
    if (typeof this.autoplay () === 'string' || this.autoplay () === true && this.options_.normalizeAutoplay) {
      Autoplay = falsch;
    }

    //Schnapp dir technologiespezifische Optionen aus den Player-Optionen und füge Quelle und übergeordnetes Element hinzu, um sie zu verwenden.
    const techOptions = {
      Quelle,
      automatisches Abspielen,
      'Native Controls for Touch': this.options_.NativeControlsForTouch,
      'playerID': this.id (),
      'techID': `$ {this.id ()} _$ {camelTechName} _api`,
      'playsinline': this.options_.playsinline,
      'vorladen': this.options_.preload,
      'Schleife': this.options_.loop,
      'PictureInPicture': this.options_.DisablePictureInPicture,
      'stumm': this.options_.muted,
      'Poster': this.poster (),
      'Sprache': this.language (),
      'playerELINGEST': this.playerELINGEST_ || falsch,
      'vtt.js': this.options_ ['vtt.js'],
      'Poster überschreiben kann':!! this.options_.tech kann Poster überschreiben,
      'enableSourceSet': this.options_.enableSourceSet,
      'Versprechen': this.options_.promise
    };

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

      techOptions [props.getterName] = dieser [props.PrivateName];
    });

    assign (techOptions, this.options_ [TitleTechName]);
    assign (techOptions, this.options_ [CamelTechName]);
    assign (techOptions, this.options_ [Techname.toLowerCase ()]);

    if (this.tag) {
      techOptions.tag = diese.tag;
    }

    if (source && source.src === this.cache_.src && this.cache_.currentTime > 0) {
      techOptions.startTime = this.Cache_.CurrentTime;
    }

    //Technologie-Instanz initialisieren
    const techClass = tech.getTech (TechName);

    wenn (! TechClass) {
      einen neuen Fehler auslösen (`Es ist kein Techniker mit dem Namen '$ {titleTechName} 'vorhanden! '$ {titleTechName} 'sollte mit VideoJS.registerTech () registriert werden '`);
    }

    this.tech_ = neue TechClass (TechOptions);

    //Player.triggerReady ist immer asynchron, also muss das nicht asynchron sein
    this.tech_.ready (fn.Bind (this, this.handletechReady_), wahr);

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

    //Höre dir alle HTML5-definierten Ereignisse an und löse sie auf dem Player aus
    tech_events_retrigger.forEach (Ereignis) => {
      this.on (this.tech_, event, (e) => this [`handleTech$ {toTitleCase (event)} _`] (e));
    });

    object.keys (TECH_EVENTS_QUEUE) .forEach ((Ereignis) => {
      this.on (this.tech_, ereignis, (EventObj) => {
        if (this.tech_.playbackRate () === 0 && this.tech_.seeking ()) {
          this.queuedCallbacks_.push ({
            Rückruf: this [`handleTech$ {TECH_EVENTS_QUEUE [event]} _`] .bind (this),
            Veranstaltung: EventObj
          });
          rückkehr;
        }
        this [`handleTech$ {TECH_EVENTS_QUEUE [event]} _`] (eventObj);
      });
    });

    this.on (this.tech_, 'loadstart', (e) => this.handleTechLoadStart_ (e));
    this.on (this.tech_, 'sourceset', (e) => this.handleTechSourceSet_ (e));
    this.on (this.tech_, 'waiting', (e) => this.handletechWaiting_ (e));
    this.on (this.tech_, 'beendet', (e) => this.handletTechEnded_ (e));
    this.on (this.tech_, 'suche', (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_, 'fullscreenchange', (e, data) => this.handleTechFullScreenChange_ (e, Daten));
    this.on (this.tech_, 'fullscreenerror', (e, err) => this.handleTechFullScreenError_ (e, Fehler));
    this.on (this.tech_, 'enterpictureinpicture', (e) => this.handleTechenterPicturePicture_ (e));
    this.on (this.tech_, 'leavePictureInPicture', (e) => this.handleTechLeavePictureInPicture_ (e));
    this.on (this.tech_, 'Fehler', (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_, 'geladene Metadaten', this.boundUpdateStyleel_);

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

    if (this.controls () &&! this.using NativeControls ()) {
      this.addTechControlsListeners_();
    }

    //Füge das Tech-Element im DOM hinzu, falls es nicht schon da war
    //Achte darauf, dass du nicht das originale Videoelement einfügst, wenn du Html5 verwendest
    wenn (this.tech_.el () .parentNode! == this.el () && (titleTechname! == 'Html5' |! diese.tag)) {
      dom.prependTo (this.tech_.el (), this.el ());
    }

    //Entferne die ursprüngliche Video-Tag-Referenz, nachdem die erste Technologie geladen wurde
    if (this.tag) {
      this.tag.player = null;
      this.tag = null;
    }
  }

  /**
   * Entlade und entsorge die aktuelle Wiedergabe {@link Tech}.
   *
   * @privat
   * /
  unloadTech_ () {
    //Speichere die aktuellen Textspuren, damit wir dieselben Textspuren mit der nächsten Technologie wiederverwenden können
    TRACK_TYPES.names.forEach((name) => {
      const props = TRACK_TYPES[name];

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

    this.isReady_ = false;

    this.tech_.dispose();

    this.tech_ = falsch;

    wenn (this.isPosterFromTech_) {
      this.poster_ = '';
      this.trigger('posterchange');
    }

    this.isPosterFromTech_ = false;
  }

  /**
   * Gibt einen Verweis auf die aktuelle {@link Tech} zurück.
   * Es wird standardmäßig eine Warnung vor der Gefahr einer direkten Nutzung der Technologie gedruckt
   * aber jedes Argument, das angegeben wird, bringt die Warnung zum Schweigen.
   *
   * @param {*} [Sicherheit]
   * Alles wurde weitergegeben, um die Warnung zum Schweigen zu bringen
   *
   * @return {Technik}
   * Die Technologie
   * /
  Technik (Sicherheit) {
    if (Sicherheit === undefiniert) {
      log.warn ('Die Technologie direkt zu verwenden kann gefährlich sein. Ich hoffe du weißt, was du tust. \ n' +
        „Weitere Informationen finden Sie unter https://github.com/videojs/video.js/issues/2617. \ n');
    }

    gib this.tech_ zurück;
  }

  /**
   * Richten Sie Klick- und Touch-Listener für das Wiedergabeelement ein
   *
   * - Auf Desktops: Ein Klick auf das Video selbst schaltet die Wiedergabe um
   * - Auf Mobilgeräten: Ein Klick auf das Video schaltet die Steuerung um
   * was durch Umschalten des Benutzerstatus zwischen aktiv und
   * inaktiv
   * - Ein Tippen kann signalisieren, dass ein Benutzer aktiv oder inaktiv geworden ist
   * z. B. ein kurzes Tippen auf einen iPhone-Film sollte die Bedienelemente anzeigen. Noch ein
   * Durch schnelles Tippen sollten sie wieder ausgeblendet werden (signalisiert, dass der Benutzer inaktiv ist)
   * Anzeigestatus)
   * - Darüber hinaus möchten wir weiterhin, dass der Benutzer danach als inaktiv gilt
   * ein paar Sekunden Inaktivität.
   *
   * > Hinweis: Der einzige Teil der iOS-Interaktion, den wir mit diesem Setup nicht nachahmen können
   * zählt ein Berühren und Halten des Videoelements als Aktivität, um
   * lassen Sie die Steuerelemente weiterhin anzeigen, aber das sollte kein Problem sein. Ein Berühren und Halten
   * bei allen Steuerelementen bleibt der Benutzer weiterhin aktiv
   *
   * @privat
   * /
  Fügen Sie TechControlsListeners_ () {hinzu
    //Achte darauf, alle vorherigen Listener zu entfernen, falls wir mehrmals angerufen werden.
    this.removeTechControlsListeners_();

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

    //Wenn die Steuerelemente ausgeblendet wären, wollen wir nicht, dass sich das ohne ein Tap-Event ändert
    //also überprüfen wir, ob die Steuerelemente bereits angezeigt wurden, bevor wir den Benutzer melden
    //Aktivität
    this.on (this.tech_, 'touchstart', this.BoundHandleTechTouchStart_);
    this.on (this.tech_, 'touchmove', this.boundHandleTechTouchMove_);
    this.on (this.tech_, 'touchend', this.boundHandleTechTouchEnd_);

    //Der Tap Listener muss hinter dem berührenden Zuhörer her kommen, weil der Tap
    //Der Listener storniert jede ReportedUserActivity, wenn userActive gesetzt wird (false)
    this.on (this.tech_, 'tap', this.boundHandleTechTap_);
  }

  /**
   * Entferne die Listener, die für die Klick- und Tippsteuerung verwendet wurden. Das wird benötigt für
   * Umschalten zu deaktivierten Steuerungen, bei denen ein Tippen/Berühren nichts bewirken sollte.
   *
   * @privat
   * /
  removeTechControlsListeners_ () {
    //Wir wollen nicht einfach `this.off () `verwenden, weil vielleicht noch andere benötigt werden
    //Zuhörer wurden von Technikern hinzugefügt, die dies erweitern.
    this.off (this.tech_, 'tap', 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_);
  }

  /**
   * Der Spieler wartet darauf, dass die Technologie bereit ist
   *
   * @privat
   * /
  handleTechReady_ () {
    this.triggerReady();

    //Behalte die gleiche Lautstärke wie zuvor
    wenn (this.cache_.volume) {
      this.techCall_ ('setVolume', this.cache_.volume);
    }

    //Schau, ob der Techniker beim Laden ein Poster mit höherer Auflösung gefunden hat
    this.handleTechPosterChange_ ();

    //Aktualisiere die Dauer, falls verfügbar
    this.handleTechDurationChange_();
  }

  /**
   * Das `loadstart`-Ereignis, das vom {@link Tech} ausgelöst wurde, erneut auslösen. Diese
   * Die Funktion löst auch {@link Player #firstplay} aus, wenn es der erste Ladestart ist
   * für ein Video.
   *
   * @fires Spieler #loadstart
   * @Feuer Spieler#Erstes Spiel
   * @listens Technik #loadstart
   * @privat
   * /
  handleTechLoadStart_ () {
    // TODO: Aktualisieren Sie, um stattdessen das Ereignis `emptied` zu verwenden. Siehe #1277.

    this.removeClass('vjs-ended');
    this.removeClass('vjs-seeking');

    //setzt den Fehlerstatus zurück
    this.error(null);

    //Aktualisiere die Dauer
    this.handleTechDurationChange_();

    //Wenn es schon läuft, wollen wir jetzt ein Firstplay-Event auslösen.
    //Das Firstplay-Event basiert sowohl auf den Play- als auch auf den Loadstart-Ereignissen
    //was bei einer neuen Quelle in beliebiger Reihenfolge passieren kann
    wenn (! this.pausiert ()) {
      /**
       * Wird ausgelöst, wenn der Benutzeragent anfängt, nach Mediendaten zu suchen
       *
       * @event Spieler #loadstart
       * @Typ {EventTarget~Event}
       * /
      this.trigger('loadstart');
      this.trigger('firstplay');
    } sonst {
      //setzt den HasStarted Status zurück
      this.hasStarted (falsch);
      this.trigger('loadstart');
    }

    //Autoplay erfolgt nach dem Ladestart für den Browser,
    //also ahmen wir dieses Verhalten nach
    this.manualAutoPlay_ (this.autoplay () === wahr & this.options_.normalizeAutoplay? 'abspielen': this.autoplay ());
  }

  /**
   * Behandle Autoplay-Zeichenkettenwerte und nicht den typischen booleschen Wert
   * Werte, mit denen sich der Techniker befassen sollte. Beachten Sie, dass dies nicht
   * Teil jeder Spezifikation. Gültige Werte und was sie bewirken können
   * gefunden auf dem Autoplay-Getter bei Player #autoplay ()
   * /
  ManualAutoPlay_ (Typ) {
    wenn (! this.tech_ || Typ! == 'Zeichenfolge') {
      rückkehr;
    }

    //Speichere den ursprünglichen Wert von muted (), setze muted auf true und versuche, () abzuspielen.
    //Bei Ablehnung des Versprechens den gespeicherten Wert auf stummgeschaltet wiederherstellen
    const resolveMuted = () => {
      const previouslyMuted = this.muted ();

      this.muted (wahr);

      const restoreMuted = () => {
        this.muted (zuvor stummgeschaltet);
      };

      //Bei Beendigung des Spiels stummgeschaltet wiederherstellen
      this.playTerminatedQueue_.push (restoreMuted);

      const mutedPromise = this.play ();

      wenn (! isPromise (mutedPromise)) {
        rückkehr;
      }

      gib mutedPromise.catch zurück (Fehler => {
        RestoreMuted ();
        gibt bei ManualAutoPlay einen neuen Fehler (`Rejection) aus. Wiederherstellung des gedämpften Werts. $ {Fehler? Fehler: „} `);
      });
    };

    lass es versprechen;

    //wenn stummgeschaltet standardmäßig auf true gesetzt ist
    //das einzige was wir tun können ist Call Play
    if (type === 'any' &&! this.muted ()) {
      promise = this.play();

      if (isPromise (versprechen)) {
        promise = promise.catch (resolveMuted);
      }
    } sonst wenn (type === 'muted' &&! this.muted ()) {
      versprechen = resolveMuted ();
    } sonst {
      promise = this.play();
    }

    wenn (! isPromise (Versprechen)) {
      rückkehr;
    }

    Rückgabeversprechen.then () => {
      this.trigger ({Typ: 'Autoplay-Erfolg', Autoplay: Typ});
    }) .catch () => {
      this.trigger ({Typ: 'Autoplay-Fehler', Autoplay: Typ});
    });
  }

  /**
   * Aktualisiere die internen Quellcaches, sodass wir die richtige Quelle von zurückgeben
   * `src () `, `currentSource ()` und `currentSources () `.
   *
   * > Hinweis: `CurrentSources` wird nicht aktualisiert, wenn die übergebene Quelle existiert
   * im aktuellen `CurrentSources`-Cache.
   *
   *
   * @param {Tech~SourceObject} srcObj
   * Eine Zeichenfolge oder Objektquelle, auf die unsere Caches aktualisiert werden sollen.
   * /
  updateSourceCaches_ (srcObj = „) {

    sei src = srcoBJ;
    sei der Typ = „;

    if (Typ von src! == 'Zeichenfolge') {
      src = srcoBj.src;
      typ = srcobj.Type;
    }

    //stelle sicher, dass alle Caches auf Standardwerte gesetzt sind
    //um eine Nullprüfung zu verhindern
    this.cache_.source = this.cache_.source || {};
    this.cache_.sources = this.cache_.sources || [];

    //versuche den Typ des übergebenen src zu ermitteln
    wenn (src &&! typ) {
      type = findMimeType (this, src);
    }

    //aktualisiere den `CurrentSource`-Cache immer
    this.cache_.source = mergeOptions ({}, srcObj, {src, type});

    const matchingSources = this.cache_.sources.filter (s) => s.src && s.src === src);
    const sourceElSources = [];
    const sourceEls = this.$$ ('Quelle');
    const matchingSourceEls = [];

    für (sei i = 0; i < sourceELS.length; i++) {
      const sourceObj = dom.getAttributes (sourceELs [i]);

      Quelle elsources.push (Quelle Obj);

      if (sourceObj.src && sourceObj.src === src) {
        MatchingSourceels.push (Quellobj.src);
      }
    }

    //wenn wir passende Quellen haben, aber keine passenden Quellen
    //der aktuelle Quellcache ist nicht aktuell
    if (matchingSourceEls.length &&! MatchingSources.length) {
      this.cache_.sources = Quelle ELSources;
    //wenn wir keine passende Quelle oder Quelle haben, setze den
    //Quell-Cache zum `CurrentSource`-Cache
    } sonst wenn (! MatchingSources.length) {
      this.cache_.sources = [this.cache_.source];
    }

    //aktualisiere den Tech `src`-Cache
    this.cache_.src = src;
  }

  /**
   * *EXPERIMENTAL* Wird ausgelöst, wenn die Quelle auf dem {@link Tech} eingestellt oder geändert wird
   * wodurch das Medienelement neu geladen wird.
   *
   * Es wird für die ursprüngliche Quelle und jede nachfolgende Quelle ausgelöst.
   * Dieses Ereignis ist ein benutzerdefiniertes Ereignis von Video.js und wird vom {@link Tech} ausgelöst.
   *
   * Das Event-Objekt für dieses Ereignis enthält eine `src`-Eigenschaft, die die Quelle enthalten wird
   * das war verfügbar, als das Ereignis ausgelöst wurde. Dies ist in der Regel nur erforderlich, wenn Video.js
   * wechselt die Technik, während die Quelle geändert wurde.
   *
   * Es wird auch ausgelöst, wenn `load` auf dem Player (oder Medienelement) aufgerufen wird
   * weil das {@link https://html.spec.whatwg.org/multipage/media.html#dom-media-load|specification für `load`}
   * sagt, dass der Algorithmus zur Ressourcenauswahl abgebrochen und neu gestartet werden muss.
   * In diesem Fall ist es sehr wahrscheinlich, dass die Eigenschaft `src` auf das gesetzt wird
   * leere Zeichenfolge `""`, um anzuzeigen, dass wir nicht wissen, was die Quelle sein wird, aber
   * dass es sich ändert.
   *
   * *Diese Veranstaltung ist derzeit noch experimentell und kann sich in kleineren Versionen ändern. *
   * __Um dies zu verwenden, übergeben Sie dem Player die Option `enableSourceSet`. __
   *
   * @event Spieler #sourceset
   * @Typ {EventTarget~Event}
   * @prop {string} src
   * Die Quell-URL, die verfügbar war, als das `sourceset` ausgelöst wurde.
   * Es wird eine leere Zeichenfolge sein, wenn wir die Quelle nicht wissen können
   * aber wisse, dass sich die Quelle ändern wird.
   * /
  /**
   * Das `sourceset`-Ereignis, das vom {@link Tech} ausgelöst wurde, erneut auslösen.
   *
   * @fires Spieler #sourceset
   * @listens Technik #sourceset
   * @privat
   * /
  handleTechSourceSet_ (Ereignis) {
    //aktualisiere den Quell-Cache nur, wenn die Quelle
    //wurde nicht mit der Player-API aktualisiert
    wenn (! this.changeSRC_) {
      lass UpdateSourceCaches = (src) => this.UpdateSourceCaches_ (src);
      const playerSrc = this.currentSource () .src;
      const eventSrc = event.src;

      //wenn wir einen PlayerSrc haben, der kein Blob ist, und einen Tech-Src, der ein Blob ist
      wenn (PlayersRC &&! (/^blob:/) .test (playersRC) && (/^blob:/) .test (EventSrc)) {

        //wenn sowohl die Tech-Quelle als auch die Player-Quelle aktualisiert wurden, gehen wir davon aus
        //etwas wie @videojs /http-streaming hat den Sourceset gemacht und die Aktualisierung des Quellcaches übersprungen.
        wenn (! This.LastSource_ || (This.LastSource_.tech! == EventSrc & this.LastSource_.Player! == SpielerRC)) {
          updateSourceCaches = () => {};
        }
      }

      //aktualisiere die Quelle sofort auf die ursprüngliche Quelle
      //in einigen Fällen ist dies eine leere Zeichenfolge
      Quellcaches aktualisieren (EventSrc);

      //wenn das `sourceset` `src` ein leerer String wäre
      //warte auf einen `loadstart` um den Cache auf `currentSrc` zu aktualisieren.
      //Wenn ein Sourceset vor einem `Loadstart` passiert, setzen wir den Zustand zurück
      wenn (! event.src) {
        this.tech_.any (['Quellset', 'loadstart'], (e) => {
          //wenn dort ein Sourceset vor einem `Loadstart` passiert
          //ist nichts zu tun wie das `handleTechSourceSet_`
          //wird erneut aufgerufen und das wird dort erledigt.
          if (e.type === 'Quellsatz') {
            rückkehr;
          }

          const techSRC = this.techGet ('CurrentSrc');

          this.lastsource_.tech = TechSRC;
          this.updateSourceCaches_ (TechSRC);
        });
      }
    }
    this.lastSource_ = {Spieler: this.currentSource () .src, tech: event.src};

    this.trigger({
      src: event.src,
      typ: 'Quellensatz'
    });
  }

  /**
   * Fügt die Klasse vjs-has-started hinzu oder entfernt sie
   *
   * @Feuer Spieler#Erstes Spiel
   *
   * @param {boolean} Anfrage
   * - true: fügt die Klasse hinzu
   * - false: Entferne die Klasse
   *
   * @return {boolean}
   * der boolesche Wert von hasStarted_
   * /
  hasStarted (Anfrage) {
    if (Anfrage === undefiniert) {
      //agiere als Getter, wenn wir keine Änderungswünsche haben
      gib this.hasStarted_ zurück;
    }

    if (anfrage === this.hasStarted_) {
      rückkehr;
    }

    this.hasStarted_ = Anfrage;

    if (this.hasStarted_) {
      this.addClass('vjs-has-started');
      this.trigger('firstplay');
    } sonst {
      this.removeClass ('vjs wurde gestartet');
    }
  }

  /**
   * Wird ausgelöst, wenn das Medium die Wiedergabe beginnt oder fortsetzt
   *
   * @see [Spezifikation] {@link https://html.spec.whatwg.org/multipage/embedded-content.html#dom-media-play}
   * @fires Spieler #play
   * @listens Tech#play
   * @privat
   * /
  handleTechPlay_ () {
    this.removeClass('vjs-ended');
    this.removeClass('vjs-paused');
    this.addClass('vjs-playing');

    //verstecke das Poster, wenn der Benutzer auf Play klickt
    this.hasStarted (wahr);
    /**
     * Wird immer dann ausgelöst, wenn ein {@link Tech #play} -Ereignis eintritt. Zeigt an, dass
     * Die Wiedergabe wurde gestartet oder fortgesetzt.
     *
     * @event Spieler #play
     * @Typ {EventTarget~Event}
     * /
    this.trigger ('abspielen');
  }

  /**
   * Das `ratechange`-Ereignis, das vom {@link Tech} ausgelöst wurde, erneut auslösen.
   *
   * Wenn sich Ereignisse in der Warteschlange befanden, während die Wiedergabegeschwindigkeit Null war, starten Sie
   * diese Ereignisse jetzt.
   *
   * @privat
   * @method Spieler #handleTechRateChange_
   * @fires Spieler #ratechange
   * @listens Technik #ratechange
   * /
  handleTechrateChange_ () {
    if (this.tech_.playbackRate () > 0 && this.Cache_.lastPlaybackRate === 0) {
      this.queuedCallbacks_.forEach ((in die Warteschlange gestellt) => in die Warteschlange gestellt.callback (in die Warteschlange gewartet.event));
      this.queuedCallbacks_ = [];
    }
    this.cache_.lastPlaybackRate = this.tech_.playbackRate ();
    /**
     * Wird ausgelöst, wenn die Wiedergabegeschwindigkeit des Audios/Videos geändert wird
     *
     * @event Spieler #ratechange
     * @Typ {Ereignis}
     * /
    this.trigger ('Ratenänderung');
  }

  /**
   * Das `waiting`-Ereignis, das vom {@link Tech} ausgelöst wurde, erneut auslösen.
   *
   * @fires Spieler #waiting
   * @listens Technik #waiting
   * @privat
   * /
  handleTechWaiting_ () {
    this.addClass ('vjs-waiting');
    /**
     * Eine ReadyState-Änderung am DOM-Element hat dazu geführt, dass die Wiedergabe gestoppt wurde.
     *
     * @event Spieler #waiting
     * @Typ {EventTarget~Event}
     * /
    this.trigger ('wartet');

    //Browser können nach einem Warteereignis ein Timeupdate-Ereignis ausgeben. Um zu verhindern
    //vorzeitiges Entfernen der Warteklasse, warte auf die Änderung der Uhrzeit.
    const timeWhenWaiting = this.currentTime ();
    const timeUpdateListener = () => {
      if (timeWhenWaiting! == this.currentTime ()) {
        this.removeClass('vjs-waiting');
        this.off ('timeupdate', timeUpdateListener);
      }
    };

    this.on ('timeupdate', timeUpdateListener);
  }

  /**
   * Das `canplay`-Ereignis, das vom {@link Tech} ausgelöst wurde, erneut auslösen.
   * > Anmerkung: Dies ist zwischen Browsern nicht konsistent. Siehe #1351
   *
   * @fires Spieler #canplay
   * @listens Technik #canplay
   * @privat
   * /
  handleTechCanPlay_ () {
    this.removeClass('vjs-waiting');
    /**
     * Das Medium hat einen ReadyState von HAVE_FUTURE_DATA oder höher.
     *
     * @event Spieler #canplay
     * @Typ {EventTarget~Event}
     * /
    this.trigger ('kann abspielen');
  }

  /**
   * Das `canplaythrough`-Ereignis, das vom {@link Tech} ausgelöst wurde, erneut auslösen.
   *
   * @fires Spieler #canplaythrough
   * @listens Technik #canplaythrough
   * @privat
   * /
  handleTechCanPlaythrough_ () {
    this.removeClass('vjs-waiting');
    /**
     * Das Medium hat einen ReadyState von HAVE_ENOUGH_DATA oder höher. Das bedeutet, dass
     * Die gesamte Mediendatei kann ohne Pufferung abgespielt werden.
     *
     * @event Spieler #canplaythrough
     * @Typ {EventTarget~Event}
     * /
    this.trigger ('kann durchspielen');
  }

  /**
   * Löse das `playing`-Event erneut aus, das vom {@link Tech} ausgelöst wurde.
   *
   * @fires Spieler #playing
   * @listens Technik #playing
   * @privat
   * /
  HandleTechPlaying_ () {
    this.removeClass('vjs-waiting');
    /**
     * Das Medium ist nicht mehr für die Wiedergabe gesperrt und die Wiedergabe hat begonnen.
     *
     * @event Spieler #playing
     * @Typ {EventTarget~Event}
     * /
    this.trigger ('wird abgespielt');
  }

  /**
   * Das `seeking`-Ereignis, das vom {@link Tech} ausgelöst wurde, erneut auslösen.
   *
   * @fires Spieler #seeking
   * @listens Technik #seeking
   * @privat
   * /
  handleTechSeeking_ () {
    this.addClass ('vjs-seeking');
    /**
     * Wird immer dann abgefeuert, wenn der Spieler zu einer neuen Zeit springt
     *
     * @event Spieler #seeking
     * @Typ {EventTarget~Event}
     * /
    this.trigger ('sucht');
  }

  /**
   * Das `seeked`-Ereignis, das vom {@link Tech} ausgelöst wurde, erneut auslösen.
   *
   * @fires Spieler #seeked
   * @listens Technik #seeked
   * @privat
   * /
  handleTechSeeked_ () {
    this.removeClass('vjs-seeking');
    this.removeClass('vjs-ended');
    /**
     * Wird abgefeuert, wenn der Spieler mit dem Sprung in eine neue Zeit fertig ist
     *
     * @event Spieler #seeked
     * @Typ {EventTarget~Event}
     * /
    this.trigger ('gesucht');
  }

  /**
   * Das `firstplay`-Ereignis, das vom {@link Tech} ausgelöst wurde, erneut auslösen.
   *
   * @Feuer Spieler#Erstes Spiel
   * @listens Technik #firstplay
   * @deprecated Ab 6.0 ist das Ereignis firstplay veraltet.
   * Ab 6.0 sind das Übergeben der Option `starttime` an den Spieler und das Firstplay-Event veraltet.
   * @privat
   * /
  handleTechFirstPlay_ () {
    //Wenn das erste Starttime-Attribut angegeben ist
    //dann beginnen wir mit dem angegebenen Offset in Sekunden
    wenn (this.options_.starttime) {
      log.warn ('Die Option `starttime` an den Spieler weiterzugeben wird in 6.0 veraltet sein');
      this.currentTime (this.options_.starttime);
    }

    this.addClass('vjs-has-started');
    /**
     * Wird ausgelöst, wenn ein Video zum ersten Mal abgespielt wird. Nicht Teil der HLS-Spezifikation, und das ist
     * wahrscheinlich noch nicht die beste Implementierung, also sparsam verwenden. Wenn Sie keine haben
     * Um die Wiedergabe zu verhindern, verwenden Sie stattdessen `myPlayer.one ('play'); `.
     *
     * @event Spieler #firstplay
     * @deprecated Ab 6.0 ist das Ereignis firstplay veraltet.
     * @Typ {EventTarget~Event}
     * /
    this.trigger('firstplay');
  }

  /**
   * Das `Pause`-Ereignis, das vom {@link Tech} ausgelöst wurde, erneut auslösen.
   *
   * @fires Spieler #pause
   * @listens Technik #pause
   * @privat
   * /
  handleTechPause_ () {
    this.removeClass('vjs-playing');
    this.addClass('vjs-paused');
    /**
     * Wird immer dann ausgelöst, wenn die Medien angehalten wurden
     *
     * @event Spieler #pause
     * @Typ {EventTarget~Event}
     * /
    this.trigger ('Pause');
  }

  /**
   * Das `endet` Ereignis, das vom {@link Tech} ausgelöst wurde, erneut auslösen.
   *
   * @fires Spieler #ended
   * @listens Technik #ended
   * @privat
   * /
  handleTechended_ () {
    this.addClass('vjs-ended');
    this.removeClass('vjs-waiting');
    wenn (this.options_.loop) {
      this.currentTime(0);
      dieses.play ();
    } sonst wenn (! this.pausiert ()) {
      this.pause();
    }

    /**
     * Wird ausgelöst, wenn das Ende der Medienressource erreicht ist (CurrentTime == Dauer)
     *
     * @event Spieler #ended
     * @Typ {EventTarget~Event}
     * /
    this.trigger ('beendet');
  }

  /**
   * Wird ausgelöst, wenn die Dauer der Medienressource zum ersten Mal bekannt ist oder geändert wird
   *
   * @listens Tech#durationchange
   * @privat
   * /
  handleTechDurationChange_ () {
    this.duration (this.techGet_ ('Dauer'));
  }

  /**
   * Klicken Sie mit einem Klick auf das Medienelement, um es abzuspielen oder zu pausieren
   *
   * @param {EventTarget~Event} event
   *        das Ereignis, das diese Funktion ausgelöst hat
   *
   * @listens Technik #click
   * @privat
   * /
  handleTechClick_ (Ereignis) {
    //Wenn die Steuerung deaktiviert ist, sollte ein Klick die Wiedergabe nicht umschalten, weil
    //der Klick wird als Kontrolle betrachtet
    if (!this.controls_) {
      rückkehr;
    }

    wenn (
      this.options_ === undefiniert ||
      this.options_.userActions === undefiniert ||
      this.options_.UserActions.click === undefiniert ||
      This.Options_.UserActions.Click! == falsch
    ) {

      wenn (
        this.options_ !== undefiniert &&
        this.options_.userActions !== undefiniert &&
        typeofthis.options_.userActions.click === 'Funktion'
      ) {

        this.options_.userActions.click.call (dies, Ereignis);

      } sonst wenn (this.paused ()) {
        SilencePromise (this.play ());
      } sonst {
        this.pause();
      }
    }
  }

  /**
   * Doppelklicken Sie auf das Medienelement, um den Vollbildmodus aufzurufen oder zu verlassen
   *
   * @param {EventTarget~Event} event
   *        das Ereignis, das diese Funktion ausgelöst hat
   *
   * @listens Technik #dblclick
   * @privat
   * /
  handleTechDoubleClick_ (Ereignis) {
    if (!this.controls_) {
      rückkehr;
    }

    //wir wollen den Vollbildmodus nicht umschalten
    //beim Doppelklicken in eine Kontrollleiste oder ein Modal
    const inAllowEdels = Array.prototype.some.call (
      dieses.$$ ('.vjs-control-bar, .vjs-modal-dialog'),
      de => el.contains (event.target)
    );

    wenn (! in Allowels) {
      /*
       * Optionen. Benutzeraktionen. Doppelklicken
       *
       * Bei `undefined` oder `true` schaltet ein Doppelklick den Vollbildmodus um, sofern Steuerelemente vorhanden sind
       * Auf `false` setzen, um die Doppelklickverarbeitung zu deaktivieren
       * Auf eine Funktion setzen, die einen externen Doppelklick-Handler ersetzt
       * /
      wenn (
        this.options_ === undefiniert ||
        this.options_.userActions === undefiniert ||
        this.options_.UserActions.DoubleClick === undefiniert ||
        this.options_.userActions.Doubleclick! == falsch
      ) {

        wenn (
          this.options_ !== undefiniert &&
          this.options_.userActions !== undefiniert &&
          typeof this.options_.userActions.DoubleClick === 'Funktion'
        ) {

          this.options_.userActions.DoubleClick.Call (dies, Ereignis);

        } sonst wenn (this.isFullScreen ()) {
          this.exitFullscreen();
        } sonst {
          this.requestFullScreen ();
        }
      }
    }
  }

  /**
   * Tippen Sie einfach auf das Medienelement. Es wird den Benutzer umschalten
   * Aktivitätsstatus, in dem die Steuerelemente ein- und ausgeblendet werden.
   *
   * @listens Technik #tap
   * @privat
   * /
  handleTechTap_ () {
    this.userActive (! this.userActive ());
  }

  /**
   * Berühren Sie zum Starten
   *
   * @listens Technik #touchstart
   * @privat
   * /
  handleTechTouchStart_ () {
    this.userWasActive = this.userActive ();
  }

  /**
   * Berühren Sie, um sich zu bewegen
   *
   * @listens Technik #touchmove
   * @privat
   * /
  handleTechTouchMove_ () {
    wenn (this.userWasActive) {
      this.reportUserActivity();
    }
  }

  /**
   * Berühren Sie bis zum Ende
   *
   * @param {EventTarget~Event} event
   * das berührende Ereignis, das ausgelöst hat
   *        diese Funktion
   *
   * @listens Technik #touchend
   * @privat
   * /
  handleTechTouchEnd_ (Veranstaltung) {
    //Stoppt, dass auch Mausereignisse passieren
    wenn (event.cancelable) {
      event.preventDefault();
    }
  }

  /**
   * native Klickereignisse auf der SWF werden unter IE11, Win8.1RT nicht ausgelöst
   * verwende stattdessen Stageclick-Ereignisse, die innerhalb der SWF ausgelöst wurden
   *
   * @privat
   * @listens Stageclick
   * /
  handleStageClick_ () {
    this.reportUserActivity();
  }

  /**
   * @privat
   * /
  toggleFullScreenClass_ () {
    if (this.isFullscreen()) {
      this.addClass ('vjs-fullscreen');
    } sonst {
      this.removeClass ('vjs-fullscreen');
    }
  }

  /**
   * wenn das Fschange-Ereignis des Dokuments ausgelöst wird, ruft es dies auf
   * /
  documentFullScreenChange_ (e) {
    const targetPlayer = e.target.player;

    //wenn ein anderer Spieler im Vollbildmodus war
    //führe einen Nullcheck für TargetPlayer durch, da ältere Firefox's das Dokument als e.target angeben würden
    wenn (TargetPlayer && TargetPlayer! == das) {
      rückkehr;
    }

    const el = this.el ();
    sei isFS = document [this.FSAPI_.FullScreenElement] === el;

    wenn (! ISFs & el.matches) {
      isFS = el.matches (':' + this.fsapi_.FullScreen);
    } sonst wenn (! isFS && el.msMatchesSelector) {
      isFS = el.msMatchesSelector (':' + this.fsapi_.FullScreen);
    }

    this.isFullScreen (iSFS);
  }

  /**
   * Technische Änderung im Vollbildmodus durchführen
   *
   * @param {EventTarget~Event} event
   * das FullscreenChange-Ereignis, das diese Funktion ausgelöst hat
   *
   * @param {Object} data
   * die Daten, die mit der Veranstaltung gesendet wurden
   *
   * @privat
   * @listens Technik #fullscreenchange
   * @fires Player#fullscreenchange
   * /
  handleTechFullScreenChange_ (Ereignis, Daten) {
    wenn (Daten) {
      wenn (data.nativeIOSFullScreen) {
        this.addClass ('vjs-ios-native-fs');
        this.tech_.one ('webkitendfullscreen', () => {
          this.removeClass ('vjs-ios-native-fs');
        });
      }
      this.isFullScreen (Data.isFullScreen);
    }
  }

  handleTechFullScreenError_ (Ereignis, Fehler) {
    this.trigger ('Vollbildfehler', Fehler);
  }

  /**
   * @privat
   * /
  TogglePictureClass_ () {
    if (this.isInPictureInPicture()) {
      this.addClass ('vjs-bild-im-bild-im-bild-');
    } sonst {
      this.removeClass ('vjs-bild-im-bild');
    }
  }

  /**
   * Handle Tech Geben Sie Bild im Bild ein.
   *
   * @param {EventTarget~Event} event
   * das enterpictureinpicture-Ereignis, das diese Funktion ausgelöst hat
   *
   * @privat
   * @listens Technik #enterpictureinpicture
   * /
  handleTechenterPictureInPicture_ (Ereignis) {
    this.isinPictureInPicture (wahr);
  }

  /**
   * Handle Tech Leave Bild-im-Bild.
   *
   * @param {EventTarget~Event} event
   * das leavePictureInPictureInPicture-Ereignis, das diese Funktion ausgelöst hat
   *
   * @privat
   * @listens Technik #leavepictureinpicture
   * /
  handleTechLeavePictureInPicture_ (Ereignis) {
    this.isInPictureInPicture (falsch);
  }

  /**
   * Wird ausgelöst, wenn beim Laden eines Audio/Videos ein Fehler aufgetreten ist.
   *
   * @privat
   * @listens Technik #error
   * /
  HandletechError_ () {
    konstanter Fehler = this.tech_.error ();

    this.error (Fehler);
  }

  /**
   * Das `Textdata`-Ereignis, das vom {@link Tech} ausgelöst wurde, erneut auslösen.
   *
   * @fires Spieler #textdata
   * @listens Technik #textdata
   * @privat
   * /
  handleTechTextData_ () {
    sei data = null;

    wenn (arguments.length > 1) {
      Daten = Argumente [1];
    }

    /**
     * Wird ausgelöst, wenn wir ein Textdaten-Ereignis vom Techniker erhalten
     *
     * @event Spieler #textdata
     * @Typ {EventTarget~Event}
     * /
    this.trigger ('Textdaten', Daten);
  }

  /**
   * Ruft ein Objekt für zwischengespeicherte Werte ab.
   *
   * @return {Object}
   * holt den aktuellen Objekt-Cache
   * /
  getCache () {
    gib this.cache_ zurück;
  }

  /**
   * Setzt das interne Cache-Objekt zurück.
   *
   * Wenn Sie diese Funktion außerhalb des Player-Konstruktors oder der Reset-Methode verwenden, können
   * haben unbeabsichtigte Nebenwirkungen.
   *
   * @privat
   * /
  resetCache_ () {
    dieser.cache_ = {

      //Im Moment wird die CurrentTime nicht _wirklich_ zwischengespeichert, weil sie immer
      //vom Techniker abgerufen (siehe: CurrentTime). Der Vollständigkeit halber
      //wir setzen es hier auf Null, um sicherzustellen, dass wir, falls wir das tun, tatsächlich mit dem Caching beginnen
      //es, wir setzen es zusammen mit allem anderen zurück.
      aktuelleZeit: 0,
      InitTime: 0,
      InactivityTimeout: this.options_.inactivityTimeout,
      Dauer: NaN,
      Letzter Band: 1,
      lastPlaybackRate: this.defaultPlaybackRate (),
      Medien: null,
      src: „,
      Quelle: {},
      quellen: [],
      playbackRates: [],
      volumen: 1
    };
  }

  /**
   * Werte an die Wiedergabe-Technologie übergeben
   *
   * @param {string} [Methode]
   * die Methode zum Aufrufen
   *
   * @param {Objekt} arg
   * das zu übergebende Argument
   *
   * @privat
   * /
  techCall_ (Methode, arg) {
    //Wenn es noch nicht fertig ist, rufe Methode auf, wenn es soweit ist

    this.ready(function() {
      if (Methode in Middleware.allowedSetters) {
        gib middleware.set (this.middleware_, this.tech_, method, arg) zurück;

      } else if (Methode in middleware.allowedMediators) {
        gib middleware.mediate (this.middleware_, this.tech_, method, arg) zurück;
      }

      versuche {
        if (this.tech_) {
          this.tech_ [Methode] (arg);
        }
      } catch (e) {
        log(e);
        werfen e;
      }
    }, true);
  }

  /**
   * Get Calls kann nicht auf die Technik warten und muss es manchmal auch nicht.
   *
   * @param {string} -Methode
   * Technische Methode
   *
   * @return {Funktion|undefined}
   * die Methode oder undefiniert
   *
   * @privat
   * /
  TechGet_ (Methode) {
    wenn (! this.tech_ ||! this.tech_.isReady_) {
      rückkehr;
    }

    if (Methode in Middleware.allowedGetters) {
      gib middleware.get (this.middleware_, this.tech_, method) zurück;

    } else if (Methode in middleware.allowedMediators) {
      gib middleware.mediate (this.middleware_, this.tech_, method) zurück;
    }

    //Flash stirbt gerne und lädt neu, wenn du es versteckst oder neu positionierst.
    //In diesen Fällen verschwinden die Objektmethoden und wir erhalten Fehler.
    // TODO: Wird das für andere Techniker als Flash benötigt?
    //Wenn das passiert, werden wir die Fehler erkennen und die Techniker darüber informieren, dass es nicht mehr bereit ist.
    versuche {
      gib this.tech_ [Methode] () zurück;
    } catch (e) {

      //Beim Erstellen zusätzlicher Tech Libs ist eine erwartete Methode möglicherweise noch nicht definiert
      if (this.tech_ [Methode] === undefiniert) {
        log (`Video.js: Die $ {method} -Methode ist für die $ {this.techName_} -Wiedergabe-Technologie nicht definiert.`, e);
        werfen e;
      }

      //Wenn eine Methode für das Objekt nicht verfügbar ist, wird ein TypeError ausgelöst
      if (e.name === 'TypeError') {
        log (`Video.js: $ {method} ist für das $ {this.techName_} -Element der Wiedergabe-Technologie nicht verfügbar.`, e);
        this.tech_.isReady_ = falsch;
        werfen e;
      }

      //Wenn der Fehler unbekannt ist, einfach loggen und werfen
      log(e);
      werfen e;
    }
  }

  /**
   * Versuchen Sie, die Wiedergabe bei der ersten Gelegenheit zu starten.
   *
   * @return {Versprechen|undefiniert}
   * Gibt ein Versprechen zurück, wenn der Browser Promises unterstützt (oder eines
   * wurde als Option übergeben). Dieses Versprechen wird am eingelöst
   * der Rückgabewert von Play. Wenn dies undefiniert ist, erfüllt es die
   * Versprechenskette andernfalls wird die Versprechenskette erfüllt, wenn
   * Das Spielversprechen ist erfüllt.
   * /
  abspielen () {
    const PromiseClass = this.options_.Promise || window.Promise;

    if (PromiseClass) {
      gib eine neue PromiseClass zurück ((resolve) => {
        this.play_ (lösen);
      });
    }

    gib this.play_ () zurück;
  }

  /**
   * Die eigentliche Logik für das Abspielen erfordert einen Rückruf, der auf der
   * gibt den Wert des Spiels zurück. Dies ermöglicht es uns, das Spielversprechen einzuhalten, falls
   * ist eines in modernen Browsern.
   *
   * @privat
   * @param {Funktion} [Rückruf]
   * Der Callback, der aufgerufen werden soll, wenn die Techniker spielen, wird tatsächlich aufgerufen
   * /
  play_ (Rückruf = SilencePromise) {
    this.playCallbacks_.push (Rückruf);

    const issrcReady = Boolean (! this.changeSRC_ && (this.src () || this.currentSrc ()));

    //behandle Aufrufe von play_ irgendwie wie die `one` Event-Funktion
    wenn (this.waitToPlay_) {
      this.off (['bereit', 'loadstart'], this.waitToPlay_);
      this.waitToPlay_ = null;
    }

    //wenn der Player/Tech noch nicht bereit ist oder der SRC selbst noch nicht bereit ist
    //einen Anruf in die Warteschlange stellen, um auf `ready` oder `loadstart` abzuspielen
    wenn (! Das.ist bereit _ ||! ist SRC bereit) {
      this.waitToPlay_ = (e) => {
        this.play_ ();
      };
      this.one (['bereit', 'loadstart'], this.waitToPlay_);

      //Wenn wir in Safari sind, besteht eine hohe Wahrscheinlichkeit, dass der Ladestart nach dem Zeitraum der Geste ausgelöst wird
      //in diesem Fall müssen wir das Videoelement vorbereiten, indem wir load aufrufen, damit es rechtzeitig fertig ist
      wenn (! issrcReady & (browser.is_ANY_Safari | Browser.is_iOS)) {
        this.load ();
      }
      rückkehr;
    }

    //Wenn der Player/Techniker bereit ist und wir eine Quelle haben, können wir die Wiedergabe versuchen.
    const val = this.techGet_ ('abspielen');

    //Das Spiel wurde beendet, wenn der zurückgegebene Wert Null ist
    wenn (val === null) {
      this.runPlayTerminatedQueue_ ();
    } sonst {
      this.runPlayCallbacks_ (val);
    }
  }

  /**
   * Diese Funktionen werden ausgeführt, wenn das Spiel beendet ist. Wenn spiele
   * RunPlayCallbacks_ ist ausgeführt, diese Funktion wird nicht ausgeführt. Das ermöglicht uns
   * um zwischen einem abgebrochenen Spiel und einem tatsächlichen Call to Play zu unterscheiden.
   * /
  runPlayTerminatedQueue_ () {
    const queue = this.playTerminatedQueue_.slice (0);

    this.playTerminatedQueue_ = [];

    queue.forEach (Funktion (q) {
      q ();
    });
  }

  /**
   * Wenn sich ein Rückruf zum Abspielen verzögert, müssen wir diese ausführen
   * Rückrufe, wenn das Spiel tatsächlich vom Techniker aufgerufen wird. Diese Funktion
   * führt die verzögerten Callbacks aus und akzeptiert den Rückgabewert
   * von der Technik.
   *
   * @param {undefined|Promise} val
   * Der Rückgabewert des Technikers.
   * /
  runPlayCallbacks_ (val) {
    const Callbacks = this.playCallbacks_.slice (0);

    this.playCallbacks_ = [];
    //Clear Play TerminatedQueue, da wir ein echtes Spiel beendet haben
    this.playTerminatedQueue_ = [];

    callbacks.forEach (function (cb) {
      cb (oval);
    });
  }

  /**
   * Unterbrechen Sie die Videowiedergabe
   *
   * @return {Player}
   * Ein Verweis auf das Spielerobjekt, für das diese Funktion aufgerufen wurde
   * /
  Pause () {
    this.techCall_ ('Pause');
  }

  /**
   * Überprüfe, ob der Spieler angehalten hat oder noch nicht gespielt hat
   *
   * @return {boolean}
   * - falsch: wenn das Medium gerade abgespielt wird
   * - wahr: wenn das Medium gerade nicht abgespielt wird
   * /
  angehalten () {
    //Der Anfangszustand von pausiert sollte wahr sein (in Safari ist er tatsächlich falsch)
    return (this.techGet_ ('pausiert') === falsch)? falsch: wahr;
  }

  /**
   * Ruft ein TimeRange-Objekt ab, das die aktuellen Zeitbereiche darstellt, die der Benutzer
   * hat gespielt.
   *
   * @return {TimeRange}
   * Ein Zeitbereichsobjekt, das alle Zeitintervalle darstellt, die
   * wurde gespielt.
   * /
  gespielt() {
    gib this.techGet_ ('gespielt') || createTimeRange (0, 0) zurück;
  }

  /**
   * Gibt zurück, ob der Benutzer „scrubbt“ oder nicht. Schrubben ist
   * wenn der Benutzer auf den Fortschrittsbalken geklickt hat und
   * Ziehen Sie es entlang des Fortschrittsbalkens.
   *
   * @param {boolean} [isScrubbing]
   * ob der Benutzer schrubbt oder nicht
   *
   * @return {boolean}
   * Der Wert des Schrubbens beim Schrubben
   * /
  schrubben (isScrubbing) {
    if (typeof isScrubbing === 'undefiniert') {
      gib this.scrubbing_ zurück;
    }
    this.scrubbing_ =!! schrubbt;
    this.techCall_ ('setScrubbing', this.scrubbing_);

    wenn (isScrubbing) {
      this.addClass ('vjs-Scrubbing');
    } sonst {
      this.removeClass ('vjs-scrubbing');
    }
  }

  /**
   * Aktuelle Uhrzeit abrufen oder einstellen (in Sekunden)
   *
   * @param {number|string} [Sekunden]
   * Die Zeit, nach der gesucht werden muss, in Sekunden
   *
   * @return {number}
   * - die aktuelle Uhrzeit in Sekunden beim Abrufen
   * /
  CurrentTime (Sekunden) {
    if (Art der Sekunden! == 'undefiniert') {
      if (Sekunden < 0) {
        Sekunden = 0;
      }
      wenn (! Das.IsReady_ || Das.ChangingSRC_ ||! this.tech_ ||! this.tech_.isReady_) {
        this.cache_.initTime = Sekunden;
        this.off ('kann abspielen', this.boundApplyInitTime_);
        this.one ('kann abspielen', this.boundApplyInitTime_);
        rückkehr;
      }
      this.techCall_ ('setCurrentTime', Sekunden);
      this.cache_.initTime = 0;
      rückkehr;
    }

    //letzte CurrentTime zwischenspeichern und zurückgeben. Der Standardwert ist 0 Sekunden
    // //
    //Das Caching der CurrentTime soll eine massive Menge an Lesevorgängen auf den Technikern verhindern
    //CurrentTime beim Scrubben, bietet aber möglicherweise doch keinen großen Leistungsvorteil.
    //Sollte getestet werden. Außerdem muss etwas die aktuelle Uhrzeit ablesen, sonst wird der Cache
    //werde nie aktualisiert.
    this.cache_.currentTime = (this.techGet_ ('CurrentTime') || 0);
    gib this.cache_.currentTime zurück;
  }

  /**
   * Wenden Sie den im Cache gespeicherten Wert von initTime als CurrentTime an.
   *
   * @privat
   * /
  applyInitTime_ () {
    this.currentTime (this.cache_.initTime);
  }

  /**
   * Ruft normalerweise die Länge des Videos in Sekunden ab;
   * in allen außer den seltensten Anwendungsfällen wird ein Argument NICHT an die Methode übergeben
   *
   * > **HINWEIS**: Das Video muss mit dem Laden begonnen haben, bevor die Dauer erreicht werden kann
   * bekannt, und je nach Preload-Verhalten möglicherweise erst bekannt, wenn das Video gestartet wird
   * spielen.
   *
   * @fires Spieler #durationchange
   *
   * @param {Zahl} [Sekunden]
   * Die Dauer des Videos, die eingestellt werden sollen, in Sekunden
   *
   * @return {number}
   * - Die Dauer des Videos in Sekunden beim Abrufen
   * /
  Dauer (Sekunden) {
    if (Sekunden === undefiniert) {
      //gib NaN zurück, wenn die Dauer nicht bekannt ist
      gib this.cache_.duration zurück! == undefiniert? this.cache_.duration: NaN;
    }

    Sekunden = parseFloat (Sekunden);

    //Standardisieren Sie auf Infinity, um zu signalisieren, dass das Video live ist
    if (Sekunden < 0) {
      Sekunden = Unendlich;
    }

    wenn (Sekunden! == this.cache_.duration) {
      //Den zuletzt eingestellten Wert für optimiertes Scrubbing zwischenspeichern (insb. Blitz)
      // TODO: Benötigen Sie andere Technologien als Flash?
      this.cache_.duration = Sekunden;

      if (Sekunden === Unendlich) {
        this.addClass ('vjs-live');
      } sonst {
        this.removeClass ('vjs-live');
      }
      wenn (! Sinan (Sekunden) {
        //DurationChange nicht auslösen, es sei denn, der Wert für die Dauer ist bekannt.
        // @see [Spec]{@link https://www.w3.org/TR/2011/WD-html5-20110113/video.html#media-element-load-algorithm}

        /**
         * @event Spieler #durationchange
         * @Typ {EventTarget~Event}
         * /
        this.trigger('Daueränderung');
      }
    }
  }

  /**
   * Berechnet, wie viel Zeit im Video noch übrig ist. Kein Teil
   * der nativen Video-API.
   *
   * @return {number}
   * Die verbleibende Zeit in Sekunden
   * /
  Verbleibende Zeit () {
    gib this.duration () - this.currentTime () zurück;
  }

  /**
   * Eine Restzeitfunktion, die verwendet werden soll, wenn
   * Die Uhrzeit soll dem Benutzer direkt angezeigt werden.
   *
   * @return {number}
   * Die gerundete verbleibende Zeit in Sekunden
   * /
  Anzeige der verbleibenden Zeit () {
    gib Math.floor (this.duration ()) zurück - Math.floor (this.currentTime ());
  }

  // //
  //So etwas wie eine Reihe von Teilen des Videos, die heruntergeladen wurden.

  /**
   * Holen Sie sich ein TimeRange-Objekt mit einem Array der Zeiten des Videos
   * die heruntergeladen wurden. Wenn Sie nur den Prozentsatz der
   * Video, das heruntergeladen wurde, verwende BufferedPercent.
   *
   * @see [Gepufferte Spezifikation] {@link http://dev.w3.org/html5/spec/video.html#dom-media-buffered}
   *
   * @return {TimeRange}
   * Ein simuliertes TimeRange-Objekt (folgt der HTML-Spezifikation)
   * /
  gepuffert() {
    let buffered = this.techGet_ ('gepuffert');

    if (!buffered || !buffered.length) {
      gepuffert = createTimeRange(0, 0);
    }

    Rückkehr gepuffert;
  }

  /**
   * Ermittelt den Prozentsatz (als Dezimalzahl) des heruntergeladenen Videos.
   * Diese Methode ist nicht Teil der nativen HTML-Video-API.
   *
   * @return {number}
   * Eine Dezimalzahl zwischen 0 und 1, die den Prozentsatz darstellt
   * das ist gepuffert, 0 steht für 0% und 1 für 100
   * /
  bufferedPercent() {
    gib bufferedPercent (this.buffered (), this.duration ()) zurück;
  }

  /**
   * Ruft die Endzeit des letzten gepufferten Zeitbereichs ab
   * Dies wird in der Fortschrittsleiste verwendet, um alle Zeitbereiche zu kapseln.
   *
   * @return {number}
   * Das Ende des letzten gepufferten Zeitbereichs
   * /
  bufferEndend () {
    const buffered = this.buffered ();
    konstante Dauer = this.duration ();
    sei end = buffered.end (buffered.length - 1);

    wenn (Ende > Dauer) {
      ende = Dauer;
    }

    Ende der Rückfahrt;
  }

  /**
   * Rufen Sie die aktuelle Lautstärke des Mediums ab oder stellen Sie sie ein
   *
   * @param {Zahl} [percentAsDecimal]
   * Das neue Volumen als Dezimalprozent:
   *         - 0 ist stumm/0%/aus
   *         - 1.0 ist 100 %/vollständig
   *         - 0,5 ist das halbe Volumen oder 50%
   *
   * @return {number}
   * Die aktuelle Lautstärke in Prozent beim Abrufen
   * /
  Volumen (PercentAsDecimal) {
    lass es fliegen;

    wenn (PercentAsDecimal! == undefiniert) {
      //Wert auf einen Wert zwischen 0 und 1 zwingen
      vol = Math.max (0, Math.min (1, parseFloat (PercentAsDecimal)));
      this.cache_.volume = Band;
      this.techCall_ ('setVolume', vol);

      wenn (Band > 0) {
        this.lastVolume_ (vol);
      }

      rückkehr;
    }

    //Der Standardwert ist 1, wenn die aktuelle Lautstärke zurückgegeben wird.
    vol = parseFloat (this.techGet_ ('Volumen'));
    return (isNaN (vol))? 1: Band;
  }

  /**
   * Ermitteln Sie den aktuellen Stummschaltzustand oder schalten Sie die Stummschaltung ein oder aus
   *
   * @param {boolean} [stummgeschaltet]
   *        - true zum Stummschalten
   *        - false zum Aufheben der Stummschaltung
   *
   * @return {boolean}
   * - wahr, wenn die Stummschaltung an ist und wird
   * - falsch, wenn die Stummschaltung aus ist und
   * /
  stummgeschaltet (stummgeschaltet) {
    wenn (stummgeschaltet! == undefiniert) {
      this.techCall_ ('setMuted', stummgeschaltet);
      rückkehr;
    }
    gib this.techGet_ ('stummgeschaltet') || false zurück;
  }

  /**
   * Ruft den aktuellen DefaultMuted-Status ab oder schaltet DefaultMuted ein oder aus. defaultMuted
   * zeigt den Stummschaltzustand bei der ersten Wiedergabe an.
   *
   * ``js
   * var myPlayer = videojs ('some-player-id');
   *
   * MyPlayer.src (“ http://www.example.com/path/to/video.mp4 „);
   *
   *//get, sollte falsch sein
   *   console.log(myPlayer.defaultMuted());
   *//auf true gesetzt
   * myPlayer.defaultMuted (wahr);
   *//get sollte wahr sein
   *   console.log(myPlayer.defaultMuted());
   * ```
   *
   * @param {boolean} [DefaultStummgeschaltet]
   *        - true zum Stummschalten
   *        - false zum Aufheben der Stummschaltung
   *
   * @return {boolean|Spieler}
   * - wahr, wenn defaultMuted aktiviert ist und
   * - false wenn defaultMuted ausgeschaltet ist und
   * - Ein Verweis auf den aktuellen Spieler beim Einstellen
   * /
  Standardstummgeschaltet (DefaultStummgeschaltet) {
    if (defaultStummgeschaltet! == undefiniert) {
      gib this.techCall_ ('setDefaultMuted', defaultMuted) zurück;
    }
    gib this.techGet_ ('defaultMuted') zurück || false;
  }

  /**
   * Holen Sie sich die letzte Lautstärke oder stellen Sie sie ein
   *
   * @param {Zahl} [percentAsDecimal]
   * Das neue letzte Volumen als Dezimalprozent:
   *         - 0 ist stumm/0%/aus
   *         - 1.0 ist 100 %/vollständig
   *         - 0,5 ist das halbe Volumen oder 50%
   *
   * @return {number}
   * der aktuelle Wert von LastVolume als Prozentsatz beim Abrufen
   *
   * @privat
   * /
  LastVolume_ (Prozentsätze als Dezimalzahl) {
    wenn (PercentAsDecimal! = undefiniert & PercentasDecimal! == 0) {
      this.cache_.lastVolume = Prozentwert als Dezimalzahl;
      rückkehr;
    }
    gib this.cache_.lastVolume zurück;
  }

  /**
   * Prüfen Sie, ob die aktuelle Technologie den nativen Vollbildmodus unterstützt
   * (z. B. mit integrierten Steuerelementen wie iOS)
   *
   * @return {boolean}
   * wenn nativer Vollbildschirm unterstützt wird
   * /
  supportsFullScreen() {
    gib this.techGet_ ('supportsFullScreen') zurück || false;
  }

  /**
   * Überprüfe, ob sich der Player im Vollbildmodus befindet, oder teile dem Spieler mit, dass er
   * ist oder ist nicht im Vollbildmodus.
   *
   * > HINWEIS: Ab der neuesten HTML5-Spezifikation ist isFullScreen kein offizieller
   * Eigenschaft und stattdessen Document.FullScreenElement wird verwendet. Aber isFullScreen ist
   * immer noch ein wertvolles Eigentum für interne Spielerabläufe.
   *
   * @param {boolean} [ISFs]
   * Stellen Sie den aktuellen Vollbildstatus des Spielers ein
   *
   * @return {boolean}
   * - wahr, wenn der Vollbildmodus aktiviert ist und
   * - falsch wenn der Vollbildmodus ausgeschaltet ist und
   * /
  isFullScreen (iSFS) {
    wenn (ISFs! == undefiniert) {
      const oldValue = this.isFullScreen_;

      this.isFullScreen_ = Boolescher Wert (iSFS);

      //Wenn wir den Vollbildstatus geändert haben und wir uns im Präfixmodus befinden, lösen Sie den Vollbildwechsel aus
      //dies ist der einzige Ort, an dem wir FullscreenChange-Ereignisse für ältere Browser auslösen
      //Der FullWindow-Modus wird als Ereignis mit Präfix behandelt und erhält auch ein FullscreenChange-Ereignis
      wenn (this.isFullScreen_! == Alter Wert & this.fapi_.Präfix) {
        /**
           * @event Spieler #fullscreenchange
           * @Typ {EventTarget~Event}
           * /
        this.trigger ('Vollbildwechsel');
      }

      this.toggleFullScreenClass_ ();
      rückkehr;
    }
    gib this.isFullScreen_ zurück;
  }

  /**
   * Erhöhen Sie die Größe des Videos auf den Vollbildmodus
   * In einigen Browsern wird der Vollbildmodus nativ nicht unterstützt, daher wird
   * „Vollfenstermodus“, in dem das Video das Browserfenster füllt.
   * In Browsern und Geräten, die den nativen Vollbildmodus unterstützen, manchmal
   * Die Standardsteuerelemente des Browsers werden angezeigt und nicht der benutzerdefinierte Skin von Video.js.
   * Dies beinhaltet die meisten Mobilgeräte (iOS, Android) und ältere Versionen von
   * Safari.
   *
   * @param {Objekt} [Vollbildoptionen]
   * Überschreibt die Vollbild-Optionen des Players
   *
   * @fires Player#fullscreenchange
   * /
  requestFullScreen (FullScreenOptions) {
    const PromiseClass = this.options_.Promise || window.Promise;

    if (PromiseClass) {
      const self = this;

      return new PromiseClass((resolve, reject) => {
        function offHandler() {
          self.off('fullscreenerror', errorHandler);
          self.off('fullscreenchange', changeHandler);
        }
        function changeHandler() {
          offHandler();
          resolve();
        }
        function errorHandler(e, err) {
          offHandler();
          zurückweisen(err);
        }

        self.one('fullscreenchange', changeHandler);
        self.one('fullscreenerror', errorHandler);

        const promise = self.requestFullScreenHelper_ (FullScreenOptions);

        if (Versprechen) {
          promise.then(offHandler, offHandler);
          promise.then(resolve, reject);
        }
      });
    }

    gib this.requestFullScreenHelper_ () zurück;
  }

  requestFullScreenHelper_ (FullScreenOptions) {
    lass fsOptions;

    //Übergeben Sie in spezifikationskonformen Browsern nur Vollbild-Optionen an requestFullScreen.
    //Verwende die Standardeinstellungen oder die vom Spieler konfigurierte Option, sofern sie nicht direkt an diese Methode übergeben werden.
    wenn (! this.fsapi_.Präfix) {
      fsOptions = this.options_.fullscreen & this.options_.fullscreen.options || {};
      wenn (FullScreenOptions! == undefiniert) {
        fsOptions = Vollbildoptionen;
      }
    }

    //Diese Methode funktioniert wie folgt:
    //1. wenn eine Vollbild-API verfügbar ist, benutze sie
    //1. requestFullScreen mit möglichen Optionen aufrufen
    //2. Wenn wir ein Versprechen von oben bekommen haben, benutze es, um isFullScreen () zu aktualisieren
    //2. Andernfalls, falls der Techniker den Vollbildmodus unterstützt, rufen Sie dort `enterFullScreen` auf.
    //Dies wird insbesondere für iPhone, ältere iPads und Browser ohne Safari unter iOS verwendet.
    //3. Verwenden Sie andernfalls den Modus „FullWindow“
    if (this.fsApi_.requestFullscreen) {
      const promise = this.el_ [this.fsapi_.requestFullScreen] (fsOptions);

      if (Versprechen) {
        promise.then () => this.isFullScreen (wahr), () => this.isFullScreen (falsch));
      }

      versprechen zurückgeben;
    } else if (this.tech_.supportsFullScreen() && !this.options_.preferFullWindow === true) {
      //Wir können die video.js Steuerelemente nicht im Vollbildmodus verwenden, aber wir können sie im Vollbildmodus verwenden
      //mit nativen Steuerelementen
      this.techCall_ ('Vollbild eingeben');
    } sonst {
      //Der Vollbildmodus wird nicht unterstützt, also dehnen wir das Videoelement einfach aus
      //fülle das Viewport
      this.enterFullWindow ();
    }
  }

  /**
   * Bringen Sie das Video auf die normale Größe zurück, nachdem Sie sich im Vollbildmodus befunden haben
   *
   * @fires Player#fullscreenchange
   * /
  ExitFullScreen () {
    const PromiseClass = this.options_.Promise || window.Promise;

    if (PromiseClass) {
      const self = this;

      return new PromiseClass((resolve, reject) => {
        function offHandler() {
          self.off('fullscreenerror', errorHandler);
          self.off('fullscreenchange', changeHandler);
        }
        function changeHandler() {
          offHandler();
          resolve();
        }
        function errorHandler(e, err) {
          offHandler();
          zurückweisen(err);
        }

        self.one('fullscreenchange', changeHandler);
        self.one('fullscreenerror', errorHandler);

        const promise = self.exitFullScreenHelper_ ();

        if (Versprechen) {
          promise.then(offHandler, offHandler);
          //Ordne das Versprechen unseren Lösungs-/Ablehnungsmethoden zu
          promise.then(resolve, reject);
        }
      });
    }

    gib this.exitFullScreenHelper_ () zurück;
  }

  exitFullScreenHelper_ () {
    if (this.fsApi_.requestFullscreen) {
      const promise = dokument [this.fsapi_.exitFullScreen] ();

      if (Versprechen) {
        //Wir teilen das Versprechen hier auf, also wollen wir das
        //potenzieller Fehler, damit diese Kette keine unbehandelten Fehler hat
        silencePromise (promise.then () => this.isFullScreen (falsch)));
      }

      versprechen zurückgeben;
    } else if (this.tech_.supportsFullScreen() && !this.options_.preferFullWindow === true) {
      this.techCall_ ('Vollbild beenden');
    } sonst {
      this.exitFullWindow();
    }
  }

  /**
   * Wenn der Vollbildmodus nicht unterstützt wird, können wir den
   * Videocontainer so breit, wie es der Browser zulässt.
   *
   * @fires Spieler #enterFullWindow
   * /
  Geben Sie FullWindow () ein {
    this.isFullScreen (wahr);
    this.isFullWindow = wahr;

    //Speichern des ursprünglichen Doc-Overflow-Werts, zu dem zurückgekehrt werden soll, wenn der Vollbildmodus ausgeschaltet ist
    this.DocOrigOverflow = document.documentElement.style.overflow;

    //Füge einen Listener für die ESC-Taste hinzu, um den Vollbildmodus zu verlassen
    events.on (document, 'keydown', this.boundFullWindowOnescKey_);

    //Alle Scrollbalken ausblenden
    document.documentElement.style.overflow = 'versteckt';

    //Vollbildstile anwenden
    dom.addClass (document.body, 'vjs-full window');

    /**
     * @event Spieler #enterFullWindow
     * @Typ {EventTarget~Event}
     * /
    this.trigger ('EnterFullWindow');
  }

  /**
   * Suchen Sie nach einem Anruf, um entweder das gesamte Fenster zu verlassen oder
   * Vollbild auf ESC-Taste
   *
   * @param {string} Ereignis
   * Ereignis, um zu überprüfen, ob die Taste gedrückt wurde
   * /
  FullWindowOnESCKey (Ereignis) {
    if (keycode.isEventKey(event, 'Esc')) {
      if (this.isFullScreen () === wahr) {
        wenn (! this.isFullWindow) {
          this.exitFullscreen();
        } sonst {
          this.exitFullWindow();
        }
      }
    }
  }

  /**
   * Vollständiges Fenster verlassen
   *
   * @fires Spieler #exitFullWindow
   * /
  exitFullWindow () {
    this.isFullScreen (falsch);
    this.isFullWindow = falsch;
    Events.off(document, 'keydown', this.boundFullWindowOnEscKey_);

    //Scrollbalken einblenden.
    document.documentElement.style.overflow = this.DocOrigOverflow;

    //Vollbildstile entfernen
    dom.removeClass (document.body, 'vjs-full window');

    //Ändere die Größe der Box, des Controllers und des Posters auf die Originalgröße
    //this.positionAll ();
    /**
     * @event Spieler #exitFullWindow
     * @Typ {EventTarget~Event}
     * /
    this.trigger ('exitFullWindow');
  }

  /**
   * Deaktivieren Sie den Bild-im-Bild-Modus.
   *
   * @param {boolean} value
   * - true deaktiviert den Bild-im-Bild-Modus
   * - false aktiviert den Bild-im-Bild-Modus
   * /
  disablePictureInPicture (Wert) {
    if (Wert === undefiniert) {
      gib this.techGet_ ('disablePictureInPicture') zurück;
    }
    this.techCall_ ('setDisablePictureInPicture', Wert);
    this.options_.disablePictureInPicture = Wert;
    this.trigger ('Bild im Bild geändert deaktivieren');
  }

  /**
   * Überprüfe, ob sich der Spieler im Bild-im-Bild-Modus befindet, oder teile dem Spieler mit, dass er
   * ist im Bild-im-Bild-Modus oder nicht.
   *
   * @param {boolean} [ISPip]
   * Stellen Sie den aktuellen Bild-im-Bild-Status des Spielers ein
   *
   * @return {boolean}
   * - wahr, wenn Picture-in-Picture aktiviert ist und
   * - falsch, wenn Picture-im-Picture ausgeschaltet ist und
   * /
  isinPictureInPicture (isPIP) {
    wenn (ISPIP! == undefiniert) {
      this.isInPictureInPicture_ =!! Ein Slip;
      this.togglePictureClass_ ();
      rückkehr;
    }
    kehre zurück!! Das. ist auf dem Bild in Bild _;
  }

  /**
   * Erstellen Sie ein schwebendes Videofenster, das sich immer über anderen Fenstern befindet, so dass die Benutzer
   * weiterhin Medien konsumieren, während sie mit anderen Inhaltsseiten interagieren, oder
   * anwendungen auf ihrem Gerät.
   *
   * @see [Spec]{@link https://wicg.github.io/picture-in-picture}
   *
   * @fires Spieler #enterpictureinpicture
   *
   * @return {Versprechen}
   *         Ein Versprechen mit einem Picture-in-Picture-Fenster.
   * /
  requestPictureInPicture() {
    if ('PictureInPictureEnabled' im Dokument && this.disablePictureInPicture () === false) {
      /**
       * Dieses Ereignis wird ausgelöst, wenn der Spieler den Bild-im-Bild-Modus betritt
       *
       * @event Spieler #enterpictureinpicture
       * @Typ {EventTarget~Event}
       * /
      gib this.techGet_ ('requestPictureInPicture') zurück;
    }
  }

  /**
   * Beenden Sie den Bild-im-Bild-Modus.
   *
   * @see [Spec]{@link https://wicg.github.io/picture-in-picture}
   *
   * @fires Spieler #leavepictureinpicture
   *
   * @return {Versprechen}
   * Ein Versprechen.
   * /
  ExitPictureInPicture () {
    if ('PictureInPictureEnabled' im Dokument) {
      /**
       * Dieses Ereignis wird ausgelöst, wenn der Spieler das Bild im Bildmodus verlässt
       *
       * @event Spieler #leavepictureinpicture
       * @Typ {EventTarget~Event}
       * /
      gib document.exitPictureInPicture () zurück;
    }
  }

  /**
   * Wird aufgerufen, wenn dieser Player den Fokus hat und eine Taste gedrückt wird, oder wenn
   * Jede Komponente dieses Players erhält einen Tastendruck, den er nicht verarbeitet.
   * Dies ermöglicht spielerweite Hotkeys (entweder wie unten definiert oder optional)
   * durch eine externe Funktion).
   *
   * @param {EventTarget~Event} event
   *        Das "Keydown"-Ereignis, das zum Aufruf dieser Funktion geführt hat.
   *
   * @listens keydown
   * /
  handleKeyDown(event) {
    const {userActions} = this.options_;

    //Raus, wenn Hotkeys nicht konfiguriert sind.
    wenn (! Benutzeraktionen ||! UserActions.Hotkeys) {
      rückkehr;
    }

    //Funktion, die bestimmt, ob ein Element ausgeschlossen werden soll oder nicht
    //Umgang mit Hotkeys.
    const excludeElement = (de) => {
      const tagName = el.tagName.toLowerCase ();

      //Der erste und einfachste Test ist für `contenteditable` Elemente.
      wenn (el.isContentEditable) {
        true zurückgeben;
      }

      //Eingaben, die diesen Typen entsprechen, lösen weiterhin die Hotkey-Behandlung aus als
      //es sind keine Texteingaben.
      const allowedInputTypes = [
        schaltfläche",
        'Kästchen',
        'versteckt',
        'Funk',
        'zurücksetzen',
        'absenden'
      ];

      if (TagName === 'Eingabe') {
        return AllowedInputTypes.indexOf (el.type) === -1;
      }

      //Der letzte Test erfolgt anhand des Tag-Namens. Diese Tags werden vollständig ausgeschlossen.
      const excludedTags = ['Textbereich'];

      gib excludedTags.indexOf (tagName) zurück! = -1;
    };

    //Aussteigen, wenn sich der Benutzer auf ein interaktives Formularelement konzentriert.
    wenn (excludeElement (this.el_.ownerDocument.activeElement)) {
      rückkehr;
    }

    if (typeof UserActions.Hotkeys === 'Funktion') {
      UserActions.Hotkeys.Call (dies, Ereignis);
    } sonst {
      this.handleHotKeys (Ereignis);
    }
  }

  /**
   * Wird aufgerufen, wenn dieser Spieler ein Hotkey-Keydown-Ereignis erhält.
   * Unterstützte spielerweite Hotkeys sind:
   *
   * f - Vollbild umschalten
   * m - schaltet die Stummschaltung ein
   * k oder Space - schaltet zwischen Wiedergabe/Pause um
   *
   * @param {EventTarget~Event} event
   *        Das "Keydown"-Ereignis, das zum Aufruf dieser Funktion geführt hat.
   * /
  handleHotKeys (Ereignis) {
    const Hotkeys = this.options_.UserActions? this.options_.userActions.Hotkeys: {};

    //setze FullScreenKey, MuteKey, playPauseKey von `hotkeys`, verwende Standardwerte, falls nicht gesetzt
    konstant {
      fullScreenKey = keydownEvent => keycode.isEventKey (keydownEvent, 'f'),
      muteKey = keydownEvent => keycode.isEventKey (keydownEvent, 'm'),
      playPauseKey = KeydownEvent => (keyCode.ISEventKey (keydownEvent, 'k') || keycode.isEventKey (keydownEvent, 'Space'))
    } = Tastenkombinationen;

    if (fullScreenKey.call (this, event)) {
      event.preventDefault();
      event.stopPropagation();

      const fsToggle = Component.getComponent ('fullScreenToggle');

      if (document [this.fsapi_.FullScreenEnabled]! == falsch) {
        fstoggle.prototype.handleClick.Call (dies, Ereignis);
      }

    } sonst wenn (muteKey.call (this, event)) {
      event.preventDefault();
      event.stopPropagation();

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

      muteToggle.prototype.handleClick.call (dies, Ereignis);

    } sonst wenn (playPauseKey.call (this, event)) {
      event.preventDefault();
      event.stopPropagation();

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

      playToggle.prototype.handleClick.call (dies, Ereignis);
    }
  }

  /**
   * Prüfe, ob der Spieler einen bestimmten Mimetype spielen kann
   *
   * @see https://www.w3.org/TR/2011/WD-html5-20110113/video.html#dom-navigator-canplaytype
   *
   * @param {string} type
   *        Der zu prüfende Mimetyp
   *
   * @return {string}
   *         'wahrscheinlich', 'vielleicht', oder '' (leere Zeichenfolge)
   * /
  canPlayType (Typ) {
    können;

    //Die einzelnen Wiedergabetechnologien in der Reihenfolge der Optionen durchspielen
    für (sei i = 0, j = this.options_.techOrder; i < j.length; i++) {
      const TechName = j [i];
      let tech = Tech.getTech(techName);

      // Unterstützung des alten Verhaltens, dass Techs als Komponenten registriert werden.
      // Entfernen, sobald das veraltete Verhalten entfernt wird.
      if (!tech) {
        tech = Component.getComponent(techName);
      }

      // Prüfen Sie, ob der aktuelle Tech definiert ist, bevor Sie fortfahren
      if (!tech) {
        log.error(`Der "${techName}" tech ist undefiniert. Die Überprüfung der Browserunterstützung für diese Technologie wurde übersprungen");
        weiter;
      }

      // Prüfen Sie, ob der Browser diese Technologie unterstützt
      wenn (tech.isSupported ()) {
        can = tech.canPlayType (Typ);

        wenn (kann) {
          zurückgeben können;
        }
      }
    }

    zurückgeben '';
  }

  /**
   * Wählen Sie die Quelle basierend auf der technischen Reihenfolge oder der Quellreihenfolge
   * Verwendet die Auswahl der Quellreihenfolge, wenn `options.sourceOrder` wahr ist. Andernfalls
   * standardmäßig die Auswahl der technischen Reihenfolge
   *
   * @param {Array} Quellen
   * Die Quellen für ein Medienelement
   *
   * @return {object|Boolean}
   * Objekt der Quelle und technische Bestellung oder falsch
   * /
  SelectSource (Quellen) {
    //Ruft nur die in `TechOrder` angegebenen Techniker ab, die existieren und von der unterstützt werden
    //aktuelle Plattform
    Const techs =
      this.options_.techOrder
        .map (TechName) => {
          gib [TechName, Tech.getTech (TechName)] zurück;
        })
        .filter (([TechName, Technik]) => {
          // Prüfen Sie, ob der aktuelle Tech definiert ist, bevor Sie fortfahren
          wenn (Technik) {
            // Prüfen Sie, ob der Browser diese Technologie unterstützt
            gib tech.isSupported () zurück;
          }

          log.error(`Der "${techName}" tech ist undefiniert. Die Überprüfung der Browserunterstützung für diese Technologie wurde übersprungen");
          return false;
        });

    //Iteriere jedes `innerArray`-Element einmal pro `outerArray`-Element und führe es aus
    //`tester` mit beiden. Wenn `tester` einen nicht falschen Wert zurückgibt, beenden Sie das Programm vorzeitig und kehren Sie zurück
    //dieser Wert.
    const findFirstPassingTechSourcePair = Funktion (outerArray, innerArray, Tester) {
      gefunden lassen;

      outerArray.some (outerChoice) => {
        gib innerArray.some ((innerChoice) => {zurück
          gefunden = Tester (OuterChoice, InnerChoice);

          wenn (gefunden) {
            true zurückgeben;
          }
        });
      });

      Rückgabe gefunden;
    };

    lass SourceandTech gründen;
    const flip = (fn) => (a, b) => fn (b, a);
    const finder = ([TechName, tech], Quelle) => {
      if (tech.canPlaySource (source, this.options_ [techname.toLowerCase ()]) {
        return {Quelle, Technologie: TechName};
      }
    };

    //Abhängig von der Richtigkeit von `options.sourceOrder` vertauschen wir die Reihenfolge der Techniker und Quellen
    //um anhand ihrer Priorität aus ihnen auszuwählen.
    wenn (this.options_.sourceOrder) {
      //Reihenfolge an erster Stelle
      foundSourceAndTech = findFirstPassingTechSourcePair (Quellen, Technologien, flip (Finder));
    } sonst {
      //Technisch erste Bestellung
      FoundSourceAndTech = FindFirstPassingTechSourcePair (Techniker, Quellen, Finder);
    }

    gib foundSourceandTech zurück || falsch;
  }

  /**
   * Führt die Quelleinstellung aus und ruft Logik ab
   *
   * @param {Tech~SourceObject|Tech~SourceObject[]|string} [Quelle]
   *        Ein SourceObject, ein Array von SourceObjects oder ein String, der auf
   *        eine URL zu einer Medienquelle. Es wird _höchst empfohlen_, dass ein Objekt
   *        oder Array von Objekten verwendet, so dass die Quellenauswahl
   *        algorithmen können den "Typ" berücksichtigen.
   *
   *        Wenn nicht angegeben, fungiert diese Methode als Getter.
   * @param {boolean} isRetry
   * Zeigt an, ob dies intern als Ergebnis eines Wiederholungsversuchs aufgerufen wird
   *
   * @return {string|undefined}
   *         Wenn das Argument "Quelle" fehlt, wird die aktuelle Quelle zurückgegeben
   *         URL. Andernfalls gibt nichts zurück/undefined zurück.
   * /
  handlesRC_ (Quelle, isRetry) {
    // Getter-Verwendung
    if (Quelltyp === 'undefiniert') {
      gib this.cache_.src zurück || „;
    }

    //Setzt das Wiederholungsverhalten für die neue Quelle zurück
    wenn (this.resetRetryOnError_) {
      this.resetRetryOnError_ ();
    }

    //filtere ungültige Quellen heraus und verwandle unsere Quelle in
    //ein Array von Quellobjekten
    const sources = FilterSource (Quelle);

    //wenn eine Quelle übergeben wurde, ist sie ungültig, weil
    //es wurde auf ein Array der Länge Null gefiltert. Also müssen wir
    //zeige einen Fehler
    if (!sources.length) {
      this.setTimeout(function() {
        this.error({ code: 4, message: this.options_.notSupportedMessage });
      }, 0);
      rückkehr;
    }

    //erste Quellen
    this.changingSrc_ = true;

    //Aktualisiere die zwischengespeicherte Quellliste nur, wenn wir nach einem Fehler nicht erneut versuchen, eine neue Quelle zu verwenden,
    //da wir in diesem Fall die ausgefallene (n) Quelle (n) in den Cache aufnehmen wollen
    if (!isRetry) {
      this.cache_.sources = sources;
    }

    this.updateSourceCaches_ (Quellen [0]);

    //MiddlewareSource ist die Quelle, nachdem sie durch Middleware geändert wurde
    Middleware.setSource (this, sources [0], (MiddlewareSource, mws) => {
      this.middleware_ = mws;

      //da SourceSet asynchron ist, müssen wir den Cache erneut aktualisieren, nachdem wir eine Quelle ausgewählt haben, da
      //Die ausgewählte Quelle könnte aufgrund des Cache-Updates über diesem Callback nicht in Ordnung sein.
      if (!isRetry) {
        this.cache_.sources = sources;
      }

      this.updateSourceCaches_ (Middleware-Quelle);

      const err = this.src_ (MiddlewareSource);

      if (err) {
        wenn (sources.length > 1) {
          gib this.handlesRC_ (sources.slice (1)) zurück;
        }

        this.changingSrc_ = false;

        //Wir müssen das in ein Timeout einbauen, um den Leuten die Möglichkeit zu geben, Fehler-Event-Handler hinzuzufügen
        this.setTimeout(function() {
          this.error({ code: 4, message: this.options_.notSupportedMessage });
        }, 0);

        //wir konnten keinen passenden Techniker finden, aber lassen Sie uns den Delegierten trotzdem benachrichtigen, dass dies der richtige ist
        //das braucht einen besseren Kommentar dazu, warum das nötig ist
        this.triggerReady();

        rückkehr;
      }

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

    //Versuche es mit einer anderen verfügbaren Quelle, falls diese vor der Wiedergabe fehlschlägt.
    wenn (this.options_.retryOnError && sources.length > 1) {
      konstante Wiederholung = () => {
        //Entferne das Fehlermodal
        this.error(null);
        this.handlesRC_ (sources.slice (1), wahr);
      };

      const stopListeningForErrors = () => {
        this.off('error', retry);
      };

      this.one ('Fehler', erneut versuchen);
      this.one ('spielt', stoppListeningForErrors);

      this.resetRetryOnError_ = () => {
        this.off('error', retry);
        this.off ('wird abgespielt', stoppListeningForErrors);
      };
    }
  }

  /**
   * Holen Sie sich die Videoquelle oder stellen Sie sie ein.
   *
   * @param {Tech~SourceObject|Tech~SourceObject[]|string} [Quelle]
   *        Ein SourceObject, ein Array von SourceObjects oder ein String, der auf
   *        eine URL zu einer Medienquelle. Es wird _höchst empfohlen_, dass ein Objekt
   *        oder Array von Objekten verwendet, so dass die Quellenauswahl
   *        algorithmen können den "Typ" berücksichtigen.
   *
   *        Wenn nicht angegeben, fungiert diese Methode als Getter.
   *
   * @return {string|undefined}
   *         Wenn das Argument "Quelle" fehlt, wird die aktuelle Quelle zurückgegeben
   *         URL. Andernfalls gibt nichts zurück/undefined zurück.
   * /
  src (Quelle) {
    gib this.handlesRC_ zurück (Quelle, falsch);
  }

  /**
   * Setzt das Quellobjekt auf die Technologie, gibt einen booleschen Wert zurück, der angibt, ob
   * Es gibt eine Technologie, die die Quelle abspielen kann oder nicht
   *
   * @param {Tech~SourceObject} source
   * Das Quellobjekt, das auf dem Tech gesetzt werden soll
   *
   * @return {boolean}
   * - Stimmt, wenn es keine Technologie gibt, um diese Quelle abzuspielen
   *         - Falsch sonst
   *
   * @privat
   * /
  src_ (Quelle) {
    const sourceTech = this.selectSource ([Quelle]);

    wenn (! SourceTech) {
      true zurückgeben;
    }

    wenn (! titleCaseEquals (sourceTech.tech, this.techName_) {
      this.changingSrc_ = true;
      //lade diese Technologie mit der ausgewählten Quelle
      this.loadtech_ (Quelltech.tech, Quelltech.Quelle);
      this.tech_.ready () => {
        this.changingSrc_ = false;
      });
      return false;
    }

    //warte, bis der Techniker bereit ist, die Quelle einzustellen
    //und stelle es wenn möglich synchron ein (#2326)
    this.ready(function() {

      //Die setSource-Tech-Methode wurde mit Quellhandlern hinzugefügt
      //also werden ältere Techniker es nicht unterstützen
      //Wir müssen den direkten Prototyp für den Fall überprüfen, in dem Unterklassen
      //der Technologie unterstützt keine Quellcodehandler
      wenn (this.tech_.constructor.prototype.hasOwnProperty ('setSource')) {
        this.techCall_ ('setSource', Quelle);
      } sonst {
        this.techCall_ ('src', source.src);
      }

      this.changingSrc_ = false;
    }, true);

    return false;
  }

  /**
   * Beginne mit dem Laden der src-Daten.
   * /
  laden () {
    this.techCall_ ('laden');
  }

  /**
   * Setze den Player zurück. Lädt die erste Technologie im TechOrder,
   * entfernt alle Textspuren im existierenden `tech`,
   * und ruft `reset` auf dem `tech` auf.
   * /
  reset() {
    const PromiseClass = this.options_.Promise || window.Promise;

    wenn (this.paued () ||! PromiseClass) {
      Das.doReset_ ();
    } sonst {
      const playPromise = this.play ();

      silencePromise (playPromise.then () => this.doReset_ ());
    }
  }

  oder Reset_ () {
    if (this.tech_) {
      this.tech_.clearTracks ('text');
    }
    this.resetCache_();
    diese.poster („);
    this.loadTech_ (this.options_.techOrder [0], null);
    this.techCall_ ('zurücksetzen');
    this.resetControlBarUI_ ();
    if (isEvented(this)) {
      this.trigger ('playerreset');
    }
  }

  /**
   * Setze die Benutzeroberfläche der Control Bar zurück, indem du Untermethoden aufrufst, die das Zurücksetzen
   * alle Komponenten der Control Bar
   * /
  resetControlBarUI_ () {
    this.resetProgressBar_ ();
    this.resetPlaybackRate_ ();
    this.resetVolumeBar_ ();
  }

  /**
   * Setze den Fortschritt der Technik zurück, sodass der Fortschrittsbalken in der Benutzeroberfläche zurückgesetzt wird
   * /
  resetProgressBar_ () {
    this.currentTime(0);

    const {DurationDisplay, RemainingTimeDisplay} = this.controlBar || {};

    wenn (DurationDisplay) {
      Dauer Display.updateContent ();
    }

    wenn (RemainingTimeDisplay) {
      Verbleibende Zeit Display.updateContent ();
    }
  }

  /**
   * Wiedergabeverhältnis zurücksetzen
   * /
  Wiedergaberate zurücksetzen_ () {
    this.playbackRate (this.defaultPlaybackRate ());
    this.handleTechRateChange_ ();
  }

  /**
   * Lautstärkeleiste zurücksetzen
   * /
  ResetVolumeBar_ () {
    diese.volume (1.0);
    this.trigger ('Volumenänderung');
  }

  /**
   * Gibt alle aktuellen Quellobjekte zurück.
   *
   * @return {Tech~SourceObject[]}
   * Die aktuellen Quellobjekte
   * /
  Aktuelle Quellen () {
    const source = this.currentSource ();
    konstante Quellen = [];

    //gehe von `{}` oder `{src}` aus
    if (Object.keys (source) .length! == 0) {
      sources.push (Quelle);
    }

    gib this.cache_.sources || sources zurück;
  }

  /**
   * Gibt das aktuelle Quellobjekt zurück.
   *
   * @return {Tech~SourceObject}
   * Das aktuelle Quellobjekt
   * /
  Aktuelle Quelle () {
    gib this.cache_.source zurück || {};
  }

  /**
   * Gibt die vollständig qualifizierte URL des aktuellen Quellwerts zurück, z. B. http://mysite.com/video.mp4
   * Kann in Verbindung mit `CurrentType` verwendet werden, um beim Neuaufbau des aktuellen Quellobjekts zu helfen.
   *
   * @return {string}
   * Die aktuelle Quelle
   * /
  currentSrc() {
    gib this.currentSource () && this.currentSource () .src || „;
  }

  /**
   * Ruft den aktuellen Quelltyp ab, z. B. Video/MP4
   * Dadurch können Sie das aktuelle Quellobjekt neu erstellen, sodass Sie es laden können
   * Quelle und Technik später
   *
   * @return {string}
   * Der Quell-MIME-Typ
   * /
  Aktueller Typ () {
    gib this.currentSource () && this.currentSource () .type || „;
  }

  /**
   * Holen Sie sich das Preload-Attribut oder legen Sie es fest
   *
   * @param {boolean} [Wert]
   * - wahr bedeutet, dass wir vorladen sollten
   * - falsch bedeutet, dass wir nicht vorladen sollten
   *
   * @return {string}
   * Der Preload-Attributwert beim Abrufen
   * /
  Vorspannung (Wert) {
    if (Wert !== undefiniert) {
      this.techCall_ ('setPreload', Wert);
      this.options_.preload = Wert;
      rückkehr;
    }
    gib this.techGet_ ('preload') zurück;
  }

  /**
   * Rufen Sie die Autoplay-Option ab oder stellen Sie sie ein. Wenn das ein boolescher Wert ist, wird es
   * modifizieren Sie das Attribut der Technologie. Wenn dies eine Zeichenfolge ist, ist das Attribut auf
   * Die Technologie wird entfernt und `Player` kümmert sich um das Autoplay bei Ladestarts.
   *
   * @param {boolean|Zeichenfolge} [Wert]
   * - wahr: Autoplay mithilfe des Browserverhaltens
   * - falsch: nicht automatisch abspielen
   * - 'play': rufe play () bei jedem Ladestart auf
   * - 'muted': rufe muted () auf und spiele () bei jedem Ladestart
   * - 'any': rufe play () bei jedem Ladestart auf. Wenn das fehlschlägt, rufe muted () und dann play () auf.
   * - *: Bei anderen als den hier aufgelisteten Werten wird `autoplay` auf true gesetzt
   *
   * @return {booles|Zeichenfolge}
   * Der aktuelle Wert von Autoplay beim Abrufen
   * /
  Autoplay (Wert) {
    // Getter-Verwendung
    if (Wert === undefiniert) {
      gib this.options_.autoplay || false zurück;
    }

    lass TechAutoplay;

    //wenn der Wert ein gültiger String ist, setze ihn darauf oder normalisiere `true` auf 'play', falls nötig
    if (typeof value === 'string' && (/(any|play|muted)/) .test (value) || value === true && this.options_.normalizeAutoplay) {
      this.options_.autoplay = Wert;
      this.manualAutoPlay_ (Wertetyp === 'Zeichenfolge'? Wert: 'abspielen');
      TechAutoplay = falsch;

    //Jeder falsche Wert setzt Autoplay im Browser auf Falsch,
    //lass uns das Gleiche machen
    } sonst wenn (! Wert) {
      this.options_.autoplay = falsch;

    //Jeder andere Wert (also wahr) setzt Autoplay auf true
    } sonst {
      this.options_.autoplay = wahr;
    }

    TechAutoplay = Typ von TechAutoPlay === 'undefiniert'? this.options_.autoplay: TechAutoPlay;

    //wenn wir keinen Techniker haben, stehen wir nicht in der Warteschlange
    //ein setAutoPlay-Aufruf bei tech ready. Wir machen das, weil
    //Die Autoplay-Option wird im Konstruktor übergeben und wir
    //muss nicht zweimal gesetzt werden
    if (this.tech_) {
      this.techCall_ ('setAutoplay', TechAutoPlay);
    }
  }

  /**
   * Setzt das playsinline-Attribut oder entfernt es.
   * Playsinline teilt dem Browser mit, dass die Wiedergabe nicht im Vollbildmodus bevorzugt wird.
   *
   * @param {boolean} [Wert]
   * - true bedeutet, dass wir standardmäßig versuchen sollten, Inline zu spielen
   * - false bedeutet, dass wir den Standard-Wiedergabemodus des Browsers verwenden sollten,
   * was in den meisten Fällen Inline ist. iOS Safari ist eine bemerkenswerte Ausnahme
   * und spielt standardmäßig im Vollbildmodus ab.
   *
   * @return {string|Spieler}
   * - der aktuelle Wert von playsinline
   *         - den Player bei der Einstellung
   *
   * @see [Spec]{@link https://html.spec.whatwg.org/#attr-video-playsinline}
   * /
  playsinline (Wert) {
    if (Wert !== undefiniert) {
      this.techCall_ ('setPlaysInLine', Wert);
      this.options_.playsinline = Wert;
      dies zurückgeben;
    }
    gib this.techGet_ ('playsinline') zurück;
  }

  /**
   * Ruft das Loop-Attribut für das Videoelement ab oder legt es fest.
   *
   * @param {boolean} [Wert]
   * - wahr bedeutet, dass wir das Video in einer Endlosschleife abspielen sollten
   * - falsch bedeutet, dass wir das Video nicht in einer Endlosschleife abspielen sollten
   *
   * @return {boolean}
   * Der aktuelle Wert von Loop beim Abrufen
   * /
  Schleife (Wert) {
    if (Wert !== undefiniert) {
      this.techCall_ ('setLoop', Wert);
      this.options_.loop = Wert;
      rückkehr;
    }
    gib this.techGet_ ('loop') zurück;
  }

  /**
   * Rufen Sie die Quell-URL für das Posterbild ab oder legen Sie sie fest
   *
   * @fires Player#Plakatwechsel
   *
   * @param {Zeichenfolge} [src]
   * Quell-URL des Posterbilds
   *
   * @return {string}
   * Der aktuelle Wert des Posters beim Kauf
   * /
  plakat (src) {
    if (src === undefiniert) {
      gib this.poster_ zurück;
    }

    //Der richtige Weg, ein Poster zu entfernen, besteht darin, es als leere Zeichenfolge zu setzen
    //andere falsche Werte lösen Fehler aus
    if (!src) {
      src = „;
    }

    if (src === this.poster_) {
      rückkehr;
    }

    //aktualisiere die interne Poster-Variable
    this.poster_ = src;

    //aktualisiere das Technologie-Poster
    this.techCall_ ('setPoster', src);

    this.isPosterFromTech_ = false;

    //Komponenten darauf aufmerksam machen, dass das Poster gesetzt wurde
    /**
     * Dieses Event wird ausgelöst, wenn das Posterbild auf dem Player geändert wird.
     *
     * @event Spieler #posterchange
     * @Typ {EventTarget~Event}
     * /
    this.trigger('posterchange');
  }

  /**
   * Einige Techniker (z. B. YouTube) können eine Posterquelle in einem
   * asynchroner Weg. Wir möchten, dass die Poster-Komponente dies verwendet
   * Quelltext als Poster, um die Kontrollen der Technologie zu vertuschen.
   * (Die Play-Schaltfläche von YouTube). Wir wollen dies jedoch nur verwenden.
   * Quelle falls der Player-User kein Poster durchgestellt hat
   * die normalen APIs.
   *
   * @fires Player#Plakatwechsel
   * @listens Technik #posterchange
   * @privat
   * /
  handleTechPosterChange_ () {
    wenn ((! this.poster_ | this.options_.techCanOverridePoster) & this.tech_ & this.tech_.poster) {
      const newPoster = this.tech_.poster () || „;

      wenn (NewPoster! == dieses.Poster_) {
        this.poster_ = Neues Poster;
        this.isPosterFromTech_ = wahr;

        //Lass die Komponenten wissen, dass sich das Poster geändert hat
        this.trigger('posterchange');
      }
    }
  }

  /**
   * Ermittelt oder legt fest, ob die Steuerelemente angezeigt werden oder nicht.
   *
   * @fires Spieler #controlsenabled
   *
   * @param {boolean} [bool]
   * - wahr, um die Steuerung einzuschalten
   * - Falsch, um die Steuerung auszuschalten
   *
   * @return {boolean}
   * Der aktuelle Wert der Kontrollen beim Abrufen
   * /
  Steuerelemente (bool) {
    if (bool === undefiniert) {
      kehre zurück!! this.controls_;
    }

    bool = !!bool;

    // Kein Änderungsereignis auslösen, wenn es sich nicht tatsächlich geändert hat
    wenn (this.controls_ === bool) {
      rückkehr;
    }

    this.controls_ = bool;

    wenn (this.usingNativeControls ()) {
      this.techCall_ ('setControls', bool);
    }

    wenn (this.controls_) {
      this.removeClass ('vjs-controls-disabled');
      this.addClass('vjs-controls-enabled');
      /**
       * @event Spieler #controlsenabled
       * @Typ {EventTarget~Event}
       * /
      this.trigger ('Steuerelemente aktiviert');
      if (!this.usingNativeControls()) {
        this.addTechControlsListeners_();
      }
    } sonst {
      this.removeClass ('vjs-controls-enabled');
      this.addClass('vjs-controls-disabled');
      /**
       * @event Spieler #controlsdisabled
       * @Typ {EventTarget~Event}
       * /
      this.trigger ('Steuerung deaktiviert');
      if (!this.usingNativeControls()) {
        this.removeTechControlsListeners_();
      }
    }
  }

  /**
   * Schaltet die nativen Steuerungen ein/aus. Native Steuerelemente sind die integrierten Steuerelemente
   * Geräte (z. B. Standard-iPhone-Steuerungen) oder andere Technologien
   * (z. B. Vimeo Controls)
   * **Dies sollte nur durch den aktuellen Techniker festgelegt werden, da nur der Techniker es weiß
   * ob es native Steuerelemente unterstützen kann**
   *
   * @fires Spieler #usingnativecontrols
   * @fires Spieler #usingcustomcontrols
   *
   * @param {boolean} [bool]
   * - true, um die nativen Steuerelemente zu aktivieren
   * - false, um native Steuerelemente auszuschalten
   *
   * @return {boolean}
   * Der aktuelle Wert nativer Steuerelemente beim Abrufen
   * /
  Verwenden von NativeControls (bool) {
    if (bool === undefiniert) {
      kehre zurück!! Das. using NativeControls_;
    }

    bool = !!bool;

    // Kein Änderungsereignis auslösen, wenn es sich nicht tatsächlich geändert hat
    wenn (this.UsingNativeControls_ === bool) {
      rückkehr;
    }

    this.UsingNativeControls_ = bool;

    wenn (this.UsingNativeControls_) {
      this.addClass ('vjs-using-native-controls');

      /**
       * Der Player verwendet die native Gerätesteuerung
       *
       * @event Spieler #usingnativecontrols
       * @Typ {EventTarget~Event}
       * /
      this.trigger ('native Steuerelemente verwenden');
    } sonst {
      this.removeClass ('vjs-using-native-controls');

      /**
       * Der Spieler verwendet die benutzerdefinierten HTML-Steuerelemente
       *
       * @event Spieler #usingcustomcontrols
       * @Typ {EventTarget~Event}
       * /
      this.trigger ('benutzerdefinierte Steuerelemente verwenden');
    }
  }

  /**
   * Setze oder erhalte den aktuellen MediaError
   *
   * @fires Spieler #error
   *
   * @param {mediaError|string|Zahl} [Fehler]
   * Ein MediaError oder eine Zeichenkette/Zahl, die umgewandelt werden soll
   * in einen MediaError
   *
   * @return {MediaError|null}
   * Der aktuelle MediaError beim Abrufen von (oder null)
   * /
  error(err) {
    if (err === undefiniert) {
      gib this.error_ || null; zurück
    }

    //erlaube Hooks, das Fehlerobjekt zu modifizieren
    haken ('beforeerror') .forEach ((hookFunction) => {
      const newErr = hookFunction (das, err);

      wenn (! (
        (isObject (newErr) &&! Array.isArray (neuere Version) |
        typeof newErr === 'Zeichenfolge' ||
        typeof newErr === 'Zahl' ||
        Neuerer R === null
      ) {
        this.log.error ('Bitte gib einen Wert zurück, den MediaError in den Beforeerror−Hooks erwartet');
        rückkehr;
      }

      err = neuerer RR;
    });

    //Unterdrückt die erste Fehlermeldung für keine kompatible Quelle bis
    //Benutzerinteraktion
    if (this.options_.suppressNotSupportedError &&
        Fehler & Fehlercode === 4
    ) {
      const triggerSuppressedError = Funktion () {
        this.error (Fehler);
      };

      this.options_.suppressNotSupportedError = falsch;
      this.any (['click', 'touchstart'], triggerSuppressedError);
      this.one ('loadstart', function () {
        this.off (['click', 'touchstart'], triggerSuppressedError);
      });
      rückkehr;
    }

    //auf Standard zurücksetzen
    wenn (Fehler === null) {
      this.error_ = Fehler;
      this.removeClass ('vjs-Fehler');
      wenn (this.errorDisplay) {
        this.errorDisplay.close ();
      }
      rückkehr;
    }

    this.error_ = new MediaError(err);

    //füge dem Player den vjs-error-Klassennamen hinzu
    this.addClass ('vjs-Fehler');

    //protokolliert den Namen des Fehlertyps und jede Nachricht
    //IE11 protokolliert „[Object Object]“ und forderte Sie auf, die Nachricht zu erweitern, um das Fehlerobjekt zu sehen
    log.error (`(CODE: $ {this.error_.code} $ {mediaError.errorTypes [this.error_.code]})`, this.error_.message, this.error_);

    /**
     * @event Spieler #error
     * @Typ {EventTarget~Event}
     * /
    this.trigger('error');

    //Hooks über den Fehler pro Spieler benachrichtigen
    hooks ('Fehler') .forEach ((hookFunction) => hookFunction (dies, diese.error_));

    rückkehr;
  }

  /**
   * Benutzeraktivität melden
   *
   * @param {Object} event
   * Event-Objekt
   * /
  reportUserActivity (Ereignis) {
    this.userActivity_ = true;
  }

  /**
   * Abrufen/setzen, ob der Benutzer aktiv ist
   *
   * @fires Spieler #useractive
   * @fires Spieler #userinactive
   *
   * @param {boolean} [bool]
   * - wahr, wenn der Benutzer aktiv ist
   * - falsch, wenn der Benutzer inaktiv ist
   *
   * @return {boolean}
   * Der aktuelle Wert von UserActive beim Abrufen
   * /
  userActive (bool) {
    if (bool === undefiniert) {
      gib this.UserActive_; zurück
    }

    bool = !!bool;

    if (bool === this.UserActive_) {
      rückkehr;
    }

    this.UserActive_ = bool;

    wenn (this.userActive_) {
      this.userActivity_ = true;
      this.removeClass ('vjs-user-inactive');
      this.addClass ('vjs-user-active');
      /**
       * @event Spieler #useractive
       * @Typ {EventTarget~Event}
       * /
      this.trigger ('useractive');
      rückkehr;
    }

    //Chrome/Safari/IE hat Fehler, bei denen das Ändern des Cursors möglich ist
    //löst ein Mousemove-Ereignis aus. Dies verursacht ein Problem, wenn Sie sich verstecken.
    //der Cursor, wenn der Benutzer inaktiv ist, und eine Mausbewegung signalisiert dem Benutzer
    //Aktivität. Es ist unmöglich, in den inaktiven Modus zu wechseln. Konkret
    //Das passiert im Vollbildmodus, wenn wir den Cursor wirklich verstecken müssen.
    // //
    //Wenn das in ALLEN Browsern behoben ist, kann es entfernt werden
    //https://code.google.com/p/chromium/issues/detail?id=103041
    if (this.tech_) {
      this.tech_.one ('mousemove', Funktion (e) {
        e. stopPropagation ();
        e.preventDefault ();
      });
    }

    this.userActivity_ = false;
    this.removeClass ('vjs-user-active');
    this.addClass ('vjs-user-inactive');
    /**
     * @event Spieler #userinactive
     * @Typ {EventTarget~Event}
     * /
    this.trigger ('userinactive');
  }

  /**
   * Anhand des Timeout-Werts auf Benutzeraktivitäten achten
   *
   * @privat
   * /
  ListenForUserActivity _ () {
    lass die Maus laufen;
    lass LastMoveX;
    lass LastMovey;
    const handleActivity = fn.Bind (this, this.reportUserActivity);

    const handleMouseMove = Funktion (e) {
      //#1068 - Beugt Mousemove-Spamming vor
      //Chrome-Fehler: https://code.google.com/p/chromium/issues/detail?id=366970
      wenn (e.ScreenX! == LastMoveX | e.SCREENY! == Letzter Film) {
        LastMoveX = e.ScreenX;
        LastMovey = e.Screeny;
        handleActivity();
      }
    };

    const handleMouseDown = Funktion () {
      handleActivity();
      // Solange sie das Gerät berühren oder die Maus gedrückt halten,
      // Wir betrachten sie als aktiv, auch wenn sie ihren Finger oder ihre Maus nicht bewegen.
      // Wir wollen also weiterhin aktualisieren, dass sie aktiv sind
      this.clearInterval(mouseInProgress);
      //UserActivity=True jetzt setzen und das Intervall auf die gleiche Zeit setzen
      //da das ActivityCheck-Intervall (250) sicherstellen sollte, dass wir das nie verpassen
      //nächster ActivityCheck
      mouseinProgress = this.setInterval (handleActivity, 250);
    };

    const handleMouseUpAndMouseLeave = Funktion (Ereignis) {
      handleActivity();
      //Stoppt das Intervall, in dem die Aktivität aufrechterhalten wird, wenn die Maus/Berührung ausgeschaltet ist
      this.clearInterval(mouseInProgress);
    };

    //Jede Mausbewegung wird als Benutzeraktivität betrachtet
    this.on ('mousedown', handleMouseDown);
    this.on ('mousemove', handleMouseMove);
    this.on ('mouseup', handleMouseUp und MouseLeave);
    this.on ('mouseleave', handleMouseUp und Mouseleave);

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

    //Behebt einen Fehler auf Android und iOS, bei dem beim Tippen auf ProgressBar (wenn die Kontrollleiste angezeigt wird)
    //ControlBar würde standardmäßig nicht mehr ausgeblendet sein.
    wenn (ControlBar &&! Browser.is_iOS &&! Browser.is_Android) {

      ControlBar.on ('mouseenter', function (event) {
        if (this.player () .options_.inactivityTimeout! == 0) {
          this.player () .cache_.inactivityTimeout = this.player () .options_.inactivityTimeout;
        }
        this.player () .options_.InactivityTimeout = 0;
      });

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

    }

    //Achte auf die Tastaturnavigation
    //Sollte das InProgress-Intervall wegen der Tastenwiederholung nicht verwenden müssen
    this.on ('keydown', handleActivity);
    this.on ('keyup', handleActivity);

    //Führe alle 250 Millisekunden ein Intervall aus, anstatt alles reinzustopfen
    //die Mousemove/Touchmove-Funktion selbst, um Leistungseinbußen zu verhindern.
    //`this.reportUserActivity` setzt this.userActivity_ einfach auf true, was
    //wird dann von dieser Schleife aufgenommen
    //http://ejohn.org/blog/learning-from-twitter/
    lass InactivityTimeout;

    this.setInterval (Funktion () {
      //Überprüfe, ob eine Maus-/Touch-Aktivität stattgefunden hat
      if (!this.userActivity_) {
        rückkehr;
      }

      //Setze den Aktivitätstracker zurück
      this.userActivity_ = false;

      //Wenn der Benutzerstatus inaktiv war, setzen Sie den Status auf aktiv
      this.userActive(true);

      //Löscht ein vorhandenes Inaktivitäts-Timeout, um den Timer erneut zu starten
      this.clearTimeout (InactivityTimeout);

      const timeout = this.options_.InactivityTimeout;

      wenn (timeout <= 0) {
        rückkehr;
      }

      //In <timeout> Millisekunden, wenn keine Aktivität mehr stattgefunden hat
      //Der Benutzer wird als inaktiv betrachtet
      inactivityTimeout = this.setTimeout (function () {
        //Schützen Sie sich vor dem Fall, dass der InactivityTimeout nur ausgelöst werden kann
        //bevor die nächste Benutzeraktivität von der Aktivitätscheckschleife aufgenommen wird
        //verursacht ein Flackern
        if (!this.userActivity_) {
          this.userActive (falsch);
        }
      }, timeout);

    }, 250);
  }

  /**
   * Ruft die aktuelle Wiedergabegeschwindigkeit ab oder legt sie fest. Eine Wiedergabegeschwindigkeit von
   * 1,0 steht für normale Geschwindigkeit und 0,5 würde für halbe Geschwindigkeit stehen
   * Wiedergabe zum Beispiel.
   *
   * @see https://html.spec.whatwg.org/multipage/embedded-content.html#dom-media-playbackrate
   *
   * @param {Zahl} [Rate]
   * Neue Wiedergabegeschwindigkeit zum Einstellen.
   *
   * @return {number}
   * Die aktuelle Wiedergabegeschwindigkeit beim Abrufen von oder 1,0
   * /
  PlaybackRate (Rate) {
    if (rate !== undefined) {
      //HINWEIS: this.cache_.lastPlaybackRate wird vom Tech-Handler gesetzt
      //das ist oben registriert
      this.techCall_ ('setPlaybackRate', rate);
      rückkehr;
    }

    if (this.tech_ && this.tech_.featuresPlaybackRate) {
      gib this.cache_.lastPlaybackRate || this.techGet_ ('playbackRate') zurück;
    }
    1,0 zurück;
  }

  /**
   * Ruft die aktuelle Standardwiedergaberate ab oder legt sie fest. Eine Standardwiedergaberate von
   * 1,0 steht beispielsweise für eine normale Geschwindigkeit und 0,5 für eine Wiedergabe mit halber Geschwindigkeit.
   * DefaultPlaybackRate gibt nur an, wie hoch die anfängliche PlaybackRate eines Videos war, nicht
   * nicht die aktuelle PlaybackRate.
   *
   * @see https://html.spec.whatwg.org/multipage/embedded-content.html#dom-media-defaultplaybackrate
   *
   * @param {Zahl} [Rate]
   * Neue Standardwiedergaberate zum Einstellen.
   *
   * @return {Nummer|Spieler}
   * - Die Standard-Wiedergabegeschwindigkeit beim Abrufen von oder 1.0
   *         - den Player bei der Einstellung
   * /
  defaultPlaybackRate (Rate) {
    if (rate !== undefined) {
      gib this.techCall_ ('setDefaultPlaybackRate', rate) zurück;
    }

    if (this.tech_ && this.tech_.featuresPlaybackRate) {
      gib this.techGet_ ('defaultPlaybackRate') zurück;
    }
    1,0 zurück;
  }

  /**
   * Ruft das Audio-Flag ab oder setzt es
   *
   * @param {boolean} bool
   * - true signalisiert, dass es sich um einen Audioplayer handelt
   * - falsch signalisiert, dass es sich nicht um einen Audioplayer handelt
   *
   * @return {boolean}
   * Der aktuelle Wert von isAudio beim Abrufen
   * /
  isAudio (bool) {
    wenn (bool! == undefiniert) {
      this.isAudio_ =!! boolen;
      rückkehr;
    }

    kehre zurück!! Das.isAudio_;
  }

  EnableAudioOnlyUI_ () {
    //Aktualisiere das Styling sofort, um die Kontrollleiste anzuzeigen, damit wir ihre Höhe ermitteln können
    this.addClass ('vjs-Nur-Audio-Modus');

    const playerChildren = this.children ();
    const ControlBar = this.getChild ('ControlBar');
    const ControlBarHeight = ControlBar & ControlBar.currentHeight ();

    //Verstecke alle Player-Komponenten außer der Kontrollleiste. Komponenten der Steuerleiste
    //werden nur für Videos benötigt, die mit CSS ausgeblendet werden
    PlayerChildren.forEach (Kind => {
      wenn (Kind === ControlBar) {
        rückkehr;
      }

      wenn (child.el_ &&! child.hasClass ('vjs-hidden')) {
        kind.hide ();

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

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

    //Stelle die Höhe des Spielers genauso ein wie in der Kontrollleiste
    this.height (ControlBarHeight);
    this.trigger('audioonlymodechange');
  }

  disableAudioOnlyUI_ () {
    this.removeClass ('vjs-Nur-Audio-Modus');

    //Spielerkomponenten anzeigen, die zuvor ausgeblendet waren
    this.audioOnlyCache_.hiddenChildren.forEach (kind => child.show ());

    //Spielergröße zurücksetzen
    this.height (this.AudioOnlyCache_.playerHeight);
    this.trigger('audioonlymodechange');
  }

  /**
   * Ruft den aktuellen AudioOnlyMode-Status ab oder setzt AudioOnlyMode auf true oder false.
   *
   * Wenn Sie diesen Wert auf `true` setzen, werden alle Player-Komponenten außer der Kontrollleiste ausgeblendet,
   * sowie Steuerleistenkomponenten, die nur für Video benötigt werden.
   *
   * @param {boolean} [Wert]
   * Der Wert, auf den AudioOnlyMode gesetzt werden soll.
   *
   * @return {Promise|boolean}
   * Ein Versprechen wird zurückgegeben, wenn der Status gesetzt wird, und ein boolescher Wert, wenn der Status gesetzt wird
   *        der gegenwärtige Zustand
   * /
  AudioOnlyMode (Wert) {
    if (Art des Wertes! == 'boolean' || Wert === this.AudioOnlyMode_) {
      gib this.AudioOnlyMode_ zurück;
    }

    this.AudioOnlyMode_ = Wert;

    const PromiseClass = this.options_.Promise || window.Promise;

    if (PromiseClass) {
      //Aktiviere den Modus „Nur Audio“
      wenn (Wert) {
        const exitPromises = [];

        //Fullscreen und PiP werden in AudioOnlyMode nicht unterstützt, also beenden Sie es, wenn nötig.
        if (this.isInPictureInPicture()) {
          exitPromises.push (this.exitPictureInPicture ());
        }

        if (this.isFullscreen()) {
          exitPromises.push (this.exitFullScreen ());
        }

        wenn (this.AudioPosterMode ()) {
          exitPromises.push (this.AudioPosterMode (falsch));
        }

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

      //Deaktiviere den Modus „Nur Audio“
      return promiseClass.resolve () .then () => this.disableAudioOnlyUI_ ());
    }

    wenn (Wert) {
      if (this.isInPictureInPicture()) {
        this.exitPictureInPicture ();
      }

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

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

  EnablePosterModeUI_ () {
    //Verstecken Sie das Videoelement und zeigen Sie das Posterbild an, um PosterModeUI zu aktivieren
    const tech = this.tech_ && this.tech_;

    tech.hide ();
    this.addClass ('vjs-audio-poster-mode');
    this.trigger('audiopostermodechange');
  }

  deaktiviere PosterModeUI_ () {
    //Zeige das Videoelement und blende das Posterbild aus, um PosterModeUI zu deaktivieren
    const tech = this.tech_ && this.tech_;

    tech.show ();
    this.removeClass ('vjs-audio-poster-mode');
    this.trigger('audiopostermodechange');
  }

  /**
   * Ruft den aktuellen AudioPosterMode-Status ab oder setzt AudioPosterMode auf true oder false
   *
   * @param {boolean} [Wert]
   * Der Wert, auf den AudioPosterMode gesetzt werden soll.
   *
   * @return {Promise|boolean}
   * Ein Versprechen wird zurückgegeben, wenn der Status gesetzt wird, und ein boolescher Wert, wenn der Status gesetzt wird
   *        der gegenwärtige Zustand
   * /
  AudioPosterMode (Wert) {

    if (Art des Wertes! == 'boolean' || Wert === this.AudioPosterMode_) {
      gib this.AudioPosterMode_ zurück;
    }

    this.AudioPosterMode_ = Wert;

    const PromiseClass = this.options_.Promise || window.Promise;

    if (PromiseClass) {

      wenn (Wert) {

        if (this.audioOnlyMode()) {
          const AudioOnlyModePromise = this.AudioOnlyMode (falsch);

          gib AudioOnlyModePromise.then () => {
            //aktiviere den Audio-Poster-Modus, nachdem der Nur-Audiomodus deaktiviert wurde
            this.enablePosterModeUI_();
          });
        }

        return PromiseClass.resolve().then(() => {
          //aktiviere den Audio-Poster-Modus
          this.enablePosterModeUI_();
        });
      }

      return PromiseClass.resolve().then(() => {
        //Deaktiviere den Audio-Poster-Modus
        this.disablePosterModeUI_();
      });
    }

    wenn (Wert) {

      if (this.audioOnlyMode()) {
        this.audioOnlyMode (falsch);
      }

      this.enablePosterModeUI_();
      rückkehr;
    }

    this.disablePosterModeUI_();
  }

  /**
   * Eine Hilfsmethode zum Hinzufügen eines {@link TextTrack} zu unserem
   * {@link TextTrackList}.
   *
   * Zusätzlich zu den W3C-Einstellungen ermöglichen wir das Hinzufügen zusätzlicher Informationen über Optionen.
   *
   * @see http://www.w3.org/html/wg/drafts/html/master/embedded-content-0.html#dom-media-addtexttrack
   *
   * @param {Zeichenfolge} [Art]
   * die Art von TextTrack, den Sie hinzufügen
   *
   * @param {string} [label]
   * das Label, das dem TextTrack-Label gegeben werden soll
   *
   * @param {string} [Sprache]
   * die Sprache, die auf dem TextTrack eingestellt werden soll
   *
   * @return {TextTrack|undefined}
   * der TextTrack, der hinzugefügt oder undefiniert wurde
   * wenn es keine Technologie gibt
   * /
  addTextTrack(kind, label, language) {
    if (this.tech_) {
      gib this.tech_.addTextTrack zurück (Art, Bezeichnung, Sprache);
    }
  }

  /**
   * Erstellen Sie ein Remote-Objekt {@link TextTrack} und ein {@link HtmlTrackElement}.
   * Wenn ManualCleanup auf false gesetzt ist, wird der Track automatisch entfernt
   * bei Quelländerungen.
   *
   * @param {Object} options
   * Optionen, die während der Erstellung an {@link htmlTrackElement} übergeben werden. Sehen
   * {@link HtmlTrackElement} für Objekteigenschaften, die Sie verwenden sollten.
   *
   * @param {boolean} [manualCleanup=true] wenn auf false gesetzt, wird der TextTrack
   * bei einem Quellwechsel entfernt
   *
   * @return {htmlTrackElement}
   * das HTMLTrackElement, das erstellt und hinzugefügt wurde
   * zur HtmlTrackElementList und zur Fernbedienung
   * Text-Trackliste
   *
   * @deprecated Der Standardwert des Parameters "manualCleanup" ist standardmäßig
   * in zukünftigen Versionen von Video.js auf „falsch“
   * /
  addRemoteTextTrack(options, manualCleanup) {
    if (this.tech_) {
      gib this.tech_.addRemoteTextTrack zurück (Optionen, ManualCleanup);
    }
  }

  /**
   * Entferne eine Fernbedienung {@link TextTrack} aus dem jeweiligen
   * {@link textTrackList} und {@link htmlTrackElementList}.
   *
   * @param {Object} verfolgen
   * Remote {@link TextTrack} zum Entfernen
   *
   * @return {undefined}
   * gibt nichts zurück
   * /
  removeRemoteTextTrack (obj = {}) {
    lass {track} = obj;

    if (!track) {
      Titel = obj;
    }

    //destrukturiere die Eingabe in ein Objekt mit einem Track-Argument, standardmäßig mit Argumenten [0]
    //Das gesamte Argument wird standardmäßig auf ein leeres Objekt gesetzt, wenn nichts übergeben wurde

    if (this.tech_) {
      gib this.tech_.removeRemoteTextTrack (track) zurück;
    }
  }

  /**
   * Ruft verfügbare Metriken zur Qualität der Medienwiedergabe ab, wie sie vom W3C's Media
   * Wiedergabequalität API.
   *
   * @see [Spec]{@link https://wicg.github.io/media-playback-quality}
   *
   * @return {Object|undefined}
   * Ein Objekt mit unterstützten Qualitätsmetriken für die Medienwiedergabe oder undefiniert, falls vorhanden
   * ist keine Technologie oder die Technologie unterstützt sie nicht.
   * /
  getVideoPlaybackQuality() {
    gib this.techGet_ ('getVideoPlaybackQuality') zurück;
  }

  /**
   * Videobreite abrufen
   *
   * @return {number}
   * aktuelle Videobreite
   * /
  VideoBreite () {
    gib this.tech_ && this.tech_.videoWidth & this.tech_.videoWidth () || 0;
  }

  /**
   * Videohöhe abrufen
   *
   * @return {number}
   * aktuelle Videohöhe
   * /
  Videohöhe () {
    gib this.tech_ && this.tech_.VideoHeight && this.tech_.videoHeight () || 0; zurück
  }

  /**
   * Der Sprachcode des Spielers.
   *
   * Das Ändern der Sprache wird ausgelöst
   * [Sprachwechsel] {@link Spieler #Event: Sprachwechsel}
   * welche Komponenten zur Aktualisierung des Steuertextes verwendet werden können.
   * ClickableComponent aktualisiert seinen Steuertext standardmäßig am
   * [Sprachwechsel] {@link Spieler #Event: Sprachwechsel}.
   *
   * @fires Spieler #languagechange
   *
   * @param {Zeichenfolge} [Code]
   * der Sprachcode, auf den der Player eingestellt werden soll
   *
   * @return {string}
   * Der aktuelle Sprachcode beim Abrufen
   * /
  Sprache (Code) {
    if (code === undefiniert) {
      gib this.language_ zurück;
    }

    wenn (this.language_! == Zeichenfolge (Code) .toLowerCase ()) {
      this.language_ = Zeichenfolge (Code) .toLowerCase ();

      //beim ersten Init ist es möglich, dass einige Dinge nicht passieren
      if (isEvented(this)) {
        /**
        * Wird ausgelöst, wenn sich die Spielersprache ändert
        *
        * @event Spieler #languagechange
        * @Typ {EventTarget~Event}
        * /
        this.trigger ('Sprachwechsel');
      }
    }
  }

  /**
   * Holen Sie sich das Sprachwörterbuch des Spielers
   * Jedes Mal zusammenführen, da ein neu hinzugefügtes Plugin VideoJS.addLanguage () jederzeit aufrufen kann
   * Die direkt in den Spieleroptionen angegebenen Sprachen haben Vorrang
   *
   * @return {Array}
   * Eine Reihe unterstützter Sprachen
   * /
  Sprachen () {
    gib mergeOptions zurück (player.prototype.options_.languages, this.languages_);
  }

  /**
   * gibt ein JavaScript-Objekt zurück, das den aktuellen Track repräsentiert
   * Informationen. **GIBT es nicht als JSON zurück**
   *
   * @return {Object}
   * Objekt, das den aktuellen Stand der Track-Informationen darstellt
   * /
  zu JSON () {
    const-Optionen = mergeOptions (this.options_);
    const tracks = options.tracks;

    options.tracks = [];

    for (let i = 0; i < tracks.length; i++) {
      lettrack = Spuren [i];

      //Tracks tiefenzusammenführen und den Player auf Null setzen, also keine zirkulären Verweise
      track = mergeOptions (track);
      track.player = undefiniert;
      options.tracks [i] = verfolgen;
    }

    Rückgabeoptionen;
  }

  /**
   * Erzeugt einen einfachen modalen Dialog (eine Instanz von {@link modalDialog}
   * Komponente), die den Spieler sofort mit beliebigen
   * Inhalt und entfernt sich beim Schließen von selbst.
   *
   * @param {string|function|element|array|Null} Inhalt
   * Entspricht dem gleichnamigen Parameter von {@link modalDialog #content}.
   * Die einfachste Verwendung besteht darin, eine Zeichenfolge oder ein DOM bereitzustellen
   *        element.
   *
   * @param {Object} [Optionen]
   * Zusätzliche Optionen, die an den {@link modalDialog} weitergegeben werden.
   *
   * @return {modalDialog}
   * der {@link modalDialog}, der erstellt wurde
   * /
  createModal (Inhalt, Optionen) {
    optionen = Optionen || {};
    options.content = Inhalt || „;

    const modal = new ModalDialog (dies, Optionen);

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

    modal.open ();
    modales zurückgeben;
  }

  /**
   * Ändere die Breakpoint-Klassen, wenn der Spieler die Größe ändert.
   *
   * @privat
   * /
  AktuellesBreakPoint_ () {
    wenn (! diese.responsive ()) {
      rückkehr;
    }

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

    für (sei i = 0; i < breakPoint_Order.length; i++) {
      const candidateBreakpoint = BREAKPOINT_ORDER [i];
      const maxWidth = this.breakpoints_ [candidateBreakpoint];

      wenn (currentWidth <= maxWidth) {

        //Der aktuelle Breakpoint hat sich nicht geändert, es gibt nichts zu tun.
        wenn (currentBreakpoint === candidateBreakpoint) {
          rückkehr;
        }

        //Entferne eine Klasse nur, wenn es einen aktuellen Breakpoint gibt.
        wenn (currentBreakPoint) {
          this.removeClass (BREAKPOINT_CLASSES [CurrentBreakpoint]);
        }

        this.addClass (BREAKPOINT_CLASSES [CandidateBreakpoint]);
        this.breakpoint_ = CandidateBreakpoint;
        pause;
      }
    }
  }

  /**
   * Löscht den aktuellen Breakpoint.
   *
   * @privat
   * /
  Entferne den aktuellen Breakpoint _ () {
    const Klassenname = this.currentBreakPointClass ();

    this.breakpoint_ = '';

    wenn (Klassenname) {
      this.removeClass (Klassenname);
    }
  }

  /**
   * Erhalte Breakpoints oder setze sie für den Spieler.
   *
   * Der Aufruf dieser Methode mit einem Objekt oder `true` entfernt alle vorherigen
   * benutzerdefinierte Breakpoints und beginnen Sie wieder mit den Standardeinstellungen.
   *
   * @param {object|Boolean} [Breakpoints]
   * Wenn ein Objekt angegeben ist, kann es verwendet werden, um benutzerdefinierte bereitzustellen
   *         haltepunkte. Wenn `true` angegeben ist, werden Standard-Breakpoints gesetzt.
   * Wenn dieses Argument nicht angegeben ist, wird einfach der aktuelle Wert zurückgegeben
   *         haltepunkte.
   *
   * @param {Zahl} [breakpoints.tiny]
   * Die maximale Breite für die Klasse „vjs-layout-tiny“.
   *
   * @param {Zahl} [breakpoints.xsmall]
   * Die maximale Breite für die Klasse „vjs-layout-x-small“.
   *
   * @param {Zahl} [breakpoints.small]
   * Die maximale Breite für die Klasse „vjs-layout-small“.
   *
   * @param {Zahl} [breakpoints.medium]
   * Die maximale Breite für die Klasse „vjs-layout-medium“.
   *
   * @param {Zahl} [breakpoints.large]
   * Die maximale Breite für die Klasse „vjs-layout-large“.
   *
   * @param {Zahl} [breakpoints.xlarge]
   * Die maximale Breite für die Klasse „vjs-layout-x-large“.
   *
   * @param {Zahl} [breakpoints.huge]
   * Die maximale Breite für die Klasse „vjs-layout-huge“.
   *
   * @return {Object}
   * Ein Objekt, das Breakpoint-Namen maximalen Breitenwerten zuordnet.
   * /
  Haltepunkte (Haltepunkte) {

    // Wird als Getter verwendet.
    if (Breakpoints === undefiniert) {
      return assign(this.breakpoints_);
    }

    this.breakpoint_ = '';
    this.breakpoints_ = assign ({}, DEFAULT_BREAKPOINTS, Breakpoints);

    //Wenn sich die Breakpoint-Definitionen ändern, müssen wir die aktuelle Version aktualisieren
    //ausgewählter Breakpoint.
    this.updateCurrentBreakpoint_();

    //Klonen Sie die Breakpoints, bevor Sie zurückkehren.
    return assign(this.breakpoints_);
  }

  /**
   * Erhalte oder setze eine Flagge, die angibt, ob sich dieser Spieler anpassen sollte oder nicht
   * seine Benutzeroberfläche basiert auf seinen Abmessungen.
   *
   * @param {boolean} -Wert
   * Sollte `true` sein, wenn der Spieler seine Benutzeroberfläche entsprechend anpassen soll
   * Abmessungen; andernfalls sollte `falsch` sein.
   *
   * @return {boolean}
   * Wird „wahr“ sein, wenn dieser Spieler seine Benutzeroberfläche an seine anpassen sollte
   * Abmessungen; andernfalls ist es `falsch`.
   * /
  responsiv (Wert) {

    // Wird als Getter verwendet.
    if (Wert === undefiniert) {
      gib this.responsive_ zurück;
    }

    Wert = Boolean (Wert);
    const current = this.responsive_;

    //Nichts hat sich geändert.
    if (Wert === aktuell) {
      rückkehr;
    }

    //Der Wert hat sich tatsächlich geändert, lege ihn fest.
    this.responsive_ = Wert;

    //Beginne, auf Breakpoints zu warten und setze den anfänglichen Breakpoint, wenn
    //Der Player reagiert jetzt.
    wenn (Wert) {
      this.on ('playerresize', this.boundUpdateCurrentBreakPoint_);
      this.updateCurrentBreakpoint_();

    //Höre auf, nach Breakpoints zu suchen, wenn der Player nicht mehr reagiert.
    } sonst {
      this.off ('playerresize', this.boundUpdateCurrentBreakPoint_);
      this.removeCurrentBreakPoint_ ();
    }

    rückgabewert;
  }

  /**
   * Ruft den aktuellen Breakpoint-Namen ab, falls vorhanden.
   *
   * @return {string}
   * Wenn aktuell ein Breakpoint gesetzt ist, gibt a den Schlüssel aus dem
   * Breakpoint-Objekt, das dazu passt. Andernfalls wird eine leere Zeichenfolge zurückgegeben.
   * /
  aktueller Breakpoint () {
    gib this.breakpoint_ zurück;
  }

  /**
   * Ruft den aktuellen Breakpoint-Klassennamen ab.
   *
   * @return {string}
   * Der passende Klassenname (z. B. `"vjs-layout-tiny"` oder
   * `"vjs-layout-large"`) für den aktuellen Breakpoint. Leere Zeichenfolge wenn
   * es gibt keinen aktuellen Breakpoint.
   * /
  CurrentBreakPointClass () {
    return BREAKPOINT_CLASSES [this.breakpoint_] || „;
  }

  /**
   * Ein Objekt, das ein einzelnes Medium beschreibt.
   *
   * Eigenschaften, die nicht Teil dieser Typbeschreibung sind, werden beibehalten; also
   * Dies kann auch als generischer Mechanismus zur Speicherung von Metadaten angesehen werden.
   *
   * @see {@link https://wicg.github.io/mediasession/#the-mediametadata-interface}
   * @typedef {Object} player~MediaObject
   *
   * @property {string} [album]
   *           Unbenutzt, außer wenn dieses Objekt an die `MediaSession` übergeben wird
   *           API.
   *
   * @property {string} [Künstler]
   *           Unbenutzt, außer wenn dieses Objekt an die `MediaSession` übergeben wird
   *           API.
   *
   * @property {Object []} [Kunstwerk]
   *           Unbenutzt, außer wenn dieses Objekt an die `MediaSession` übergeben wird
   *           API. Falls nicht angegeben, wird über das `Poster` aufgefüllt, wenn
   * verfügbar.
   *
   * @property {string} [Plakat]
   * URL zu einem Bild, das vor der Wiedergabe angezeigt wird.
   *
   * @property {tech~sourceobject|tech~sourceObject [] |string} [src]
   * Ein einzelnes Quellobjekt, ein Array von Quellobjekten oder eine Zeichenfolge
   * Verweis auf eine URL zu einer Medienquelle. Es wird _sehr empfehlenswert_
   * dass hier ein Objekt oder ein Array von Objekten verwendet wird, also diese Quelle
   * Auswahlalgorithmen können den `Typ` berücksichtigen.
   *
   * @property {string} [Titel]
   *           Unbenutzt, außer wenn dieses Objekt an die `MediaSession` übergeben wird
   *           API.
   *
   * @property {Objekt []} [TextTracks]
   * Eine Reihe von Objekten, die zur Erstellung von Textspuren verwendet werden können, wie folgt
   * das {@link https://www.w3.org/TR/html50/embedded-content-0.html#the-track-element|native Track-Element-Format}.
   * Zur leichteren Entfernung werden diese als „Remote-Text“ erstellt
   * verfolgt und ist so eingestellt, dass Änderungen an der Quelle automatisch bereinigt werden.
   *
   * Diese Objekte können Eigenschaften wie `src`, `kind`, `label` haben,
   * und `Sprache`, siehe {@link Tech #createRemoteTextTrack}.
   * /

  /**
   * Füllen Sie den Player mit einem {@link Player~MediaObject|MediaObject}.
   *
   * @param {player~mediaObject} Medien
   * Ein Medienobjekt.
   *
   * @param {Function} bereit
   * Ein Rückruf, der angerufen wird, wenn der Spieler bereit ist.
   * /
  loadMedia (media, bereit) {
    wenn (! Medien | Art des Mediums! == 'Objekt') {
      rückkehr;
    }

    this.reset ();

    //Klone das Medienobjekt, damit es nicht von außen mutiert werden kann.
    this.cache_.media = MergeOptions (Medien);

    const {Kunstwerk, Poster, src, TextTracks} = this.cache_.media;

    //Falls `Artwork` nicht angegeben ist, erstelle es mit `Poster`.
    wenn (! Kunstwerk und Poster) {
      this.cache_.media.artwork = [{
        src: Plakat,
        Typ: getMimeType (Poster)
      }];
    }

    wenn (src) {
      diese.src (src);
    }

    wenn (Poster) {
      dieses.poster (Plakat);
    }

    wenn (Array.isArray (TextTracks)) {
      textTracks.forEach (tt => this.addRemoteTextTrack (tt, falsch));
    }

    this.ready(ready);
  }

  /**
   * Besorge dir einen Klon des aktuellen {@link player~mediaObject} für diesen Spieler.
   *
   * Wenn die `loadMedia`-Methode nicht verwendet wurde, wird versucht, eine zurückzugeben
   * {@link player~mediaObject} basiert auf dem aktuellen Status des Spielers.
   *
   * @return {player~mediaObject}
   * /
  getMedia () {
    wenn (! this.cache_.media) {
      const poster = this.poster ();
      const src = this.CurrentSources ();
      const textTracks = array.prototype.Map.call (this.remoteTextTracks (), (tt) => ({
        Art: tt.kind,
        Etikett: tt.label,
        sprache: tt.language,
        src: tt.src
      }));

      const media = {src, textTracks};

      wenn (Poster) {
        media.poster = Plakat;
        mediales Kunstwerk = [{
          src: media.poster,
          Typ: getMimeType (media.poster)
        }];
      }

      Medien zurückgeben;
    }

    gib mergeOptions (this.cache_.media) zurück;
  }

  /**
   * Ruft Tag-Einstellungen ab
   *
   * @param {Element} tag
   * Das Spielerkennzeichen
   *
   * @return {Object}
   * Ein Objekt, das alle Einstellungen enthält
   * für ein Spieler-Tag
   * /
  statische getTagSettings (Tag) {
    const baseOptions = {
      quellen: [],
      Titel: []
    };

    const tagOptions = dom.getAttributes (Tag);
    const DataSetup = TagOptions ['Daten-Setup'];

    wenn (dom.hasClass (tag, 'vjs-fill')) {
      tagOptions.Fill = wahr;
    }
    wenn (dom.hasClass (tag, 'vjs-fluid')) {
      tagOptions.Fluid = wahr;
    }

    // Prüfen, ob data-setup attr existiert.
    wenn (DataSetup! == null) {
      //Analysieren Sie die JSON-Optionen
      //Wenn die Zeichenfolge leer ist, mache sie zu einem analysierbaren JSON-Objekt.
      const [Fehler, Daten] = safeParseTuple (DataSetup || '{}');

      if (err) {
        log.error (Fehler);
      }
      assign (TagOptions, Daten);
    }

    zuweisen (BaseOptions, TagOptions);

    //Holen Sie sich die Tag-Kindereinstellungen
    wenn (tag.hasChildNodes ()) {
      const children = tag.ChildNodes;

      für (sei i = 0, j = kinder.length; i < j; i++) {
        const child = Kinder [i];
        //Groß- und Kleinschreibung ändern: http://ejohn.org/blog/nodename-case-sensitivity/
        const childName = child.nodeName.toLowerCase ();

        if (ChildName === 'Quelle') {
          baseOptions.sources.push (dom.getAttributes (Kind));
        } sonst wenn (childName === 'track') {
          baseOptions.tracks.push (dom.getAttributes (Kind));
        }
      }
    }

    BaseOptions zurückgeben;
  }

  /**
   * Stellen Sie fest, ob Flexbox unterstützt wird oder nicht
   *
   * @return {boolean}
   * - wahr, wenn Flexbox unterstützt wird
   * - falsch, wenn Flexbox nicht unterstützt wird
   * /
  Flex wird nicht unterstützt _ () {
    const elem = document.createElement ('i');

    //Hinweis: Wir verwenden FlexBasis (oder FlexOrder) nicht, aber es ist eines der mehr
    //allgemeine Flex-Funktionen, auf die wir uns verlassen können, wenn wir nach Flex-Unterstützung suchen.
    kehre zurück! ('FlexBasis' in elem.style) ||
            'WebKitFlexBasis' in elem.style ||
            'MozFlexBasis' in elem.style ||
            'msFlexBasis' in elem.style ||
            //IE10-spezifisch (Flex-Spezifikation 2012), der Vollständigkeit halber verfügbar
            'msFlexOrder' in elem.style);
  }

  /**
   * Stellen Sie den Debug-Modus ein, um Protokolle auf Infoebene zu aktivieren/deaktivieren.
   *
   * @param {boolean} aktiviert
   * @fires Spieler #debugon
   * @fires Spieler #debugoff
   * /
  debuggen (aktiviert) {
    if (aktiviert === undefiniert) {
      gib this.debugEnabled_ zurück;
    }
    wenn (aktiviert) {
      this.trigger ('debugon');
      this.previousLogLevel_ = diese.log.level;
      this.log.level ('debuggen');
      this.debugEnabled_ = wahr;
    } sonst {
      this.trigger ('Debugoff');
      this.log.level (this.previousLogLevel_);
      this.previousLogLevel_ = undefiniert;
      this.debugEnabled_ = false;
    }

  }

  /**
   * Stellen Sie die aktuellen Wiedergabegeschwindigkeiten ein oder rufen Sie sie ab.
   * Nimmt ein Array und aktualisiert das Menü mit den Wiedergaberaten mit den neuen Elementen.
   * Geben Sie ein leeres Array ein, um das Menü auszublenden.
   * Andere Werte als Arrays werden ignoriert.
   *
   * @fires Spieler #playbackrateschange
   * @param {number []} Neue Preise
   * Die neuen Raten, auf die das Menü mit den Wiedergaberaten aktualisiert werden sollte.
   * Ein leeres Array wird das Menü verbergen
   * @return {number []} Bei Verwendung als Getter werden die aktuellen Wiedergabegeschwindigkeiten zurückgegeben
   * /
  Wiedergaberaten (neue Raten) {
    if (newRates === undefiniert) {
      gib this.cache_.playbackRates zurück;
    }

    //ignoriere jeden Wert, der kein Array ist
    wenn (! Array.isArray (neue Raten) {
      rückkehr;
    }

    //ignoriere alle Arrays, die nicht nur Zahlen enthalten
    wenn (! newrates.every ((rate) => Ratentyp === 'Zahl')) {
      rückkehr;
    }

    this.cache_.playbackRates = Neue Raten;

    /**
    * wird ausgelöst, wenn die Wiedergabegeschwindigkeit in einem Player geändert wird
    *
    * @event Spieler #playbackrateschange
    * @Typ {EventTarget~Event}
    * /
    this.trigger ('Die Wiedergabegeschwindigkeit ändert sich');
  }
}

/**
 * Abrufen der {@link VideoTrackList}
 * @link https://html.spec.whatwg.org/multipage/embedded-content.html#videotracklist
 *
 * @return {videoTrackList}
 * die aktuelle Video-Trackliste
 *
 * @method player.prototype.VideoTracks
 * /

/**
 * Abrufen der {@link AudioTrackList}
 * @link https://html.spec.whatwg.org/multipage/embedded-content.html#audiotracklist
 *
 * @return {AudioTrackList}
 * die aktuelle Audio-Trackliste
 *
 * @method Player.prototype.AudioTracks
 * /

/**
 * Abrufen der {@link TextTrackList}
 *
 * @link http://www.w3.org/html/wg/drafts/html/master/embedded-content-0.html#dom-media-texttracks
 *
 * @return {TextTrackList}
 * die aktuelle Text-Titelliste
 *
 * @method player.prototype.TextTracks
 * /

/**
 * Hol dir die Fernbedienung {@link textTrackList}
 *
 * @return {TextTrackList}
 * Die aktuelle Remote-Text-Titelliste
 *
 * @method player.prototype.remoteTextTracks
 * /

/**
 * Holen Sie sich die Remote-Tracks von {@link htmlTrackElementList}.
 *
 * @return {htmlTrackElementList}
 * Die aktuelle Liste der Remote-Texttrack-Elemente
 *
 * @method player.prototype.remoteTextTrackels
 * /

track_types.names.forEach (Funktion (Name) {
  const props = TRACK_TYPES[name];

  player.prototype [props.getterName] = Funktion () {
    if (this.tech_) {
      gib this.tech_ [props.getterName] () zurück;
    }

    //falls wir LoadTech_ noch nicht haben, erstellen wir {Video, Audio, Text} Tracks_
    //diese werden beim Laden an den Techniker weitergegeben
    this[props.privateName] = this[props.privateName] || new props.ListClass();
    return this[props.privateName];
  };
});

/**
 * Ruft die Crossorigin-Option des `Player` ab oder legt sie fest. Für den HTML5-Player bedeutet dies
 * setzt die Eigenschaft "crossOrigin" auf den Tag "<video>", um den CORS zu steuern
 * verhalten.
 *
 * @see [Video Element Attributes]{@link https://developer.mozilla.org/en-US/docs/Web/HTML/Element/video#attr-crossorigin}
 *
 * @param {string} [Wert]
 * Der Wert, auf den der Crossorigin des `Player` gesetzt werden soll. Wenn ein Argument
 *        angegeben wird, muss eine der Optionen "anonymous" oder "use-credentials" sein.
 *
 * @return {string|undefined}
 * - Der aktuelle Crossorigin-Wert des `Player` beim Abrufen.
 *         - undefiniert bei der Einstellung
 * /
player.prototype.crossOrigin = player.prototype.CrossOrigin;

/**
 * Weltweite Aufzählung der Spieler.
 *
 * Die Schlüssel sind die Spieler-IDs und die Werte sind entweder der {@link Player}
 * Instanz oder `Null` für entlassene Spieler.
 *
 * @Typ {Objekt}
 * /
Spieler.Spieler = {};

const navigator = Fensternavigator;

/*
 * Optionen für Spielerinstanzen, die mithilfe von Optionen angezeigt wurden
 * Optionen = Player.prototype.options_
 * Nehmen Sie Änderungen an den Optionen vor, nicht hier.
 *
 * @Typ {Objekt}
 * @privat
 * /
player.prototype.options_ = {
  //Standardreihenfolge der Fallback-Technologie
  Technischer Auftrag: tech.defaultTechOrder_,

  html5: {},

  //Standard-Inaktivitäts-Timeout
  Inaktivitäts-Timeout: 2000,

  //Standard-Wiedergabegeschwindigkeiten
  playbackRates: [],
  //Füge die Auswahl der Wiedergabegeschwindigkeit hinzu, indem du Raten hinzufü
  //'Wiedergaberaten': [0,5, 1, 1,5, 2],
  liveui: falsch,

  //Mitgelieferte Steuersets
  kinder: [
    'MediaLoader',
    'Hinteres Bild',
    'TextTrackDisplay',
    'Spinner laden',
    'Große Play-Taste',
    'LiveTracker',
    'Steuerleiste',
    'Fehleranzeige',
    'TextTrack-Einstellungen',
    'Größenverwalter'
  ],

  language: navigator && (navigator.languages && navigator.languages [0] || navigator.userLanguage || navigator.language) || 'de',

  //Gebietsschemas und ihre Sprachübersetzungen
  Sprachen: {},

  //Standardnachricht, die angezeigt wird, wenn ein Video nicht abgespielt werden kann.
  Meldung wird nicht unterstützt: 'Für dieses Medium wurde keine kompatible Quelle gefunden. ',

  normalizeAutoplay: falsch,

  Vollbild: {
    Optionen: {
      navigationUI: 'verstecken'
    }
  },

  Haltepunkte: {},
  responsiv: falsch,
  AudioOnlyMode: falsch,
  audioPosterMode: falsch
};

[
  /**
   * Gibt zurück, ob sich der Spieler im Status „beendet“ befindet oder nicht.
   *
   * @return {Boolean} Wahr, wenn sich der Spieler im Zustand „Beendet“ befindet, andernfalls falsch.
   * @method Spieler #ended
   * /
  beendet",
  /**
   * Gibt zurück, ob sich der Spieler im Status „Sucht“ befindet oder nicht.
   *
   * @return {Boolean} Wahr, wenn sich der Spieler im Suchzustand befindet, falsch, wenn nicht.
   * @method Spieler #seeking
   * /
  suchen",
  /**
   * Gibt die TimeRange der aktuell verfügbaren Medien zurück
   * um danach zu suchen.
   *
   * @return {timeRanges} die durchsuchbaren Intervalle der Medien-Timeline
   * @method Spieler #seekable
   * /
  suchbar',
  /**
   * Gibt den aktuellen Status der Netzwerkaktivität für das Element zurück, von
   * die Codes in der folgenden Liste.
   * - NETWORK_EMPTY (numerischer Wert 0)
   * Das Element wurde noch nicht initialisiert. Alle Attribute sind in
   * ihre Anfangszustände.
   * - NETWORK_IDLE (numerischer Wert 1)
   * Der Algorithmus zur Ressourcenauswahl des Elements ist aktiv und hat
   * hat eine Ressource ausgewählt, aber sie verwendet das Netzwerk nicht wirklich unter
   * dieses Mal.
   * - NETWORK_LOADING (numerischer Wert 2)
   * Der Benutzeragent versucht aktiv, Daten herunterzuladen.
   * - NETWORK_NO_SOURCE (numerischer Wert 3)
   * Der Algorithmus zur Ressourcenauswahl des Elements ist aktiv, hat aber
   * noch keine Ressource zur Verwendung gefunden.
   *
   * @see https://html.spec.whatwg.org/multipage/embedded-content.html#network-states
   * @return {number} der aktuelle Status der Netzwerkaktivität
   * @method Spieler #networkState
   * /
  netzwerk-Status",
  /**
   * Gibt einen Wert zurück, der den aktuellen Zustand des Elements ausdrückt
   * in Bezug auf das Rendern der aktuellen Wiedergabeposition aus dem
   * Codes in der folgenden Liste.
   * - HAVE_NOTHING (numerischer Wert 0)
   * Es sind keine Informationen zur Medienressource verfügbar.
   * - HAVE_METADATA (numerischer Wert 1)
   * Von der Ressource wurde so viel beschafft, dass die Dauer des
   * Ressource ist verfügbar.
   * - HAVE_CURRENT_DATA (numerischer Wert 2)
   * Daten für die unmittelbar aktuelle Wiedergabeposition sind verfügbar.
   * - HAVE_FUTURE_DATA (numerischer Wert 3)
   * Daten für die unmittelbar aktuelle Wiedergabeposition sind verfügbar, als
   * sowie genügend Daten für den Benutzeragenten, um den aktuellen
   * Wiedergabeposition in Richtung der Wiedergabe.
   * - HAVE_ENOUGH_DATA (numerischer Wert 4)
   * Der Benutzeragent schätzt, dass genügend Daten verfügbar sind für
   * Wiedergabe, um ununterbrochen fortzufahren.
   *
   * @see https://html.spec.whatwg.org/multipage/embedded-content.html#dom-media-readystate
   * @return {number} der aktuelle Renderstatus der Wiedergabe
   * @method Spieler #readyState
   * /
  'Bereitschaft'
] .forEach (Funktion (fn) {
  player.prototype [fn] = Funktion () {
    gib this.techGet_ (fn) zurück;
  };
});

tech_events_retrigger.forEach (Funktion (Ereignis) {
  player.prototype [`handleTech$ {toTitleCase (event)} _`] = function () {
    gib this.trigger (event) zurück;
  };
});

/**
 * Wird abgefeuert, wenn der Spieler die anfänglichen Dauer- und Dimensionsinformationen hat
 *
 * @Ereignis Player#geladenMetadaten
 * @Typ {EventTarget~Event}
 * /

/**
 * Wird ausgelöst, wenn der Player Daten an der aktuellen Wiedergabeposition heruntergeladen hat
 *
 * @event Player#loadeddata
 * @Typ {EventTarget~Event}
 * /

/**
 * Wird ausgelöst, wenn sich die aktuelle Wiedergabeposition geändert hat *
 * Während der Wiedergabe wird dieser alle 15-250 Millisekunden ausgelöst, abhängig von
 * Verwendetechnologie.
 *
 * @event Player#timeupdate
 * @Typ {EventTarget~Event}
 * /

/**
 * Wird ausgelöst, wenn sich die Lautstärke ändert
 *
 * @event Player#Lautstärkeänderung
 * @Typ {EventTarget~Event}
 * /

/**
 * Meldet, ob für einen Player ein Plugin verfügbar ist oder nicht.
 *
 * Es wird nicht gemeldet, ob das Plugin jemals initialisiert wurde oder nicht
 * auf diesem Spieler. Dafür [UsingPlugin] {@link Player #usingPlugin}.
 *
 * @method Spieler #hasPlugin
 * @param {string} name
 *         Der Name eines Plugins.
 *
 * @return {boolean}
 * Ob für diesen Player das angeforderte Plugin verfügbar ist oder nicht.
 * /

/**
 * Meldet namentlich, ob ein Spieler ein Plugin verwendet oder nicht.
 *
 * Bei Basis-Plugins wird nur gemeldet, ob das Plugin _immer_
 * auf diesem Player initialisiert.
 *
 * @method Spieler #usingPlugin
 * @param {string} name
 *         Der Name eines Plugins.
 *
 * @return {boolean}
 * Ob dieser Player das angeforderte Plugin verwendet oder nicht.
 * /

component.registerComponent ('Spieler', Spieler);
Standard-Player exportieren;